Гоночный бот для Telegram
बनाया गया: 18 दिसंबर 2024
बनाया गया: 18 दिसंबर 2024
Создай мне телеграмм бота о гоночных трассах, чтобы при выборе любой трассы можно узнать про нее информацию и лидерах (нужны кнопки чтобы при нажатии на кнопку выдавало ин
Конечно! Я помогу вам создать Telegram-бота о гоночных трассах с использованием Python и библиотеки python-telegram-bot
. Бот будет иметь кнопки для выбора трассы, и при нажатии на кнопку будет отображаться информация о выбранной трассе и текущих лидерах.
Создайте бота в Telegram:
/newbot
и следуйте инструкциям для создания нового бота.Установите необходимые библиотеки:
Убедитесь, что у вас установлен Python (рекомендуется версия 3.7 и выше). Затем установите библиотеку python-telegram-bot
:
bashpip install python-telegram-bot==20.3
Создайте структуру данных: Для простоты мы будем использовать словари для хранения информации о трассах и лидерах. В реальном приложении вы можете использовать базу данных или API для динамического получения данных.
Напишите код бота:
pythonfrom telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup from telegram.ext import ApplicationBuilder, CommandHandler, CallbackQueryHandler, ContextTypes import logging # Включаем логирование logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO ) logger = logging.getLogger(__name__) # Данные о трассах и лидерах tracks = { "Монако": { "описание": "Узкие улочки и сложные повороты делают трассу Монако одной из самых престижных.", "лидеры": ["Льюис Хэмилтон", "Макс Ферстаппен", "Себастьян Феттель"] }, "Спа-Франкоршам": { "описание": "Трасса в Бельгии известна своими изменчивыми погодными условиями и длинными прямыми.", "лидеры": ["Карлос Сайнс", "Шарль Леклер", "Ландо Норрис"] }, "Сильверстоун": { "описание": "Историческая трасса в Великобритании, известная своими скоростными поворотами.", "лидеры": ["Валттери Боттас", "Даниил Квят", "Льюис Хэмилтон"] } # Добавьте другие трассы по необходимости } async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): keyboard = [ [InlineKeyboardButton(track, callback_data=track)] for track in tracks.keys() ] reply_markup = InlineKeyboardMarkup(keyboard) await update.message.reply_text('Выберите гоночную трассу:', reply_markup=reply_markup) async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE): query = update.callback_query await query.answer() track_name = query.data track_info = tracks.get(track_name) if track_info: description = track_info["описание"] leaders = ", ".join(track_info["лидеры"]) response = f"**Трасса:** {track_name}\n\n**Описание:** {description}\n\n**Лидеры:** {leaders}" else: response = "Информация о трассе не найдена." await query.edit_message_text(text=response, parse_mode="Markdown") async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text('Используйте /start для начала.') def main(): # Вставьте сюда токен вашего бота TOKEN = 'ВАШ_ТОКЕН_БОТА' application = ApplicationBuilder().token(TOKEN).build() # Обработчики команд application.add_handler(CommandHandler("start", start)) application.add_handler(CommandHandler("help", help_command)) # Обработчик нажатий на кнопки application.add_handler(CallbackQueryHandler(button_callback)) # Запуск бота application.run_polling() if __name__ == '__main__': main()
Запустите бота:
Сохраните код в файл, например, bot.py
.
Вставьте ваш токен бота вместо 'ВАШ_ТОКЕН_БОТА'
.
Запустите бота командой:
bashpython bot.py
Дополнительные улучшения:
/start
.Этот пример предоставляет базовую структуру для создания Telegram-бота о гоночных трассах. Вы можете расширять функционал бота в зависимости от ваших потребностей, добавляя новые возможности и улучшая пользовательский опыт. Если у вас возникнут вопросы или потребуется дополнительная помощь, не стесняйтесь обращаться!
измени чтобы кнопки были снизу + был выход назад и создатели
Ниже приведен пример кода Telegram-бота, в котором:
Основная логика:
Главное меню (/start
):
Отображает список трасс и кнопку "Создатели" внизу.
Выбор трассы:
При нажатии на кнопку с названием трассы отображается информация о трассе и кнопка "Назад" внизу для возвращения к главному меню.
Создатели:
При нажатии на кнопку "Создатели" отображается информация о создателях и кнопка "Назад" для возвращения к главному меню.
Назад:
Возвращает пользователя в главное меню с выбором трасс.
pythonfrom telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup from telegram.ext import ApplicationBuilder, CommandHandler, CallbackQueryHandler, ContextTypes import logging # Включаем логирование logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO ) logger = logging.getLogger(__name__) # Данные о трассах и лидерах tracks = { "Монако": { "описание": "Узкие улочки и сложные повороты делают трассу Монако одной из самых престижных.", "лидеры": ["Льюис Хэмилтон", "Макс Ферстаппен", "Себастьян Феттель"] }, "Спа-Франкоршам": { "описание": "Трасса в Бельгии известна своими изменчивыми погодными условиями и длинными прямыми.", "лидеры": ["Карлос Сайнс", "Шарль Леклер", "Ландо Норрис"] }, "Сильверстоун": { "описание": "Историческая трасса в Великобритании, известная своими скоростными поворотами.", "лидеры": ["Валттери Боттас", "Даниил Квят", "Льюис Хэмилтон"] } # Добавьте другие трассы по необходимости } # Информация о создателях creators_info = "Этот бот был создан командой энтузиастов автогонок: Иван, Мария и Алексей." # Генерация клавиатуры для главного меню def main_menu_keyboard(): buttons = [[InlineKeyboardButton(track, callback_data=f"track|{track}")] for track in tracks.keys()] # Добавляем кнопку "Создатели" в самый низ buttons.append([InlineKeyboardButton("Создатели", callback_data="creators")]) return InlineKeyboardMarkup(buttons) # Клавиатура с кнопкой "Назад" def back_button_keyboard(): return InlineKeyboardMarkup([[InlineKeyboardButton("Назад", callback_data="back")]]) async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text('Выберите гоночную трассу:', reply_markup=main_menu_keyboard()) async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE): query = update.callback_query await query.answer() data = query.data if data.startswith("track|"): # Выбрана трасса track_name = data.split("|")[1] track_info = tracks.get(track_name) if track_info: description = track_info["описание"] leaders = ", ".join(track_info["лидеры"]) response = f"**Трасса:** {track_name}\n\n**Описание:** {description}\n\n**Лидеры:** {leaders}" else: response = "Информация о трассе не найдена." await query.edit_message_text(text=response, parse_mode="Markdown", reply_markup=back_button_keyboard()) elif data == "creators": # Показать информацию о создателях await query.edit_message_text(text=creators_info, reply_markup=back_button_keyboard()) elif data == "back": # Возвращение в главное меню await query.edit_message_text("Выберите гоночную трассу:", reply_markup=main_menu_keyboard()) else: # Неизвестный ответ await query.edit_message_text("Произошла ошибка, попробуйте снова.", reply_markup=main_menu_keyboard()) async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text('Используйте /start для начала.') def main(): # Вставьте сюда токен вашего бота TOKEN = 'ВАШ_ТОКЕН_БОТА' application = ApplicationBuilder().token(TOKEN).build() # Обработчики команд application.add_handler(CommandHandler("start", start)) application.add_handler(CommandHandler("help", help_command)) # Обработчик нажатий на кнопки application.add_handler(CallbackQueryHandler(button_callback)) # Запуск бота application.run_polling() if __name__ == '__main__': main()
добавить чтобы после кнопки на трассу были еще 3 кнопки (назад) (лидеры) (история о трассе) (повороты опасные)
Понял вас! Давайте расширим функциональность вашего Telegram-бота, добавив дополнительные кнопки после выбора трассы: "Лидеры", "История о трассе", "Опасные повороты" и "Назад". При нажатии на каждую из этих кнопок будет отображаться соответствующая информация.
Главное меню (/start
):
Отображает список трасс и кнопку "Создатели" внизу.
Выбор трассы:
При нажатии на кнопку с названием трассы отображается описание трассы и четыре кнопки:
Создатели:
При нажатии на кнопку "Создатели" отображается информация о создателях и кнопка "Назад" для возвращения в главное меню.
Обновите структуру данных трасс:
Добавьте дополнительные поля history
и dangerous_turns
для каждой трассы.
Обновите обработчик кнопок:
Добавьте обработку новых кнопок "Лидеры", "История о трассе" и "Опасные повороты".
Обновите клавиатуры:
Создайте отдельные клавиатуры для разных уровней меню.
pythonfrom telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup from telegram.ext import ApplicationBuilder, CommandHandler, CallbackQueryHandler, ContextTypes import logging # Включаем логирование logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO ) logger = logging.getLogger(__name__) # Данные о трассах и лидерах tracks = { "Монако": { "описание": "Узкие улочки и сложные повороты делают трассу Монако одной из самых престижных.", "лидеры": ["Льюис Хэмилтон", "Макс Ферстаппен", "Себастьян Феттель"], "история": "Трасса Монако впервые вошла в календарь Формулы-1 в 1950 году и с тех пор является символом роскоши и престижа.", "опасные_повороты": "Поворот «Fairmont Hairpin» — один из самых медленных и сложных поворотов в Формуле-1." }, "Спа-Франкоршам": { "описание": "Трасса в Бельгии известна своими изменчивыми погодными условиями и длинными прямыми.", "лидеры": ["Карлос Сайнс", "Шарль Леклер", "Ландо Норрис"], "история": "Спа-Франкоршам был открыт в 1921 году и стал одним из самых легендарных треков в мире автоспорта.", "опасные_повороты": "Поворот «Eau Rouge» — знаменитый быстрый подъем, переходящий в крутой поворот." }, "Сильверстоун": { "описание": "Историческая трасса в Великобритании, известная своими скоростными поворотами.", "лидеры": ["Валттери Боттас", "Даниил Квят", "Льюис Хэмилтон"], "история": "Сильверстоун был первым домом Гран-при Формулы-1 в 1950 году и является местом многих исторических событий.", "опасные_повороты": "Поворот «Becketts» — последовательность быстрых поворотов, требующих высокой точности." } # Добавьте другие трассы по необходимости } # Информация о создателях creators_info = "Этот бот был создан командой энтузиастов автогонок: Иван, Мария и Алексей." # Генерация клавиатуры для главного меню def main_menu_keyboard(): buttons = [[InlineKeyboardButton(track, callback_data=f"track|{track}")] for track in tracks.keys()] # Добавляем кнопку "Создатели" в самый низ buttons.append([InlineKeyboardButton("Создатели", callback_data="creators")]) return InlineKeyboardMarkup(buttons) # Клавиатура с кнопками после выбора трассы def track_menu_keyboard(track_name): buttons = [ [InlineKeyboardButton("Лидеры", callback_data=f"leaders|{track_name}")], [InlineKeyboardButton("История о трассе", callback_data=f"history|{track_name}")], [InlineKeyboardButton("Опасные повороты", callback_data=f"dangerous_turns|{track_name}")], [InlineKeyboardButton("Назад", callback_data="back_to_main")] ] return InlineKeyboardMarkup(buttons) # Клавиатура с кнопкой "Назад" для разделов трассы def back_button_keyboard(track_name): return InlineKeyboardMarkup([[InlineKeyboardButton("Назад", callback_data=f"track_menu|{track_name}")]]) async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text('Выберите гоночную трассу:', reply_markup=main_menu_keyboard()) async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE): query = update.callback_query await query.answer() data = query.data if data.startswith("track|"): # Выбрана трасса track_name = data.split("|")[1] track_info = tracks.get(track_name) if track_info: description = track_info["описание"] response = f"**Трасса:** {track_name}\n\n**Описание:** {description}" else: response = "Информация о трассе не найдена." await query.edit_message_text(text=response, parse_mode="Markdown", reply_markup=track_menu_keyboard(track_name)) elif data.startswith("leaders|"): # Показать лидеров track_name = data.split("|")[1] track_info = tracks.get(track_name) if track_info: leaders = ", ".join(track_info["лидеры"]) response = f"**Лидеры на трассе {track_name}:**\n{leaders}" else: response = "Информация о лидерах не найдена." await query.edit_message_text(text=response, parse_mode="Markdown", reply_markup=back_button_keyboard(track_name)) elif data.startswith("history|"): # Показать историю трассы track_name = data.split("|")[1] track_info = tracks.get(track_name) if track_info: history = track_info["история"] response = f"**История трассы {track_name}:**\n{history}" else: response = "Информация об истории трассы не найдена." await query.edit_message_text(text=response, parse_mode="Markdown", reply_markup=back_button_keyboard(track_name)) elif data.startswith("dangerous_turns|"): # Показать опасные повороты трассы track_name = data.split("|")[1] track_info = tracks.get(track_name) if track_info: dangerous_turns = track_info["опасные_повороты"] response = f"**Опасные повороты на трассе {track_name}:**\n{dangerous_turns}" else: response = "Информация об опасных поворотах не найдена." await query.edit_message_text(text=response, parse_mode="Markdown", reply_markup=back_button_keyboard(track_name)) elif data == "creators": # Показать информацию о создателях await query.edit_message_text(text=creators_info, reply_markup=main_menu_keyboard()) elif data == "back_to_main": # Возвращение в главное меню await query.edit_message_text("Выберите гоночную трассу:", reply_markup=main_menu_keyboard()) elif data.startswith("track_menu|"): # Возвращение к меню трассы track_name = data.split("|")[1] await query.edit_message_text("Выберите опцию:", reply_markup=track_menu_keyboard(track_name)) else: # Неизвестный ответ await query.edit_message_text("Произошла ошибка, попробуйте снова.", reply_markup=main_menu_keyboard()) async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text('Используйте /start для начала.') def main(): # Вставьте сюда токен вашего бота TOKEN = 'ВАШ_ТОКЕН_БОТА' application = ApplicationBuilder().token(TOKEN).build() # Обработчики команд application.add_handler(CommandHandler("start", start)) application.add_handler(CommandHandler("help", help_command)) # Обработчик нажатий на кнопки application.add_handler(CallbackQueryHandler(button_callback)) # Запуск бота application.run_polling() if __name__ == '__main__': main()
Расширение структуры данных трасс:
Каждая трасса теперь содержит дополнительные поля:
история
: Описание истории трассы.опасные_повороты
: Информация об опасных поворотах на трассе.pythontracks = { "Монако": { "описание": "Узкие улочки и сложные повороты делают трассу Монако одной из самых престижных.", "лидеры": ["Льюис Хэмилтон", "Макс Ферстаппен", "Себастьян Феттель"], "история": "Трасса Монако впервые вошла в календарь Формулы-1 в 1950 году и с тех пор является символом роскоши и престижа.", "опасные_повороты": "Поворот «Fairmont Hairpin» — один из самых медленных и сложных поворотов в Формуле-1." }, # Другие трассы... }
Создание дополнительных клавиатур:
track_menu_keyboard(track_name)
: Клавиатура с кнопками "Лидеры", "История о трассе", "Опасные повороты" и "Назад".back_button_keyboard(track_name)
: Клавиатура только с кнопкой "Назад" для возврата к меню трассы.pythondef track_menu_keyboard(track_name): buttons = [ [InlineKeyboardButton("Лидеры", callback_data=f"leaders|{track_name}")], [InlineKeyboardButton("История о трассе", callback_data=f"history|{track_name}")], [InlineKeyboardButton("Опасные повороты", callback_data=f"dangerous_turns|{track_name}")], [InlineKeyboardButton("Назад", callback_data="back_to_main")] ] return InlineKeyboardMarkup(buttons) def back_button_keyboard(track_name): return InlineKeyboardMarkup([[InlineKeyboardButton("Назад", callback_data=f"track_menu|{track_name}")]])
Обработка новых типов данных в button_callback
:
leaders|track_name
: Показать лидеров на выбранной трассе.history|track_name
: Показать историю трассы.dangerous_turns|track_name
: Показать информацию об опасных поворотах.back_to_main
: Возврат в главное меню.track_menu|track_name
: Возврат к меню трассы после просмотра деталей.pythonelif data.startswith("leaders|"): # Показать лидеров ... elif data.startswith("history|"): # Показать историю трассы ... elif data.startswith("dangerous_turns|"): # Показать опасные повороты трассы ... elif data == "back_to_main": # Возвращение в главное меню ... elif data.startswith("track_menu|"): # Возвращение к меню трассы ...
Навигация между меню:
Пользователь отправляет команду /start
.
Пользователь нажимает на кнопку с названием трассы (например, "Монако").
Пользователь выбирает одну из опций (например, "Лидеры").
Пользователь нажимает "Назад".
Пользователь может выбрать другие опции или нажать "Назад" в главном меню, чтобы выбрать другую трассу или посмотреть информацию о создателях.
Хранение данных в базе данных:
Вместо использования жестко закодированных данных можно хранить информацию о трассах в базе данных (например, SQLite, PostgreSQL) для более гибкого управления и обновления информации.
Динамическое обновление лидеров:
Интегрируйте API или скрипты, которые будут автоматически обновлять информацию о лидерах на трассах в реальном времени.
Добавление изображений:
Вы можете добавить фотографии трасс или профилей лидеров для улучшения визуального представления. Для этого используйте метод send_photo
вместо edit_message_text
.
Локализация:
Если планируется поддержка нескольких языков, реализуйте механизм локализации сообщений и кнопок.
Обработка ошибок:
Добавьте более подробную обработку ошибок и информирование пользователя в случае возникновения проблем.
Теперь ваш Telegram-бот обладает более расширенной функциональностью, позволяющей пользователям получать подробную информацию о гоночных трассах, включая лидеров, историю и опасные повороты. Вы можете продолжать улучшать бота, добавляя новые функции и оптимизируя пользовательский опыт. Если у вас возникнут дополнительные вопросы или потребуется помощь, не стесняйтесь обращаться!
сделать чтобы кнопки располагались снизу а не в чате
Ниже приведён пример бота, который использует ReplyKeyboardMarkup вместо inline-кнопок. Это позволит кнопкам отображаться внизу экрана, а не внутри сообщений. При этом нам придётся отказаться от callback_data
и перейти к обработке текстовых сообщений, так как обычная клавиатура не поддерживает обратные вызовы.
Основная логика:
Главное меню:
Клавиатура с кнопками "Выбрать трассу" и "Создатели".
При выборе "Выбрать трассу":
Отображается список трасс снизу. Пользователь выбирает нужную трассу.
После выбора трассы:
Отображается клавиатура с кнопками: "Лидеры", "История о трассе", "Опасные повороты" и "Назад".
При выборе "Назад" на уровне трассы:
Возвращаемся к списку трасс.
При выборе "Назад" на уровне списка трасс:
Возвращаемся в главное меню.
Обратите внимание:
ConversationHandler
из python-telegram-bot
, чтобы отслеживать состояние диалога.tracks
.pythonimport logging 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 = range(3) # Данные о трассах tracks = { "Монако": { "описание": "Узкие улочки и сложные повороты делают трассу Монако одной из самых престижных.", "лидеры": ["Льюис Хэмилтон", "Макс Ферстаппен", "Себастьян Феттель"], "история": "Трасса Монако впервые вошла в календарь Формулы-1 в 1950 году и с тех пор является символом роскоши и престижа.", "опасные_повороты": "Поворот «Fairmont Hairpin» — один из самых медленных и сложных поворотов в Формуле-1." }, "Спа-Франкоршам": { "описание": "Трасса в Бельгии известна своими изменчивыми погодными условиями и длинными прямыми.", "лидеры": ["Карлос Сайнс", "Шарль Леклер", "Ландо Норрис"], "история": "Спа-Франкоршам был открыт в 1921 году и стал одним из самых легендарных треков в мире автоспорта.", "опасные_повороты": "Поворот «Eau Rouge» — знаменитый быстрый подъем, переходящий в крутой поворот." }, "Сильверстоун": { "описание": "Историческая трасса в Великобритании, известная своими скоростными поворотами.", "лидеры": ["Валттери Боттас", "Даниил Квят", "Льюис Хэмилтон"], "история": "Сильверстоун был первым домом Гран-при Формулы-1 в 1950 году и является местом многих исторических событий.", "опасные_повороты": "Поворот «Becketts» — последовательность быстрых поворотов, требующих высокой точности." } } 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 ) async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text( "Добро пожаловать! Выберите опцию:", reply_markup=main_menu_keyboard() ) return CHOOSE_MAIN async def main_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): 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): 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): 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 = ", ".join(track_info["лидеры"]) response = f"**Лидеры на трассе {current_track}:**\n{leaders}" elif text == "История о трассе": response = f"**История трассы {current_track}:**\n{track_info['история']}" elif text == "Опасные повороты": response = f"**Опасные повороты на трассе {current_track}:**\n{track_info['опасные_повороты']}" else: response = "Неизвестная команда. Попробуйте снова." await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) return TRACK_MENU 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)] }, fallbacks=[CommandHandler("cancel", cancel), CommandHandler("help", help_command)] ) application.add_handler(conv_handler) application.run_polling() if __name__ == '__main__': main()
ReplyKeyboardMarkup
, чтобы клавиатура отображалась внизу экрана.callback_data
к обработке текстового ввода от пользователя.ConversationHandler
для управления состояниями бота.Таким образом, кнопки теперь располагаются снизу чата в отдельной панели клавиатуры, а не "в тексте" сообщения.
добавить анти спам и видео к каждой трассе
Ниже приведён обновлённый пример кода, в котором добавлены:
Анти-спам:
Простой механизм, который отслеживает время между сообщениями одного пользователя и ограничивает частоту отправки сообщений. Если пользователь отправляет слишком много сообщений за короткий промежуток времени, бот будет отвечать предупреждением и игнорировать сообщения, пока не закончится период тайм-аута.
Видео для каждой трассы:
В словарь tracks
добавлено поле video
, содержащее ссылку на видео. После выбора трассы бот отправляет пользователю видео, связанное с данной трассой.
Примечание по анти-спаму:
Реализован простой механизм.
/start
и /help
).pythonimport logging import time from telegram import ( Update, ReplyKeyboardMarkup, ReplyKeyboardRemove, InputMediaVideo ) 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 = range(3) # Настройки анти-спама MAX_MESSAGES = 5 # Максимум сообщений за период TIME_WINDOW = 10 # Интервал в секундах для подсчёта сообщений TIMEOUT_DURATION = 20 # Длительность тайм-аута в секундах # Данные о трассах (добавлено поле video для каждой трассы) tracks = { "Монако": { "описание": "Узкие улочки и сложные повороты делают трассу Монако одной из самых престижных.", "лидеры": ["Льюис Хэмилтон", "Макс Ферстаппен", "Себастьян Феттель"], "история": "Трасса Монако впервые вошла в календарь Формулы-1 в 1950 году и с тех пор является символом роскоши и престижа.", "опасные_повороты": "Поворот «Fairmont Hairpin» — один из самых медленных и сложных поворотов в Формуле-1.", "video": "https://www.example.com/monaco.mp4" }, "Спа-Франкоршам": { "описание": "Трасса в Бельгии известна своими изменчивыми погодными условиями и длинными прямыми.", "лидеры": ["Карлос Сайнс", "Шарль Леклер", "Ландо Норрис"], "история": "Спа-Франкоршам был открыт в 1921 году и стал одним из самых легендарных треков в мире автоспорта.", "опасные_повороты": "Поворот «Eau Rouge» — знаменитый быстрый подъем, переходящий в крутой поворот.", "video": "https://www.example.com/spa.mp4" }, "Сильверстоун": { "описание": "Историческая трасса в Великобритании, известная своими скоростными поворотами.", "лидеры": ["Валттери Боттас", "Даниил Квят", "Льюис Хэмилтон"], "история": "Сильверстоун был первым домом Гран-при Формулы-1 в 1950 году и является местом многих исторических событий.", "опасные_повороты": "Поворот «Becketts» — последовательность быстрых поворотов, требующих высокой точности.", "video": "https://www.example.com/silverstone.mp4" } } 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 ) 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 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 # Анти-спам обработчик async def anti_spam(update: Update, context: ContextTypes.DEFAULT_TYPE): user_id = update.effective_user.id current_time = time.time() # Если установлен тайм-аут, проверим не истёк ли он if "spam_timeout_until" in context.user_data: if current_time < context.user_data["spam_timeout_until"]: # Тайм-аут ещё действует await update.message.reply_text("Вы слишком часто отправляли сообщения. Подождите немного.") return False else: # Тайм-аут истёк, уберём его context.user_data.pop("spam_timeout_until") # Инициализация массива таймстампов if "timestamps" not in context.user_data: context.user_data["timestamps"] = [] # Добавим текущую метку времени context.user_data["timestamps"].append(current_time) # Очистим старые таймстампы context.user_data["timestamps"] = [t for t in context.user_data["timestamps"] if current_time - t <= TIME_WINDOW] # Проверим количество сообщений за последние TIME_WINDOW секунд if len(context.user_data["timestamps"]) > MAX_MESSAGES: # Превышен лимит await update.message.reply_text("Слишком много сообщений! Пожалуйста, подождите 20 секунд.") context.user_data["spam_timeout_until"] = current_time + TIMEOUT_DURATION return False return True 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=ReplyKeyboardRemove()) # Отправляем видео video_url = track_info["video"] await update.message.reply_video(video_url) # Отображаем меню опций await update.message.reply_text("Выберите опцию:", 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 = ", ".join(track_info["лидеры"]) response = f"**Лидеры на трассе {current_track}:**\n{leaders}" elif text == "История о трассе": response = f"**История трассы {current_track}:**\n{track_info['история']}" elif text == "Опасные повороты": response = f"**Опасные повороты на трассе {current_track}:**\n{track_info['опасные_повороты']}" else: response = "Неизвестная команда. Попробуйте снова." await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) return TRACK_MENU 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)] }, fallbacks=[CommandHandler("cancel", cancel), CommandHandler("help", help_command)] ) application.add_handler(conv_handler) application.run_polling() if __name__ == '__main__': main()
Видео к каждой трассе:
В словаре tracks
у каждой трассы появилось поле video
с ссылкой на видео. После выбора трассы бот сначала отправляет текстовое описание, затем видео, а затем отображает меню опций.
Анти-спам функциональность:
main_menu_choice
, track_choice
, track_menu_choice
), вызывается функция anti_spam
./start
таймеры анти-спама сбрасываются.Теперь бот позволяет:
import logging
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 = range(3)
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 (Карусель), Brünnchen (Брюнхен), и Flugplatz (Аэродром)."
},
"Монца": {
"описание": "Трасса в Бельгии известна своими изменчивыми погодными условиями и длинными прямыми.",
"лидеры": ["Автодром Монца имеет долгую историю и видел победы многих легендарных гонщиков. Назвать конкретного - лидера сложно, так как это зависит от периода времени и класса гонок. Среди наиболее успешных гонщиков, многократно побеждавших на Монце, можно назвать Михаэля Шумахера, Льюиса Хэмилтона, и много других выдающихся пилотов Формулы-1."],
"история": "Автодром Монца был открыт в 1922 году и является одной из старейших трасс Формулы-1. Первоначальная трасса проходила частично по общественным дорогам. Трасса неоднократно перестраивалась и модернизировалась, но сохранила свою скоростную сущность. Монца славится своей высокой скоростью и длинными прямыми, что делает её одной из самых быстрых трасс в мире. Она также является свидетелем многих знаменательных событий и трагических аварий в истории автоспорта..",
"опасные_повороты": "Традиционно самой сложной частью трассы Монца считается секция Lesmo (Лесмо), состоящая из двух быстрых поворотов. Высокие скорости и быстрый переход от одного поворота к другому требуют от пилотов максимальной точности и контроля над машиной. Кроме того, Curva Grande (Большой поворот) — быстрый и протяженный поворот, где ошибки могут привести к серьезным последствиям."
},
"": {
"описание": "Историческая трасса в Великобритании, известная своими скоростными поворотами.",
"лидеры": ["Валттери Боттас", "Даниил Квят", "Льюис Хэмилтон"],
"история": "Сильверстоун был первым домом Гран-при Формулы-1 в 1950 году и является местом многих исторических событий.",
"опасные_повороты": "Поворот «Becketts» — последовательность быстрых поворотов, требующих высокой точности."
}
}
creators_info = "Этот бот был создан командой: Ратников Иван, ШУКРОНА, Новиков Елисей, Елизарова Ксения, Пискунов Кирилл, Колосов Дмитрий ."
def is_spam(user_id: int):
current_time = time.time()
if user_id in user_last_message_time:
last_time = user_last_message_time[user_id]
if current_time - last_time < 1: # 1 секунды между сообщениями
return True
user_last_message_time[user_id] = current_time
return False
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
)
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text(
"Добро пожаловать! Выберите опцию:",
reply_markup=main_menu_keyboard()
)
return CHOOSE_MAIN
async def main_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE):
text = update.message.text
textif 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):
text = update.message.text
textif 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):
text = update.message.text
current_track = context.user_data.get("current_track")
textif 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 = ", ".join(track_info["лидеры"]) response = f"**Лидеры на трассе {current_track}:**\n{leaders}" elif text == "История о трассе": response = f"**История трассы {current_track}:**\n{track_info['история']}" elif text == "Опасные повороты": response = f"**Опасные повороты на трассе {current_track}:**\n{track_info['опасные_повороты']}" else: response = "Неизвестная команда. Попробуйте снова." await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) return TRACK_MENU
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 = "8166041667:AAHsjC8QSxSsqIt3zxGUm6YeGyEcp9QRr08"
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)] }, fallbacks=[CommandHandler("cancel", cancel), CommandHandler("help", help_command)] ) application.add_handler(conv_handler) application.run_polling()
if name == 'main':
main() в этот код добавить кнопки на каждый опасный поворот и про них чутка информации и так с каждый трассой
Конечно! Давайте обновим ваш Telegram-бот, добавив функциональность отображения отдельных кнопок для каждого опасного поворота на выбранной трассе. При нажатии на кнопку конкретного поворота бот будет предоставлять подробную информацию о нем. Также мы улучшим анти-спам механизм, чтобы обеспечить более надежную защиту от нежелательных сообщений.
опасные_повороты
на словарь, где ключи — названия поворотов, а значения — описание каждого поворота.CHOOSE_DANGEROUS_TURN
для обработки выбора конкретного поворота.context.user_data
для хранения временных меток сообщений.pythonimport 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 = "Этот бот был создан командой: Ратников Иван, Шукрона, Новиков Елисей, Елизарова Ксения, Пискунов Кирилл, Колосов Дмитрий." # Инициализация словаря для анти-спама # Используем 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: 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 = "ВАШ_ТОКЕН_БОТА" 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()
Обновление структуры данных трасс:
опасные_повороты
теперь является словарем, где ключи — названия поворотов, а значения — описание каждого поворота.python"опасные_повороты": { "Karussell": "Описание поворота Karussell — один из самых известных и сложных поворотов на Нюрбургринге. Требует максимальной концентрации и точности.", "Brünnchen": "Поворот Brünnchen характеризуется высокой скоростью и требованием к точности траектории.", "Flugplatz": "Поворот Flugplatz — это длинный и быстрый поворот, который требует отличного контроля над автомобилем." }
Добавление нового состояния CHOOSE_DANGEROUS_TURN
:
Создание новой клавиатуры dangerous_turns_keyboard
:
pythondef 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)
Обработка выбора опасного поворота:
track_menu_choice
, при выборе "Опасные повороты", бот отображает клавиатуру с перечислением поворотов.dangerous_turn_choice
бот обрабатывает выбор конкретного поворота и выводит соответствующую информацию.Улучшение анти-спам механизма:
context.user_data
для хранения временных меток сообщений и тайм-аутов.anti_spam
проверяет количество сообщений за последние TIME_WINDOW
секунд и устанавливает тайм-аут, если лимит превышен.pythonasync 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
Навигация и клавиатуры:
/start
.
Добавление видео к каждой трассе:
tracks
, добавив поле video
, содержащее ссылку на видео или ID видеофайла.send_video
.Расширение анти-спам механизма:
Хранение данных в базе данных:
Обработка ошибок и исключений:
Теперь ваш Telegram-бот обладает расширенной функциональностью, позволяющей пользователям выбирать трассу, просматривать подробную информацию о ней, а также получать информацию о каждом опасном повороте на трассе. Анти-спам механизм поможет поддерживать комфортное взаимодействие с ботом, предотвращая злоупотребления. Вы можете продолжать улучшать бота, добавляя новые трассы, повороты и другие полезные функции по мере необходимости.
Если у вас возникнут дополнительные вопросы или потребуется помощь с дальнейшими улучшениями, не стесняйтесь обращаться!