Отправка видео по ссылке
Creato il: 19 dicembre 2024
Creato il: 19 dicembre 2024
Aktualisieren des Videoformats
Der Benutzer möchte den Python-Skript ändern, um Video-Links über URLs zu senden, anstatt lokale Dateien zu benutzen und auch andere Video-Versandoptionen zu entfernen.
Aktualisieren der Videodaten
Ich denke darüber nach, die 'videos'-Felder in den Streckeninformationen durch URLs zu ersetzen und die Videoversandfunktion so anzupassen, dass Videos als Links gesendet werden.
Austausch der Methoden
Ich überlege, wie der Code so geändert werden kann, dass Video-URLs anstelle von lokalen Dateien übermittelt werden, und die send_video
-Funktion entfernt oder ignoriert wird.
Kommunizieren von Video-Links
Ich schlage vor, den Handler track_menu_choice
so anzupassen, dass er Video-Links anstelle von Uploads sendet, und die send_video
-Funktion zu entfernen.
Überarbeiten des Codes
Ich sollte den Benutzer darauf hinweisen, dass ich den tracks
-Dictionarys 'videos'-Feld durch URLs ersetzt habe, die send_video
-Funktion entfernt und die track_menu_choice
-Funktion angepasst habe, um Video-Links zu senden.
Gedanken für 5 Sekunden
Чтобы изменить бота таким образом, чтобы он отправлял видео через ссылки, а не загружал локальные файлы, необходимо внести несколько изменений в ваш код. Ниже приведены шаги и соответствующие изменения, которые нужно выполнить:
Обновите данные о видео:
Вместо хранения локальных имен файлов, храните URL-адреса видео. Это можно сделать, заменив значения в списках videos
на прямые ссылки на видео. Например:
tracks = { "Нюрбургринг-Нордшляйфе": { # ... другие поля ... "videos": [ "https://example.com/nurburgring/video01.mp4", "https://example.com/nurburgring/video02.mp4" ], # ... другие поля ... }, "Монца": { # ... другие поля ... "videos": [ "https://example.com/monza/video1.mp4", "https://example.com/monza/video2.mp4", "https://example.com/monza/video3.mp4" ], # ... другие поля ... }, "Спа-Франкоршам": { # ... другие поля ... "videos": [ "https://example.com/spa/video1.mp4" ], # ... другие поля ... } }
Удалите или игнорируйте функцию send_video
:
Поскольку видео теперь будут отправляться через ссылки, функция send_video
больше не нужна. Вы можете удалить её из кода:
# Удалите или закомментируйте функцию send_video # async def send_video(context, chat_id, video_filename, caption=None, reply_markup=None): # ...
Измените обработчик track_menu_choice
для отправки ссылок на видео:
Вместо загрузки видео с локального диска, бот будет отправлять ссылки пользователю. Вот как можно изменить соответствующий раздел:
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 == "Видео трассы": videos = track_info.get("videos", []) if videos: response = f"**Видео трассы {current_track}:**\n" + "\n".join([f"[Видео {i+1}]({video})" for i, video in enumerate(videos)]) await update.message.reply_text( response, parse_mode="Markdown", disable_web_page_preview=True, 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
Объяснение изменений:
disable_web_page_preview=True
отключает предварительный просмотр ссылок для более аккуратного отображения.Удалите настройку и создание папки VIDEOS_DIR
:
Поскольку видео больше не хранятся локально, можно удалить все упоминания о папке VIDEOS_DIR
:
# Удалите или закомментируйте следующие строки: # VIDEOS_DIR = "videos" # ALLOWED_VIDEO_EXTENSIONS = {'.mp4', '.mov', '.avi', '.mkv'} # В функции main, удалите создание папки: # for directory in [PHOTOS_DIR, VIDEOS_DIR]: # if not os.path.isdir(directory): # os.makedirs(directory) # logger.info(f"Создана папка для медиафайлов: {directory}")
Обновите функцию main
:
Убедитесь, что в функции main
не создаются директории для видео и что функция send_video
не используется. Ваш main
может выглядеть следующим образом:
def main(): TOKEN = "YOUR_TELEGRAM_BOT_TOKEN" # Замените на ваш токен # Убедитесь, что папка с фотографиями существует 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()
Дополнительные улучшения:
Полный обновленный пример кода:
Ниже приведен полный обновленный код с учетом всех вышеуказанных изменений:
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_PHOTO_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.bmp'} # Данные о трассах (обновлено поле 'videos' для каждой трассы с URL-адресами) 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", "videos": [ "https://example.com/nurburgring/video01.mp4", "https://example.com/nurburgring/video02.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", "videos": [ "https://example.com/monza/video1.mp4", "https://example.com/monza/video2.mp4", "https://example.com/monza/video3.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", "videos": [ "https://example.com/spa/video1.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 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 == "Видео трассы": videos = track_info.get("videos", []) if videos: response = f"**Видео трассы {current_track}:**\n" + "\n".join([f"[Видео {i+1}]({video})" for i, video in enumerate(videos)]) await update.message.reply_text( response, parse_mode="Markdown", disable_web_page_preview=True, 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" # Замените на ваш токен # Убедитесь, что папка с фотографиями существует 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()
Важно:
"YOUR_TELEGRAM_BOT_TOKEN"
на реальный токен вашего бота и убедитесь, что он не доступен третьим лицам.После внесения этих изменений ваш бот будет отправлять пользователям ссылки на видео вместо загрузки локальных файлов, что упростит управление медиа и снизит нагрузку на сервер.
Überarbeiten des Codes
Ich schaue mir den Telegram-Bot an, der Informationen über Rennstrecken verwaltet, jetzt mit YouTube-Videolinks anstelle von lokalen Dateien.
Erstmals hochladen
Ich denke darüber nach, wie Videos jetzt vielleicht aus einem speziellen Ordner in die Geschichte der Rennstrecke hochgeladen werden können, um die Verwaltung der Videos modern und einfach zu gestalten.
Festlegen von Abläufen
Die Nutzerin möchte möglicherweise, dass der Bot Videos aus einem Ordner in die Historie des Rennstrecken-Geschichtsverlaufs hochladen und dabei sowohl verlinkte Videos als auch Ordnervideos verwalten kann.
Videohinzufügung
Die Nutzerin möchte möglicherweise, dass Videos aus einem Ordner in der Historie der Rennstrecke gesendet werden, um die Informationen visuell zu ergänzen, besonders wenn die Geschichte der Strecke angezeigt wird.
Einfügen von Videofunktionen
Ich bin dabei, den Bot so zu aktualisieren, dass er Videos aus einem Ordner hinzufügen kann, um die Geschichtserzählung der Rennstrecke visuell zu unterstützen, wenn 'История о трассе' ausgewählt wird.
Verknüpfen und hochladen
Ich denke darüber nach, wie der Bot Videos sowohl durch Links als auch durch Hochladen aus einem Ordner senden kann, um die Historie der Rennstrecke visuell zu unterstützen.
Hinzufügen von Videofunktionalität
Ich denke darüber nach, wie Videos aus einem Ordner in die Historie der Rennstrecke integriert werden können. Eine neue Funktion könnte entwickelt werden, um diese Videofunktionen im Bot zu unterstützen.
Einfügen von Videofunktionen
Ich denke darüber nach, wie der Bot auch Videos aus einem Ordner neben den verlinkten Videos senden kann, um die Historie der Rennstrecke umfassender zu gestalten.
Implementieren der Funktionalität
Ich werde die 'send_video'-Funktion neu einführen, um lokale Videos aus einem Ordner zu den Rennstrecken-historischen Informationen hinzuzufügen, und auch das 'history_videos'-Feld zu jedem Track hinzufügen.
Reintroducing functions
Ich werde sowohl die 'send_video'-Funktion einführen als auch das 'history_videos'-Feld zu jedem Track hinzufügen, um lokale Videos aus einem speziellen Ordner den geschichtlichen Informationen der Rennstrecke hinzuzufügen.
Korrigiere und aktualisiere
Ich denke darüber nach, wie fehlende Kommas in der 'videos'-Liste von 'Спа-Франкоршам' behoben werden können, um die Funktionalität des Bots bereitzustellen.
Gedanken für 12 Sekunden
Чтобы добавить возможность отправки видео, связанных с историей трассы, загруженных из определённой папки, необходимо внести следующие изменения в ваш бот:
Добавить папку для видео истории:
Создайте новую папку, например, history_videos
, где будут храниться видеофайлы, связанные с историей каждой трассы.
Обновить структуру данных трасс:
Для каждой трассы добавьте новое поле, например, history_videos
, которое будет содержать список имен видеофайлов, находящихся в папке history_videos
.
Восстановить функцию send_video
:
Поскольку вы теперь будете отправлять видео из локальных файлов для истории трассы, необходимо вернуть функцию send_video
в ваш код.
Изменить обработчик track_menu_choice
:
В разделе "История трассы" добавьте отправку связанных видео из папки history_videos
.
Обеспечить наличие необходимых папок:
Убедитесь, что папка history_videos
существует. Если нет, создайте её при запуске бота.
Исправить синтаксическую ошибку:
В предоставленном вами коде в разделе Спа-Франкоршам
отсутствует запятая между двумя ссылками на видео. Это приведёт к ошибке при запуске.
Ниже приведён обновлённый код с учётом всех вышеуказанных изменений:
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" HISTORY_VIDEOS_DIR = "history_videos" # Новая папка для видео истории # Разрешённые расширения файлов ALLOWED_PHOTO_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.bmp'} ALLOWED_VIDEO_EXTENSIONS = {'.mp4', '.mov', '.avi', '.mkv'} # Данные о трассах (обновлено поле 'videos' для каждой трассы с URL-адресами и добавлено 'history_videos') 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", "videos": [ "https://www.youtube.com/watch?v=MzH_Y-ytRmI&t=677s", "https://www.youtube.com/watch?v=JTGvHskGKI4" ], "history_videos": [ "history_videos/nurburgring_history1.mp4", "history_videos/nurburgring_history2.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", "videos": [ "https://www.youtube.com/watch?v=W8tAKhRJtRk", "https://www.youtube.com/watch?v=ugZimpxZhXE" ], "history_videos": [ "history_videos/monza_history1.mp4", "history_videos/monza_history2.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", "videos": [ "https://www.youtube.com/watch?v=LRBfQdD-fIE", "https://www.youtube.com/watch?v=sj1HuWqVQLw" ], "history_videos": [ "history_videos/spa_history1.mp4", "history_videos/spa_history2.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_local_video(context, chat_id, video_filename, caption=None, reply_markup=None): # Полный путь к файлу file_path = os.path.join(HISTORY_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: await context.bot.send_video( chat_id=chat_id, video=video_file, 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 ) 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()) # Отправка видео истории, если они есть history_videos = track_info.get("history_videos", []) for video_filename in history_videos: await send_local_video( context, chat_id=update.effective_chat.id, video_filename=video_filename, caption=None, # Можно добавить описание видео 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 == "Видео трассы": videos = track_info.get("videos", []) if videos: response = f"**Видео трассы {current_track}:**\n" + "\n".join([f"[Видео {i+1}]({video})" for i, video in enumerate(videos)]) await update.message.reply_text( response, parse_mode="Markdown", disable_web_page_preview=True, 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 = "8166041667:AAHsjC8QSxSsqIt3zxGUm6YeGyEcp9QRr08" # Замените на ваш токен # Убедитесь, что папки с фотографиями и видео истории существуют for directory in [PHOTOS_DIR, HISTORY_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()
Создание папки history_videos
:
HISTORY_VIDEOS_DIR = "history_videos" # Новая папка для видео истории
В main()
функция добавляет проверку и создание этой папки:
for directory in [PHOTOS_DIR, HISTORY_VIDEOS_DIR]: if not os.path.isdir(directory): os.makedirs(directory) logger.info(f"Создана папка для медиафайлов: {directory}")
Обновление структуры данных трасс:
Для каждой трассы добавлено поле history_videos
, содержащее список видеофайлов:
"history_videos": [ "history_videos/nurburgring_history1.mp4", "history_videos/nurburgring_history2.mp4" ],
Убедитесь, что файлы nurburgring_history1.mp4
и nurburgring_history2.mp4
существуют в папке history_videos
. Повторите аналогичные изменения для других трасс.
Восстановление функции send_video
:
Функция send_local_video
предназначена для отправки локальных видеофайлов:
async def send_local_video(context, chat_id, video_filename, caption=None, reply_markup=None): # Полный путь к файлу file_path = os.path.join(HISTORY_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: await context.bot.send_video( chat_id=chat_id, video=video_file, 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 ) 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_choice
для отправки видео истории:
В блоке elif text == "История о трассе":
добавлена отправка видео истории:
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()) # Отправка видео истории, если они есть history_videos = track_info.get("history_videos", []) for video_filename in history_videos: await send_local_video( context, chat_id=update.effective_chat.id, video_filename=video_filename, caption=None, # Можно добавить описание видео reply_markup=track_menu_keyboard() )
Это позволит отправлять все видео, перечисленные в history_videos
, после отправки текста и фотографии истории.
Исправление синтаксической ошибки:
В вашем первоначальном коде в разделе Спа-Франкоршам
отсутствовала запятая между двумя ссылками на видео. Исправьте это следующим образом:
"videos": [ "https://www.youtube.com/watch?v=LRBfQdD-fIE", "https://www.youtube.com/watch?v=sj1HuWqVQLw" ],
Дополнительные рекомендации:
Безопасность токена: Убедитесь, что вы никогда не публикуете свой токен бота публично. В приведённом коде токен указан явно для примера. Рекомендуется использовать переменные окружения или другие методы для хранения токена безопасно.
Проверка наличия файлов: Убедитесь, что все упомянутые файлы (фото и видео) существуют в соответствующих папках, иначе пользователи будут получать сообщения о недоступности медиа.
Оптимизация отправки видео: Если количество видео в истории велико, рассмотрите возможность отправки их по одному или использования инлайн-клавиатур для более удобного доступа.
your_bot_directory/
├── photos/
│ ├── nurburgring/
│ │ └── karussell.jpg
│ │ └── brunnchen.jpg
│ │ └── flugplatz.jpg
│ ├── nurburgring/history.jpg
│ ├── monza/
│ │ └── lesmo.jpg
│ │ └── curva_grande.jpg
│ ├── monza/history.jpg
│ ├── spa/
│ │ └── eau_rouge.jpg
│ │ └── pouhon.jpg
│ ├── spa/history.jpg
├── history_videos/
│ ├── nurburgring_history1.mp4
│ ├── nurburgring_history2.mp4
│ ├── monza_history1.mp4
│ ├── monza_history2.mp4
│ ├── spa_history1.mp4
│ ├── spa_history2.mp4
├── your_bot_script.py
После внесения всех указанных изменений ваш бот сможет отправлять как ссылки на видео трасс, так и локально сохранённые видео, связанные с историей каждой трассы. Это обеспечит более богатый и информативный опыт для пользователей вашего бота.
Не забудьте протестировать бота после внесения изменений, чтобы убедиться, что все функции работают корректно и медиафайлы отправляются без ошибок.
Einen kompakten Überblick
Der Benutzer möchte, dass der Bot zur ursprünglichen Funktion zurückkehrt, jedoch ein zweites Video über die Strecke in den Überblick einfügt.
Optionen durchgehen
Der Benutzer möchte Video-Links senden und beim Versand von Verlaufsinformationen ein Foto mit einem Video wie "a short video about the track" hinzufügen, entweder von einem Link oder lokal.
Zurück zum Ursprung
Der Benutzer möchte lokale Dateien entfernen und stattdessen Video-Links verwenden, um ein Video mit der Bildunterschrift "Краткое видео о трассе..." beizufügen.
Anpassungen vornehmen
Der Benutzer möchte lokale Dateien entfernen, stattdessen Video-Links verwenden und nach dem Versand von Verlaufsinformationen ein Video mit der Bildunterschrift "Краткое видео о трассе..." einfügen.
Videoeinschränkungen beachten
Telegram-Bots können keine eingebetteten Videos senden. Der Vorschlag ist, einen Video-Link mit einer Bildunterschrift zu schicken.
Video-URL senden
Wenn das Video öffentlich gehostet wird, kann der Bot 'send_video' verwenden, um die URL zu senden, und Telegram wird es herunterladen und an den Benutzer senden.
Anpassen und integrieren
Der Vorschlag beinhaltet das Entfernen von 'history_videos', das Hinzufügen eines 'history_video' Feldes mit URL und die Aktualisierung des 'track_menu_choice' Handlers, um Video-URLs in den Überblick zu integrieren.
Zurück zum Ursprünglichen
Der Fokus liegt auf dem Entfernen von 'history_videos', dem Hinzufügen des 'history_video' Feldes mit einem Video-Link und der Anpassung des 'track_menu_choice' Handlers, um ein Video über URL und Bildunterschrift zum Überblick hinzuzufügen.
Anpassen und aktualisieren
Der Bot sollte 'history_video' aufnehmen, um den Überblick zu verbessern. Ich werde die aktuelle Implementierung so ändern, dass sie genau den Anforderungen entspricht.
Gedanken für 11 Sekunden
Понял вашу задачу. Вы хотите вернуть функциональность бота к исходному состоянию, при этом добавить отправку видео в разделе "История трассы" в качестве второго сообщения с подписью "Краткое видео о трассе...". Ниже приведены подробные шаги и обновлённый код для реализации этого изменения.
Удаление изменений, предложенных ранее:
Если вы ранее добавляли поле history_videos
и связанные функции для отправки локальных видео, необходимо их удалить. Мы вернёмся к использованию ссылок на видео, как было изначально.
Добавление поля history_video
в структуру данных трасс:
Для каждой трассы добавьте новое поле history_video
, которое будет содержать URL ссылки на короткое видео о трассе.
Обновление обработчика track_menu_choice
:
В разделе "История трассы" после отправки истории и фото, бот будет отправлять вторым сообщением видео с подписью "Краткое видео о трассе...".
Обеспечение корректности ссылок на видео:
Убедитесь, что все ссылки на видео корректны и доступны. Если вы используете ссылки с YouTube, Telegram автоматически создаст предпросмотр видео.
Безопасность токена:
В представленном коде токен бота указан явно. Никогда не публикуйте ваш реальный токен публично. Рекомендуется использовать переменные окружения или файлы конфигурации для хранения токена безопасно.
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_PHOTO_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.bmp'} # Данные о трассах (добавлено поле 'history_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", "videos": [ "https://www.youtube.com/watch?v=MzH_Y-ytRmI&t=677s", "https://www.youtube.com/watch?v=JTGvHskGKI4" ], "history_video": "https://www.youtube.com/watch?v=example1", # Замените на реальную ссылку "опасные_повороты": { "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", "videos": [ "https://www.youtube.com/watch?v=W8tAKhRJtRk", "https://www.youtube.com/watch?v=ugZimpxZhXE" ], "history_video": "https://www.youtube.com/watch?v=example2", # Замените на реальную ссылку "опасные_повороты": { "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", "videos": [ "https://www.youtube.com/watch?v=LRBfQdD-fIE", "https://www.youtube.com/watch?v=sj1HuWqVQLw" ], "history_video": "https://www.youtube.com/watch?v=example3", # Замените на реальную ссылку "опасные_повороты": { "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 ) # Вспомогательная функция для отправки видео из URL async def send_video_url(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()) # Отправка видео истории, если оно есть history_video = track_info.get("history_video") if history_video: caption_video = "Краткое видео о трассе..." await send_video_url( context, chat_id=update.effective_chat.id, video_url=history_video, caption=caption_video, 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 == "Видео трассы": videos = track_info.get("videos", []) if videos: response = f"**Видео трассы {current_track}:**\n" + "\n".join([f"[Видео {i+1}]({video})" for i, video in enumerate(videos)]) await update.message.reply_text( response, parse_mode="Markdown", disable_web_page_preview=True, 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" # Замените на ваш токен # Убедитесь, что папка с фотографиями существует 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()
Добавление поля history_video
в данные трасс:
В структуре данных каждой трассы добавлено новое поле history_video
, содержащее ссылку на короткое видео о трассе. Пример:
"history_video": "https://www.youtube.com/watch?v=example1", # Замените на реальную ссылку
Важно: Замените "https://www.youtube.com/watch?v=example1"
на реальные ссылки на ваши видео.
Добавление функции send_video_url
:
Эта функция используется для отправки видео по URL. Она принимает контекст, идентификатор чата, URL видео, подпись и клавиатуру.
async def send_video_url(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 == "История о трассе": 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()) # Отправка видео истории, если оно есть history_video = track_info.get("history_video") if history_video: caption_video = "Краткое видео о трассе..." await send_video_url( context, chat_id=update.effective_chat.id, video_url=history_video, caption=caption_video, reply_markup=track_menu_keyboard() )
Исправление синтаксической ошибки в разделе "Спа-Франкоршам":
В вашем предыдущем коде в разделе "Спа-Франкоршам" отсутствовала запятая между двумя ссылками на видео, что приводило к ошибке. Убедитесь, что ссылки разделены запятыми:
"videos": [ "https://www.youtube.com/watch?v=LRBfQdD-fIE", "https://www.youtube.com/watch?v=sj1HuWqVQLw" ],
Замена токена бота:
В вашем коде токен был указан явно. Никогда не публикуйте ваш реальный токен публично. Вместо этого используйте переменные окружения или файлы конфигурации для безопасного хранения токена.
TOKEN = "YOUR_TELEGRAM_BOT_TOKEN" # Замените на ваш токен
Рекомендация: Используйте переменные окружения. Пример:
import os TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
И установите переменную окружения TELEGRAM_BOT_TOKEN
в вашей системе.
Убедитесь, что структура папок и файлы соответствуют путям, указанным в вашем коде.
your_bot_directory/
├── photos/
│ ├── nurburgring/
│ │ └── karussell.jpg
│ │ └── brunnchen.jpg
│ │ └── flugplatz.jpg
│ ├── nurburgring/history.jpg
│ ├── monza/
│ │ └── lesmo.jpg
│ │ └── curva_grande.jpg
│ ├── monza/history.jpg
│ ├── spa/
│ │ └── eau_rouge.jpg
│ │ └── pouhon.jpg
│ ├── spa/history.jpg
├── your_bot_script.py
После внесения всех изменений:
Проверьте ссылки на видео: Убедитесь, что все ссылки на видео корректны и доступны. Попробуйте открыть их в браузере.
Проверьте наличие файлов: Убедитесь, что все указанные фотографии находятся в соответствующих папках.
Запустите бота: Запустите скрипт и протестируйте функциональность бота, особенно раздел "История трассы", чтобы убедиться, что видео отправляется корректно.
Обработка ошибок: Если видео не отправляется, проверьте логирование для выявления возможных ошибок.
Оптимизация отправки видео: Если вы планируете отправлять большие видеофайлы, учтите ограничения Telegram на размер файлов (до 50 МБ для видео через URL и до 200 МБ для загружаемых файлов).
Использование встроенных кнопок: Для улучшения пользовательского опыта можно использовать инлайн-клавиатуры с кнопками для открытия видео.
Локальное хранение видео: Если ссылки на видео часто недоступны или изменяются, рассмотрите возможность локального хранения видео и отправки их напрямую через бота. Однако это потребует дополнительных ресурсов на сервере.
Внесённые изменения позволяют вашему боту отправлять дополнительное видео в разделе "История трассы" как второе сообщение с подписью "Краткое видео о трассе...". Это улучшает информативность и взаимодействие с пользователями. Не забудьте протестировать все функции после внесения изменений, чтобы убедиться в их корректной работе.
Если у вас возникнут дополнительные вопросы или потребуется помощь с другими функциями бота, не стесняйтесь обращаться!
Überprüfen der Videooptionen
Ich schaue mir an, wie der Bot die URLs der Videos verwenden kann, und denke darüber nach, wie das lokale Anhängen eines Videos als zweite Nachricht aussieht.
Anpassen des Bots
Ich schaue mir an, wie der Bot das Video aus einem Ordner liest und sendet, indem ich einen neuen "history_videos"-Ordner erstelle und die tracks-Datenstruktur anpasse.
Anpassen des Bots
Ich strebe an, den Handler zu ändern, sodass Videos lokal statt per URL gesendet werden, und denke darüber nach, ob es besser ist, den Dateinamen statt der URL zu verwenden.
Gedanken für 5 Sekunden
Понял вашу задачу. Вы хотите изменить бота таким образом, чтобы видео в разделе "История трассы" загружались из локальной папки на вашем сервере или компьютере, а не из внешних ссылок. Для этого необходимо внести следующие изменения в ваш код:
Создать папку для хранения видео историй:
Создайте новую папку, например, history_videos
, где будут храниться видеофайлы, связанные с историей каждой трассы.
Обновить структуру данных трасс:
Для каждой трассы добавьте новое поле, например, history_video
, которое будет содержать имя видеофайла, находящегося в папке history_videos
.
Создать функцию для отправки локальных видеофайлов:
Добавьте функцию, аналогичную send_photo
, но предназначенную для отправки видеофайлов.
Изменить обработчик track_menu_choice
:
В разделе "История трассы" после отправки текста и фото, бот будет отправлять видео из локальной папки с подписью "Краткое видео о трассе...".
Обеспечить наличие необходимых папок и файлов:
Убедитесь, что папка history_videos
существует и содержит все необходимые видеофайлы.
Безопасность токена:
Убедитесь, что ваш токен бота не публикуется публично. Используйте переменные окружения или файлы конфигурации для безопасного хранения токена.
Ниже представлен обновлённый код бота с учётом всех вышеуказанных изменений:
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" HISTORY_VIDEOS_DIR = "history_videos" # Новая папка для видео историй # Разрешённые расширения файлов ALLOWED_PHOTO_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.bmp'} ALLOWED_VIDEO_EXTENSIONS = {'.mp4', '.mov', '.avi', '.mkv'} # Данные о трассах (добавлено поле 'history_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", "videos": [ "https://www.youtube.com/watch?v=MzH_Y-ytRmI&t=677s", "https://www.youtube.com/watch?v=JTGvHskGKI4" ], "history_video": "nurburgring_history.mp4", # Имя видеофайла в папке history_videos "опасные_повороты": { "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", "videos": [ "https://www.youtube.com/watch?v=W8tAKhRJtRk", "https://www.youtube.com/watch?v=ugZimpxZhXE" ], "history_video": "monza_history.mp4", # Имя видеофайла в папке history_videos "опасные_повороты": { "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", "videos": [ "https://www.youtube.com/watch?v=LRBfQdD-fIE", "https://www.youtube.com/watch?v=sj1HuWqVQLw" ], "history_video": "spa_history.mp4", # Имя видеофайла в папке history_videos "опасные_повороты": { "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_local_video(context, chat_id, video_filename, caption=None, reply_markup=None): # Полный путь к файлу file_path = os.path.join(HISTORY_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: await context.bot.send_video( chat_id=chat_id, video=video_file, 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 ) 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()) # Отправка видео истории, если оно есть history_video = track_info.get("history_video") if history_video: caption_video = "Краткое видео о трассе..." await send_local_video( context, chat_id=update.effective_chat.id, video_filename=history_video, caption=caption_video, 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 == "Видео трассы": videos = track_info.get("videos", []) if videos: response = f"**Видео трассы {current_track}:**\n" + "\n".join([f"[Видео {i+1}]({video})" for i, video in enumerate(videos)]) await update.message.reply_text( response, parse_mode="Markdown", disable_web_page_preview=True, 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" # Замените на ваш токен # Убедитесь, что папки с фотографиями и видео историй существуют for directory in [PHOTOS_DIR, HISTORY_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()
Создание папки history_videos
:
HISTORY_VIDEOS_DIR = "history_videos" # Новая папка для видео историй
В функции main()
добавлена проверка и создание этой папки:
for directory in [PHOTOS_DIR, HISTORY_VIDEOS_DIR]: if not os.path.isdir(directory): os.makedirs(directory) logger.info(f"Создана папка для медиафайлов: {directory}")
Обновление структуры данных трасс:
Для каждой трассы добавлено поле history_video
, содержащее имя видеофайла:
"history_video": "nurburgring_history.mp4", # Имя видеофайла в папке history_videos
Важно: Убедитесь, что файлы nurburgring_history.mp4
, monza_history.mp4
, spa_history.mp4
и другие соответствующие видео находятся в папке history_videos
.
Создание функции send_local_video
:
Эта функция отвечает за отправку локальных видеофайлов из папки history_videos
:
async def send_local_video(context, chat_id, video_filename, caption=None, reply_markup=None): # Полный путь к файлу file_path = os.path.join(HISTORY_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: await context.bot.send_video( chat_id=chat_id, video=video_file, 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 ) 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_choice
для отправки локального видео:
В блоке "История о трассе"
добавлена отправка видео из локальной папки после отправки текста и фото:
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()) # Отправка видео истории, если оно есть history_video = track_info.get("history_video") if history_video: caption_video = "Краткое видео о трассе..." await send_local_video( context, chat_id=update.effective_chat.id, video_filename=history_video, caption=caption_video, reply_markup=track_menu_keyboard() )
Обеспечение наличия видеофайлов:
Убедитесь, что в папке history_videos
находятся все необходимые видеофайлы, указанные в поле history_video
для каждой трассы. Например:
your_bot_directory/
├── photos/
│ ├── nurburgring/
│ │ └── karussell.jpg
│ │ └── brunnchen.jpg
│ │ └── flugplatz.jpg
│ ├── nurburgring/history.jpg
│ ├── monza/
│ │ └── lesmo.jpg
│ │ └── curva_grande.jpg
│ ├── monza/history.jpg
│ ├── spa/
│ │ └── eau_rouge.jpg
│ │ └── pouhon.jpg
│ ├── spa/history.jpg
├── history_videos/
│ ├── nurburgring_history.mp4
│ ├── monza_history.mp4
│ ├── spa_history.mp4
├── your_bot_script.py
Замена токена бота:
В вашем коде токен был указан явно. Никогда не публикуйте ваш реальный токен публично. Вместо этого используйте переменные окружения или файлы конфигурации для безопасного хранения токена.
Пример использования переменных окружения:
import os TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
И установите переменную окружения TELEGRAM_BOT_TOKEN
в вашей системе.
Убедитесь, что структура папок и файлы соответствуют путям, указанным в вашем коде.
your_bot_directory/
├── photos/
│ ├── nurburgring/
│ │ └── karussell.jpg
│ │ └── brunnchen.jpg
│ │ └── flugplatz.jpg
│ ├── nurburgring/history.jpg
│ ├── monza/
│ │ └── lesmo.jpg
│ │ └── curva_grande.jpg
│ ├── monza/history.jpg
│ ├── spa/
│ │ └── eau_rouge.jpg
│ │ └── pouhon.jpg
│ ├── spa/history.jpg
├── history_videos/
│ ├── nurburgring_history.mp4
│ ├── monza_history.mp4
│ ├── spa_history.mp4
├── your_bot_script.py
После внесения всех изменений:
Проверьте наличие видеофайлов: Убедитесь, что все указанные видеофайлы находятся в папке history_videos
и имеют правильные имена.
Запустите бота: Запустите скрипт и протестируйте функциональность бота, особенно раздел "История трассы", чтобы убедиться, что видео отправляется корректно.
Проверьте размер видео: Убедитесь, что видеофайлы не превышают ограничение Telegram (до 50 МБ для видео через URL и до 200 МБ для загружаемых файлов).
Обработка ошибок: Если видео не отправляется, проверьте логирование для выявления возможных ошибок.
Оптимизация отправки видео: Если видео слишком большие, рассмотрите возможность компрессии или использования более эффективных форматов.
Использование инлайн-клавиатур: Для улучшения пользовательского опыта можно использовать инлайн-клавиатуры с кнопками для открытия видео.
Безопасность токена: Если вы ещё не используете переменные окружения для хранения токена, настоятельно рекомендуется это сделать, чтобы предотвратить компрометацию вашего бота.
Внесённые изменения позволяют вашему боту отправлять видеофайлы из локальной папки history_videos
в разделе "История трассы" как второе сообщение с подписью "Краткое видео о трассе...". Это обеспечивает более безопасное и контролируемое управление медиафайлами, а также улучшает взаимодействие с пользователями вашего бота.
Если у вас возникнут дополнительные вопросы или потребуется помощь с другими функциями бота, не стесняйтесь обращаться!
Änderung der Entscheidungen
Zuerst URLs für Videos, dann lokale Dateien, jetzt eine Link in der Historie mit "короткий обзор о гоночной трассе".
Änderungen im Ablauf
Der Bot soll den Text und das Bild senden, gefolgt von einer Nachricht mit einem Link "Краткий обзор о гоночной трассе". Lokale Video-Funktion soll entfernt werden.
Video-Link hinzufügen
Der Bot-Code wird angepasst, um lokale Video-Funktionen zu entfernen und stattdessen einen Link "Краткий обзор о гоночной трассе" in der Historie anzuzeigen.
Gedanken für ein paar Sekunden
Понял вашу задачу. Вы хотите вернуть функциональность бота к исходному состоянию, убрав загрузку видео из локальных папок, и вместо этого добавить в разделе "История трассы" ссылку с надписью "Краткий обзор о гоночной трассе". Ниже приведены подробные шаги и обновлённый код для реализации этого изменения.
Удаление функциональности загрузки видео из локальных папок:
history_video
из структуры данных трасс.send_local_video
из кода.history_videos
в функции main()
.Добавление ссылки на видео в разделе "История трассы":
history_video_link
, которое будет содержать URL ссылки на краткий обзор видео.track_menu_choice
, чтобы после отправки истории и фото, бот отправлял сообщение со ссылкой "Краткий обзор о гоночной трассе".Обеспечение корректности ссылок на видео:
Безопасность токена:
Ниже представлен обновлённый код бота с учётом всех вышеуказанных изменений:
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_PHOTO_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.bmp'} # Данные о трассах (добавлено поле 'history_video_link' для каждой трассы) 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", "videos": [ "https://www.youtube.com/watch?v=MzH_Y-ytRmI&t=677s", "https://www.youtube.com/watch?v=JTGvHskGKI4" ], "history_video_link": "https://www.youtube.com/watch?v=example1", # Замените на реальную ссылку "опасные_повороты": { "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", "videos": [ "https://www.youtube.com/watch?v=W8tAKhRJtRk", "https://www.youtube.com/watch?v=ugZimpxZhXE" ], "history_video_link": "https://www.youtube.com/watch?v=example2", # Замените на реальную ссылку "опасные_повороты": { "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", "videos": [ "https://www.youtube.com/watch?v=LRBfQdD-fIE", "https://www.youtube.com/watch?v=sj1HuWqVQLw" ], "history_video_link": "https://www.youtube.com/watch?v=example3", # Замените на реальную ссылку "опасные_повороты": { "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_link(context, chat_id, link, text, reply_markup=None): try: # Используем Markdown для создания гиперссылки message = f"[{text}]({link})" await context.bot.send_message( chat_id=chat_id, text=message, parse_mode="Markdown", disable_web_page_preview=True, reply_markup=reply_markup ) except Exception as e: logger.error(f"Ошибка при отправке ссылки: {e}") await context.bot.send_message( chat_id=chat_id, text=f"{text}\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()) # Отправка ссылки на видео обзора, если она есть history_video_link = track_info.get("history_video_link") if history_video_link: link_text = "Краткий обзор о гоночной трассе" await send_video_link( context, chat_id=update.effective_chat.id, link=history_video_link, text=link_text, 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 == "Видео трассы": videos = track_info.get("videos", []) if videos: response = f"**Видео трассы {current_track}:**\n" + "\n".join([f"[Видео {i+1}]({video})" for i, video in enumerate(videos)]) await update.message.reply_text( response, parse_mode="Markdown", disable_web_page_preview=True, 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" # Замените на ваш токен # Убедитесь, что папка с фотографиями существует 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()
Удаление функциональности загрузки видео из локальных папок:
Удаление поля history_video
:
В структуре данных трасс удалено поле history_video
, которое ранее указывало на локальные видеофайлы. Вместо этого добавлено поле history_video_link
, содержащее URL ссылки на видео.
"history_video_link": "https://www.youtube.com/watch?v=example1", # Замените на реальную ссылку
Удаление функции send_local_video
:
Функция для отправки локальных видеофайлов send_local_video
была удалена, так как больше не требуется отправлять видео из локальных папок.
Удаление проверки и создания папки history_videos
:
В функции main()
удалена проверка и создание папки history_videos
, поскольку она больше не используется.
Добавление ссылки на видео в разделе "История трассы":
Добавление поля history_video_link
:
Для каждой трассы добавлено новое поле history_video_link
, содержащее URL ссылки на краткий обзор видео.
"history_video_link": "https://www.youtube.com/watch?v=example1", # Замените на реальную ссылку
Создание функции send_video_link
:
Добавлена новая функция send_video_link
, которая отвечает за отправку сообщения со ссылкой с заданным текстом.
async def send_video_link(context, chat_id, link, text, reply_markup=None): try: # Используем Markdown для создания гиперссылки message = f"[{text}]({link})" await context.bot.send_message( chat_id=chat_id, text=message, parse_mode="Markdown", disable_web_page_preview=True, reply_markup=reply_markup ) except Exception as e: logger.error(f"Ошибка при отправке ссылки: {e}") await context.bot.send_message( chat_id=chat_id, text=f"{text}\n\n*Ссылка недоступна.*", parse_mode="Markdown", reply_markup=reply_markup )
Обновление обработчика track_menu_choice
:
В разделе "История трассы" после отправки текста и фото, добавлено отправление сообщения со ссылкой "Краткий обзор о гоночной трассе".
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()) # Отправка ссылки на видео обзора, если она есть history_video_link = track_info.get("history_video_link") if history_video_link: link_text = "Краткий обзор о гоночной трассе" await send_video_link( context, chat_id=update.effective_chat.id, link=history_video_link, text=link_text, reply_markup=track_menu_keyboard() )
Обеспечение корректности ссылок на видео:
Замените примеры ссылок на реальные URL:
Убедитесь, что в поле history_video_link
для каждой трассы указаны действительные и доступные URL ссылки на видео. Примеры:
"history_video_link": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", # Пример реальной ссылки
Безопасность токена:
Использование переменных окружения:
Для повышения безопасности рекомендуется хранить токен бота в переменных окружения, а не в явном виде в коде. Например:
import os TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
Затем установите переменную окружения TELEGRAM_BOT_TOKEN
на вашем сервере или локальной машине.
Убедитесь, что структура папок и файлы соответствуют путям, указанным в вашем коде.
your_bot_directory/
├── photos/
│ ├── nurburgring/
│ │ └── karussell.jpg
│ │ └── brunnchen.jpg
│ │ └── flugplatz.jpg
│ ├── nurburgring/history.jpg
│ ├── monza/
│ │ └── lesmo.jpg
│ │ └── curva_grande.jpg
│ ├── monza/history.jpg
│ ├── spa/
│ │ └── eau_rouge.jpg
│ │ └── pouhon.jpg
│ ├── spa/history.jpg
├── your_bot_script.py
После внесения всех изменений выполните следующие шаги для тестирования бота:
Проверьте наличие фотофайлов:
Убедитесь, что все указанные фотографии находятся в соответствующих папках и имеют правильные имена.
Проверьте ссылки на видео:
Убедитесь, что все ссылки в поле history_video_link
корректны и ведут на доступные видео.
Запустите бота:
Запустите скрипт и протестируйте функциональность бота, особенно раздел "История трассы", чтобы убедиться, что сообщение со ссылкой отправляется корректно.
Проверьте форматирование:
Убедитесь, что сообщения отображаются правильно, ссылки работают, а фотографии загружаются без ошибок.
Обработка ошибок:
Если ссылки не работают или фотографии не отправляются, проверьте логирование для выявления возможных ошибок.
Оптимизация форматирования ссылок:
Если вы хотите, чтобы ссылка выглядела как кнопка, вы можете использовать инлайн-клавиатуры с кнопкой-ссылкой. Ниже приведен пример, как это реализовать.
Использование инлайн-клавиатур:
Инлайн-клавиатуры позволяют сделать интерфейс более интерактивным и удобным для пользователей.
Пример изменения функции отправки ссылки на видео с использованием инлайн-клавиатуры:
from telegram import InlineKeyboardButton, InlineKeyboardMarkup async def send_video_link(context, chat_id, link, text, reply_markup=None): try: keyboard = [ [InlineKeyboardButton(text, url=link)] ] reply_markup = InlineKeyboardMarkup(keyboard) await context.bot.send_message( chat_id=chat_id, text="", reply_markup=reply_markup ) except Exception as e: logger.error(f"Ошибка при отправке ссылки: {e}") await context.bot.send_message( chat_id=chat_id, text=f"{text}\n\n*Ссылка недоступна.*", parse_mode="Markdown", reply_markup=reply_markup )
И в обработчике track_menu_choice
замените вызов функции send_video_link
на обновлённый вариант.
Управление ограничениями Telegram:
Помните, что Telegram накладывает ограничения на размер отправляемых файлов и количество сообщений. Убедитесь, что ваши ссылки и файлы соответствуют этим ограничениям.
Обеспечение безопасности:
Регулярно проверяйте логи на наличие ошибок и следите за безопасностью токена вашего бота.
Внесённые изменения позволяют вашему боту отправлять ссылку на видео в разделе "История трассы" с надписью "Краткий обзор о гоночной трассе" вместо загрузки видео из локальных папок. Это упрощает управление медиафайлами и делает код более чистым и безопасным.
Если у вас возникнут дополнительные вопросы или потребуется помощь с другими функциями бота, не стесняйтесь обращаться!