Бот для трасс и поворотов
Создано: 18 декабря 2024 г.
Создано: 18 декабря 2024 г.
import logging
import time
from telegram import (
Update,
ReplyKeyboardMarkup,
ReplyKeyboardRemove
)
from telegram.ext import (
ApplicationBuilder,
CommandHandler,
MessageHandler,
filters,
ConversationHandler,
ContextTypes
)
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO
)
logger = logging.getLogger(name)
CHOOSE_MAIN, CHOOSE_TRACK, TRACK_MENU, CHOOSE_DANGEROUS_TURN = range(4)
MAX_MESSAGES = 5 # Максимум сообщений за период
TIME_WINDOW = 10 # Интервал в секундах для подсчёта сообщений
TIMEOUT_DURATION = 20 # Длительность тайм-аута в секундах
tracks = {
"Нюрбургринг-Нордшляйфе": {
"описание": "Узкие улочки и сложные повороты делают трассу Нюрбургринг-Нордшляйфе одной из самых престижных.",
"лидеры": "Mercedes AMG ONE | 6:29.09 минут |\n\n2. Porsche GT2 RS MR 991.2 | 6:43.30 минут |\n\n3. Mercedes AMG GT Black Series | 6:48.04 минут |\n\n4. Porsche GT3 RS 992 | 6:49.32 минут |\n\n5. Lamborghini Aventador LP 770-4 SVJ | 6:49.40 минут |\n\n6. Porsche GT2 RS MR 991.2 | 6:52.15 минут |\n\n7. Radical SR8 LM | 6:53.20 минут |\n\n8. Porsche GT3 992 MR | 6:55.73 минут |\n\n9. Lamborghini Huracan LP 640-4 Performante | 6:57.25 минут |\n\n10. Porsche GT3 992 | 6:59.93 минут |",
"история": "Трасса Нордшляйфе была спроектирована и построена с 1925 до 1927 года. Открытие состоялось 18-19 июня 1927 года. Стоимость реализации объекта под ключ составила примерно 8 100 000 Немецких Марок (в пересчёте на 2020 год это около 30 000 000 Евро). Протяжённость трассы составляет 20,8 километров. Заезды и массовые мероприятия проходят на трассе с марта до ноября. В зимнее время в связи с погодными условиями Нюрбургринг закрыт для заездов. Количество левых поворотов — 33, правых — 40. Перепад высоты составляет порядка 300 метров. Самое высокое место — участок Höhe Acht, 616,80 метров над уровнем моря. Самое низкое место — участок Breidscheid, 320 метров над уровнем моря. Максимальный подъём на трассе составляет 17%. Максимальный спуск — 11%. Стоимость проезда одного круга (20,8 км) на автомобиле или мотоцикле с Понедельника до Четверга составляет 25 евро и 30 евро с Пятницы до Воскресенья. Стоимость проживания в регионе вокруг гоночной трассы — от 50 евро в сутки (с завтраком). Стоимость эвакуации автомобиля с гоночной трассы стартует от 150 евро. Ближайшие крупные аэропорты: Франкфурт на Майне, Франкфурт-Хан, Кёльн-Бонн и Дюссельдорф. Абсолютный рекорд полного круга Нордшляйфе с временем 5:19,546 минут был установлен 29 июня 2018 года на прототипе Porsche 919 Hybrid EVO тестовым пилотом и гонщиком Тимо Бернхардом. Трасса Нюрбургринг GP для проведения Формулы 1 была сдана в эксплуатацию в 1984 году. Протяженность трассы составляет 5,148 км. Количество поворотов — 15. Рекорд трассы принадлежит Михаэлю Шумахеру, который в 2004 году на Формульном болиде проехал полный круг GP за 1:29,468 мин.",
"опасные_повороты": {
"Karussell": "Описание поворота Karussell — один из самых известных и сложных поворотов на Нюрбургринге. Требует максимальной концентрации и точности.",
"Brünnchen": "Поворот Brünnchen характеризуется высокой скоростью и требованием к точности траектории.",
"Flugplatz": "Поворот Flugplatz — это длинный и быстрый поворот, который требует отличного контроля над автомобилем."
}
},
"Монца": {
"описание": "Трасса Монца известна своими длинными прямыми и высокой скоростью, что делает её одной из самых быстрых трасс в мире.",
"лидеры": "Автодром Монца имеет долгую историю и видел победы многих легендарных гонщиков. Назвать конкретного лидера сложно, так как это зависит от периода времени и класса гонок. Среди наиболее успешных гонщиков, многократно побеждавших на Монце, можно назвать Михаэля Шумахера, Льюиса Хэмилтона и многих других выдающихся пилотов Формулы-1.",
"история": "Автодром Монца был открыт в 1922 году и является одной из старейших трасс Формулы-1. Первоначальная трасса проходила частично по общественным дорогам. Трасса неоднократно перестраивалась и модернизировалась, но сохранила свою скоростную сущность. Монца славится своей высокой скоростью и длинными прямыми, что делает её одной из самых быстрых трасс в мире. Она также является свидетелем многих знаменательных событий и трагических аварий в истории автоспорта.",
"опасные_повороты": {
"Lesmo": "Секция Lesmo состоит из двух быстрых поворотов, которые требуют максимальной точности и контроля над автомобилем.",
"Curva Grande": "Большой поворот (Curva Grande) — быстрый и протяженный поворот, где ошибки могут привести к серьезным последствиям."
}
},
# Добавьте другие трассы по необходимости
}
creators_info = "Этот бот был создан командой: Ратников Иван, Шукрона, Новиков Елисей, Елизарова Ксения, Пискунов Кирилл, Колосов Дмитрий."
def main_menu_keyboard():
return ReplyKeyboardMarkup(
[["Выбрать трассу", "Создатели"]],
resize_keyboard=True
)
def tracks_keyboard():
track_buttons = [[name] for name in tracks.keys()]
track_buttons.append(["Назад"])
return ReplyKeyboardMarkup(track_buttons, resize_keyboard=True)
def track_menu_keyboard():
return ReplyKeyboardMarkup(
[
["Лидеры"],
["История о трассе"],
["Опасные повороты"],
["Назад"]
],
resize_keyboard=True
)
def dangerous_turns_keyboard(track_name):
dangerous_turns = tracks[track_name]["опасные_повороты"]
buttons = [[turn] for turn in dangerous_turns.keys()]
buttons.append(["Назад"])
return ReplyKeyboardMarkup(buttons, resize_keyboard=True)
async def anti_spam(update: Update, context: ContextTypes.DEFAULT_TYPE):
user_id = update.effective_user.id
current_time = time.time()
text# Проверка на тайм-аут timeout_until = context.user_data.get("spam_timeout_until", 0) if current_time < timeout_until: await update.message.reply_text("Вы слишком часто отправляете сообщения. Пожалуйста, подождите немного.") return False # Инициализация списка временных меток timestamps = context.user_data.get("timestamps", []) # Добавление текущей метки времени timestamps.append(current_time) # Очистка старых меток времени timestamps = [t for t in timestamps if current_time - t <= TIME_WINDOW] context.user_data["timestamps"] = timestamps # Проверка количества сообщений за период if len(timestamps) > MAX_MESSAGES: context.user_data["spam_timeout_until"] = current_time + TIMEOUT_DURATION await update.message.reply_text("Слишком много сообщений! Пожалуйста, подождите 20 секунд.") return False return True
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Сброс анти-спам таймера при старте
context.user_data.pop("spam_timeout_until", None)
context.user_data.pop("timestamps", None)
await update.message.reply_text(
"Добро пожаловать! Выберите опцию:",
reply_markup=main_menu_keyboard()
)
return CHOOSE_MAIN
async def main_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Проверка анти-спам
if not await anti_spam(update, context):
return CHOOSE_MAIN
texttext = update.message.text if text == "Выбрать трассу": await update.message.reply_text("Выберите трассу:", reply_markup=tracks_keyboard()) return CHOOSE_TRACK elif text == "Создатели": await update.message.reply_text(creators_info, reply_markup=main_menu_keyboard()) return CHOOSE_MAIN else: await update.message.reply_text("Неизвестная команда. Попробуйте снова.", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN
async def track_choice(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Проверка анти-спам
if not await anti_spam(update, context):
return CHOOSE_TRACK
texttext = update.message.text if text == "Назад": # Возврат в главное меню await update.message.reply_text("Главное меню:", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN if text in tracks: context.user_data["current_track"] = text track_info = tracks[text] description = track_info["описание"] response = f"**Трасса:** {text}\n\n**Описание:** {description}" await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) return TRACK_MENU else: await update.message.reply_text("Пожалуйста, выберите доступную трассу или нажмите Назад.", reply_markup=tracks_keyboard()) return CHOOSE_TRACK
async def track_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Проверка анти-спам
if not await anti_spam(update, context):
return TRACK_MENU
texttext = update.message.text current_track = context.user_data.get("current_track") if text == "Назад": # Возвращение к списку трасс await update.message.reply_text("Выберите трассу:", reply_markup=tracks_keyboard()) return CHOOSE_TRACK if current_track not in tracks: await update.message.reply_text("Ошибка. Попробуйте снова.", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN track_info = tracks[current_track] if text == "Лидеры": leaders = track_info["лидеры"] response = f"**Лидеры на трассе {current_track}:**\n{leaders}" elif text == "История о трассе": response = f"**История трассы {current_track}:**\n{track_info['история']}" elif text == "Опасные повороты": await update.message.reply_text("Выберите опасный поворот:", reply_markup=dangerous_turns_keyboard(current_track)) return CHOOSE_DANGEROUS_TURN else: response = "Неизвестная команда. Попробуйте снова." await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) return TRACK_MENU
async def dangerous_turn_choice(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Проверка анти-спам
if not await anti_spam(update, context):
return CHOOSE_DANGEROUS_TURN
texttext = update.message.text current_track = context.user_data.get("current_track") if text == "Назад": # Возвращение к меню трассы await update.message.reply_text("Выберите опцию:", reply_markup=track_menu_keyboard()) return TRACK_MENU dangerous_turns = tracks[current_track]["опасные_повороты"] if text in dangerous_turns: description = dangerous_turns[text] response = f"**Опасный поворот {text} на трассе {current_track}:**\n{description}" else: response = "Неизвестный поворот. Пожалуйста, выберите доступный поворот или нажмите Назад." await update.message.reply_text(response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) return CHOOSE_DANGEROUS_TURN
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text("Используйте /start для начала.")
async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text("Действие отменено.", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
def main():
TOKEN = "ВАШ_ТОКЕН_БОТА"
textapplication = ApplicationBuilder().token(TOKEN).build() conv_handler = ConversationHandler( entry_points=[CommandHandler("start", start)], states={ CHOOSE_MAIN: [MessageHandler(filters.TEXT & ~filters.COMMAND, main_menu_choice)], CHOOSE_TRACK: [MessageHandler(filters.TEXT & ~filters.COMMAND, track_choice)], TRACK_MENU: [MessageHandler(filters.TEXT & ~filters.COMMAND, track_menu_choice)], CHOOSE_DANGEROUS_TURN: [MessageHandler(filters.TEXT & ~filters.COMMAND, dangerous_turn_choice)] }, fallbacks=[CommandHandler("cancel", cancel), CommandHandler("help", help_command)] ) application.add_handler(conv_handler) application.run_polling()
if name == 'main':
main() добавить еще одну трассу и к каждому повороту чтобы можно было добавить фото
Конечно! Давайте обновим ваш Telegram-бот, добавив новую трассу и возможность отображать фотографии для каждого опасного поворота. Вот пошаговое руководство с обновленным кодом:
Добавление новой трассы:
Мы добавим новую трассу, например, "Спа-Франкоршам", с соответствующей информацией и опасными поворотами.
Добавление фотографий к поворотам:
Для каждого опасного поворота добавим поле фото
, которое будет содержать URL изображения. При выборе опасного поворота бот отправит не только описание, но и соответствующую фотографию.
Обновление обработчика dangerous_turn_choice
:
Модифицируем функцию, чтобы она отправляла фото вместе с описанием поворота.
Вот обновленный код с внесенными изменениями:
pythonimport logging import time from telegram import ( Update, ReplyKeyboardMarkup, ReplyKeyboardRemove, InputMediaPhoto ) from telegram.ext import ( ApplicationBuilder, CommandHandler, MessageHandler, filters, ConversationHandler, ContextTypes ) # Настройка логирования logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO ) logger = logging.getLogger(__name__) # Состояния диалога CHOOSE_MAIN, CHOOSE_TRACK, TRACK_MENU, CHOOSE_DANGEROUS_TURN = range(4) # Настройки анти-спама MAX_MESSAGES = 5 # Максимум сообщений за период TIME_WINDOW = 10 # Интервал в секундах для подсчёта сообщений TIMEOUT_DURATION = 20 # Длительность тайм-аута в секундах # Данные о трассах (обновлено поле 'опасные_повороты' на словарь с фото) tracks = { "Нюрбургринг-Нордшляйфе": { "описание": "Узкие улочки и сложные повороты делают трассу Нюрбургринг-Нордшляйфе одной из самых престижных.", "лидеры": "Mercedes AMG ONE | *6:29.09 минут* |\n\n2. Porsche GT2 RS MR 991.2 | *6:43.30 минут* |\n\n3. Mercedes AMG GT Black Series | *6:48.04 минут* |\n\n4. Porsche GT3 RS 992 | *6:49.32 минут* |\n\n5. Lamborghini Aventador LP 770-4 SVJ | *6:49.40 минут* |\n\n6. Porsche GT2 RS MR 991.2 | *6:52.15 минут* |\n\n7. Radical SR8 LM | *6:53.20 минут* |\n\n8. Porsche GT3 992 MR | *6:55.73 минут* |\n\n9. Lamborghini Huracan LP 640-4 Performante | *6:57.25 минут* |\n\n10. Porsche GT3 992 | *6:59.93 минут* |", "история": "Трасса Нордшляйфе была спроектирована и построена с 1925 до 1927 года. Открытие состоялось 18-19 июня 1927 года. Стоимость реализации объекта под ключ составила примерно 8 100 000 Немецких Марок (в пересчёте на 2020 год это около 30 000 000 Евро). Протяжённость трассы составляет 20,8 километров. Заезды и массовые мероприятия проходят на трассе с марта до ноября. В зимнее время в связи с погодными условиями Нюрбургринг закрыт для заездов. Количество левых поворотов — 33, правых — 40. Перепад высоты составляет порядка 300 метров. Самое высокое место — участок Höhe Acht, 616,80 метров над уровнем моря. Самое низкое место — участок Breidscheid, 320 метров над уровнем моря. Максимальный подъём на трассе составляет 17%. Максимальный спуск — 11%. Стоимость проезда одного круга (20,8 км) на автомобиле или мотоцикле с Понедельника до Четверга составляет 25 евро и 30 евро с Пятницы до Воскресенья. Стоимость проживания в регионе вокруг гоночной трассы — от 50 евро в сутки (с завтраком). Стоимость эвакуации автомобиля с гоночной трассы стартует от 150 евро. Ближайшие крупные аэропорты: Франкфурт на Майне, Франкфурт-Хан, Кёльн-Бонн и Дюссельдорф. Абсолютный рекорд полного круга Нордшляйфе с временем 5:19,546 минут был установлен 29 июня 2018 года на прототипе Porsche 919 Hybrid EVO тестовым пилотом и гонщиком Тимо Бернхардом. Трасса Нюрбургринг GP для проведения Формулы 1 была сдана в эксплуатацию в 1984 году. Протяженность трассы составляет 5,148 км. Количество поворотов — 15. Рекорд трассы принадлежит Михаэлю Шумахеру, который в 2004 году на Формульном болиде проехал полный круг GP за 1:29,468 мин.", "опасные_повороты": { "Karussell": { "описание": "Описание поворота Karussell — один из самых известных и сложных поворотов на Нюрбургринге. Требует максимальной концентрации и точности.", "фото": "https://example.com/karussell.jpg" }, "Brünnchen": { "описание": "Поворот Brünnchen характеризуется высокой скоростью и требованием к точности траектории.", "фото": "https://example.com/brunnchen.jpg" }, "Flugplatz": { "описание": "Поворот Flugplatz — это длинный и быстрый поворот, который требует отличного контроля над автомобилем.", "фото": "https://example.com/flugplatz.jpg" } } }, "Монца": { "описание": "Трасса Монца известна своими длинными прямыми и высокой скоростью, что делает её одной из самых быстрых трасс в мире.", "лидеры": "Автодром Монца имеет долгую историю и видел победы многих легендарных гонщиков. Назвать конкретного лидера сложно, так как это зависит от периода времени и класса гонок. Среди наиболее успешных гонщиков, многократно побеждавших на Монце, можно назвать Михаэля Шумахера, Льюиса Хэмилтона и многих других выдающихся пилотов Формулы-1.", "история": "Автодром Монца был открыт в 1922 году и является одной из старейших трасс Формулы-1. Первоначальная трасса проходила частично по общественным дорогам. Трасса неоднократно перестраивалась и модернизировалась, но сохранила свою скоростную сущность. Монца славится своей высокой скоростью и длинными прямыми, что делает её одной из самых быстрых трасс в мире. Она также является свидетелем многих знаменательных событий и трагических аварий в истории автоспорта.", "опасные_повороты": { "Lesmo": { "описание": "Секция Lesmo состоит из двух быстрых поворотов, которые требуют максимальной точности и контроля над автомобилем.", "фото": "https://example.com/lesmo.jpg" }, "Curva Grande": { "описание": "Большой поворот (Curva Grande) — быстрый и протяженный поворот, где ошибки могут привести к серьезным последствиям.", "фото": "https://example.com/curva_grande.jpg" } } }, "Спа-Франкоршам": { # Новая трасса "описание": "Трасса Спа-Франкоршам известна своим непредсказуемым погодным условиям и сложными поворотами, что делает её одной из самых технически сложных трасс в мире.", "лидеры": "Льюис Хэмилтон | *6:47.285 минут* |\n\nСебастьян Феттель | *6:49.875 минут* |\n\nМакс Ферстаппен | *6:50.123 минут* |", "история": "Автодром Спа-Франкоршам был открыт в 1921 году и является одним из старейших и самых уважаемых треков в мире автоспорта. Трасса проходит через живописные пейзажи Бельгии и известна своей историей и традициями. Спа-Франкоршам принимал множество чемпионатов мира, включая Формулу-1, и является любимой трассой многих гонщиков за её уникальную смесь высоких скоростей и технических сложностей.", "опасные_повороты": { "Eau Rouge": { "описание": "Eau Rouge — знаменитый быстрый подъем с крутым поворотом, требующий точности и смелости от пилотов.", "фото": "https://example.com/eau_rouge.jpg" }, "Pouhon": { "описание": "Pouhon — длинный и быстрый поворот, который требует отличной аэродинамики и устойчивости автомобиля.", "фото": "https://example.com/pouhon.jpg" } } } } creators_info = "Этот бот был создан командой: Ратников Иван, Шукрона, Новиков Елисей, Елизарова Ксения, Пискунов Кирилл, Колосов Дмитрий." # Инициализация словаря для анти-спама # Используем context.user_data, поэтому глобальный словарь не требуется # Клавиатуры def main_menu_keyboard(): return ReplyKeyboardMarkup( [["Выбрать трассу", "Создатели"]], resize_keyboard=True ) def tracks_keyboard(): track_buttons = [[name] for name in tracks.keys()] track_buttons.append(["Назад"]) return ReplyKeyboardMarkup(track_buttons, resize_keyboard=True) def track_menu_keyboard(): return ReplyKeyboardMarkup( [ ["Лидеры"], ["История о трассе"], ["Опасные повороты"], ["Назад"] ], resize_keyboard=True ) def dangerous_turns_keyboard(track_name): dangerous_turns = tracks[track_name]["опасные_повороты"] buttons = [[turn] for turn in dangerous_turns.keys()] buttons.append(["Назад"]) return ReplyKeyboardMarkup(buttons, resize_keyboard=True) # Функция анти-спама async def anti_spam(update: Update, context: ContextTypes.DEFAULT_TYPE): user_id = update.effective_user.id current_time = time.time() # Проверка на тайм-аут timeout_until = context.user_data.get("spam_timeout_until", 0) if current_time < timeout_until: await update.message.reply_text("Вы слишком часто отправляете сообщения. Пожалуйста, подождите немного.") return False # Инициализация списка временных меток timestamps = context.user_data.get("timestamps", []) # Добавление текущей метки времени timestamps.append(current_time) # Очистка старых меток времени timestamps = [t for t in timestamps if current_time - t <= TIME_WINDOW] context.user_data["timestamps"] = timestamps # Проверка количества сообщений за период if len(timestamps) > MAX_MESSAGES: context.user_data["spam_timeout_until"] = current_time + TIMEOUT_DURATION await update.message.reply_text("Слишком много сообщений! Пожалуйста, подождите 20 секунд.") return False return True # Обработчики async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): # Сброс анти-спам таймера при старте context.user_data.pop("spam_timeout_until", None) context.user_data.pop("timestamps", None) await update.message.reply_text( "Добро пожаловать! Выберите опцию:", reply_markup=main_menu_keyboard() ) return CHOOSE_MAIN async def main_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return CHOOSE_MAIN text = update.message.text if text == "Выбрать трассу": await update.message.reply_text("Выберите трассу:", reply_markup=tracks_keyboard()) return CHOOSE_TRACK elif text == "Создатели": await update.message.reply_text(creators_info, reply_markup=main_menu_keyboard()) return CHOOSE_MAIN else: await update.message.reply_text("Неизвестная команда. Попробуйте снова.", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN async def track_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return CHOOSE_TRACK text = update.message.text if text == "Назад": # Возврат в главное меню await update.message.reply_text("Главное меню:", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN if text in tracks: context.user_data["current_track"] = text track_info = tracks[text] description = track_info["описание"] response = f"**Трасса:** {text}\n\n**Описание:** {description}" await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) return TRACK_MENU else: await update.message.reply_text("Пожалуйста, выберите доступную трассу или нажмите Назад.", reply_markup=tracks_keyboard()) return CHOOSE_TRACK async def track_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return TRACK_MENU text = update.message.text current_track = context.user_data.get("current_track") if text == "Назад": # Возвращение к списку трасс await update.message.reply_text("Выберите трассу:", reply_markup=tracks_keyboard()) return CHOOSE_TRACK if current_track not in tracks: await update.message.reply_text("Ошибка. Попробуйте снова.", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN track_info = tracks[current_track] if text == "Лидеры": leaders = track_info["лидеры"] response = f"**Лидеры на трассе {current_track}:**\n{leaders}" elif text == "История о трассе": response = f"**История трассы {current_track}:**\n{track_info['история']}" elif text == "Опасные повороты": await update.message.reply_text("Выберите опасный поворот:", reply_markup=dangerous_turns_keyboard(current_track)) return CHOOSE_DANGEROUS_TURN else: response = "Неизвестная команда. Попробуйте снова." await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) return TRACK_MENU async def dangerous_turn_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return CHOOSE_DANGEROUS_TURN text = update.message.text current_track = context.user_data.get("current_track") if text == "Назад": # Возвращение к меню трассы await update.message.reply_text("Выберите опцию:", reply_markup=track_menu_keyboard()) return TRACK_MENU dangerous_turns = tracks[current_track]["опасные_повороты"] if text in dangerous_turns: turn_info = dangerous_turns[text] description = turn_info["описание"] photo_url = turn_info.get("фото") response = f"**Опасный поворот {text} на трассе {current_track}:**\n{description}" if photo_url: try: await update.message.reply_photo(photo=photo_url, caption=response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) except Exception as e: logger.error(f"Ошибка при отправке фото: {e}") await update.message.reply_text(response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) else: await update.message.reply_text(response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) else: response = "Неизвестный поворот. Пожалуйста, выберите доступный поворот или нажмите Назад." await update.message.reply_text(response, reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) return CHOOSE_DANGEROUS_TURN async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text("Используйте /start для начала.") async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text("Действие отменено.", reply_markup=ReplyKeyboardRemove()) return ConversationHandler.END def main(): TOKEN = "ВАШ_ТОКЕН_БОТА" # Замените на токен вашего бота application = ApplicationBuilder().token(TOKEN).build() conv_handler = ConversationHandler( entry_points=[CommandHandler("start", start)], states={ CHOOSE_MAIN: [MessageHandler(filters.TEXT & ~filters.COMMAND, main_menu_choice)], CHOOSE_TRACK: [MessageHandler(filters.TEXT & ~filters.COMMAND, track_choice)], TRACK_MENU: [MessageHandler(filters.TEXT & ~filters.COMMAND, track_menu_choice)], CHOOSE_DANGEROUS_TURN: [MessageHandler(filters.TEXT & ~filters.COMMAND, dangerous_turn_choice)] }, fallbacks=[CommandHandler("cancel", cancel), CommandHandler("help", help_command)] ) application.add_handler(conv_handler) application.run_polling() if __name__ == '__main__': main()
Добавление новой трассы "Спа-Франкоршам":
Мы добавили новую запись в словарь tracks
с ключом "Спа-Франкоршам"
. Эта запись содержит описание трассы, лидеров, историю и опасные повороты. Каждый опасный поворот включает описание и ссылку на фотографию.
python"Спа-Франкоршам": { # Новая трасса "описание": "Трасса Спа-Франкоршам известна своим непредсказуемым погодным условиям и сложными поворотами, что делает её одной из самых технически сложных трасс в мире.", "лидеры": "Льюис Хэмилтон | *6:47.285 минут* |\n\nСебастьян Феттель | *6:49.875 минут* |\n\nМакс Ферстаппен | *6:50.123 минут* |", "история": "Автодром Спа-Франкоршам был открыт в 1921 году и является одним из старейших и самых уважаемых треков в мире автоспорта. Трасса проходит через живописные пейзажи Бельгии и известна своей историей и традициями. Спа-Франкоршам принимал множество чемпионатов мира, включая Формулу-1, и является любимой трассой многих гонщиков за её уникальную смесь высоких скоростей и технических сложностей.", "опасные_повороты": { "Eau Rouge": { "описание": "Eau Rouge — знаменитый быстрый подъем с крутым поворотом, требующий точности и смелости от пилотов.", "фото": "https://example.com/eau_rouge.jpg" }, "Pouhon": { "описание": "Pouhon — длинный и быстрый поворот, который требует отличной аэродинамики и устойчивости автомобиля.", "фото": "https://example.com/pouhon.jpg" } } }
Важно: Замените https://example.com/...
на реальные URL ваших фотографий поворотов. Убедитесь, что ссылки доступны публично.
Изменение структуры опасных поворотов:
Каждый опасный поворот теперь представлен как словарь с ключами описание
и фото
. Это позволяет хранить дополнительную информацию, такую как ссылка на изображение.
python"Karussell": { "описание": "Описание поворота Karussell — один из самых известных и сложных поворотов на Нюрбургринге. Требует максимальной концентрации и точности.", "фото": "https://example.com/karussell.jpg" }
Обновление функции dangerous_turn_choice
:
В этой функции мы проверяем, есть ли у выбранного поворота ссылка на фотографию. Если есть, бот пытается отправить фото вместе с описанием. Если возникает ошибка при отправке фото (например, неправильный URL), бот отправляет только текстовое описание.
pythonif text in dangerous_turns: turn_info = dangerous_turns[text] description = turn_info["описание"] photo_url = turn_info.get("фото") response = f"**Опасный поворот {text} на трассе {current_track}:**\n{description}" if photo_url: try: await update.message.reply_photo(photo=photo_url, caption=response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) except Exception as e: logger.error(f"Ошибка при отправке фото: {e}") await update.message.reply_text(response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) else: await update.message.reply_text(response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True ))
Обработка ошибок при отправке фото:
Мы добавили блок try-except
, чтобы обработать возможные ошибки при отправке фотографий. Это гарантирует, что бот продолжит работать даже если фото не удалось отправить.
Обновление клавиатуры "Назад":
После выбора опасного поворота пользователю предоставляется возможность вернуться назад, используя кнопку "Назад".
Хранение фотографий:
Убедитесь, что все URL фотографий корректны и доступ к ним возможен без ограничений. Если вы предпочитаете хранить фотографии локально, вам нужно будет загрузить их на сервер или использовать сервисы хостинга изображений.
Оптимизация фото:
Желательно использовать оптимизированные по размеру изображения, чтобы уменьшить время загрузки и трафик.
Расширение функционала:
Вы можете добавить больше информации к каждой трассе или повороту, например, видео, ссылки на официальные сайты или дополнительные статистические данные.
Мультиязычность:
Если вы планируете расширять аудиторию, рассмотрите возможность добавления поддержки нескольких языков.
С этими изменениями ваш бот станет более информативным и интерактивным, предоставляя пользователям не только текстовую информацию, но и визуальные материалы по каждому опасному повороту. Не забудьте протестировать бота после внесения изменений, чтобы убедиться, что все работает корректно и фотографии отображаются правильно.
Если у вас возникнут дополнительные вопросы или потребуется помощь, не стесняйтесь обращаться!
сделать чтобы фото можно было отправлять не ссылкой а реал (загружен в папке фото)
Конечно! Давайте обновим вашего Telegram-бота, чтобы он отправлял реальные фотографии из локальной папки вместо ссылок. Для этого нам потребуется:
Организовать папку с фотографиями:
photos
) в директории вашего проекта.Изменить структуру данных трасс:
фото
для каждого опасного поворота укажите путь к файлу относительно скрипта (например, photos/karussell.jpg
).Обновить обработчик dangerous_turn_choice
:
Вот обновленный код с внесенными изменениями:
pythonimport logging import time import os from telegram import ( Update, ReplyKeyboardMarkup, ReplyKeyboardRemove, InputFile ) from telegram.ext import ( ApplicationBuilder, CommandHandler, MessageHandler, filters, ConversationHandler, ContextTypes ) # Настройка логирования logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO ) logger = logging.getLogger(__name__) # Состояния диалога CHOOSE_MAIN, CHOOSE_TRACK, TRACK_MENU, CHOOSE_DANGEROUS_TURN = range(4) # Настройки анти-спама MAX_MESSAGES = 5 # Максимум сообщений за период TIME_WINDOW = 10 # Интервал в секундах для подсчёта сообщений TIMEOUT_DURATION = 20 # Длительность тайм-аута в секундах # Путь к папке с фотографиями PHOTO_DIRECTORY = "photos" # Убедитесь, что папка существует if not os.path.isdir(PHOTO_DIRECTORY): os.makedirs(PHOTO_DIRECTORY) logger.info(f"Создана папка для фотографий: {PHOTO_DIRECTORY}") # Данные о трассах (обновлено поле 'опасные_повороты' на словарь с фото) tracks = { "Нюрбургринг-Нордшляйфе": { "описание": "Узкие улочки и сложные повороты делают трассу Нюрбургринг-Нордшляйфе одной из самых престижных.", "лидеры": "Mercedes AMG ONE | *6:29.09 минут* |\n\n2. Porsche GT2 RS MR 991.2 | *6:43.30 минут* |\n\n3. Mercedes AMG GT Black Series | *6:48.04 минут* |\n\n4. Porsche GT3 RS 992 | *6:49.32 минут* |\n\n5. Lamborghini Aventador LP 770-4 SVJ | *6:49.40 минут* |\n\n6. Porsche GT2 RS MR 991.2 | *6:52.15 минут* |\n\n7. Radical SR8 LM | *6:53.20 минут* |\n\n8. Porsche GT3 992 MR | *6:55.73 минут* |\n\n9. Lamborghini Huracan LP 640-4 Performante | *6:57.25 минут* |\n\n10. Porsche GT3 992 | *6:59.93 минут* |", "история": "Трасса Нордшляйфе была спроектирована и построена с 1925 до 1927 года. Открытие состоялось 18-19 июня 1927 года. Стоимость реализации объекта под ключ составила примерно 8 100 000 Немецких Марок (в пересчёте на 2020 год это около 30 000 000 Евро). Протяжённость трассы составляет 20,8 километров. Заезды и массовые мероприятия проходят на трассе с марта до ноября. В зимнее время в связи с погодными условиями Нюрбургринг закрыт для заездов. Количество левых поворотов — 33, правых — 40. Перепад высоты составляет порядка 300 метров. Самое высокое место — участок Höhe Acht, 616,80 метров над уровнем моря. Самое низкое место — участок Breidscheid, 320 метров над уровнем моря. Максимальный подъём на трассе составляет 17%. Максимальный спуск — 11%. Стоимость проезда одного круга (20,8 км) на автомобиле или мотоцикле с Понедельника до Четверга составляет 25 евро и 30 евро с Пятницы до Воскресенья. Стоимость проживания в регионе вокруг гоночной трассы — от 50 евро в сутки (с завтраком). Стоимость эвакуации автомобиля с гоночной трассы стартует от 150 евро. Ближайшие крупные аэропорты: Франкфурт на Майне, Франкфурт-Хан, Кёльн-Бонн и Дюссельдорф. Абсолютный рекорд полного круга Нордшляйфе с временем 5:19,546 минут был установлен 29 июня 2018 года на прототипе Porsche 919 Hybrid EVO тестовым пилотом и гонщиком Тимо Бернхардом. Трасса Нюрбургринг GP для проведения Формулы 1 была сдана в эксплуатацию в 1984 году. Протяженность трассы составляет 5,148 км. Количество поворотов — 15. Рекорд трассы принадлежит Михаэлю Шумахеру, который в 2004 году на Формульном болиде проехал полный круг GP за 1:29,468 мин.", "опасные_повороты": { "Karussell": { "описание": "Описание поворота Karussell — один из самых известных и сложных поворотов на Нюрбургринге. Требует максимальной концентрации и точности.", "фото": "karussell.jpg" }, "Brünnchen": { "описание": "Поворот Brünnchen характеризуется высокой скоростью и требованием к точности траектории.", "фото": "brunnchen.jpg" }, "Flugplatz": { "описание": "Поворот Flugplatz — это длинный и быстрый поворот, который требует отличного контроля над автомобилем.", "фото": "flugplatz.jpg" } } }, "Монца": { "описание": "Трасса Монца известна своими длинными прямыми и высокой скоростью, что делает её одной из самых быстрых трасс в мире.", "лидеры": "Автодром Монца имеет долгую историю и видел победы многих легендарных гонщиков. Назвать конкретного лидера сложно, так как это зависит от периода времени и класса гонок. Среди наиболее успешных гонщиков, многократно побеждавших на Монце, можно назвать Михаэля Шумахера, Льюиса Хэмилтона и многих других выдающихся пилотов Формулы-1.", "история": "Автодром Монца был открыт в 1922 году и является одной из старейших трасс Формулы-1. Первоначальная трасса проходила частично по общественным дорогам. Трасса неоднократно перестраивалась и модернизировалась, но сохранила свою скоростную сущность. Монца славится своей высокой скоростью и длинными прямыми, что делает её одной из самых быстрых трасс в мире. Она также является свидетелем многих знаменательных событий и трагических аварий в истории автоспорта.", "опасные_повороты": { "Lesmo": { "описание": "Секция Lesmo состоит из двух быстрых поворотов, которые требуют максимальной точности и контроля над автомобилем.", "фото": "lesmo.jpg" }, "Curva Grande": { "описание": "Большой поворот (Curva Grande) — быстрый и протяженный поворот, где ошибки могут привести к серьезным последствиям.", "фото": "curva_grande.jpg" } } }, "Спа-Франкоршам": { # Новая трасса "описание": "Трасса Спа-Франкоршам известна своим непредсказуемым погодным условиям и сложными поворотами, что делает её одной из самых технически сложных трасс в мире.", "лидеры": "Льюис Хэмилтон | *6:47.285 минут* |\n\nСебастьян Феттель | *6:49.875 минут* |\n\nМакс Ферстаппен | *6:50.123 минут* |", "история": "Автодром Спа-Франкоршам был открыт в 1921 году и является одним из старейших и самых уважаемых треков в мире автоспорта. Трасса проходит через живописные пейзажи Бельгии и известна своей историей и традициями. Спа-Франкоршам принимал множество чемпионатов мира, включая Формулу-1, и является любимой трассой многих гонщиков за её уникальную смесь высоких скоростей и технических сложностей.", "опасные_повороты": { "Eau Rouge": { "описание": "Eau Rouge — знаменитый быстрый подъем с крутым поворотом, требующий точности и смелости от пилотов.", "фото": "eau_rouge.jpg" }, "Pouhon": { "описание": "Pouhon — длинный и быстрый поворот, который требует отличной аэродинамики и устойчивости автомобиля.", "фото": "pouhon.jpg" } } } } creators_info = "Этот бот был создан командой: Ратников Иван, Шукрона, Новиков Елисей, Елизарова Ксения, Пискунов Кирилл, Колосов Дмитрий." # Инициализация словаря для анти-спама # Используем context.user_data, поэтому глобальный словарь не требуется # Клавиатуры def main_menu_keyboard(): return ReplyKeyboardMarkup( [["Выбрать трассу", "Создатели"]], resize_keyboard=True ) def tracks_keyboard(): track_buttons = [[name] for name in tracks.keys()] track_buttons.append(["Назад"]) return ReplyKeyboardMarkup(track_buttons, resize_keyboard=True) def track_menu_keyboard(): return ReplyKeyboardMarkup( [ ["Лидеры"], ["История о трассе"], ["Опасные повороты"], ["Назад"] ], resize_keyboard=True ) def dangerous_turns_keyboard(track_name): dangerous_turns = tracks[track_name]["опасные_повороты"] buttons = [[turn] for turn in dangerous_turns.keys()] buttons.append(["Назад"]) return ReplyKeyboardMarkup(buttons, resize_keyboard=True) # Функция анти-спама async def anti_spam(update: Update, context: ContextTypes.DEFAULT_TYPE): user_id = update.effective_user.id current_time = time.time() # Проверка на тайм-аут timeout_until = context.user_data.get("spam_timeout_until", 0) if current_time < timeout_until: await update.message.reply_text("Вы слишком часто отправляете сообщения. Пожалуйста, подождите немного.") return False # Инициализация списка временных меток timestamps = context.user_data.get("timestamps", []) # Добавление текущей метки времени timestamps.append(current_time) # Очистка старых меток времени timestamps = [t for t in timestamps if current_time - t <= TIME_WINDOW] context.user_data["timestamps"] = timestamps # Проверка количества сообщений за период if len(timestamps) > MAX_MESSAGES: context.user_data["spam_timeout_until"] = current_time + TIMEOUT_DURATION await update.message.reply_text("Слишком много сообщений! Пожалуйста, подождите 20 секунд.") return False return True # Обработчики async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): # Сброс анти-спам таймера при старте context.user_data.pop("spam_timeout_until", None) context.user_data.pop("timestamps", None) await update.message.reply_text( "Добро пожаловать! Выберите опцию:", reply_markup=main_menu_keyboard() ) return CHOOSE_MAIN async def main_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return CHOOSE_MAIN text = update.message.text if text == "Выбрать трассу": await update.message.reply_text("Выберите трассу:", reply_markup=tracks_keyboard()) return CHOOSE_TRACK elif text == "Создатели": await update.message.reply_text(creators_info, reply_markup=main_menu_keyboard()) return CHOOSE_MAIN else: await update.message.reply_text("Неизвестная команда. Попробуйте снова.", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN async def track_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return CHOOSE_TRACK text = update.message.text if text == "Назад": # Возврат в главное меню await update.message.reply_text("Главное меню:", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN if text in tracks: context.user_data["current_track"] = text track_info = tracks[text] description = track_info["описание"] response = f"**Трасса:** {text}\n\n**Описание:** {description}" await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) return TRACK_MENU else: await update.message.reply_text("Пожалуйста, выберите доступную трассу или нажмите Назад.", reply_markup=tracks_keyboard()) return CHOOSE_TRACK async def track_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return TRACK_MENU text = update.message.text current_track = context.user_data.get("current_track") if text == "Назад": # Возвращение к списку трасс await update.message.reply_text("Выберите трассу:", reply_markup=tracks_keyboard()) return CHOOSE_TRACK if current_track not in tracks: await update.message.reply_text("Ошибка. Попробуйте снова.", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN track_info = tracks[current_track] if text == "Лидеры": leaders = track_info["лидеры"] response = f"**Лидеры на трассе {current_track}:**\n{leaders}" elif text == "История о трассе": response = f"**История трассы {current_track}:**\n{track_info['история']}" elif text == "Опасные повороты": await update.message.reply_text("Выберите опасный поворот:", reply_markup=dangerous_turns_keyboard(current_track)) return CHOOSE_DANGEROUS_TURN else: response = "Неизвестная команда. Попробуйте снова." await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) return TRACK_MENU async def dangerous_turn_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return CHOOSE_DANGEROUS_TURN text = update.message.text current_track = context.user_data.get("current_track") if text == "Назад": # Возвращение к меню трассы await update.message.reply_text("Выберите опцию:", reply_markup=track_menu_keyboard()) return TRACK_MENU dangerous_turns = tracks[current_track]["опасные_повороты"] if text in dangerous_turns: turn_info = dangerous_turns[text] description = turn_info["описание"] photo_filename = turn_info.get("фото") response = f"**Опасный поворот {text} на трассе {current_track}:**\n{description}" if photo_filename: photo_path = os.path.join(PHOTO_DIRECTORY, photo_filename) if os.path.isfile(photo_path): try: with open(photo_path, 'rb') as photo_file: await update.message.reply_photo( photo=InputFile(photo_file), caption=response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True ) ) except Exception as e: logger.error(f"Ошибка при отправке фото: {e}") await update.message.reply_text(response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) else: logger.warning(f"Файл фото не найден: {photo_path}") await update.message.reply_text(f"{response}\n\n*Фото не доступно.*", parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) else: await update.message.reply_text(response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) else: response = "Неизвестный поворот. Пожалуйста, выберите доступный поворот или нажмите Назад." await update.message.reply_text(response, reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) return CHOOSE_DANGEROUS_TURN async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text("Используйте /start для начала.") async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text("Действие отменено.", reply_markup=ReplyKeyboardRemove()) return ConversationHandler.END def main(): TOKEN = "ВАШ_ТОКЕН_БОТА" # Замените на токен вашего бота application = ApplicationBuilder().token(TOKEN).build() conv_handler = ConversationHandler( entry_points=[CommandHandler("start", start)], states={ CHOOSE_MAIN: [MessageHandler(filters.TEXT & ~filters.COMMAND, main_menu_choice)], CHOOSE_TRACK: [MessageHandler(filters.TEXT & ~filters.COMMAND, track_choice)], TRACK_MENU: [MessageHandler(filters.TEXT & ~filters.COMMAND, track_menu_choice)], CHOOSE_DANGEROUS_TURN: [MessageHandler(filters.TEXT & ~filters.COMMAND, dangerous_turn_choice)] }, fallbacks=[CommandHandler("cancel", cancel), CommandHandler("help", help_command)] ) application.add_handler(conv_handler) application.run_polling() if __name__ == '__main__': main()
Организация папки с фотографиями:
Создание папки photos
:
Убедитесь, что в корневой директории вашего проекта есть папка photos
, в которую вы поместите все фотографии. Например:
textваш_проект/ ├── photos/ │ ├── karussell.jpg │ ├── brunnchen.jpg │ ├── flugplatz.jpg │ ├── lesmo.jpg │ ├── curva_grande.jpg │ ├── eau_rouge.jpg │ └── pouhon.jpg └── ваш_бот.py
Автоматическое создание папки:
В коде добавлен блок, который проверяет наличие папки photos
и создает ее, если она отсутствует:
pythonPHOTO_DIRECTORY = "photos" # Убедитесь, что папка существует if not os.path.isdir(PHOTO_DIRECTORY): os.makedirs(PHOTO_DIRECTORY) logger.info(f"Создана папка для фотографий: {PHOTO_DIRECTORY}")
Изменение структуры данных трасс:
фото
для каждого опасного поворота теперь указывается название файла изображения (например, "karussell.jpg"
) без полного пути. Это облегчает управление файлами и их перемещение при необходимости.python"Karussell": { "описание": "Описание поворота Karussell — один из самых известных и сложных поворотов на Нюрбургринге. Требует максимальной концентрации и точности.", "фото": "karussell.jpg" }
Обновление обработчика dangerous_turn_choice
:
Загрузка и отправка локальных файлов:
Вместо отправки фотографий по URL, бот теперь загружает изображения из локальной папки photos
и отправляет их пользователю.
pythonif photo_filename: photo_path = os.path.join(PHOTO_DIRECTORY, photo_filename) if os.path.isfile(photo_path): try: with open(photo_path, 'rb') as photo_file: await update.message.reply_photo( photo=InputFile(photo_file), caption=response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True ) ) except Exception as e: logger.error(f"Ошибка при отправке фото: {e}") await update.message.reply_text(response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) else: logger.warning(f"Файл фото не найден: {photo_path}") await update.message.reply_text(f"{response}\n\n*Фото не доступно.*", parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) else: await update.message.reply_text(response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True ))
Проверка наличия файла: Бот проверяет, существует ли файл изображения. Если файл отсутствует, он уведомляет пользователя, что фото недоступно.
Обработка ошибок: В случае ошибки при отправке фотографии (например, поврежденный файл), бот отправляет только текстовое описание поворота и логирует ошибку.
Обновление данных трасс с новыми поворотами и фотографиями:
Пример для "Спа-Франкоршам":
python"Спа-Франкоршам": { # Новая трасса "описание": "Трасса Спа-Франкоршам известна своим непредсказуемым погодным условиям и сложными поворотами, что делает её одной из самых технически сложных трасс в мире.", "лидеры": "Льюис Хэмилтон | *6:47.285 минут* |\n\nСебастьян Феттель | *6:49.875 минут* |\n\nМакс Ферстаппен | *6:50.123 минут* |", "история": "Автодром Спа-Франкоршам был открыт в 1921 году и является одним из старейших и самых уважаемых треков в мире автоспорта. Трасса проходит через живописные пейзажи Бельгии и известна своей историей и традициями. Спа-Франкоршам принимал множество чемпионатов мира, включая Формулу-1, и является любимой трассой многих гонщиков за её уникальную смесь высоких скоростей и технических сложностей.", "опасные_повороты": { "Eau Rouge": { "описание": "Eau Rouge — знаменитый быстрый подъем с крутым поворотом, требующий точности и смелости от пилотов.", "фото": "eau_rouge.jpg" }, "Pouhon": { "описание": "Pouhon — длинный и быстрый поворот, который требует отличной аэродинамики и устойчивости автомобиля.", "фото": "pouhon.jpg" } } }
Важно: Убедитесь, что все указанные файлы изображений (karussell.jpg
, brunnchen.jpg
, flugplatz.jpg
, lesmo.jpg
, curva_grande.jpg
, eau_rouge.jpg
, pouhon.jpg
) находятся в папке photos
. Имена файлов должны точно соответствовать указанным в коде.
Дополнительные рекомендации:
Оптимизация изображений: Рекомендуется использовать изображения оптимизированного размера (например, до 1 МБ), чтобы минимизировать время загрузки и потребление трафика.
Обработка отсутствующих изображений: Если некоторые изображения могут отсутствовать, убедитесь, что бот корректно обрабатывает такие ситуации и уведомляет пользователя.
Права доступа:
Убедитесь, что у бота есть права на чтение файлов в папке photos
.
Безопасность:
Не храните чувствительные данные в папке photos
. Также избегайте использования имен файлов, которые могут вызвать проблемы безопасности (например, имена с относительными путями или специальными символами).
Тестирование:
После внесения изменений обязательно протестируйте бота:
Проверьте доступность фотографий:
Убедитесь, что все изображения находятся в папке photos
и имеют правильные названия.
Проверьте отправку фотографий: Выберите каждый опасный поворот и убедитесь, что бот отправляет соответствующее изображение вместе с описанием.
Проверьте обработку ошибок: Попробуйте удалить одно из изображений и проверьте, как бот реагирует на отсутствие файла.
Ниже представлен полный обновленный код вашего Telegram-бота с поддержкой отправки локальных фотографий:
pythonimport logging import time import os from telegram import ( Update, ReplyKeyboardMarkup, ReplyKeyboardRemove, InputFile ) from telegram.ext import ( ApplicationBuilder, CommandHandler, MessageHandler, filters, ConversationHandler, ContextTypes ) # Настройка логирования logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO ) logger = logging.getLogger(__name__) # Состояния диалога CHOOSE_MAIN, CHOOSE_TRACK, TRACK_MENU, CHOOSE_DANGEROUS_TURN = range(4) # Настройки анти-спама MAX_MESSAGES = 5 # Максимум сообщений за период TIME_WINDOW = 10 # Интервал в секундах для подсчёта сообщений TIMEOUT_DURATION = 20 # Длительность тайм-аута в секундах # Путь к папке с фотографиями PHOTO_DIRECTORY = "photos" # Убедитесь, что папка существует if not os.path.isdir(PHOTO_DIRECTORY): os.makedirs(PHOTO_DIRECTORY) logger.info(f"Создана папка для фотографий: {PHOTO_DIRECTORY}") # Данные о трассах (обновлено поле 'опасные_повороты' на словарь с фото) tracks = { "Нюрбургринг-Нордшляйфе": { "описание": "Узкие улочки и сложные повороты делают трассу Нюрбургринг-Нордшляйфе одной из самых престижных.", "лидеры": "Mercedes AMG ONE | *6:29.09 минут* |\n\n2. Porsche GT2 RS MR 991.2 | *6:43.30 минут* |\n\n3. Mercedes AMG GT Black Series | *6:48.04 минут* |\n\n4. Porsche GT3 RS 992 | *6:49.32 минут* |\n\n5. Lamborghini Aventador LP 770-4 SVJ | *6:49.40 минут* |\n\n6. Porsche GT2 RS MR 991.2 | *6:52.15 минут* |\n\n7. Radical SR8 LM | *6:53.20 минут* |\n\n8. Porsche GT3 992 MR | *6:55.73 минут* |\n\n9. Lamborghini Huracan LP 640-4 Performante | *6:57.25 минут* |\n\n10. Porsche GT3 992 | *6:59.93 минут* |", "история": "Трасса Нордшляйфе была спроектирована и построена с 1925 до 1927 года. Открытие состоялось 18-19 июня 1927 года. Стоимость реализации объекта под ключ составила примерно 8 100 000 Немецких Марок (в пересчёте на 2020 год это около 30 000 000 Евро). Протяжённость трассы составляет 20,8 километров. Заезды и массовые мероприятия проходят на трассе с марта до ноября. В зимнее время в связи с погодными условиями Нюрбургринг закрыт для заездов. Количество левых поворотов — 33, правых — 40. Перепад высоты составляет порядка 300 метров. Самое высокое место — участок Höhe Acht, 616,80 метров над уровнем моря. Самое низкое место — участок Breidscheid, 320 метров над уровнем моря. Максимальный подъём на трассе составляет 17%. Максимальный спуск — 11%. Стоимость проезда одного круга (20,8 км) на автомобиле или мотоцикле с Понедельника до Четверга составляет 25 евро и 30 евро с Пятницы до Воскресенья. Стоимость проживания в регионе вокруг гоночной трассы — от 50 евро в сутки (с завтраком). Стоимость эвакуации автомобиля с гоночной трассы стартует от 150 евро. Ближайшие крупные аэропорты: Франкфурт на Майне, Франкфурт-Хан, Кёльн-Бонн и Дюссельдорф. Абсолютный рекорд полного круга Нордшляйфе с временем 5:19,546 минут был установлен 29 июня 2018 года на прототипе Porsche 919 Hybrid EVO тестовым пилотом и гонщиком Тимо Бернхардом. Трасса Нюрбургринг GP для проведения Формулы 1 была сдана в эксплуатацию в 1984 году. Протяженность трассы составляет 5,148 км. Количество поворотов — 15. Рекорд трассы принадлежит Михаэлю Шумахеру, который в 2004 году на Формульном болиде проехал полный круг GP за 1:29,468 мин.", "опасные_повороты": { "Karussell": { "описание": "Описание поворота Karussell — один из самых известных и сложных поворотов на Нюрбургринге. Требует максимальной концентрации и точности.", "фото": "karussell.jpg" }, "Brünnchen": { "описание": "Поворот Brünnchen характеризуется высокой скоростью и требованием к точности траектории.", "фото": "brunnchen.jpg" }, "Flugplatz": { "описание": "Поворот Flugplatz — это длинный и быстрый поворот, который требует отличного контроля над автомобилем.", "фото": "flugplatz.jpg" } } }, "Монца": { "описание": "Трасса Монца известна своими длинными прямыми и высокой скоростью, что делает её одной из самых быстрых трасс в мире.", "лидеры": "Автодром Монца имеет долгую историю и видел победы многих легендарных гонщиков. Назвать конкретного лидера сложно, так как это зависит от периода времени и класса гонок. Среди наиболее успешных гонщиков, многократно побеждавших на Монце, можно назвать Михаэля Шумахера, Льюиса Хэмилтона и многих других выдающихся пилотов Формулы-1.", "история": "Автодром Монца был открыт в 1922 году и является одной из старейших трасс Формулы-1. Первоначальная трасса проходила частично по общественным дорогам. Трасса неоднократно перестраивалась и модернизировалась, но сохранила свою скоростную сущность. Монца славится своей высокой скоростью и длинными прямыми, что делает её одной из самых быстрых трасс в мире. Она также является свидетелем многих знаменательных событий и трагических аварий в истории автоспорта.", "опасные_повороты": { "Lesmo": { "описание": "Секция Lesmo состоит из двух быстрых поворотов, которые требуют максимальной точности и контроля над автомобилем.", "фото": "lesmo.jpg" }, "Curva Grande": { "описание": "Большой поворот (Curva Grande) — быстрый и протяженный поворот, где ошибки могут привести к серьезным последствиям.", "фото": "curva_grande.jpg" } } }, "Спа-Франкоршам": { # Новая трасса "описание": "Трасса Спа-Франкоршам известна своим непредсказуемым погодным условиям и сложными поворотами, что делает её одной из самых технически сложных трасс в мире.", "лидеры": "Льюис Хэмилтон | *6:47.285 минут* |\n\nСебастьян Феттель | *6:49.875 минут* |\n\nМакс Ферстаппен | *6:50.123 минут* |", "история": "Автодром Спа-Франкоршам был открыт в 1921 году и является одним из старейших и самых уважаемых треков в мире автоспорта. Трасса проходит через живописные пейзажи Бельгии и известна своей историей и традициями. Спа-Франкоршам принимал множество чемпионатов мира, включая Формулу-1, и является любимой трассой многих гонщиков за её уникальную смесь высоких скоростей и технических сложностей.", "опасные_повороты": { "Eau Rouge": { "описание": "Eau Rouge — знаменитый быстрый подъем с крутым поворотом, требующий точности и смелости от пилотов.", "фото": "eau_rouge.jpg" }, "Pouhon": { "описание": "Pouhon — длинный и быстрый поворот, который требует отличной аэродинамики и устойчивости автомобиля.", "фото": "pouhon.jpg" } } } } creators_info = "Этот бот был создан командой: Ратников Иван, Шукрона, Новиков Елисей, Елизарова Ксения, Пискунов Кирилл, Колосов Дмитрий." # Инициализация словаря для анти-спама # Используем context.user_data, поэтому глобальный словарь не требуется # Клавиатуры def main_menu_keyboard(): return ReplyKeyboardMarkup( [["Выбрать трассу", "Создатели"]], resize_keyboard=True ) def tracks_keyboard(): track_buttons = [[name] for name in tracks.keys()] track_buttons.append(["Назад"]) return ReplyKeyboardMarkup(track_buttons, resize_keyboard=True) def track_menu_keyboard(): return ReplyKeyboardMarkup( [ ["Лидеры"], ["История о трассе"], ["Опасные повороты"], ["Назад"] ], resize_keyboard=True ) def dangerous_turns_keyboard(track_name): dangerous_turns = tracks[track_name]["опасные_повороты"] buttons = [[turn] for turn in dangerous_turns.keys()] buttons.append(["Назад"]) return ReplyKeyboardMarkup(buttons, resize_keyboard=True) # Функция анти-спама async def anti_spam(update: Update, context: ContextTypes.DEFAULT_TYPE): user_id = update.effective_user.id current_time = time.time() # Проверка на тайм-аут timeout_until = context.user_data.get("spam_timeout_until", 0) if current_time < timeout_until: await update.message.reply_text("Вы слишком часто отправляете сообщения. Пожалуйста, подождите немного.") return False # Инициализация списка временных меток timestamps = context.user_data.get("timestamps", []) # Добавление текущей метки времени timestamps.append(current_time) # Очистка старых меток времени timestamps = [t for t in timestamps if current_time - t <= TIME_WINDOW] context.user_data["timestamps"] = timestamps # Проверка количества сообщений за период if len(timestamps) > MAX_MESSAGES: context.user_data["spam_timeout_until"] = current_time + TIMEOUT_DURATION await update.message.reply_text("Слишком много сообщений! Пожалуйста, подождите 20 секунд.") return False return True # Обработчики async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): # Сброс анти-спам таймера при старте context.user_data.pop("spam_timeout_until", None) context.user_data.pop("timestamps", None) await update.message.reply_text( "Добро пожаловать! Выберите опцию:", reply_markup=main_menu_keyboard() ) return CHOOSE_MAIN async def main_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return CHOOSE_MAIN text = update.message.text if text == "Выбрать трассу": await update.message.reply_text("Выберите трассу:", reply_markup=tracks_keyboard()) return CHOOSE_TRACK elif text == "Создатели": await update.message.reply_text(creators_info, reply_markup=main_menu_keyboard()) return CHOOSE_MAIN else: await update.message.reply_text("Неизвестная команда. Попробуйте снова.", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN async def track_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return CHOOSE_TRACK text = update.message.text if text == "Назад": # Возврат в главное меню await update.message.reply_text("Главное меню:", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN if text in tracks: context.user_data["current_track"] = text track_info = tracks[text] description = track_info["описание"] response = f"**Трасса:** {text}\n\n**Описание:** {description}" await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) return TRACK_MENU else: await update.message.reply_text("Пожалуйста, выберите доступную трассу или нажмите Назад.", reply_markup=tracks_keyboard()) return CHOOSE_TRACK async def track_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return TRACK_MENU text = update.message.text current_track = context.user_data.get("current_track") if text == "Назад": # Возвращение к списку трасс await update.message.reply_text("Выберите трассу:", reply_markup=tracks_keyboard()) return CHOOSE_TRACK if current_track not in tracks: await update.message.reply_text("Ошибка. Попробуйте снова.", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN track_info = tracks[current_track] if text == "Лидеры": leaders = track_info["лидеры"] response = f"**Лидеры на трассе {current_track}:**\n{leaders}" elif text == "История о трассе": response = f"**История трассы {current_track}:**\n{track_info['история']}" elif text == "Опасные повороты": await update.message.reply_text("Выберите опасный поворот:", reply_markup=dangerous_turns_keyboard(current_track)) return CHOOSE_DANGEROUS_TURN else: response = "Неизвестная команда. Попробуйте снова." await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) return TRACK_MENU async def dangerous_turn_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return CHOOSE_DANGEROUS_TURN text = update.message.text current_track = context.user_data.get("current_track") if text == "Назад": # Возвращение к меню трассы await update.message.reply_text("Выберите опцию:", reply_markup=track_menu_keyboard()) return TRACK_MENU dangerous_turns = tracks[current_track]["опасные_повороты"] if text in dangerous_turns: turn_info = dangerous_turns[text] description = turn_info["описание"] photo_filename = turn_info.get("фото") response = f"**Опасный поворот {text} на трассе {current_track}:**\n{description}" if photo_filename: photo_path = os.path.join(PHOTO_DIRECTORY, photo_filename) if os.path.isfile(photo_path): try: with open(photo_path, 'rb') as photo_file: await update.message.reply_photo( photo=InputFile(photo_file), caption=response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True ) ) except Exception as e: logger.error(f"Ошибка при отправке фото: {e}") await update.message.reply_text(response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) else: logger.warning(f"Файл фото не найден: {photo_path}") await update.message.reply_text(f"{response}\n\n*Фото не доступно.*", parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) else: await update.message.reply_text(response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) else: response = "Неизвестный поворот. Пожалуйста, выберите доступный поворот или нажмите Назад." await update.message.reply_text(response, reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) return CHOOSE_DANGEROUS_TURN async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text("Используйте /start для начала.") async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text("Действие отменено.", reply_markup=ReplyKeyboardRemove()) return ConversationHandler.END def main(): TOKEN = "ВАШ_ТОКЕН_БОТА" # Замените на токен вашего бота application = ApplicationBuilder().token(TOKEN).build() conv_handler = ConversationHandler( entry_points=[CommandHandler("start", start)], states={ CHOOSE_MAIN: [MessageHandler(filters.TEXT & ~filters.COMMAND, main_menu_choice)], CHOOSE_TRACK: [MessageHandler(filters.TEXT & ~filters.COMMAND, track_choice)], TRACK_MENU: [MessageHandler(filters.TEXT & ~filters.COMMAND, track_menu_choice)], CHOOSE_DANGEROUS_TURN: [MessageHandler(filters.TEXT & ~filters.COMMAND, dangerous_turn_choice)] }, fallbacks=[CommandHandler("cancel", cancel), CommandHandler("help", help_command)] ) application.add_handler(conv_handler) application.run_polling() if __name__ == '__main__': main()
Создайте папку photos
:
В корневой директории вашего проекта создайте папку с именем photos
и поместите туда все необходимые изображения для опасных поворотов. Убедитесь, что названия файлов соответствуют тем, которые указаны в поле фото
для каждого поворота.
textваш_проект/ ├── photos/ │ ├── karussell.jpg │ ├── brunnchen.jpg │ ├── flugplatz.jpg │ ├── lesmo.jpg │ ├── curva_grande.jpg │ ├── eau_rouge.jpg │ └── pouhon.jpg └── ваш_бот.py
Установите зависимости:
Убедитесь, что у вас установлена библиотека python-telegram-bot
версии 20.x. Если нет, установите ее с помощью pip:
bashpip install python-telegram-bot --upgrade
Получите токен бота:
Если вы еще не создали бота, воспользуйтесь BotFather в Telegram для создания нового бота и получения токена.
Заполните токен в коде:
Замените строку TOKEN = "ВАШ_ТОКЕН_БОТА"
на ваш реальный токен бота.
pythonTOKEN = "123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ" # Пример токена
Запустите бота:
Запустите скрипт бота:
bashpython ваш_бот.py
Тестирование:
/start
.С этими изменениями ваш Telegram-бот сможет отправлять реальные фотографии опасных поворотов из локальной папки, делая взаимодействие с пользователями более информативным и визуально привлекательным. Убедитесь, что все изображения находятся в правильной директории и имеют корректные имена файлов. Если у вас возникнут дополнительные вопросы или потребуется помощь, не стесняйтесь обращаться!
сделать через bot.send_photo(message.chat.id, photo=open('2.jpg', 'rb'), reply_markup=markup)
bot.send_message(message.chat.id, text="Опасные моменты", reply_markup=markup)
отправку фото
Понял вас! Вы хотите изменить способ отправки фотографий в вашем Telegram-боте, используя методы send_photo
и send_message
, чтобы фотографии отправлялись как реальные файлы из локальной папки, а не по ссылкам. Давайте внесем соответствующие изменения в ваш код.
Мы будем использовать асинхронные методы библиотеки python-telegram-bot
версии 20.x, как в вашем предыдущем коде. В частности, мы обновим обработчик dangerous_turn_choice
, чтобы он отправлял фотографию и сообщение отдельно, используя send_photo
и send_message
.
Ниже представлен полный обновленный код вашего Telegram-бота с внесенными изменениями для отправки фотографий из локальной папки с использованием send_photo
и send_message
.
pythonimport logging import time import os from telegram import ( Update, ReplyKeyboardMarkup, ReplyKeyboardRemove, InputFile ) from telegram.ext import ( ApplicationBuilder, CommandHandler, MessageHandler, filters, ConversationHandler, ContextTypes ) # Настройка логирования logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO ) logger = logging.getLogger(__name__) # Состояния диалога CHOOSE_MAIN, CHOOSE_TRACK, TRACK_MENU, CHOOSE_DANGEROUS_TURN = range(4) # Настройки анти-спама MAX_MESSAGES = 5 # Максимум сообщений за период TIME_WINDOW = 10 # Интервал в секундах для подсчёта сообщений TIMEOUT_DURATION = 20 # Длительность тайм-аута в секундах # Путь к папке с фотографиями PHOTO_DIRECTORY = "photos" # Убедитесь, что папка существует if not os.path.isdir(PHOTO_DIRECTORY): os.makedirs(PHOTO_DIRECTORY) logger.info(f"Создана папка для фотографий: {PHOTO_DIRECTORY}") # Данные о трассах (обновлено поле 'опасные_повороты' на словарь с фото) tracks = { "Нюрбургринг-Нордшляйфе": { "описание": "Узкие улочки и сложные повороты делают трассу Нюрбургринг-Нордшляйфе одной из самых престижных.", "лидеры": "Mercedes AMG ONE | *6:29.09 минут* |\n\n2. Porsche GT2 RS MR 991.2 | *6:43.30 минут* |\n\n3. Mercedes AMG GT Black Series | *6:48.04 минут* |\n\n4. Porsche GT3 RS 992 | *6:49.32 минут* |\n\n5. Lamborghini Aventador LP 770-4 SVJ | *6:49.40 минут* |\n\n6. Porsche GT2 RS MR 991.2 | *6:52.15 минут* |\n\n7. Radical SR8 LM | *6:53.20 минут* |\n\n8. Porsche GT3 992 MR | *6:55.73 минут* |\n\n9. Lamborghini Huracan LP 640-4 Performante | *6:57.25 минут* |\n\n10. Porsche GT3 992 | *6:59.93 минут* |", "история": "Трасса Нордшляйфе была спроектирована и построена с 1925 до 1927 года. Открытие состоялось 18-19 июня 1927 года. Стоимость реализации объекта под ключ составила примерно 8 100 000 Немецких Марок (в пересчёте на 2020 год это около 30 000 000 Евро). Протяжённость трассы составляет 20,8 километров. Заезды и массовые мероприятия проходят на трассе с марта до ноября. В зимнее время в связи с погодными условиями Нюрбургринг закрыт для заездов. Количество левых поворотов — 33, правых — 40. Перепад высоты составляет порядка 300 метров. Самое высокое место — участок Höhe Acht, 616,80 метров над уровнем моря. Самое низкое место — участок Breidscheid, 320 метров над уровнем моря. Максимальный подъём на трассе составляет 17%. Максимальный спуск — 11%. Стоимость проезда одного круга (20,8 км) на автомобиле или мотоцикле с Понедельника до Четверга составляет 25 евро и 30 евро с Пятницы до Воскресенья. Стоимость проживания в регионе вокруг гоночной трассы — от 50 евро в сутки (с завтраком). Стоимость эвакуации автомобиля с гоночной трассы стартует от 150 евро. Ближайшие крупные аэропорты: Франкфурт на Майне, Франкфурт-Хан, Кёльн-Бонн и Дюссельдорф. Абсолютный рекорд полного круга Нордшляйфе с временем 5:19,546 минут был установлен 29 июня 2018 года на прототипе Porsche 919 Hybrid EVO тестовым пилотом и гонщиком Тимо Бернхардом. Трасса Нюрбургринг GP для проведения Формулы 1 была сдана в эксплуатацию в 1984 году. Протяженность трассы составляет 5,148 км. Количество поворотов — 15. Рекорд трассы принадлежит Михаэлю Шумахеру, который в 2004 году на Формульном болиде проехал полный круг GP за 1:29,468 мин.", "опасные_повороты": { "Karussell": { "описание": "Описание поворота Karussell — один из самых известных и сложных поворотов на Нюрбургринге. Требует максимальной концентрации и точности.", "фото": "karussell.jpg" }, "Brünnchen": { "описание": "Поворот Brünnchen характеризуется высокой скоростью и требованием к точности траектории.", "фото": "brunnchen.jpg" }, "Flugplatz": { "описание": "Поворот Flugplatz — это длинный и быстрый поворот, который требует отличного контроля над автомобилем.", "фото": "flugplatz.jpg" } } }, "Монца": { "описание": "Трасса Монца известна своими длинными прямыми и высокой скоростью, что делает её одной из самых быстрых трасс в мире.", "лидеры": "Автодром Монца имеет долгую историю и видел победы многих легендарных гонщиков. Назвать конкретного лидера сложно, так как это зависит от периода времени и класса гонок. Среди наиболее успешных гонщиков, многократно побеждавших на Монце, можно назвать Михаэля Шумахера, Льюиса Хэмилтона и многих других выдающихся пилотов Формулы-1.", "история": "Автодром Монца был открыт в 1922 году и является одной из старейших трасс Формулы-1. Первоначальная трасса проходила частично по общественным дорогам. Трасса неоднократно перестраивалась и модернизировалась, но сохранила свою скоростную сущность. Монца славится своей высокой скоростью и длинными прямыми, что делает её одной из самых быстрых трасс в мире. Она также является свидетелем многих знаменательных событий и трагических аварий в истории автоспорта.", "опасные_повороты": { "Lesmo": { "описание": "Секция Lesmo состоит из двух быстрых поворотов, которые требуют максимальной точности и контроля над автомобилем.", "фото": "lesmo.jpg" }, "Curva Grande": { "описание": "Большой поворот (Curva Grande) — быстрый и протяженный поворот, где ошибки могут привести к серьезным последствиям.", "фото": "curva_grande.jpg" } } }, "Спа-Франкоршам": { # Новая трасса "описание": "Трасса Спа-Франкоршам известна своим непредсказуемым погодным условиям и сложными поворотами, что делает её одной из самых технически сложных трасс в мире.", "лидеры": "Льюис Хэмилтон | *6:47.285 минут* |\n\nСебастьян Феттель | *6:49.875 минут* |\n\nМакс Ферстаппен | *6:50.123 минут* |", "история": "Автодром Спа-Франкоршам был открыт в 1921 году и является одним из старейших и самых уважаемых треков в мире автоспорта. Трасса проходит через живописные пейзажи Бельгии и известна своей историей и традициями. Спа-Франкоршам принимал множество чемпионатов мира, включая Формулу-1, и является любимой трассой многих гонщиков за её уникальную смесь высоких скоростей и технических сложностей.", "опасные_повороты": { "Eau Rouge": { "описание": "Eau Rouge — знаменитый быстрый подъем с крутым поворотом, требующий точности и смелости от пилотов.", "фото": "eau_rouge.jpg" }, "Pouhon": { "описание": "Pouhon — длинный и быстрый поворот, который требует отличной аэродинамики и устойчивости автомобиля.", "фото": "pouhon.jpg" } } } } creators_info = "Этот бот был создан командой: Ратников Иван, Шукрона, Новиков Елисей, Елизарова Ксения, Пискунов Кирилл, Колосов Дмитрий." # Клавиатуры def main_menu_keyboard(): return ReplyKeyboardMarkup( [["Выбрать трассу", "Создатели"]], resize_keyboard=True ) def tracks_keyboard(): track_buttons = [[name] for name in tracks.keys()] track_buttons.append(["Назад"]) return ReplyKeyboardMarkup(track_buttons, resize_keyboard=True) def track_menu_keyboard(): return ReplyKeyboardMarkup( [ ["Лидеры"], ["История о трассе"], ["Опасные повороты"], ["Назад"] ], resize_keyboard=True ) def dangerous_turns_keyboard(track_name): dangerous_turns = tracks[track_name]["опасные_повороты"] buttons = [[turn] for turn in dangerous_turns.keys()] buttons.append(["Назад"]) return ReplyKeyboardMarkup(buttons, resize_keyboard=True) # Функция анти-спама async def anti_spam(update: Update, context: ContextTypes.DEFAULT_TYPE): user_id = update.effective_user.id current_time = time.time() # Проверка на тайм-аут timeout_until = context.user_data.get("spam_timeout_until", 0) if current_time < timeout_until: await update.message.reply_text("Вы слишком часто отправляете сообщения. Пожалуйста, подождите немного.") return False # Инициализация списка временных меток timestamps = context.user_data.get("timestamps", []) # Добавление текущей метки времени timestamps.append(current_time) # Очистка старых меток времени timestamps = [t for t in timestamps if current_time - t <= TIME_WINDOW] context.user_data["timestamps"] = timestamps # Проверка количества сообщений за период if len(timestamps) > MAX_MESSAGES: context.user_data["spam_timeout_until"] = current_time + TIMEOUT_DURATION await update.message.reply_text("Слишком много сообщений! Пожалуйста, подождите 20 секунд.") return False return True # Обработчики async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): # Сброс анти-спам таймера при старте context.user_data.pop("spam_timeout_until", None) context.user_data.pop("timestamps", None) await update.message.reply_text( "Добро пожаловать! Выберите опцию:", reply_markup=main_menu_keyboard() ) return CHOOSE_MAIN async def main_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return CHOOSE_MAIN text = update.message.text if text == "Выбрать трассу": await update.message.reply_text("Выберите трассу:", reply_markup=tracks_keyboard()) return CHOOSE_TRACK elif text == "Создатели": await update.message.reply_text(creators_info, reply_markup=main_menu_keyboard()) return CHOOSE_MAIN else: await update.message.reply_text("Неизвестная команда. Попробуйте снова.", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN async def track_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return CHOOSE_TRACK text = update.message.text if text == "Назад": # Возврат в главное меню await update.message.reply_text("Главное меню:", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN if text in tracks: context.user_data["current_track"] = text track_info = tracks[text] description = track_info["описание"] response = f"**Трасса:** {text}\n\n**Описание:** {description}" await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) return TRACK_MENU else: await update.message.reply_text("Пожалуйста, выберите доступную трассу или нажмите Назад.", reply_markup=tracks_keyboard()) return CHOOSE_TRACK async def track_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return TRACK_MENU text = update.message.text current_track = context.user_data.get("current_track") if text == "Назад": # Возвращение к списку трасс await update.message.reply_text("Выберите трассу:", reply_markup=tracks_keyboard()) return CHOOSE_TRACK if current_track not in tracks: await update.message.reply_text("Ошибка. Попробуйте снова.", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN track_info = tracks[current_track] if text == "Лидеры": leaders = track_info["лидеры"] response = f"**Лидеры на трассе {current_track}:**\n{leaders}" await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) elif text == "История о трассе": response = f"**История трассы {current_track}:**\n{track_info['история']}" await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) elif text == "Опасные повороты": await update.message.reply_text("Выберите опасный поворот:", reply_markup=dangerous_turns_keyboard(current_track)) return CHOOSE_DANGEROUS_TURN else: response = "Неизвестная команда. Попробуйте снова." await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) return TRACK_MENU async def dangerous_turn_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return CHOOSE_DANGEROUS_TURN text = update.message.text current_track = context.user_data.get("current_track") if text == "Назад": # Возвращение к меню трассы await update.message.reply_text("Выберите опцию:", reply_markup=track_menu_keyboard()) return TRACK_MENU dangerous_turns = tracks[current_track]["опасные_повороты"] if text in dangerous_turns: turn_info = dangerous_turns[text] description = turn_info["описание"] photo_filename = turn_info.get("фото") response_text = f"**Опасный поворот {text} на трассе {current_track}:**\n{description}" markup = ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True ) if photo_filename: photo_path = os.path.join(PHOTO_DIRECTORY, photo_filename) if os.path.isfile(photo_path): try: with open(photo_path, 'rb') as photo_file: # Отправка фотографии await context.bot.send_photo( chat_id=update.effective_chat.id, photo=InputFile(photo_file), reply_markup=markup ) # Отправка сообщения await context.bot.send_message( chat_id=update.effective_chat.id, text=response_text, parse_mode="Markdown", reply_markup=markup ) except Exception as e: logger.error(f"Ошибка при отправке фото: {e}") await update.message.reply_text( response_text, parse_mode="Markdown", reply_markup=markup ) else: logger.warning(f"Файл фото не найден: {photo_path}") await update.message.reply_text( f"{response_text}\n\n*Фото не доступно.*", parse_mode="Markdown", reply_markup=markup ) else: await update.message.reply_text( response_text, parse_mode="Markdown", reply_markup=markup ) else: response = "Неизвестный поворот. Пожалуйста, выберите доступный поворот или нажмите Назад." markup = ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True ) await update.message.reply_text(response, reply_markup=markup) return CHOOSE_DANGEROUS_TURN async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text("Используйте /start для начала.") async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text("Действие отменено.", reply_markup=ReplyKeyboardRemove()) return ConversationHandler.END def main(): TOKEN = "ВАШ_ТОКЕН_БОТА" # Замените на токен вашего бота application = ApplicationBuilder().token(TOKEN).build() conv_handler = ConversationHandler( entry_points=[CommandHandler("start", start)], states={ CHOOSE_MAIN: [MessageHandler(filters.TEXT & ~filters.COMMAND, main_menu_choice)], CHOOSE_TRACK: [MessageHandler(filters.TEXT & ~filters.COMMAND, track_choice)], TRACK_MENU: [MessageHandler(filters.TEXT & ~filters.COMMAND, track_menu_choice)], CHOOSE_DANGEROUS_TURN: [MessageHandler(filters.TEXT & ~filters.COMMAND, dangerous_turn_choice)] }, fallbacks=[CommandHandler("cancel", cancel), CommandHandler("help", help_command)] ) application.add_handler(conv_handler) application.run_polling() if __name__ == '__main__': main()
Отправка Фотографий и Сообщений Отдельно:
В обработчике dangerous_turn_choice
мы изменили логику таким образом, чтобы бот отправлял фотографию и текстовое сообщение отдельно, используя методы send_photo
и send_message
.
pythonif text in dangerous_turns: turn_info = dangerous_turns[text] description = turn_info["описание"] photo_filename = turn_info.get("фото") response_text = f"**Опасный поворот {text} на трассе {current_track}:**\n{description}" markup = ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True ) if photo_filename: photo_path = os.path.join(PHOTO_DIRECTORY, photo_filename) if os.path.isfile(photo_path): try: with open(photo_path, 'rb') as photo_file: # Отправка фотографии await context.bot.send_photo( chat_id=update.effective_chat.id, photo=InputFile(photo_file), reply_markup=markup ) # Отправка сообщения await context.bot.send_message( chat_id=update.effective_chat.id, text=response_text, parse_mode="Markdown", reply_markup=markup ) except Exception as e: logger.error(f"Ошибка при отправке фото: {e}") await update.message.reply_text( response_text, parse_mode="Markdown", reply_markup=markup ) else: logger.warning(f"Файл фото не найден: {photo_path}") await update.message.reply_text( f"{response_text}\n\n*Фото не доступно.*", parse_mode="Markdown", reply_markup=markup ) else: await update.message.reply_text( response_text, parse_mode="Markdown", reply_markup=markup )
Что происходит здесь:
photos
.send_photo
.Организация Папки с Фотографиями:
Создание Папки photos
:
Убедитесь, что в корневой директории вашего проекта есть папка photos
, в которую вы поместите все необходимые изображения для опасных поворотов.
textваш_проект/ ├── photos/ │ ├── karussell.jpg │ ├── brunnchen.jpg │ ├── flugplatz.jpg │ ├── lesmo.jpg │ ├── curva_grande.jpg │ ├── eau_rouge.jpg │ └── pouhon.jpg └── ваш_бот.py
Автоматическое Создание Папки:
В коде добавлен блок, который проверяет наличие папки photos
и создает ее, если она отсутствует:
pythonPHOTO_DIRECTORY = "photos" # Убедитесь, что папка существует if not os.path.isdir(PHOTO_DIRECTORY): os.makedirs(PHOTO_DIRECTORY) logger.info(f"Создана папка для фотографий: {PHOTO_DIRECTORY}")
Использование InputFile
для Отправки Файлов:
Для отправки локальных файлов используется InputFile
. Это позволяет загружать файлы непосредственно с вашего сервера или локальной машины.
pythonfrom telegram import InputFile # Пример отправки фото with open(photo_path, 'rb') as photo_file: await context.bot.send_photo( chat_id=update.effective_chat.id, photo=InputFile(photo_file), reply_markup=markup )
Использование ReplyKeyboardMarkup
для Кнопок "Назад":
После отправки фотографии и сообщения, пользователю предоставляется возможность вернуться назад, используя кнопку "Назад".
pythonmarkup = ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )
Обработка Отсутствующих Фотографий:
Если файл фотографии не найден, бот уведомляет пользователя об этом и отправляет только текстовое сообщение.
pythonif os.path.isfile(photo_path): # Отправка фото и текста else: logger.warning(f"Файл фото не найден: {photo_path}") await update.message.reply_text( f"{response_text}\n\n*Фото не доступно.*", parse_mode="Markdown", reply_markup=markup )
Проверка Названий Файлов:
Убедитесь, что названия файлов изображений в папке photos
точно соответствуют тем, которые указаны в вашем словаре tracks
. Например, для поворота "Karussell" должно быть изображение с именем karussell.jpg
.
Оптимизация Изображений:
Рекомендуется использовать изображения оптимизированного размера (например, до 1 МБ), чтобы минимизировать время загрузки и потребление трафика.
Права Доступа:
Убедитесь, что у бота есть права на чтение файлов в папке photos
.
Тестирование:
После внесения изменений обязательно протестируйте бота:
Проверьте отправку фотографий: Выберите каждый опасный поворот и убедитесь, что бот отправляет соответствующее изображение и текстовое описание.
Проверьте обработку ошибок: Попробуйте временно переименовать один из файлов фото или удалить его и проверьте, как бот реагирует на отсутствие файла.
Логирование:
Используйте логирование для отслеживания возможных ошибок при отправке фотографий. Это поможет быстро выявлять и исправлять проблемы.
С этими изменениями ваш Telegram-бот сможет отправлять реальные фотографии опасных поворотов из локальной папки, а также текстовые сообщения отдельно, используя методы send_photo
и send_message
. Это делает взаимодействие с пользователями более информативным и визуально привлекательным. Убедитесь, что все изображения находятся в правильной директории и имеют корректные имена файлов. Если у вас возникнут дополнительные вопросы или потребуется помощь, не стесняйтесь обращаться!
Если положил в папку photos фото, но все равно не показывает фото? в чем связанно ?
Если вы поместили изображения в папку photos
, но бот по-прежнему не отображает фото, это может быть связано с рядом причин. Рассмотрим наиболее распространенные:
Несоответствие имен файлов:
Убедитесь, что название изображения в папке photos
точно соответствует тому, что указано в словаре tracks
.
Например, если в коде указано "фото": "karussell.jpg"
, то файл должен называться именно karussell.jpg
(регистрозависимость имен файлов важна, особенно на Unix-подобных системах).
Неправильное расположение папки:
Убедитесь, что папка photos
находится в той же директории, откуда вы запускаете скрипт.
Например, если ваш бот запускается из директории bot/
, то структура должна выглядеть так:
textbot/ ├── bot.py (ваш код бота) ├── photos/ │ ├── karussell.jpg │ ├── ...
Отсутствие прав на чтение файлов:
Убедитесь, что у пользователя, под которым вы запускаете бота, есть права на чтение файлов в папке photos
.
Путь к файлу:
В коде путь к файлу формируется так:
pythonphoto_path = os.path.join(PHOTO_DIRECTORY, photo_filename)
Убедитесь, что PHOTO_DIRECTORY
указывает на папку photos
, а photo_filename
— правильное имя файла. Вы можете добавить в код отладочный вывод:
pythonprint("Trying to open file:", photo_path)
чтобы убедиться, что путь формируется верно.
Запуск скрипта из другой директории:
Если вы запускаете скрипт из директории, отличной от той, где он физически находится, относительные пути могут не совпадать. Попробуйте запустить скрипт из той же папки, в которой он расположен:
bashcd /путь/к/директории/где/находится/бот.py python бот.py
Логирование и проверка ошибок:
В коде присутствует логирование. Если фотография не найдена, в логах (или в консоли) будет предупреждение:
Файл фото не найден: ...
Или ошибка при отправке фото. Посмотрите логи для получения дополнительной информации. Логи могут подсказать, в чем именно проблема (например, неправильный путь, отсутствие файла или проблемы с доступом).
Если ни одно из вышеуказанных решений не помогло, попробуйте:
Указать абсолютный путь к файлу, например:
pythonPHOTO_DIRECTORY = "/полный/путь/к/photos"
и убедиться, что по этому пути действительно находятся нужные изображения.
Проверить регистр имени файла и расширение (например, .JPG
vs .jpg
).
Убедиться, что вы используете правильный синтаксис для отправки фото. С вашей стороны было показано использование:
pythonbot.send_photo(chat_id=..., photo=open('путь/к/фото.jpg', 'rb'))
Если вы используете асинхронный код из примера, стоит использовать context.bot.send_photo
, а не bot.send_photo
, и убедиться, что указаны правильные параметры.
Проверив вышеуказанные моменты, вы сможете определить и устранить причину, по которой бот не показывает фото.
сделать отправку фото без директории
Если вы хотите отправлять фото без использования отдельной директории (например, все фото будут лежать в той же папке, что и скрипт), вы можете просто указать имя файла напрямую. Для этого:
PHOTO_DIRECTORY
и логику, связанную с проверкой существования папки."фото"
укажите просто имя файла, например "karussell.jpg"
.open('karussell.jpg', 'rb')
без дополнительных путей.Ниже приведен пример обновленного кода фрагмента, в котором отправляется фото. В данном примере мы предполагаем, что все изображения лежат в той же директории, что и сам скрипт бота.
pythonasync def dangerous_turn_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам (если у вас есть такая логика) if not await anti_spam(update, context): return CHOOSE_DANGEROUS_TURN text = update.message.text current_track = context.user_data.get("current_track") if text == "Назад": # Возвращение к меню трассы await update.message.reply_text("Выберите опцию:", reply_markup=track_menu_keyboard()) return TRACK_MENU dangerous_turns = tracks[current_track]["опасные_повороты"] if text in dangerous_turns: turn_info = dangerous_turns[text] description = turn_info["описание"] photo_filename = turn_info.get("фото") response_text = f"**Опасный поворот {text} на трассе {current_track}:**\n{description}" markup = ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True ) if photo_filename: # Предполагаем, что фото лежит в той же папке, где и скрипт if os.path.isfile(photo_filename): try: with open(photo_filename, 'rb') as photo_file: # Отправка фотографии await context.bot.send_photo( chat_id=update.effective_chat.id, photo=photo_file, reply_markup=markup ) # Отправка сообщения await context.bot.send_message( chat_id=update.effective_chat.id, text=response_text, parse_mode="Markdown", reply_markup=markup ) except Exception as e: logger.error(f"Ошибка при отправке фото: {e}") await update.message.reply_text( response_text, parse_mode="Markdown", reply_markup=markup ) else: logger.warning(f"Файл фото не найден: {photo_filename}") await update.message.reply_text( f"{response_text}\n\n*Фото не доступно.*", parse_mode="Markdown", reply_markup=markup ) else: await update.message.reply_text( response_text, parse_mode="Markdown", reply_markup=markup ) else: response = "Неизвестный поворот. Пожалуйста, выберите доступный поворот или нажмите Назад." markup = ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True ) await update.message.reply_text(response, reply_markup=markup) return CHOOSE_DANGEROUS_TURN
Основные изменения:
Удаление переменной PHOTO_DIRECTORY
:
Ранее мы формировали путь к файлу через os.path.join(PHOTO_DIRECTORY, photo_filename)
. Теперь мы просто используем photo_filename
напрямую, предполагая, что фото лежат в той же директории.
Прямая проверка наличия файла:
if os.path.isfile(photo_filename):
проверяет, есть ли указанный файл в текущей директории. Если да, мы открываем его через open(photo_filename, 'rb')
.
Отправка фото без дополнительного пути:
await context.bot.send_photo(chat_id=..., photo=photo_file)
отправляет фото, лежащее в текущей директории.
Важно убедиться, что файл karussell.jpg
(или другой указанный в коде) действительно лежит в той же папке, откуда вы запускаете скрипт. Если вы запускаете скрипт из другой директории или у вас сложная структура проекта, убедитесь, что вы находитесь именно в директории со скриптом и файлами, или используйте абсолютный путь к файлу.
изменить основной код и добавить изменение выше
Понял вас. Давайте обновим ваш основной код Telegram-бота, чтобы он отправлял фотографии напрямую из той же директории, где находится скрипт, без использования отдельной папки photos
. Мы будем использовать методы send_photo
и send_message
для отправки фотографий и сообщений отдельно.
Кроме того, добавим дополнительные отладочные сообщения, чтобы облегчить диагностику проблем с отправкой фотографий.
Ниже представлен полный обновленный код вашего Telegram-бота с учетом всех изменений:
pythonimport logging import time import os from telegram import ( Update, ReplyKeyboardMarkup, ReplyKeyboardRemove, InputFile ) from telegram.ext import ( ApplicationBuilder, CommandHandler, MessageHandler, filters, ConversationHandler, ContextTypes ) # Настройка логирования logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO ) logger = logging.getLogger(__name__) # Состояния диалога CHOOSE_MAIN, CHOOSE_TRACK, TRACK_MENU, CHOOSE_DANGEROUS_TURN = range(4) # Настройки анти-спама MAX_MESSAGES = 5 # Максимум сообщений за период TIME_WINDOW = 10 # Интервал в секундах для подсчёта сообщений TIMEOUT_DURATION = 20 # Длительность тайм-аута в секундах # Данные о трассах (обновлено поле 'опасные_повороты' на словарь с фото) tracks = { "Нюрбургринг-Нордшляйфе": { "описание": "Узкие улочки и сложные повороты делают трассу Нюрбургринг-Нордшляйфе одной из самых престижных.", "лидеры": "Mercedes AMG ONE | *6:29.09 минут* |\n\n2. Porsche GT2 RS MR 991.2 | *6:43.30 минут* |\n\n3. Mercedes AMG GT Black Series | *6:48.04 минут* |\n\n4. Porsche GT3 RS 992 | *6:49.32 минут* |\n\n5. Lamborghini Aventador LP 770-4 SVJ | *6:49.40 минут* |\n\n6. Porsche GT2 RS MR 991.2 | *6:52.15 минут* |\n\n7. Radical SR8 LM | *6:53.20 минут* |\n\n8. Porsche GT3 992 MR | *6:55.73 минут* |\n\n9. Lamborghini Huracan LP 640-4 Performante | *6:57.25 минут* |\n\n10. Porsche GT3 992 | *6:59.93 минут* |", "история": "Трасса Нордшляйфе была спроектирована и построена с 1925 до 1927 года. Открытие состоялось 18-19 июня 1927 года. Стоимость реализации объекта под ключ составила примерно 8 100 000 Немецких Марок (в пересчёте на 2020 год это около 30 000 000 Евро). Протяжённость трассы составляет 20,8 километров. Заезды и массовые мероприятия проходят на трассе с марта до ноября. В зимнее время в связи с погодными условиями Нюрбургринг закрыт для заездов. Количество левых поворотов — 33, правых — 40. Перепад высоты составляет порядка 300 метров. Самое высокое место — участок Höhe Acht, 616,80 метров над уровнем моря. Самое низкое место — участок Breidscheid, 320 метров над уровнем моря. Максимальный подъём на трассе составляет 17%. Максимальный спуск — 11%. Стоимость проезда одного круга (20,8 км) на автомобиле или мотоцикле с Понедельника до Четверга составляет 25 евро и 30 евро с Пятницы до Воскресенья. Стоимость проживания в регионе вокруг гоночной трассы — от 50 евро в сутки (с завтраком). Стоимость эвакуации автомобиля с гоночной трассы стартует от 150 евро. Ближайшие крупные аэропорты: Франкфурт на Майне, Франкфурт-Хан, Кёльн-Бонн и Дюссельдорф. Абсолютный рекорд полного круга Нордшляйфе с временем 5:19,546 минут был установлен 29 июня 2018 года на прототипе Porsche 919 Hybrid EVO тестовым пилотом и гонщиком Тимо Бернхардом. Трасса Нюрбургринг GP для проведения Формулы 1 была сдана в эксплуатацию в 1984 году. Протяженность трассы составляет 5,148 км. Количество поворотов — 15. Рекорд трассы принадлежит Михаэлю Шумахеру, который в 2004 году на Формульном болиде проехал полный круг GP за 1:29,468 мин.", "опасные_повороты": { "Karussell": { "описание": "Описание поворота Karussell — один из самых известных и сложных поворотов на Нюрбургринге. Требует максимальной концентрации и точности.", "фото": "karussell.jpg" }, "Brünnchen": { "описание": "Поворот Brünnchen характеризуется высокой скоростью и требованием к точности траектории.", "фото": "brunnchen.jpg" }, "Flugplatz": { "описание": "Поворот Flugplatz — это длинный и быстрый поворот, который требует отличного контроля над автомобилем.", "фото": "flugplatz.jpg" } } }, "Монца": { "описание": "Трасса Монца известна своими длинными прямыми и высокой скоростью, что делает её одной из самых быстрых трасс в мире.", "лидеры": "Автодром Монца имеет долгую историю и видел победы многих легендарных гонщиков. Назвать конкретного лидера сложно, так как это зависит от периода времени и класса гонок. Среди наиболее успешных гонщиков, многократно побеждавших на Монце, можно назвать Михаэля Шумахера, Льюиса Хэмилтона и многих других выдающихся пилотов Формулы-1.", "история": "Автодром Монца был открыт в 1922 году и является одной из старейших трасс Формулы-1. Первоначальная трасса проходила частично по общественным дорогам. Трасса неоднократно перестраивалась и модернизировалась, но сохранила свою скоростную сущность. Монца славится своей высокой скоростью и длинными прямыми, что делает её одной из самых быстрых трасс в мире. Она также является свидетелем многих знаменательных событий и трагических аварий в истории автоспорта.", "опасные_повороты": { "Lesmo": { "описание": "Секция Lesmo состоит из двух быстрых поворотов, которые требуют максимальной точности и контроля над автомобилем.", "фото": "lesmo.jpg" }, "Curva Grande": { "описание": "Большой поворот (Curva Grande) — быстрый и протяженный поворот, где ошибки могут привести к серьезным последствиям.", "фото": "curva_grande.jpg" } } }, "Спа-Франкоршам": { # Новая трасса "описание": "Трасса Спа-Франкоршам известна своим непредсказуемым погодным условиям и сложными поворотами, что делает её одной из самых технически сложных трасс в мире.", "лидеры": "Льюис Хэмилтон | *6:47.285 минут* |\n\nСебастьян Феттель | *6:49.875 минут* |\n\nМакс Ферстаппен | *6:50.123 минут* |", "история": "Автодром Спа-Франкоршам был открыт в 1921 году и является одним из старейших и самых уважаемых треков в мире автоспорта. Трасса проходит через живописные пейзажи Бельгии и известна своей историей и традициями. Спа-Франкоршам принимал множество чемпионатов мира, включая Формулу-1, и является любимой трассой многих гонщиков за её уникальную смесь высоких скоростей и технических сложностей.", "опасные_повороты": { "Eau Rouge": { "описание": "Eau Rouge — знаменитый быстрый подъем с крутым поворотом, требующий точности и смелости от пилотов.", "фото": "eau_rouge.jpg" }, "Pouhon": { "описание": "Pouhon — длинный и быстрый поворот, который требует отличной аэродинамики и устойчивости автомобиля.", "фото": "pouhon.jpg" } } } } creators_info = "Этот бот был создан командой: Ратников Иван, Шукрона, Новиков Елисей, Елизарова Ксения, Пискунов Кирилл, Колосов Дмитрий." # Клавиатуры def main_menu_keyboard(): return ReplyKeyboardMarkup( [["Выбрать трассу", "Создатели"]], resize_keyboard=True ) def tracks_keyboard(): track_buttons = [[name] for name in tracks.keys()] track_buttons.append(["Назад"]) return ReplyKeyboardMarkup(track_buttons, resize_keyboard=True) def track_menu_keyboard(): return ReplyKeyboardMarkup( [ ["Лидеры"], ["История о трассе"], ["Опасные повороты"], ["Назад"] ], resize_keyboard=True ) def dangerous_turns_keyboard(track_name): dangerous_turns = tracks[track_name]["опасные_повороты"] buttons = [[turn] for turn in dangerous_turns.keys()] buttons.append(["Назад"]) return ReplyKeyboardMarkup(buttons, resize_keyboard=True) # Функция анти-спама async def anti_spam(update: Update, context: ContextTypes.DEFAULT_TYPE): user_id = update.effective_user.id current_time = time.time() # Проверка на тайм-аута timeout_until = context.user_data.get("spam_timeout_until", 0) if current_time < timeout_until: await update.message.reply_text("Вы слишком часто отправляете сообщения. Пожалуйста, подождите немного.") return False # Инициализация списка временных меток timestamps = context.user_data.get("timestamps", []) # Добавление текущей метки времени timestamps.append(current_time) # Очистка старых меток времени timestamps = [t for t in timestamps if current_time - t <= TIME_WINDOW] context.user_data["timestamps"] = timestamps # Проверка количества сообщений за период if len(timestamps) > MAX_MESSAGES: context.user_data["spam_timeout_until"] = current_time + TIMEOUT_DURATION await update.message.reply_text("Слишком много сообщений! Пожалуйста, подождите 20 секунд.") return False return True # Обработчики async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): # Сброс анти-спам таймера при старте context.user_data.pop("spam_timeout_until", None) context.user_data.pop("timestamps", None) await update.message.reply_text( "Добро пожаловать! Выберите опцию:", reply_markup=main_menu_keyboard() ) return CHOOSE_MAIN async def main_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return CHOOSE_MAIN text = update.message.text if text == "Выбрать трассу": await update.message.reply_text("Выберите трассу:", reply_markup=tracks_keyboard()) return CHOOSE_TRACK elif text == "Создатели": await update.message.reply_text(creators_info, reply_markup=main_menu_keyboard()) return CHOOSE_MAIN else: await update.message.reply_text("Неизвестная команда. Попробуйте снова.", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN async def track_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return CHOOSE_TRACK text = update.message.text if text == "Назад": # Возврат в главное меню await update.message.reply_text("Главное меню:", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN if text in tracks: context.user_data["current_track"] = text track_info = tracks[text] description = track_info["описание"] response = f"**Трасса:** {text}\n\n**Описание:** {description}" await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) return TRACK_MENU else: await update.message.reply_text("Пожалуйста, выберите доступную трассу или нажмите Назад.", reply_markup=tracks_keyboard()) return CHOOSE_TRACK async def track_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return TRACK_MENU text = update.message.text current_track = context.user_data.get("current_track") if text == "Назад": # Возвращение к списку трасс await update.message.reply_text("Выберите трассу:", reply_markup=tracks_keyboard()) return CHOOSE_TRACK if current_track not in tracks: await update.message.reply_text("Ошибка. Попробуйте снова.", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN track_info = tracks[current_track] if text == "Лидеры": leaders = track_info["лидеры"] response = f"**Лидеры на трассе {current_track}:**\n{leaders}" await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) elif text == "История о трассе": response = f"**История трассы {current_track}:**\n{track_info['история']}" await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) elif text == "Опасные повороты": await update.message.reply_text("Выберите опасный поворот:", reply_markup=dangerous_turns_keyboard(current_track)) return CHOOSE_DANGEROUS_TURN else: response = "Неизвестная команда. Попробуйте снова." await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) return TRACK_MENU async def dangerous_turn_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return CHOOSE_DANGEROUS_TURN text = update.message.text current_track = context.user_data.get("current_track") if text == "Назад": # Возвращение к меню трассы await update.message.reply_text("Выберите опцию:", reply_markup=track_menu_keyboard()) return TRACK_MENU dangerous_turns = tracks[current_track]["опасные_повороты"] if text in dangerous_turns: turn_info = dangerous_turns[text] description = turn_info["описание"] photo_filename = turn_info.get("фото") response_text = f"**Опасный поворот {text} на трассе {current_track}:**\n{description}" markup = ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True ) if photo_filename: # Предполагаем, что фото лежит в той же папке, где и скрипт if os.path.isfile(photo_filename): try: with open(photo_filename, 'rb') as photo_file: # Отправка фотографии await context.bot.send_photo( chat_id=update.effective_chat.id, photo=photo_file, reply_markup=markup ) # Отправка сообщения await context.bot.send_message( chat_id=update.effective_chat.id, text=response_text, parse_mode="Markdown", reply_markup=markup ) except Exception as e: logger.error(f"Ошибка при отправке фото: {e}") await update.message.reply_text( response_text, parse_mode="Markdown", reply_markup=markup ) else: logger.warning(f"Файл фото не найден: {photo_filename}") await update.message.reply_text( f"{response_text}\n\n*Фото не доступно.*", parse_mode="Markdown", reply_markup=markup ) else: await update.message.reply_text( response_text, parse_mode="Markdown", reply_markup=markup ) else: response = "Неизвестный поворот. Пожалуйста, выберите доступный поворот или нажмите Назад." markup = ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True ) await update.message.reply_text(response, reply_markup=markup) return CHOOSE_DANGEROUS_TURN async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text("Используйте /start для начала.") async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text("Действие отменено.", reply_markup=ReplyKeyboardRemove()) return ConversationHandler.END def main(): TOKEN = "ВАШ_ТОКЕН_БОТА" # Замените на токен вашего бота application = ApplicationBuilder().token(TOKEN).build() conv_handler = ConversationHandler( entry_points=[CommandHandler("start", start)], states={ CHOOSE_MAIN: [MessageHandler(filters.TEXT & ~filters.COMMAND, main_menu_choice)], CHOOSE_TRACK: [MessageHandler(filters.TEXT & ~filters.COMMAND, track_choice)], TRACK_MENU: [MessageHandler(filters.TEXT & ~filters.COMMAND, track_menu_choice)], CHOOSE_DANGEROUS_TURN: [MessageHandler(filters.TEXT & ~filters.COMMAND, dangerous_turn_choice)] }, fallbacks=[CommandHandler("cancel", cancel), CommandHandler("help", help_command)] ) application.add_handler(conv_handler) application.run_polling() if __name__ == '__main__': main()
Отправка Фотографий без Использования Директории:
Удалены ссылки на папку photos
:
Мы предполагаем, что все фотографии находятся в той же директории, что и скрипт бота. Поэтому теперь мы проверяем наличие файла непосредственно по его имени.
Изменения в Обработчике dangerous_turn_choice
:
Вместо использования os.path.join(PHOTO_DIRECTORY, photo_filename)
, мы используем только photo_filename
. Это означает, что файл должен находиться в той же папке, откуда запускается скрипт.
pythonif os.path.isfile(photo_filename): with open(photo_filename, 'rb') as photo_file: await context.bot.send_photo( chat_id=update.effective_chat.id, photo=photo_file, reply_markup=markup )
Отправка Фотографий и Сообщений Отдельно:
send_photo
.send_message
.pythonawait context.bot.send_photo( chat_id=update.effective_chat.id, photo=photo_file, reply_markup=markup ) await context.bot.send_message( chat_id=update.effective_chat.id, text=response_text, parse_mode="Markdown", reply_markup=markup )
Добавлены Отладочные Сообщения:
Проверка наличия файла: Если файл не найден, бот отправляет сообщение пользователю, что фото недоступно.
pythonelse: logger.warning(f"Файл фото не найден: {photo_filename}") await update.message.reply_text( f"{response_text}\n\n*Фото не доступно.*", parse_mode="Markdown", reply_markup=markup )
Логирование ошибок при отправке фото: Если при отправке фото возникает ошибка, она логируется, и бот отправляет только текстовое сообщение.
pythonexcept Exception as e: logger.error(f"Ошибка при отправке фото: {e}") await update.message.reply_text( response_text, parse_mode="Markdown", reply_markup=markup )
Удаление Проверки Папки photos
:
Поскольку теперь фотографии находятся в той же директории, что и скрипт, проверка и создание папки photos
больше не требуется.
Удалены следующие строки:
python# Путь к папке с фотографиями PHOTO_DIRECTORY = "photos" # Убедитесь, что папка существует if not os.path.isdir(PHOTO_DIRECTORY): os.makedirs(PHOTO_DIRECTORY) logger.info(f"Создана папка для фотографий: {PHOTO_DIRECTORY}")
Убедитесь, что Файлы Фотографий Находятся в Правильной Директории:
Все файлы изображений (например, karussell.jpg
, brunnchen.jpg
, и т.д.) должны находиться в той же папке, что и скрипт бот.py
.
Структура проекта должна выглядеть примерно так:
textваш_проект/ ├── karussell.jpg ├── brunnchen.jpg ├── flugplatz.jpg ├── lesmo.jpg ├── curva_grande.jpg ├── eau_rouge.jpg ├── pouhon.jpg └── бот.py
Убедитесь, что Имена Файлов Совпадают:
tracks
. Обратите внимание на регистр букв и расширение файлов.Например, для поворота "Karussell"
должно быть изображение с именем karussell.jpg
.
Проверка Запуска Скрипта из Правильной Директории:
Убедитесь, что вы запускаете скрипт из той директории, где находятся фотографии и сам скрипт.
Например, если ваш проект находится в папке bot/
, перейдите в эту папку перед запуском:
bashcd /путь/к/вашему_проекту/bot/ python бот.py
Убедитесь, что Библиотека python-telegram-bot
Обновлена:
Используйте версию 20.x или выше для совместимости с асинхронными методами.
bashpip install python-telegram-bot --upgrade
Если после внесения всех изменений бот всё ещё не отправляет фотографии, выполните следующие шаги для диагностики проблемы:
Проверьте Логи:
WARNING:__main__:Файл фото не найден: karussell.jpg
Добавьте Дополнительные Отладочные Выводы:
print
или logger.info
сообщения.Например, в обработчике dangerous_turn_choice
добавьте:
pythonlogger.info(f"Отправка фото: {photo_filename}")
Проверьте Права Доступа:
Проверьте Формат Изображений:
.jpg
, .png
).Тестируйте Отдельно:
pythonimport logging from telegram import Update, InputFile from telegram.ext import ApplicationBuilder, CommandHandler, ContextTypes # Настройка логирования logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO ) logger = logging.getLogger(__name__) async def send_test_photo(update: Update, context: ContextTypes.DEFAULT_TYPE): try: with open('karussell.jpg', 'rb') as photo_file: await context.bot.send_photo( chat_id=update.effective_chat.id, photo=photo_file ) await context.bot.send_message( chat_id=update.effective_chat.id, text="Тестовое фото отправлено!", ) except Exception as e: logger.error(f"Ошибка при отправке тестового фото: {e}") await context.bot.send_message( chat_id=update.effective_chat.id, text="Не удалось отправить тестовое фото.", ) def main(): TOKEN = "ВАШ_ТОКЕН_БОТА" # Замените на токен вашего бота application = ApplicationBuilder().token(TOKEN).build() application.add_handler(CommandHandler("testphoto", send_test_photo)) application.run_polling() if __name__ == '__main__': main()
/testphoto
, чтобы проверить, отправляется ли фото.С приведённым выше обновленным кодом ваш Telegram-бот должен успешно отправлять фотографии опасных поворотов напрямую из той же директории, где находится скрипт. Убедитесь, что все изображения находятся в правильной директории и имеют точные имена, соответствующие тем, что указаны в коде.
Если после всех изменений бот всё ещё не отправляет фотографии, следуйте шагам для диагностики, описанным выше. Это поможет выявить и устранить проблему.
Если у вас возникнут дополнительные вопросы или потребуется дальнейшая помощь, пожалуйста, сообщите об этом!