Трассы видео и фото
Created on: December 19, 2024
Created on: December 19, 2024
Unknoten des Videos
Der Benutzer fragt sich, wie Video-Links für jede Strecke hinzugefügt werden können, während der Bot Informationen zu Übersicht, Geschichte, gefährlichen Kurven usw. zeigt. Es scheint, als würde die Erklärung der Strecken besprochen werden.
Überlegungen anstellen
Es gibt verschiedene Möglichkeiten, wie der Code für den Bot erweitert werden kann, um Video-Links zu den Strecken hinzuzufügen und sie bei Bedarf zu senden.
Hinzufügen eines 'video' Felde
Ich habe überlegt, wie ein 'video' Feld zur 'tracks' Dict hinzugefügt werden kann. Zusätzlich könnte der Handler für die 'История о трассе' Option angepasst werden, um Videos über URLs zu senden.
Menüoption hinzufügen
Hinzufüge jetzt 'Видео трассы' als neue Menüoption. Implementiere den Handler, um Videos über Links zu senden. Let me go step by step.
Beschaffung und Organisation
Ich beschaffe Informationen über Modulsysteme und Programme. Nun denke ich darüber nach, wie diese Programme Baugruppen beziehen und sorge für eine ordnungsgemäße Dokumentation.
Gedanken für 7 Sekunden
Чтобы добавить видео для каждой трассы по ссылке, необходимо выполнить следующие шаги:
Добавить поле video
в словарь tracks
для каждой трассы: Это позволит хранить ссылку на видео для каждой трассы.
Обновить клавиатуру меню трассы: Добавить новую опцию, например, "Видео трассы", чтобы пользователи могли запросить видео.
Создать функцию для отправки видео: Аналогично функции send_photo
, создадим функцию send_video
, которая будет обрабатывать отправку видео по ссылке.
Обновить обработчик выбора меню трассы: Добавить обработку новой опции "Видео трассы" и вызвать функцию send_video
при выборе этой опции.
Ниже приведён обновленный код с внесёнными изменениями:
import 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 = 10 # Длительность тайм-аута в секундах # Папка с фотографиями PHOTOS_DIR = "photos" # Убедитесь, что такая папка существует # Разрешённые расширения файлов ALLOWED_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.bmp'} # Данные о трассах (добавлено поле 'фото_история' и 'video' для каждой трассы) 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 мин.", "фото_история": "nurburgring/history.jpg", "video": "https://www.example.com/nurburgring_video.mp4", # Добавлено поле видео "опасные_повороты": { "Karussell": { "описание": "Описание поворота Karussell — один из самых известных и сложных поворотов на Нюрбургринге. Требует максимальной концентрации и точности.", "фото": "nurburgring/karussell.jpg" }, "Brünnchen": { "описание": "Поворот Brünnchen характеризуется высокой скоростью и требованием к точности траектории.", "фото": "nurburgring/brunnchen.jpg" }, "Flugplatz": { "описание": "Поворот Flugplatz — это длинный и быстрый поворот, который требует отличного контроля над автомобилем.", "фото": "nurburgring/flugplatz.jpg" } } }, "Монца": { "описание": "Трасса Монца известна своими длинными прямыми и высокой скоростью, что делает её одной из самых быстрых трасс в мире.", "лидеры": "Баррикелло (Ferrari) | *1:21,046* |\n\n2. Хэмилтон (Mercedes) | *1:18,887* |\n\n", "история": "Автодром Монца был открыт в 1922 году и является одной из старейших трасс Формулы-1. Первоначальная трасса проходила частично по общественным дорогам. Трасса неоднократно перестраивалась и модернизировалась, но сохранила свою скоростную сущность. Монца славится своей высокой скоростью и длинными прямыми, что делает её одной из самых быстрых трасс в мире. Она также является свидетелем многих знаменательных событий и трагических аварий в истории автоспорта.", "фото_история": "monza/history.jpg", "video": "https://www.example.com/monza_video.mp4", # Добавлено поле видео "опасные_повороты": { "Lesmo": { "описание": "Секция Lesmo состоит из двух быстрых поворотов, которые требуют максимальной точности и контроля над автомобилем.", "фото": "monza/lesmo.jpg" }, "Curva Grande": { "описание": "Большой поворот (Curva Grande) — быстрый и протяженный поворот, где ошибки могут привести к серьезным последствиям.", "фото": "monza/curva_grande.jpg" } } }, "Спа-Франкоршам": { # Новая трасса "описание": "Трасса Спа-Франкоршам известна своим непредсказуемым погодным условиям и сложными поворотами, что делает её одной из самых технически сложных трасс в мире.", "лидеры": "Льюис Хэмилтон | *6:47.285 минут* |\n\nСебастьян Феттель | *6:49.875 минут* |\n\nМакс Ферстаппен | *6:50.123 минут* |", "история": "Автодром Спа-Франкоршам был открыт в 1921 году и является одним из старейших и самых уважаемых треков в мире автоспорта. Трасса проходит через живописные пейзажи Бельгии и известна своей историей и традициями. Спа-Франкоршам принимал множество чемпионатов мира, включая Формулу-1, и является любимой трассой многих гонщиков за её уникальную смесь высоких скоростей и технических сложностей.", "фото_история": "spa/history.jpg", "video": "https://www.example.com/spa_video.mp4", # Добавлено поле видео "опасные_повороты": { "Eau Rouge": { "описание": "Eau Rouge — знаменитый быстрый подъем с крутым поворотом, требующий точности и смелости от пилотов.", "фото": "spa/eau_rouge.jpg" }, "Pouhon": { "описание": "Pouhon — длинный и быстрый поворот, который требует отличной аэродинамики и устойчивости автомобиля.", "фото": "spa/pouhon.jpg" } } } } creators_info = "Этот бот был создан командой:\n\n1 Ратников Иван,\n\n2 Хамраева Шукрона,\n\n3 Новиков Елисей,\n\n4 Елизарова Ксения,\n\n5 Пискунов Кирилл,\n\n6 Колосов Дмитрий." # Клавиатуры 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 send_photo(context, chat_id, photo_filename, caption=None, reply_markup=None): # Полный путь к файлу file_path = os.path.join(PHOTOS_DIR, photo_filename) # Проверка расширения файла _, ext = os.path.splitext(photo_filename) if ext.lower() not in ALLOWED_EXTENSIONS: logger.warning(f"Недопустимое расширение файла: {photo_filename}") await context.bot.send_message( chat_id=chat_id, text="Невозможно отправить файл с недопустимым расширением.", reply_markup=reply_markup ) return if os.path.isfile(file_path): try: with open(file_path, 'rb') as photo_file: if caption: if len(caption) <= 1024: # Если длина подписи допустимая, отправляем фото с подписью await context.bot.send_photo( chat_id=chat_id, photo=photo_file, caption=caption, parse_mode="Markdown", reply_markup=reply_markup ) else: # Если подпись слишком длинная, отправляем фото без подписи await context.bot.send_photo( chat_id=chat_id, photo=photo_file, reply_markup=reply_markup ) # Разбиваем длинный текст на части по 1024 символа for i in range(0, len(caption), 1024): part = caption[i:i+1024] await context.bot.send_message( chat_id=chat_id, text=part, parse_mode="Markdown", reply_markup=reply_markup ) else: # Если подпись не требуется, просто отправляем фото await context.bot.send_photo( chat_id=chat_id, photo=photo_file, reply_markup=reply_markup ) except Exception as e: logger.error(f"Ошибка при отправке фото: {e}") if caption: await context.bot.send_message( chat_id=chat_id, text=caption, parse_mode="Markdown", reply_markup=reply_markup ) else: logger.warning(f"Файл фото не найден: {file_path}") if caption: await context.bot.send_message( chat_id=chat_id, text=f"{caption}\n\n*Фото не доступно.*", parse_mode="Markdown", reply_markup=reply_markup ) # Вспомогательная функция для отправки видео по ссылке async def send_video(context, chat_id, video_url, caption=None, reply_markup=None): try: await context.bot.send_video( chat_id=chat_id, video=video_url, caption=caption, parse_mode="Markdown", reply_markup=reply_markup ) except Exception as e: logger.error(f"Ошибка при отправке видео: {e}") if caption: await context.bot.send_message( chat_id=chat_id, text=f"{caption}\n\n*Видео не доступно.*", parse_mode="Markdown", reply_markup=reply_markup ) # Обработчики 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 == "История о трассе": history = track_info["история"] response = f"**История трассы {current_track}:**\n{history}" # Получение имени файла фото истории photo_filename = track_info.get("фото_история") if photo_filename: # Отправка фото с текстом await send_photo( context, chat_id=update.effective_chat.id, photo_filename=photo_filename, caption=response, reply_markup=track_menu_keyboard() ) else: # Если фото не задано, отправляем только текст 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 elif text == "Видео трассы": # Обработка новой опции video_url = track_info.get("video") if video_url: response = f"**Видео трассы {current_track}:**" await send_video( context, chat_id=update.effective_chat.id, video_url=video_url, caption=response, reply_markup=track_menu_keyboard() ) else: await update.message.reply_text( "Видео для этой трассы недоступно.", parse_mode="Markdown", reply_markup=track_menu_keyboard() ) 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: await send_photo( context, # Передача context chat_id=update.effective_chat.id, photo_filename=photo_filename, caption=response_text, 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 = "7617191209:AAFsnLeQyrDdIbqTQYFqimbddepBpVNxbBY" # Замените на ваш действительный токен # Убедитесь, что папка с фотографиями существует if not os.path.isdir(PHOTOS_DIR): os.makedirs(PHOTOS_DIR) logger.info(f"Создана папка для фотографий: {PHOTOS_DIR}") 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()
Добавление поля video
в словарь tracks
:
"video": "https://www.example.com/nurburgring_video.mp4", # Пример ссылки на видео
Убедитесь, что вы заменили ссылки на реальные URL ваших видео. Ссылки должны быть прямыми и доступными для Telegram. Например, ссылки на видео-файлы, хранящиеся на вашем сервере или в облачном хранилище, с прямым доступом.
Обновление клавиатуры track_menu_keyboard
:
["Видео трассы"], # Добавлена новая опция
Это добавляет новую кнопку "Видео трассы" в меню выбора опций для выбранной трассы.
Создание функции send_video
:
async def send_video(context, chat_id, video_url, caption=None, reply_markup=None): try: await context.bot.send_video( chat_id=chat_id, video=video_url, caption=caption, parse_mode="Markdown", reply_markup=reply_markup ) except Exception as e: logger.error(f"Ошибка при отправке видео: {e}") if caption: await context.bot.send_message( chat_id=chat_id, text=f"{caption}\n\n*Видео не доступно.*", parse_mode="Markdown", reply_markup=reply_markup )
Эта функция отвечает за отправку видео по ссылке. Если отправка видео не удалась, бот отправит сообщение с текстом и уведомит пользователя о недоступности видео.
Обработка новой опции "Видео трассы" в track_menu_choice
:
elif text == "Видео трассы": # Обработка новой опции video_url = track_info.get("video") if video_url: response = f"**Видео трассы {current_track}:**" await send_video( context, chat_id=update.effective_chat.id, video_url=video_url, caption=response, reply_markup=track_menu_keyboard() ) else: await update.message.reply_text( "Видео для этой трассы недоступно.", parse_mode="Markdown", reply_markup=track_menu_keyboard() )
При выборе опции "Видео трассы" бот проверяет наличие ссылки на видео в словаре tracks
и отправляет видео пользователю. Если видео отсутствует, бот уведомляет об этом.
Ссылки на видео: Убедитесь, что ссылки на видео являются прямыми и доступны для Telegram. Если видео размещено на сторонних платформах (например, YouTube), необходимо использовать прямые ссылки на файл или использовать методы для встраивания видео из этих платформ.
Формат видео: Telegram поддерживает различные форматы видео, но рекомендуется использовать форматы с хорошей совместимостью, такие как MP4.
Ограничения Telegram: Обратите внимание на ограничения Telegram по размеру видеофайлов. Максимальный размер видео, которое можно отправить через бот, составляет 50 МБ. Если ваши видео превышают этот размер, рассмотрите возможность размещения видео на внешних платформах и отправки пользователям ссылок.
Тестирование: После внесения изменений обязательно протестируйте бота, чтобы убедиться, что видео отправляется корректно и ссылки работают как ожидается.
С помощью приведённых выше изменений вы сможете добавить видео для каждой трассы по ссылке, улучшив функциональность вашего Telegram-бота и предоставив пользователям более богатый контент. Не забудьте заменить примеры ссылок на реальные URL ваших видео и проверить их доступность.
Optimierung des Bots
Der Benutzer möchte den Telegram-Bot anpassen, um Video-Dateien lokal zu senden, statt sie von externen Links zu beziehen.
Code-Überarbeitung
Ich bin dabei, den Telegram-Bot umzuprogrammieren, um Video-Dateien lokal zu senden, indem das Dictionary tracks
aktualisiert und die Funktion send_video
geändert wird.
Optimierung der Ablage
Ich denke darüber nach, wie die Ordnerstruktur verbessert werden kann, etwa mit einem separaten Verzeichnis für Videos, und wie die Funktion send_video
gestaltet wird.
** Dateipfade aktualisieren **
Ich denke darüber nach, wie ich den Pfad des video
-Feldes im tracks
-Dictionary aktualisieren und dafür sorgen kann, dass die Video-Dateien im videos
-Verzeichnis platziert werden.
Organizer von Videoinformationen
Die Anleitung aktualisiert das tracks
-Dictionary, fügt ein VIDEOS_DIR
hinzu, implementiert eine send_video
-Funktion und passt die track_menu_keyboard
und track_menu_choice
Funktionen an.
Implementierung und Anpassung
Die Funktion send_video
wird eingeführt, um von lokalen Dateien zu senden, und dabei wird das tracks
-Dictionary aktualisiert und erklärt, wie Video-Dateien in einem Eigenverzeichnis platziert werden.
Gedanken für 12 Sekunden
Понял вас. Вместо отправки видео по ссылке, вы хотите отправлять видеофайлы, хранящиеся локально в папке на сервере, где запущен бот. Для этого необходимо внести следующие изменения в ваш код:
videos
, чтобы хранить все видеофайлы.tracks
: Вместо ссылок на видео, указывайте имена видеофайлов.send_video
: Аналогично функции send_photo
, создадим функцию для отправки видеофайлов из локальной папки.Ниже представлен обновлённый код с внесёнными изменениями:
import 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 = 10 # Длительность тайм-аута в секундах # Папки с медиафайлами PHOTOS_DIR = "photos" # Убедитесь, что такая папка существует VIDEOS_DIR = "videos" # Новая папка для видео # Разрешённые расширения файлов ALLOWED_PHOTO_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.bmp'} ALLOWED_VIDEO_EXTENSIONS = {'.mp4', '.mov', '.avi', '.mkv'} # Данные о трассах (добавлено поле 'фото_история' и 'video' для каждой трассы) 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 мин.", "фото_история": "nurburgring/history.jpg", "video": "nurburgring/video.mp4", # Добавлено поле видео "опасные_повороты": { "Karussell": { "описание": "Описание поворота Karussell — один из самых известных и сложных поворотов на Нюрбургринге. Требует максимальной концентрации и точности.", "фото": "nurburgring/karussell.jpg" }, "Brünnchen": { "описание": "Поворот Brünnchen характеризуется высокой скоростью и требованием к точности траектории.", "фото": "nurburgring/brunnchen.jpg" }, "Flugplatz": { "описание": "Поворот Flugplatz — это длинный и быстрый поворот, который требует отличного контроля над автомобилем.", "фото": "nurburgring/flugplatz.jpg" } } }, "Монца": { "описание": "Трасса Монца известна своими длинными прямыми и высокой скоростью, что делает её одной из самых быстрых трасс в мире.", "лидеры": "Баррикелло (Ferrari) | *1:21,046* |\n\n2. Хэмилтон (Mercedes) | *1:18,887* |\n\n", "история": "Автодром Монца был открыт в 1922 году и является одной из старейших трасс Формулы-1. Первоначальная трасса проходила частично по общественным дорогам. Трасса неоднократно перестраивалась и модернизировалась, но сохранила свою скоростную сущность. Монца славится своей высокой скоростью и длинными прямыми, что делает её одной из самых быстрых трасс в мире. Она также является свидетелем многих знаменательных событий и трагических аварий в истории автоспорта.", "фото_история": "monza/history.jpg", "video": "monza/video.mp4", # Добавлено поле видео "опасные_повороты": { "Lesmo": { "описание": "Секция Lesmo состоит из двух быстрых поворотов, которые требуют максимальной точности и контроля над автомобилем.", "фото": "monza/lesmo.jpg" }, "Curva Grande": { "описание": "Большой поворот (Curva Grande) — быстрый и протяженный поворот, где ошибки могут привести к серьезным последствиям.", "фото": "monza/curva_grande.jpg" } } }, "Спа-Франкоршам": { # Новая трасса "описание": "Трасса Спа-Франкоршам известна своим непредсказуемым погодным условиям и сложными поворотами, что делает её одной из самых технически сложных трасс в мире.", "лидеры": "Льюис Хэмилтон | *6:47.285 минут* |\n\nСебастьян Феттель | *6:49.875 минут* |\n\nМакс Ферстаппен | *6:50.123 минут* |", "история": "Автодром Спа-Франкоршам был открыт в 1921 году и является одним из старейших и самых уважаемых треков в мире автоспорта. Трасса проходит через живописные пейзажи Бельгии и известна своей историей и традициями. Спа-Франкоршам принимал множество чемпионатов мира, включая Формулу-1, и является любимой трассой многих гонщиков за её уникальную смесь высоких скоростей и технических сложностей.", "фото_история": "spa/history.jpg", "video": "spa/video.mp4", # Добавлено поле видео "опасные_повороты": { "Eau Rouge": { "описание": "Eau Rouge — знаменитый быстрый подъем с крутым поворотом, требующий точности и смелости от пилотов.", "фото": "spa/eau_rouge.jpg" }, "Pouhon": { "описание": "Pouhon — длинный и быстрый поворот, который требует отличной аэродинамики и устойчивости автомобиля.", "фото": "spa/pouhon.jpg" } } } } creators_info = "Этот бот был создан командой:\n\n1 Ратников Иван,\n\n2 Хамраева Шукрона,\n\n3 Новиков Елисей,\n\n4 Елизарова Ксения,\n\n5 Пискунов Кирилл,\n\n6 Колосов Дмитрий." # Клавиатуры 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("Слишком много сообщений! Пожалуйста, подождите 10 секунд.") return False return True # Вспомогательная функция для отправки фотографий с обработкой длинных подписей async def send_photo(context, chat_id, photo_filename, caption=None, reply_markup=None): # Полный путь к файлу file_path = os.path.join(PHOTOS_DIR, photo_filename) # Проверка расширения файла _, ext = os.path.splitext(photo_filename) if ext.lower() not in ALLOWED_PHOTO_EXTENSIONS: logger.warning(f"Недопустимое расширение файла: {photo_filename}") await context.bot.send_message( chat_id=chat_id, text="Невозможно отправить файл с недопустимым расширением.", reply_markup=reply_markup ) return if os.path.isfile(file_path): try: with open(file_path, 'rb') as photo_file: if caption: if len(caption) <= 1024: # Если длина подписи допустимая, отправляем фото с подписью await context.bot.send_photo( chat_id=chat_id, photo=photo_file, caption=caption, parse_mode="Markdown", reply_markup=reply_markup ) else: # Если подпись слишком длинная, отправляем фото без подписи await context.bot.send_photo( chat_id=chat_id, photo=photo_file, reply_markup=reply_markup ) # Разбиваем длинный текст на части по 1024 символа for i in range(0, len(caption), 1024): part = caption[i:i+1024] await context.bot.send_message( chat_id=chat_id, text=part, parse_mode="Markdown", reply_markup=reply_markup ) else: # Если подпись не требуется, просто отправляем фото await context.bot.send_photo( chat_id=chat_id, photo=photo_file, reply_markup=reply_markup ) except Exception as e: logger.error(f"Ошибка при отправке фото: {e}") if caption: await context.bot.send_message( chat_id=chat_id, text=caption, parse_mode="Markdown", reply_markup=reply_markup ) else: logger.warning(f"Файл фото не найден: {file_path}") if caption: await context.bot.send_message( chat_id=chat_id, text=f"{caption}\n\n*Фото не доступно.*", parse_mode="Markdown", reply_markup=reply_markup ) # Вспомогательная функция для отправки видео из локальной папки async def send_video(context, chat_id, video_filename, caption=None, reply_markup=None): # Полный путь к файлу file_path = os.path.join(VIDEOS_DIR, video_filename) # Проверка расширения файла _, ext = os.path.splitext(video_filename) if ext.lower() not in ALLOWED_VIDEO_EXTENSIONS: logger.warning(f"Недопустимое расширение видеофайла: {video_filename}") await context.bot.send_message( chat_id=chat_id, text="Невозможно отправить видео с недопустимым расширением.", reply_markup=reply_markup ) return if os.path.isfile(file_path): try: with open(file_path, 'rb') as video_file: if caption: await context.bot.send_video( chat_id=chat_id, video=video_file, caption=caption, parse_mode="Markdown", reply_markup=reply_markup ) else: await context.bot.send_video( chat_id=chat_id, video=video_file, reply_markup=reply_markup ) except Exception as e: logger.error(f"Ошибка при отправке видео: {e}") if caption: await context.bot.send_message( chat_id=chat_id, text=f"{caption}\n\n*Видео не доступно.*", parse_mode="Markdown", reply_markup=reply_markup ) else: logger.warning(f"Файл видео не найден: {file_path}") if caption: await context.bot.send_message( chat_id=chat_id, text=f"{caption}\n\n*Видео не доступно.*", parse_mode="Markdown", reply_markup=reply_markup ) # Обработчики 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 == "История о трассе": history = track_info["история"] response = f"**История трассы {current_track}:**\n{history}" # Получение имени файла фото истории photo_filename = track_info.get("фото_история") if photo_filename: # Отправка фото с текстом await send_photo( context, chat_id=update.effective_chat.id, photo_filename=photo_filename, caption=response, reply_markup=track_menu_keyboard() ) else: # Если фото не задано, отправляем только текст 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 elif text == "Видео трассы": # Обработка новой опции video_filename = track_info.get("video") if video_filename: response = f"**Видео трассы {current_track}:**" await send_video( context, chat_id=update.effective_chat.id, video_filename=video_filename, caption=response, reply_markup=track_menu_keyboard() ) else: await update.message.reply_text( "Видео для этой трассы недоступно.", parse_mode="Markdown", reply_markup=track_menu_keyboard() ) 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: await send_photo( context, # Передача context chat_id=update.effective_chat.id, photo_filename=photo_filename, caption=response_text, 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 = "YOUR_TELEGRAM_BOT_TOKEN_HERE" # Замените на ваш действительный токен # Убедитесь, что папки с фотографиями и видео существуют for directory in [PHOTOS_DIR, VIDEOS_DIR]: if not os.path.isdir(directory): os.makedirs(directory) logger.info(f"Создана папка для медиафайлов: {directory}") 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()
Добавление папки для видео:
VIDEOS_DIR
: Добавлена для хранения пути к папке с видеофайлами.videos
: В функции main()
добавлен цикл, который создаёт папку videos
, если она не существует.Обновление словаря tracks
:
video
: Вместо ссылки на видео, теперь указывается имя видеофайла, например, "nurburgring/video.mp4"
. Убедитесь, что соответствующие видеофайлы размещены в папке videos
."video": "nurburgring/video.mp4", # Добавлено поле видео
Создание функции send_video
:
send_video
: Отвечает за отправку видеофайлов из локальной папки. Проверяет расширение файла, наличие файла и отправляет его пользователю.ALLOWED_VIDEO_EXTENSIONS
.# Вспомогательная функция для отправки видео из локальной папки async def send_video(context, chat_id, video_filename, caption=None, reply_markup=None): # Полный путь к файлу file_path = os.path.join(VIDEOS_DIR, video_filename) # Проверка расширения файла _, ext = os.path.splitext(video_filename) if ext.lower() not in ALLOWED_VIDEO_EXTENSIONS: logger.warning(f"Недопустимое расширение видеофайла: {video_filename}") await context.bot.send_message( chat_id=chat_id, text="Невозможно отправить видео с недопустимым расширением.", reply_markup=reply_markup ) return if os.path.isfile(file_path): try: with open(file_path, 'rb') as video_file: if caption: await context.bot.send_video( chat_id=chat_id, video=video_file, caption=caption, parse_mode="Markdown", reply_markup=reply_markup ) else: await context.bot.send_video( chat_id=chat_id, video=video_file, reply_markup=reply_markup ) except Exception as e: logger.error(f"Ошибка при отправке видео: {e}") if caption: await context.bot.send_message( chat_id=chat_id, text=f"{caption}\n\n*Видео не доступно.*", parse_mode="Markdown", reply_markup=reply_markup ) else: logger.warning(f"Файл видео не найден: {file_path}") if caption: await context.bot.send_message( chat_id=chat_id, text=f"{caption}\n\n*Видео не доступно.*", parse_mode="Markdown", reply_markup=reply_markup )
Обновление клавиатуры track_menu_keyboard
:
def track_menu_keyboard(): return ReplyKeyboardMarkup( [ ["Лидеры"], ["История о трассе"], ["Опасные повороты"], ["Видео трассы"], # Добавлена новая опция ["Назад"] ], resize_keyboard=True )
Обработка новой опции "Видео трассы" в track_menu_choice
:
elif text == "Видео трассы": # Обработка новой опции video_filename = track_info.get("video") if video_filename: response = f"**Видео трассы {current_track}:**" await send_video( context, chat_id=update.effective_chat.id, video_filename=video_filename, caption=response, reply_markup=track_menu_keyboard() ) else: await update.message.reply_text( "Видео для этой трассы недоступно.", parse_mode="Markdown", reply_markup=track_menu_keyboard() )
Обновление функции main()
:
YOUR_TELEGRAM_BOT_TOKEN_HERE
на ваш действительный токен бота.videos
: Убедитесь, что папка создаётся при запуске бота, если она отсутствует.def main(): TOKEN = "YOUR_TELEGRAM_BOT_TOKEN_HERE" # Замените на ваш действительный токен # Убедитесь, что папки с фотографиями и видео существуют for directory in [PHOTOS_DIR, VIDEOS_DIR]: if not os.path.isdir(directory): os.makedirs(directory) logger.info(f"Создана папка для медиафайлов: {directory}") 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()
Размещение видеофайлов:
videos
в том же каталоге, где находится ваш скрипт бота.videos
создайте подкаталоги для каждой трассы, например:
videos/
├── nurburgring/
│ └── video.mp4
├── monza/
│ └── video.mp4
└── spa/
└── video.mp4
tracks
.Проверка форматов видео:
Тестирование:
videos
.Оптимизация:
С помощью приведённых выше изменений ваш бот сможет отправлять видеофайлы, хранящиеся локально на сервере, вместо использования внешних ссылок. Это улучшит контроль над содержимым и позволит пользователям получать медиафайлы непосредственно из вашего сервера. Не забудьте заменить YOUR_TELEGRAM_BOT_TOKEN_HERE
на ваш действительный токен бота и разместить соответствующие видеофайлы в папке videos
.
Если у вас возникнут дополнительные вопросы или потребуется помощь с другими аспектами бота, не стесняйтесь обращаться!
Anpassen der Tastenkonfiguration
Ich sehe, dass der Nutzer die Tastenkonfiguration des Hauptmenüs ändern möchte, um alle Optionen sichtbar zu machen. Die aktuellen Optionen sind "Выбрать трассу" und "Создатели".
Anpassen des Layouts
Der Nutzer möchte die Tastenkonfiguration ändern, um mehr Platz zu schaffen und Überfüllungen zu vermeiden. Vorschläge umfassen das Kombinieren von mehreren Schaltflächen pro Zeile oder Spalte.
Überarbeiten der Tastenkombinationen
Der Nutzer möchte die Tastenkombinationen so anpassen, dass alle Schaltflächen besser platziert sind, eventuell durch Anordnung mehrerer Schaltflächen pro Reihe oder durch dynamisches Anordnen.
Gedanken für 4 Sekunden
Понял вас. Если количество кнопок в меню увеличивается или текущая компоновка кнопок выглядит перегруженной, стоит изменить структуру клавиатур, распределив кнопки по нескольким столбцам или строкам. Это обеспечит более удобное и эстетичное отображение кнопок для пользователей.
Ниже представлены изменения, которые необходимо внести в ваш код для улучшения компоновки кнопок:
Распределение кнопок по нескольким столбцам: Вместо размещения каждой кнопки в отдельной строке, можно группировать кнопки по несколько в строку. Это уменьшит вертикальный размер клавиатуры и сделает её более компактной.
Динамическое создание клавиатур: Для клавиатур с большим количеством кнопок, таких как список трасс или опасных поворотов, можно использовать функцию для автоматического распределения кнопок по строкам с заданным количеством кнопок в строке.
Обновление функций клавиатур: Изменим функции, отвечающие за создание клавиатур, чтобы они поддерживали новые схемы компоновки.
Ниже приведён обновлённый код с внесёнными изменениями:
import 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 = 10 # Длительность тайм-аута в секундах # Папки с медиафайлами PHOTOS_DIR = "photos" # Убедитесь, что такая папка существует VIDEOS_DIR = "videos" # Новая папка для видео # Разрешённые расширения файлов ALLOWED_PHOTO_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.bmp'} ALLOWED_VIDEO_EXTENSIONS = {'.mp4', '.mov', '.avi', '.mkv'} # Данные о трассах (добавлено поле 'фото_история' и 'video' для каждой трассы) 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 мин.", "фото_история": "nurburgring/history.jpg", "video": "nurburgring/video.mp4", # Добавлено поле видео "опасные_повороты": { "Karussell": { "описание": "Описание поворота Karussell — один из самых известных и сложных поворотов на Нюрбургринге. Требует максимальной концентрации и точности.", "фото": "nurburgring/karussell.jpg" }, "Brünnchen": { "описание": "Поворот Brünnchen характеризуется высокой скоростью и требованием к точности траектории.", "фото": "nurburgring/brunnchen.jpg" }, "Flugplatz": { "описание": "Поворот Flugplatz — это длинный и быстрый поворот, который требует отличного контроля над автомобилем.", "фото": "nurburgring/flugplatz.jpg" } } }, "Монца": { "описание": "Трасса Монца известна своими длинными прямыми и высокой скоростью, что делает её одной из самых быстрых трасс в мире.", "лидеры": "Баррикелло (Ferrari) | *1:21,046* |\n\n2. Хэмилтон (Mercedes) | *1:18,887* |\n\n", "история": "Автодром Монца был открыт в 1922 году и является одной из старейших трасс Формулы-1. Первоначальная трасса проходила частично по общественным дорогам. Трасса неоднократно перестраивалась и модернизировалась, но сохранила свою скоростную сущность. Монца славится своей высокой скоростью и длинными прямыми, что делает её одной из самых быстрых трасс в мире. Она также является свидетелем многих знаменательных событий и трагических аварий в истории автоспорта.", "фото_история": "monza/history.jpg", "video": "monza/video.mp4", # Добавлено поле видео "опасные_повороты": { "Lesmo": { "описание": "Секция Lesmo состоит из двух быстрых поворотов, которые требуют максимальной точности и контроля над автомобилем.", "фото": "monza/lesmo.jpg" }, "Curva Grande": { "описание": "Большой поворот (Curva Grande) — быстрый и протяженный поворот, где ошибки могут привести к серьезным последствиям.", "фото": "monza/curva_grande.jpg" } } }, "Спа-Франкоршам": { # Новая трасса "описание": "Трасса Спа-Франкоршам известна своим непредсказуемым погодным условиям и сложными поворотами, что делает её одной из самых технически сложных трасс в мире.", "лидеры": "Льюис Хэмилтон | *6:47.285 минут* |\n\nСебастьян Феттель | *6:49.875 минут* |\n\nМакс Ферстаппен | *6:50.123 минут* |", "история": "Автодром Спа-Франкоршам был открыт в 1921 году и является одним из старейших и самых уважаемых треков в мире автоспорта. Трасса проходит через живописные пейзажи Бельгии и известна своей историей и традициями. Спа-Франкоршам принимал множество чемпионатов мира, включая Формулу-1, и является любимой трассой многих гонщиков за её уникальную смесь высоких скоростей и технических сложностей.", "фото_история": "spa/history.jpg", "video": "spa/video.mp4", # Добавлено поле видео "опасные_повороты": { "Eau Rouge": { "описание": "Eau Rouge — знаменитый быстрый подъем с крутым поворотом, требующий точности и смелости от пилотов.", "фото": "spa/eau_rouge.jpg" }, "Pouhon": { "описание": "Pouhon — длинный и быстрый поворот, который требует отличной аэродинамики и устойчивости автомобиля.", "фото": "spa/pouhon.jpg" } } } } creators_info = "Этот бот был создан командой:\n\n1. Ратников Иван\n2. Хамраева Шукрона\n3. Новиков Елисей\n4. Елизарова Ксения\n5. Пискунов Кирилл\n6. Колосов Дмитрий." # Функция для динамического создания клавиатур с несколькими кнопками в строке def create_keyboard(buttons, buttons_per_row=2): keyboard = [] for i in range(0, len(buttons), buttons_per_row): keyboard.append(buttons[i:i + buttons_per_row]) return ReplyKeyboardMarkup(keyboard, resize_keyboard=True) # Клавиатуры def main_menu_keyboard(): buttons = ["Выбрать трассу", "Создатели"] return create_keyboard(buttons, buttons_per_row=2) def tracks_keyboard(): buttons = list(tracks.keys()) + ["Назад"] return create_keyboard(buttons, buttons_per_row=2) # 2 кнопки в строке def track_menu_keyboard(): buttons = ["Лидеры", "История о трассе", "Опасные повороты", "Видео трассы", "Назад"] return create_keyboard(buttons, buttons_per_row=2) # 2 кнопки в строке, последняя строка с одной кнопкой def dangerous_turns_keyboard(track_name): dangerous_turns = tracks[track_name]["опасные_повороты"] buttons = list(dangerous_turns.keys()) + ["Назад"] return create_keyboard(buttons, buttons_per_row=2) # 2 кнопки в строке # Функция анти-спама 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("Слишком много сообщений! Пожалуйста, подождите 10 секунд.") return False return True # Вспомогательная функция для отправки фотографий с обработкой длинных подписей async def send_photo(context, chat_id, photo_filename, caption=None, reply_markup=None): # Полный путь к файлу file_path = os.path.join(PHOTOS_DIR, photo_filename) # Проверка расширения файла _, ext = os.path.splitext(photo_filename) if ext.lower() not in ALLOWED_PHOTO_EXTENSIONS: logger.warning(f"Недопустимое расширение файла: {photo_filename}") await context.bot.send_message( chat_id=chat_id, text="Невозможно отправить файл с недопустимым расширением.", reply_markup=reply_markup ) return if os.path.isfile(file_path): try: with open(file_path, 'rb') as photo_file: if caption: if len(caption) <= 1024: # Если длина подписи допустимая, отправляем фото с подписью await context.bot.send_photo( chat_id=chat_id, photo=photo_file, caption=caption, parse_mode="Markdown", reply_markup=reply_markup ) else: # Если подпись слишком длинная, отправляем фото без подписи await context.bot.send_photo( chat_id=chat_id, photo=photo_file, reply_markup=reply_markup ) # Разбиваем длинный текст на части по 1024 символа for i in range(0, len(caption), 1024): part = caption[i:i+1024] await context.bot.send_message( chat_id=chat_id, text=part, parse_mode="Markdown", reply_markup=reply_markup ) else: # Если подпись не требуется, просто отправляем фото await context.bot.send_photo( chat_id=chat_id, photo=photo_file, reply_markup=reply_markup ) except Exception as e: logger.error(f"Ошибка при отправке фото: {e}") if caption: await context.bot.send_message( chat_id=chat_id, text=caption, parse_mode="Markdown", reply_markup=reply_markup ) else: logger.warning(f"Файл фото не найден: {file_path}") if caption: await context.bot.send_message( chat_id=chat_id, text=f"{caption}\n\n*Фото не доступно.*", parse_mode="Markdown", reply_markup=reply_markup ) # Вспомогательная функция для отправки видео из локальной папки async def send_video(context, chat_id, video_filename, caption=None, reply_markup=None): # Полный путь к файлу file_path = os.path.join(VIDEOS_DIR, video_filename) # Проверка расширения файла _, ext = os.path.splitext(video_filename) if ext.lower() not in ALLOWED_VIDEO_EXTENSIONS: logger.warning(f"Недопустимое расширение видеофайла: {video_filename}") await context.bot.send_message( chat_id=chat_id, text="Невозможно отправить видео с недопустимым расширением.", reply_markup=reply_markup ) return if os.path.isfile(file_path): try: with open(file_path, 'rb') as video_file: if caption: await context.bot.send_video( chat_id=chat_id, video=video_file, caption=caption, parse_mode="Markdown", reply_markup=reply_markup ) else: await context.bot.send_video( chat_id=chat_id, video=video_file, reply_markup=reply_markup ) except Exception as e: logger.error(f"Ошибка при отправке видео: {e}") if caption: await context.bot.send_message( chat_id=chat_id, text=f"{caption}\n\n*Видео не доступно.*", parse_mode="Markdown", reply_markup=reply_markup ) else: logger.warning(f"Файл видео не найден: {file_path}") if caption: await context.bot.send_message( chat_id=chat_id, text=f"{caption}\n\n*Видео не доступно.*", parse_mode="Markdown", reply_markup=reply_markup ) # Обработчики 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 == "История о трассе": history = track_info["история"] response = f"**История трассы {current_track}:**\n{history}" # Получение имени файла фото истории photo_filename = track_info.get("фото_история") if photo_filename: # Отправка фото с текстом await send_photo( context, chat_id=update.effective_chat.id, photo_filename=photo_filename, caption=response, reply_markup=track_menu_keyboard() ) else: # Если фото не задано, отправляем только текст 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 elif text == "Видео трассы": # Обработка новой опции video_filename = track_info.get("video") if video_filename: response = f"**Видео трассы {current_track}:**" await send_video( context, chat_id=update.effective_chat.id, video_filename=video_filename, caption=response, reply_markup=track_menu_keyboard() ) else: await update.message.reply_text( "Видео для этой трассы недоступно.", parse_mode="Markdown", reply_markup=track_menu_keyboard() ) 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 = create_keyboard(["Назад"], buttons_per_row=1) if photo_filename: await send_photo( context, # Передача context chat_id=update.effective_chat.id, photo_filename=photo_filename, caption=response_text, reply_markup=markup ) else: await update.message.reply_text( response_text, parse_mode="Markdown", reply_markup=markup ) else: response = "Неизвестный поворот. Пожалуйста, выберите доступный поворот или нажмите Назад." markup = create_keyboard(["Назад"], buttons_per_row=1) 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 = "YOUR_TELEGRAM_BOT_TOKEN_HERE" # Замените на ваш действительный токен # Убедитесь, что папки с фотографиями и видео существуют for directory in [PHOTOS_DIR, VIDEOS_DIR]: if not os.path.isdir(directory): os.makedirs(directory) logger.info(f"Создана папка для медиафайлов: {directory}") 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()
Создание Функции create_keyboard
для Динамического Создания Клавиатур
def create_keyboard(buttons, buttons_per_row=2): keyboard = [] for i in range(0, len(buttons), buttons_per_row): keyboard.append(buttons[i:i + buttons_per_row]) return ReplyKeyboardMarkup(keyboard, resize_keyboard=True)
Эта функция принимает список кнопок и количество кнопок на строку (buttons_per_row
). Она автоматически распределяет кнопки по строкам, что позволяет легко управлять компоновкой клавиатур без необходимости вручную создавать каждую строку.
Изменение Функций Клавиатур для Использования create_keyboard
Главное Меню (main_menu_keyboard
)
def main_menu_keyboard(): buttons = ["Выбрать трассу", "Создатели"] return create_keyboard(buttons, buttons_per_row=2)
Здесь две кнопки размещаются в одной строке.
Список Трасс (tracks_keyboard
)
def tracks_keyboard(): buttons = list(tracks.keys()) + ["Назад"] return create_keyboard(buttons, buttons_per_row=2) # 2 кнопки в строке
Здесь список трасс и кнопка "Назад" распределяются по двум кнопкам в строке. Если количество трасс нечётное, последняя строка будет содержать одну кнопку.
Меню Трассы (track_menu_keyboard
)
def track_menu_keyboard(): buttons = ["Лидеры", "История о трассе", "Опасные повороты", "Видео трассы", "Назад"] return create_keyboard(buttons, buttons_per_row=2) # 2 кнопки в строке, последняя строка с одной кнопкой
Здесь основные опции трассы распределены по двум кнопкам в строке. Последняя строка содержит одну кнопку "Назад".
Опасные Повороты (dangerous_turns_keyboard
)
def dangerous_turns_keyboard(track_name): dangerous_turns = tracks[track_name]["опасные_povoroty"] buttons = list(dangerous_turns.keys()) + ["Назад"] return create_keyboard(buttons, buttons_per_row=2) # 2 кнопки в строке
Опасные повороты распределяются по двум кнопкам в строке, с кнопкой "Назад" в конце.
Изменение Обработчиков для Использования Новых Клавиатур
Все функции-обработчики (start
, main_menu_choice
, track_choice
, track_menu_choice
, dangerous_turn_choice
) остаются без изменений, кроме тех случаев, когда требуется использовать новую компоновку клавиатур через функцию create_keyboard
.
Обновление Кнопок "Назад"
В некоторых местах кнопка "Назад" размещена в отдельной строке для удобства пользователя. Например, при выборе опасного поворота:
markup = create_keyboard(["Назад"], buttons_per_row=1)
Это позволяет пользователю легко вернуться к предыдущему меню.
Общие Рекомендации по Компоновке Кнопок
Количество Кнопок на Строку: Обычно 2-3 кнопки в строке являются оптимальными для удобства пользователей. Слишком много кнопок в одной строке может затруднить навигацию, а слишком мало — привести к излишней длине клавиатуры.
Последовательность Кнопок: Старайтесь группировать похожие кнопки вместе и размещать кнопки "Назад" внизу или в отдельной строке для интуитивного понимания пользователями.
Адаптивность: Использование функции create_keyboard
позволяет легко изменять количество кнопок на строку в зависимости от размера экрана или количества доступных кнопок.
Главное Меню
[ Выбрать трассу ] [ Создатели ]
Список Трасс (пример с тремя трассами)
[ Нюрбургринг-Нордшляйфе ] [ Монца ]
[ Спа-Франкоршам ] [ Назад ]
Меню Трассы
[ Лидеры ] [ История о трассе ]
[ Опасные повороты ] [ Видео трассы ]
[ Назад ]
Опасные Повороты (пример с тремя поворотами)
[ Karussell ] [ Brünnchen ]
[ Flugplatz ] [ Назад ]
Адаптивная Компоновка Клавиатур
Вы можете изменить количество кнопок на строку в зависимости от устройства пользователя или других факторов. Например, для десктопных пользователей можно использовать больше кнопок в строке, а для мобильных — меньше.
Использование Inline Клавиатур
Если вам нужно более гибкое управление кнопками (например, ссылки, callback-данные), рассмотрите возможность использования inline-клавиатур (InlineKeyboardMarkup
). Однако для простых навигационных меню ReplyKeyboardMarkup остаётся предпочтительным.
Поддержка Языков
Если вы планируете поддерживать несколько языков, можно динамически изменять текст кнопок в зависимости от выбранного языка пользователя.
С помощью приведённых выше изменений клавиатуры вашего Telegram-бота станут более компактными и удобными для пользователей. Функция create_keyboard
значительно упрощает процесс создания клавиатур и обеспечивает гибкость в распределении кнопок. Не забудьте протестировать бота на различных устройствах, чтобы убедиться в корректности отображения клавиатур.
Если у вас возникнут дополнительные вопросы или потребуется помощь с другими аспектами бота, не стесняйтесь обращаться!