Отправка нескольких видео
Tạo vào: 19 tháng 12, 2024
Tạo vào: 19 tháng 12, 2024
import logging
import time
import os
from telegram import (
Update,
ReplyKeyboardMarkup,
ReplyKeyboardRemove,
InputFile
)
from telegram.ext import (
ApplicationBuilder,
CommandHandler,
MessageHandler,
filters,
ConversationHandler,
ContextTypes
)
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO
)
logger = logging.getLogger(name)
CHOOSE_MAIN, CHOOSE_TRACK, TRACK_MENU, CHOOSE_DANGEROUS_TURN = range(4)
MAX_MESSAGES = 5 # Максимум сообщений за период
TIME_WINDOW = 10 # Интервал в секундах для подсчёта сообщений
TIMEOUT_DURATION = 10 # Длительность тайм-аута в секундах
PHOTOS_DIR = "photos" # Убедитесь, что такая папка существует
VIDEOS_DIR = "videos" # Новая папка для видео
ALLOWED_PHOTO_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.bmp'}
ALLOWED_VIDEO_EXTENSIONS = {'.mp4', '.mov', '.avi', '.mkv'}
tracks = {
"Нюрбургринг-Нордшляйфе": {
"описание": "Узкие улочки и сложные повороты делают трассу Нюрбургринг-Нордшляйфе одной из самых престижных.",
"лидеры": "Mercedes AMG ONE | 6:29.09 минут |\n\n2. Porsche GT2 RS MR 991.2 | 6:43.30 минут |\n\n3. Mercedes AMG GT Black Series | 6:48.04 минут |\n\n4. Porsche GT3 RS 992 | 6:49.32 минут |\n\n5. Lamborghini Aventador LP 770-4 SVJ | 6:49.40 минут |\n\n6. Porsche GT2 RS MR 991.2 | 6:52.15 минут |\n\n7. Radical SR8 LM | 6:53.20 минут |\n\n8. Porsche GT3 992 MR | 6:55.73 минут |\n\n9. Lamborghini Huracan LP 640-4 Performante | 6:57.25 минут |\n\n10. Porsche GT3 992 | 6:59.93 минут |",
"история": "Трасса Нордшляйфе была спроектирована и построена с 1925 до 1927 года.Открытие состоялось 18-19 июня 1927 года. Стоимость реализации объекта под ключ составила примерно 8 100 000 Немецких Марок (в пересчёте на 2020 год это около 30 000 000 Евро). Протяжённость трассы составляет 20,8 километров. Заезды и массовые мероприятия проходят на трассе с марта до ноября. В зимнее время в связи с погодными условиями Нюрбургринг закрыт для заездов. Количество левых поворотов — 33, правых — 40. Перепад высоты составляет порядка 300 метров. Самое высокое место — участок Höhe Acht, 616,80 метров над уровнем моря. Самое низкое место — участок Breidscheid, 320 метров над уровнем моря. Максимальный подъём на трассе составляет 17%. Максимальный спуск — 11%. Стоимость проезда одного круга (20,8 км) на автомобиле или мотоцикле с Понедельника до Четверга составляет 25 евро и 30 евро с Пятницы до Воскресенья. Стоимость проживания в регионе вокруг гоночной трассы — от 50 евро в сутки (с завтраком). Стоимость эвакуации автомобиля с гоночной трассы стартует от 150 евро. Ближайшие крупные аэропорты: Франкфурт на Майне, Франкфурт-Хан, Кёльн-Бонн и Дюссельдорф. Абсолютный рекорд полного круга Нордшляйфе с временем 5:19,546 минут был установлен 29 июня 2018 года на прототипе Porsche 919 Hybrid EVO тестовым пилотом и гонщиком Тимо Бернхардом. Трасса Нюрбургринг GP для проведения Формулы 1 была сдана в эксплуатацию в 1984 году. Протяженность трассы составляет 5,148 км. Количество поворотов — 15. Рекорд трассы принадлежит Михаэлю Шумахеру, который в 2004 году на Формульном болиде проехал полный круг GP за 1:29,468 мин.",
"фото_история": "nurburgring/history.jpg",
"video": "nurburgring/video.mp4", # Добавлено поле видео
"опасные_повороты": {
"Karussell": {
"описание": "Описание поворота Karussell — один из самых известных и сложных поворотов на Нюрбургринге. Требует максимальной концентрации и точности.",
"фото": "nurburgring/karussell.jpg"
},
"Brünnchen": {
"описание": "Поворот Brünnchen характеризуется высокой скоростью и требованием к точности траектории.",
"фото": "nurburgring/brunnchen.jpg"
},
"Flugplatz": {
"описание": "Поворот Flugplatz — это длинный и быстрый поворот, который требует отличного контроля над автомобилем.",
"фото": "nurburgring/flugplatz.jpg"
}
}
},
"Монца": {
"описание": "Трасса Монца известна своими длинными прямыми и высокой скоростью, что делает её одной из самых быстрых трасс в мире.",
"лидеры": "Баррикелло (Ferrari) | 1:21,046 |\n\n2. Хэмилтон (Mercedes) | 1:18,887 |\n\n",
"история": "Автодром Монца был открыт в 1922 году и является одной из старейших трасс Формулы-1. Первоначальная трасса проходила частично по общественным дорогам. Трасса неоднократно перестраивалась и модернизировалась, но сохранила свою скоростную сущность. Монца славится своей высокой скоростью и длинными прямыми, что делает её одной из самых быстрых трасс в мире. Она также является свидетелем многих знаменательных событий и трагических аварий в истории автоспорта.",
"фото_история": "monza/history.jpg",
"video": "monza/video.mp4", # Добавлено поле видео
"опасные_повороты": {
"Lesmo": {
"описание": "Секция Lesmo состоит из двух быстрых поворотов, которые требуют максимальной точности и контроля над автомобилем.",
"фото": "monza/lesmo.jpg"
},
"Curva Grande": {
"описание": "Большой поворот (Curva Grande) — быстрый и протяженный поворот, где ошибки могут привести к серьезным последствиям.",
"фото": "monza/curva_grande.jpg"
}
}
},
"Спа-Франкоршам": { # Новая трасса
"описание": "Трасса Спа-Франкоршам известна своим непредсказуемым погодным условиям и сложными поворотами, что делает её одной из самых технически сложных трасс в мире.",
"лидеры": "Льюис Хэмилтон | 6:47.285 минут |\n\nСебастьян Феттель | 6:49.875 минут |\n\nМакс Ферстаппен | 6:50.123 минут |",
"история": "Автодром Спа-Франкоршам был открыт в 1921 году и является одним из старейших и самых уважаемых треков в мире автоспорта. Трасса проходит через живописные пейзажи Бельгии и известна своей историей и традициями. Спа-Франкоршам принимал множество чемпионатов мира, включая Формулу-1, и является любимой трассой многих гонщиков за её уникальную смесь высоких скоростей и технических сложностей.",
"фото_история": "spa/history.jpg",
"video": "spa/video.mp4", # Добавлено поле видео
"опасные_повороты": {
"Eau Rouge": {
"описание": "Eau Rouge — знаменитый быстрый подъем с крутым поворотом, требующий точности и смелости от пилотов.",
"фото": "spa/eau_rouge.jpg"
},
"Pouhon": {
"описание": "Pouhon — длинный и быстрый поворот, который требует отличной аэродинамики и устойчивости автомобиля.",
"фото": "spa/pouhon.jpg"
}
}
}
}
creators_info = "Этот бот был создан командой:\n\n1. Ратников Иван\n2. Хамраева Шукрона\n3. Новиков Елисей\n4. Елизарова Ксения\n5. Пискунов Кирилл\n6. Колосов Дмитрий."
def create_keyboard(buttons, buttons_per_row=2):
keyboard = []
for i in range(0, len(buttons), buttons_per_row):
keyboard.append(buttons[i:i + buttons_per_row])
return ReplyKeyboardMarkup(keyboard, resize_keyboard=True)
def main_menu_keyboard():
buttons = ["Выбрать трассу", "Создатели"]
return create_keyboard(buttons, buttons_per_row=2)
def tracks_keyboard():
buttons = list(tracks.keys()) + ["Назад"]
return create_keyboard(buttons, buttons_per_row=2) # 2 кнопки в строке
def track_menu_keyboard():
buttons = ["Лидеры", "История о трассе", "Опасные повороты", "Видео трассы", "Назад"]
return create_keyboard(buttons, buttons_per_row=2) # 2 кнопки в строке, последняя строка с одной кнопкой
def dangerous_turns_keyboard(track_name):
dangerous_turns = tracks[track_name]["опасные_повороты"]
buttons = list(dangerous_turns.keys()) + ["Назад"]
return create_keyboard(buttons, buttons_per_row=2) # 2 кнопки в строке
async def anti_spam(update: Update, context: ContextTypes.DEFAULT_TYPE):
user_id = update.effective_user.id
current_time = time.time()
text# Проверка на тайм-аута timeout_until = context.user_data.get("spam_timeout_until", 0) if current_time < timeout_until: await update.message.reply_text("Вы слишком часто отправляете сообщения. Пожалуйста, подождите немного.") return False # Инициализация списка временных меток timestamps = context.user_data.get("timestamps", []) # Добавление текущей метки времени timestamps.append(current_time) # Очистка старых меток времени timestamps = [t for t in timestamps if current_time - t <= TIME_WINDOW] context.user_data["timestamps"] = timestamps # Проверка количества сообщений за период if len(timestamps) > MAX_MESSAGES: context.user_data["spam_timeout_until"] = current_time + TIMEOUT_DURATION await update.message.reply_text("Слишком много сообщений! Пожалуйста, подождите 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)
text# Проверка расширения файла _, ext = os.path.splitext(photo_filename) if ext.lower() not in ALLOWED_PHOTO_EXTENSIONS: logger.warning(f"Недопустимое расширение файла: {photo_filename}") await context.bot.send_message( chat_id=chat_id, text="Невозможно отправить файл с недопустимым расширением.", reply_markup=reply_markup ) return if os.path.isfile(file_path): try: with open(file_path, 'rb') as photo_file: if caption: if len(caption) <= 1024: # Если длина подписи допустимая, отправляем фото с подписью await context.bot.send_photo( chat_id=chat_id, photo=photo_file, caption=caption, parse_mode="Markdown", reply_markup=reply_markup ) else: # Если подпись слишком длинная, отправляем фото без подписи await context.bot.send_photo( chat_id=chat_id, photo=photo_file, reply_markup=reply_markup ) # Разбиваем длинный текст на части по 1024 символа for i in range(0, len(caption), 1024): part = caption[i:i+1024] await context.bot.send_message( chat_id=chat_id, text=part, parse_mode="Markdown", reply_markup=reply_markup ) else: # Если подпись не требуется, просто отправляем фото await context.bot.send_photo( chat_id=chat_id, photo=photo_file, reply_markup=reply_markup ) except Exception as e: logger.error(f"Ошибка при отправке фото: {e}") if caption: await context.bot.send_message( chat_id=chat_id, text=caption, parse_mode="Markdown", reply_markup=reply_markup ) else: logger.warning(f"Файл фото не найден: {file_path}") if caption: await context.bot.send_message( chat_id=chat_id, text=f"{caption}\n\n*Фото не доступно.*", parse_mode="Markdown", reply_markup=reply_markup )
async def send_video(context, chat_id, video_filename, caption=None, reply_markup=None):
# Полный путь к файлу
file_path = os.path.join(VIDEOS_DIR, video_filename)
text# Проверка расширения файла _, ext = os.path.splitext(video_filename) if ext.lower() not in ALLOWED_VIDEO_EXTENSIONS: logger.warning(f"Недопустимое расширение видеофайла: {video_filename}") await context.bot.send_message( chat_id=chat_id, text="Невозможно отправить видео с недопустимым расширением.", reply_markup=reply_markup ) return if os.path.isfile(file_path): try: with open(file_path, 'rb') as video_file: if caption: await context.bot.send_video( chat_id=chat_id, video=video_file, caption=caption, parse_mode="Markdown", reply_markup=reply_markup ) else: await context.bot.send_video( chat_id=chat_id, video=video_file, reply_markup=reply_markup ) except Exception as e: logger.error(f"Ошибка при отправке видео: {e}") if caption: await context.bot.send_message( chat_id=chat_id, text=f"{caption}\n\n*Видео не доступно.*", parse_mode="Markdown", reply_markup=reply_markup ) else: logger.warning(f"Файл видео не найден: {file_path}") if caption: await context.bot.send_message( chat_id=chat_id, text=f"{caption}\n\n*Видео не доступно.*", parse_mode="Markdown", reply_markup=reply_markup )
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Сброс анти-спам таймера при старте
context.user_data.pop("spam_timeout_until", None)
context.user_data.pop("timestamps", None)
await update.message.reply_text(
"Добро пожаловать! Выберите опцию:",
reply_markup=main_menu_keyboard()
)
return CHOOSE_MAIN
async def main_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Проверка анти-спам
if not await anti_spam(update, context):
return CHOOSE_MAIN
texttext = update.message.text if text == "Выбрать трассу": await update.message.reply_text("Выберите трассу:", reply_markup=tracks_keyboard()) return CHOOSE_TRACK elif text == "Создатели": await update.message.reply_text(creators_info, reply_markup=main_menu_keyboard()) return CHOOSE_MAIN else: await update.message.reply_text("Неизвестная команда. Попробуйте снова.", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN
async def track_choice(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Проверка анти-спам
if not await anti_spam(update, context):
return CHOOSE_TRACK
texttext = update.message.text if text == "Назад": # Возврат в главное меню await update.message.reply_text("Главное меню:", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN if text in tracks: context.user_data["current_track"] = text track_info = tracks[text] description = track_info["описание"] response = f"**Трасса:** {text}\n\n**Описание:** {description}" await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) return TRACK_MENU else: await update.message.reply_text("Пожалуйста, выберите доступную трассу или нажмите Назад.", reply_markup=tracks_keyboard()) return CHOOSE_TRACK
async def track_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Проверка анти-спам
if not await anti_spam(update, context):
return TRACK_MENU
texttext = update.message.text current_track = context.user_data.get("current_track") if text == "Назад": # Возвращение к списку трасс await update.message.reply_text("Выберите трассу:", reply_markup=tracks_keyboard()) return CHOOSE_TRACK if current_track not in tracks: await update.message.reply_text("Ошибка. Попробуйте снова.", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN track_info = tracks[current_track] if text == "Лидеры": leaders = track_info["лидеры"] response = f"**Лидеры на трассе {current_track}:**\n{leaders}" await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) elif text == "История о трассе": history = track_info["история"] response = f"**История трассы {current_track}:**\n{history}" # Получение имени файла фото истории photo_filename = track_info.get("фото_история") if photo_filename: # Отправка фото с текстом await send_photo( context, chat_id=update.effective_chat.id, photo_filename=photo_filename, caption=response, reply_markup=track_menu_keyboard() ) else: # Если фото не задано, отправляем только текст await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) elif text == "Опасные повороты": await update.message.reply_text("Выберите опасный поворот:", reply_markup=dangerous_turns_keyboard(current_track)) return CHOOSE_DANGEROUS_TURN elif text == "Видео трассы": # Обработка новой опции video_filename = track_info.get("video") if video_filename: response = f"**Видео трассы {current_track}:**" await send_video( context, chat_id=update.effective_chat.id, video_filename=video_filename, caption=response, reply_markup=track_menu_keyboard() ) else: await update.message.reply_text( "Видео для этой трассы недоступно.", parse_mode="Markdown", reply_markup=track_menu_keyboard() ) else: response = "Неизвестная команда. Попробуйте снова." await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) return TRACK_MENU
async def dangerous_turn_choice(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Проверка анти-спам
if not await anti_spam(update, context):
return CHOOSE_DANGEROUS_TURN
texttext = update.message.text current_track = context.user_data.get("current_track") if text == "Назад": # Возвращение к меню трассы await update.message.reply_text("Выберите опцию:", reply_markup=track_menu_keyboard()) return TRACK_MENU dangerous_turns = tracks[current_track]["опасные_повороты"] if text in dangerous_turns: 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" # Замените на ваш действительный токен
text# Убедитесь, что папки с фотографиями и видео существуют for directory in [PHOTOS_DIR, VIDEOS_DIR]: if not os.path.isdir(directory): os.makedirs(directory) logger.info(f"Создана папка для медиафайлов: {directory}") application = ApplicationBuilder().token(TOKEN).build() conv_handler = ConversationHandler( entry_points=[CommandHandler("start", start)], states={ CHOOSE_MAIN: [MessageHandler(filters.TEXT & ~filters.COMMAND, main_menu_choice)], CHOOSE_TRACK: [MessageHandler(filters.TEXT & ~filters.COMMAND, track_choice)], TRACK_MENU: [MessageHandler(filters.TEXT & ~filters.COMMAND, track_menu_choice)], CHOOSE_DANGEROUS_TURN: [MessageHandler(filters.TEXT & ~filters.COMMAND, dangerous_turn_choice)] }, fallbacks=[CommandHandler("cancel", cancel), CommandHandler("help", help_command)] ) application.add_handler(conv_handler) application.run_polling()
if name == 'main':
main() сделать так чтобы я мог более двух видео прикреплять
Чтобы позволить вашему боту отправлять более двух видео для каждой трассы, необходимо внести следующие изменения:
Изменить структуру данных трасс (tracks
):
"video"
, храните список видеофайлов в поле "videos"
.Обновить обработчик выбора видео:
track_menu_choice
так, чтобы она могла обрабатывать и отправлять несколько видеофайлов.Обновить функцию отправки видео (если необходимо):
send_video
уже поддерживает отправку одного видео. Для отправки нескольких видео нужно вызывать эту функцию несколько раз.Ниже приведены подробные шаги и кодовые примеры для реализации этих изменений.
tracks
)Вместо использования поля "video"
как строки, измените его на список "videos"
. Например:
pythontracks = { "Нюрбургринг-Нордшляйфе": { # ... другие поля ... "videos": ["nurburgring/video1.mp4", "nurburgring/video2.mp4"], # Изменено с "video" на "videos" # ... остальные поля ... }, "Монца": { # ... другие поля ... "videos": ["monza/video1.mp4", "monza/video2.mp4", "monza/video3.mp4"], # Добавлено несколько видео # ... остальные поля ... }, "Спа-Франкоршам": { # ... другие поля ... "videos": ["spa/video1.mp4"], # Можно оставить один элемент в списке # ... остальные поля ... } }
Важно: Убедитесь, что все трассы в вашем словаре tracks
используют поле "videos"
вместо "video"
. Это необходимо для единообразия и корректной работы последующего кода.
В функции track_menu_choice
измените обработку опции "Видео трассы"
, чтобы она могла отправлять несколько видео из списка "videos"
.
Вот обновленный фрагмент кода для этой функции:
pythonasync 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", []) # Изменено с "video" на "videos" if videos: response = f"**Видео трассы {current_track}:**" await update.message.reply_text(response, parse_mode="Markdown") for video_filename in videos: await send_video( context, chat_id=update.effective_chat.id, video_filename=video_filename, caption=None, # Можно добавить описание видео, если необходимо 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
Объяснение изменений:
Получение списка видео: Используем track_info.get("videos", [])
, чтобы получить список видеофайлов. Если список пустой или поле отсутствует, вернётся пустой список.
Отправка заголовка: Отправляем заголовок "**Видео трассы {current_track}:**"
отдельно перед отправкой видео.
Цикл по видеофайлам: Проходимся по каждому видеофайлу в списке videos
и вызываем функцию send_video
для отправки каждого из них.
Описание видео: В данном примере caption
установлен в None
, но вы можете модифицировать его, чтобы добавить описание или номер видео.
Если вы хотите добавить описание для каждого видео или улучшить функцию отправки видео, вы можете внести дополнительные изменения в функцию send_video
. Однако текущая реализация уже поддерживает отправку одного видео за раз, и, следовательно, достаточно вызывать её несколько раз в цикле для отправки нескольких видео.
Ниже представлен полный код с внесёнными изменениями для поддержки отправки более двух видео:
pythonimport logging import time import os from telegram import ( Update, ReplyKeyboardMarkup, ReplyKeyboardRemove, InputFile ) from telegram.ext import ( ApplicationBuilder, CommandHandler, MessageHandler, filters, ConversationHandler, ContextTypes ) # Настройка логирования logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO ) logger = logging.getLogger(__name__) # Состояния диалога CHOOSE_MAIN, CHOOSE_TRACK, TRACK_MENU, CHOOSE_DANGEROUS_TURN = range(4) # Настройки анти-спама MAX_MESSAGES = 5 # Максимум сообщений за период TIME_WINDOW = 10 # Интервал в секундах для подсчёта сообщений TIMEOUT_DURATION = 10 # Длительность тайм-аута в секундах # Папки с медиафайлами PHOTOS_DIR = "photos" # Убедитесь, что такая папка существует VIDEOS_DIR = "videos" # Новая папка для видео # Разрешённые расширения файлов ALLOWED_PHOTO_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.bmp'} ALLOWED_VIDEO_EXTENSIONS = {'.mp4', '.mov', '.avi', '.mkv'} # Данные о трассах (обновлено поле '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": ["nurburgring/video1.mp4", "nurburgring/video2.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": ["monza/video1.mp4", "monza/video2.mp4", "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": ["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 send_video(context, chat_id, video_filename, caption=None, reply_markup=None): # Полный путь к файлу file_path = os.path.join(VIDEOS_DIR, video_filename) # Проверка расширения файла _, ext = os.path.splitext(video_filename) if ext.lower() not in ALLOWED_VIDEO_EXTENSIONS: logger.warning(f"Недопустимое расширение видеофайла: {video_filename}") await context.bot.send_message( chat_id=chat_id, text="Невозможно отправить видео с недопустимым расширением.", reply_markup=reply_markup ) return if os.path.isfile(file_path): try: with open(file_path, 'rb') as video_file: if caption: await context.bot.send_video( chat_id=chat_id, video=video_file, caption=caption, parse_mode="Markdown", reply_markup=reply_markup ) else: await context.bot.send_video( chat_id=chat_id, video=video_file, reply_markup=reply_markup ) except Exception as e: logger.error(f"Ошибка при отправке видео: {e}") if caption: await context.bot.send_message( chat_id=chat_id, text=f"{caption}\n\n*Видео не доступно.*", parse_mode="Markdown", reply_markup=reply_markup ) else: logger.warning(f"Файл видео не найден: {file_path}") if caption: await context.bot.send_message( chat_id=chat_id, text=f"{caption}\n\n*Видео не доступно.*", parse_mode="Markdown", reply_markup=reply_markup ) # Обработчики async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): # Сброс анти-спам таймера при старте context.user_data.pop("spam_timeout_until", None) context.user_data.pop("timestamps", None) await update.message.reply_text( "Добро пожаловать! Выберите опцию:", reply_markup=main_menu_keyboard() ) return CHOOSE_MAIN async def main_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return CHOOSE_MAIN text = update.message.text if text == "Выбрать трассу": await update.message.reply_text("Выберите трассу:", reply_markup=tracks_keyboard()) return CHOOSE_TRACK elif text == "Создатели": await update.message.reply_text(creators_info, reply_markup=main_menu_keyboard()) return CHOOSE_MAIN else: await update.message.reply_text("Неизвестная команда. Попробуйте снова.", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN async def track_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return CHOOSE_TRACK text = update.message.text if text == "Назад": # Возврат в главное меню await update.message.reply_text("Главное меню:", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN if text in tracks: context.user_data["current_track"] = text track_info = tracks[text] description = track_info["описание"] response = f"**Трасса:** {text}\n\n**Описание:** {description}" await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) return TRACK_MENU else: await update.message.reply_text("Пожалуйста, выберите доступную трассу или нажмите Назад.", reply_markup=tracks_keyboard()) return CHOOSE_TRACK async def track_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спам if not await anti_spam(update, context): return TRACK_MENU text = update.message.text current_track = context.user_data.get("current_track") if text == "Назад": # Возвращение к списку трасс await update.message.reply_text("Выберите трассу:", reply_markup=tracks_keyboard()) return CHOOSE_TRACK if current_track not in tracks: await update.message.reply_text("Ошибка. Попробуйте снова.", reply_markup=main_menu_keyboard()) return CHOOSE_MAIN track_info = tracks[current_track] if text == "Лидеры": leaders = track_info["лидеры"] response = f"**Лидеры на трассе {current_track}:**\n{leaders}" await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) elif text == "История о трассе": history = track_info["история"] response = f"**История трассы {current_track}:**\n{history}" # Получение имени файла фото истории photo_filename = track_info.get("фото_история") if photo_filename: # Отправка фото с текстом await send_photo( context, chat_id=update.effective_chat.id, photo_filename=photo_filename, caption=response, reply_markup=track_menu_keyboard() ) else: # Если фото не задано, отправляем только текст await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) elif text == "Опасные повороты": await update.message.reply_text("Выберите опасный поворот:", reply_markup=dangerous_turns_keyboard(current_track)) return CHOOSE_DANGEROUS_TURN elif text == "Видео трассы": # Обработка новой опции videos = track_info.get("videos", []) # Изменено с "video" на "videos" if videos: response = f"**Видео трассы {current_track}:**" await update.message.reply_text(response, parse_mode="Markdown") for video_filename in videos: await send_video( context, chat_id=update.effective_chat.id, video_filename=video_filename, caption=None, # Можно добавить описание видео, если необходимо 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, VIDEOS_DIR]: if not os.path.isdir(directory): os.makedirs(directory) logger.info(f"Создана папка для медиафайлов: {directory}") application = ApplicationBuilder().token(TOKEN).build() conv_handler = ConversationHandler( entry_points=[CommandHandler("start", start)], states={ CHOOSE_MAIN: [MessageHandler(filters.TEXT & ~filters.COMMAND, main_menu_choice)], CHOOSE_TRACK: [MessageHandler(filters.TEXT & ~filters.COMMAND, track_choice)], TRACK_MENU: [MessageHandler(filters.TEXT & ~filters.COMMAND, track_menu_choice)], CHOOSE_DANGEROUS_TURN: [MessageHandler(filters.TEXT & ~filters.COMMAND, dangerous_turn_choice)] }, fallbacks=[CommandHandler("cancel", cancel), CommandHandler("help", help_command)] ) application.add_handler(conv_handler) application.run_polling() if __name__ == '__main__': main()
Организация видеофайлов:
videos
."videos"
для каждой трассы.Проверка размеров видео:
Оптимизация отправки:
Добавление описаний к видео:
"videos"
для каждой трассы, используя словари вместо строк. Например:python"videos": [ {"filename": "nurburgring/video1.mp4", "description": "Обзор трассы"}, {"filename": "nurburgring/video2.mp4", "description": "Гонки на трассе"} ]
track_menu_choice
для обработки этого формата.Обработка ошибок:
Следуя этим шагам, вы сможете расширить функциональность вашего бота, позволяя отправлять более двух видео для каждой трассы, что сделает взаимодействие с пользователями более информативным и увлекательным.
main - ERROR - Ошибка при отправке видео: Timed out
Ошибка __main__ - ERROR - Ошибка при отправке видео: Timed out
указывает на то, что ваш бот не смог отправить видео из-за превышения времени ожидания (тайм-аута). Это может происходить по нескольким причинам, наиболее вероятные из которых связаны с размером видеофайла или проблемами с сетью. Давайте рассмотрим возможные причины и способы их устранения.
Размер видеофайла превышает лимиты Telegram:
Проблемы с сетью:
Некорректный путь к файлу или отсутствующий файл:
Перегрузка сервера или частые запросы:
Убедитесь, что размер каждого видеофайла не превышает 50 МБ. Вы можете проверить размер файла с помощью файлового менеджера или программно.
Пример проверки размера файла в Python:
pythonimport os def is_file_size_allowed(file_path, max_size_mb=50): if not os.path.isfile(file_path): return False size_bytes = os.path.getsize(file_path) size_mb = size_bytes / (1024 * 1024) return size_mb <= max_size_mb
Использование в вашем коде:
Перед отправкой видео добавьте проверку размера:
pythonasync def send_video(context, chat_id, video_filename, caption=None, reply_markup=None): # Полный путь к файлу file_path = os.path.join(VIDEOS_DIR, video_filename) # Проверка расширения файла _, ext = os.path.splitext(video_filename) if ext.lower() not in ALLOWED_VIDEO_EXTENSIONS: logger.warning(f"Недопустимое расширение видеофайла: {video_filename}") await context.bot.send_message( chat_id=chat_id, text="Невозможно отправить видео с недопустимым расширением.", reply_markup=reply_markup ) return # Проверка размера файла if not is_file_size_allowed(file_path): logger.warning(f"Видео превышает допустимый размер: {file_path}") await context.bot.send_message( chat_id=chat_id, text="Видео слишком большое для отправки. Пожалуйста, выберите другое видео или сократите размер.", reply_markup=reply_markup ) return if os.path.isfile(file_path): try: with open(file_path, 'rb') as video_file: if caption: await context.bot.send_video( chat_id=chat_id, video=video_file, caption=caption, parse_mode="Markdown", reply_markup=reply_markup ) else: await context.bot.send_video( chat_id=chat_id, video=video_file, reply_markup=reply_markup ) except Exception as e: logger.error(f"Ошибка при отправке видео: {e}") if caption: await context.bot.send_message( chat_id=chat_id, text=f"{caption}\n\n*Видео не доступно.*", parse_mode="Markdown", reply_markup=reply_markup ) else: logger.warning(f"Файл видео не найден: {file_path}") if caption: await context.bot.send_message( chat_id=chat_id, text=f"{caption}\n\n*Видео не доступно.*", parse_mode="Markdown", reply_markup=reply_markup )
Если видео превышает лимит в 50 МБ, вам необходимо уменьшить его размер. Это можно сделать с помощью различных инструментов для сжатия видео, например:
FFmpeg: мощный инструмент для обработки видео из командной строки.
Пример сжатия видео с помощью FFmpeg:
bashffmpeg -i input.mp4 -vcodec libx264 -crf 28 output.mp4
Здесь -crf
определяет качество видео. Чем выше значение, тем меньше размер файла и ниже качество. Значение по умолчанию — 23. Вы можете экспериментировать с этим параметром.
Онлайн-сервисы: существуют веб-сайты, позволяющие сжимать видео без установки дополнительного ПО.
Проверьте, что все пути к видеофайлам указаны правильно и файлы действительно существуют по этим путям.
Пример проверки наличия файла:
pythonif not os.path.isfile(file_path): logger.error(f"Видео файл не найден: {file_path}") # Дополнительная обработка, например, уведомление пользователя
Если вы отправляете несколько видео одновременно, это может привести к перегрузке. Попробуйте добавить небольшую задержку между отправками, чтобы избежать тайм-аутов.
Пример добавления задержки:
pythonimport asyncio async def track_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # ... ваш существующий код ... elif text == "Видео трассы": videos = track_info.get("videos", []) if videos: response = f"**Видео трассы {current_track}:**" await update.message.reply_text(response, parse_mode="Markdown") for video_filename in videos: await send_video( context, chat_id=update.effective_chat.id, video_filename=video_filename, caption=None, reply_markup=track_menu_keyboard() ) await asyncio.sleep(1) # Добавляет паузу в 1 секунду между отправками else: await update.message.reply_text( "Видео для этой трассы недоступно.", parse_mode="Markdown", reply_markup=track_menu_keyboard() )
Если размер видео слишком большой и вы не можете его уменьшить, рассмотрите возможность размещения видео на внешнем сервисе (например, YouTube, Vimeo) и отправки ссылки пользователю.
Пример отправки ссылки на видео:
pythonasync def track_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # ... ваш существующий код ... elif text == "Видео трассы": videos = track_info.get("videos", []) video_links = track_info.get("video_links", []) # Добавьте это поле в ваши данные трасс if video_links: response = f"**Видео трассы {current_track}:**\n" + "\n".join(video_links) await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) else: await update.message.reply_text( "Видео для этой трассы недоступно.", parse_mode="Markdown", reply_markup=track_menu_keyboard() )
Измените структуру данных трасс:
pythontracks = { "Нюрбургринг-Нордшляйфе": { # ... другие поля ... "video_links": [ "https://www.youtube.com/watch?v=example1", "https://www.youtube.com/watch?v=example2" ], # ... остальные поля ... }, # ... другие трассы ... }
Убедитесь, что вы логируете достаточную информацию для диагностики проблем. Это поможет быстрее находить и устранять ошибки.
Пример улучшенного логирования:
pythonexcept Exception as e: logger.error(f"Ошибка при отправке видео '{video_filename}' на чат {chat_id}: {e}") # Дополнительные действия, например, уведомление администратора
Если ваш бот размещен на удаленном сервере, убедитесь, что у него стабильное и достаточно быстрое интернет-соединение для загрузки больших файлов.
Убедитесь, что вы используете асинхронные функции и правильно обрабатываете задачи, чтобы избежать блокировки основного потока.
Ниже приведен обновленный фрагмент вашего кода с добавленной проверкой размера видео и обработкой ошибок:
pythonimport logging import time import os import asyncio 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__) # ... ваш существующий код ... def is_file_size_allowed(file_path, max_size_mb=50): if not os.path.isfile(file_path): return False size_bytes = os.path.getsize(file_path) size_mb = size_bytes / (1024 * 1024) return size_mb <= max_size_mb async def send_video(context, chat_id, video_filename, caption=None, reply_markup=None): # Полный путь к файлу file_path = os.path.join(VIDEOS_DIR, video_filename) # Проверка расширения файла _, ext = os.path.splitext(video_filename) if ext.lower() not in ALLOWED_VIDEO_EXTENSIONS: logger.warning(f"Недопустимое расширение видеофайла: {video_filename}") await context.bot.send_message( chat_id=chat_id, text="Невозможно отправить видео с недопустимым расширением.", reply_markup=reply_markup ) return # Проверка размера файла if not is_file_size_allowed(file_path): logger.warning(f"Видео превышает допустимый размер: {file_path}") await context.bot.send_message( chat_id=chat_id, text="Видео слишком большое для отправки. Пожалуйста, выберите другое видео или сократите размер.", reply_markup=reply_markup ) return if os.path.isfile(file_path): try: with open(file_path, 'rb') as video_file: if caption: await context.bot.send_video( chat_id=chat_id, video=video_file, caption=caption, parse_mode="Markdown", reply_markup=reply_markup ) else: await context.bot.send_video( chat_id=chat_id, video=video_file, reply_markup=reply_markup ) except Exception as e: logger.error(f"Ошибка при отправке видео '{video_filename}' на чат {chat_id}: {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 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}:**" await update.message.reply_text(response, parse_mode="Markdown") for video_filename in videos: await send_video( context, chat_id=update.effective_chat.id, video_filename=video_filename, caption=None, # Можно добавить описание видео, если необходимо reply_markup=track_menu_keyboard() ) await asyncio.sleep(1) # Пауза между отправками 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
Мониторинг и логирование:
Использование внешних хостингов для видео:
Обработка больших объемов данных:
Тестирование с разными видео:
Обновление библиотек и зависимостей:
python-telegram-bot
и других зависимостей. Обновления могут содержать исправления ошибок и улучшения производительности.Ошибка тайм-аута при отправке видео через Telegram бота чаще всего связана с превышением лимита размера файла или проблемами с сетью. Следуя приведенным выше рекомендациям, вы сможете диагностировать и устранить проблему. Важно также обеспечить хорошую обработку ошибок и информировать пользователей о возможных проблемах, чтобы улучшить взаимодействие с ботом.
Если после выполнения всех шагов проблема сохраняется, пожалуйста, предоставьте дополнительную информацию, такую как:
Это позволит более точно определить причину проблемы и найти оптимальное решение.