Отправка нескольких видео

बनाया गया: 19 दिसंबर 2024

का उपयोग करके उत्तर दिया गया OpenAI 01 द्वारा OpenAI01

प्रश्न

import logging
import time
import os
from telegram import (
Update,
ReplyKeyboardMarkup,
ReplyKeyboardRemove,
InputFile
)
from telegram.ext import (
ApplicationBuilder,
CommandHandler,
MessageHandler,
filters,
ConversationHandler,
ContextTypes
)

# Настройка логирования
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO
)
logger = logging.getLogger(__name__)

# Состояния диалога
CHOOSE_MAIN, CHOOSE_TRACK, TRACK_MENU, CHOOSE_DANGEROUS_TURN = range(4)

# Настройки анти-спама
MAX_MESSAGES = 5 # Максимум сообщений за период
TIME_WINDOW = 10 # Интервал в секундах для подсчёта сообщений
TIMEOUT_DURATION = 10 # Длительность тайм-аута в секундах

# Папки с медиафайлами
PHOTOS_DIR = "photos" # Убедитесь, что такая папка существует
VIDEOS_DIR = "videos" # Новая папка для видео

# Разрешённые расширения файлов
ALLOWED_PHOTO_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.bmp'}
ALLOWED_VIDEO_EXTENSIONS = {'.mp4', '.mov', '.avi', '.mkv'}

# Данные о трассах (добавлено поле 'фото_история' и 'video' для каждой трассы)
tracks = {
"Нюрбургринг-Нордшляйфе": {
"описание": "Узкие улочки и сложные повороты делают трассу Нюрбургринг-Нордшляйфе одной из самых престижных.",
"лидеры": "Mercedes AMG ONE | *6:29.09 минут* |\n\n2. Porsche GT2 RS MR 991.2 | *6:43.30 минут* |\n\n3. Mercedes AMG GT Black Series | *6:48.04 минут* |\n\n4. Porsche GT3 RS 992 | *6:49.32 минут* |\n\n5. Lamborghini Aventador LP 770-4 SVJ | *6:49.40 минут* |\n\n6. Porsche GT2 RS MR 991.2 | *6:52.15 минут* |\n\n7. Radical SR8 LM | *6:53.20 минут* |\n\n8. Porsche GT3 992 MR | *6:55.73 минут* |\n\n9. Lamborghini Huracan LP 640-4 Performante | *6:57.25 минут* |\n\n10. Porsche GT3 992 | *6:59.93 минут* |",
"история": "Трасса Нордшляйфе была спроектирована и построена с 1925 до 1927 года.Открытие состоялось 18-19 июня 1927 года. Стоимость реализации объекта под ключ составила примерно 8 100 000 Немецких Марок (в пересчёте на 2020 год это около 30 000 000 Евро). Протяжённость трассы составляет 20,8 километров. Заезды и массовые мероприятия проходят на трассе с марта до ноября. В зимнее время в связи с погодными условиями Нюрбургринг закрыт для заездов. Количество левых поворотов — 33, правых — 40. Перепад высоты составляет порядка 300 метров. Самое высокое место — участок Höhe Acht, 616,80 метров над уровнем моря. Самое низкое место — участок Breidscheid, 320 метров над уровнем моря. Максимальный подъём на трассе составляет 17%. Максимальный спуск — 11%. Стоимость проезда одного круга (20,8 км) на автомобиле или мотоцикле с Понедельника до Четверга составляет 25 евро и 30 евро с Пятницы до Воскресенья. Стоимость проживания в регионе вокруг гоночной трассы — от 50 евро в сутки (с завтраком). Стоимость эвакуации автомобиля с гоночной трассы стартует от 150 евро. Ближайшие крупные аэропорты: Франкфурт на Майне, Франкфурт-Хан, Кёльн-Бонн и Дюссельдорф. Абсолютный рекорд полного круга Нордшляйфе с временем 5:19,546 минут был установлен 29 июня 2018 года на прототипе Porsche 919 Hybrid EVO тестовым пилотом и гонщиком Тимо Бернхардом. Трасса Нюрбургринг GP для проведения Формулы 1 была сдана в эксплуатацию в 1984 году. Протяженность трассы составляет 5,148 км. Количество поворотов — 15. Рекорд трассы принадлежит Михаэлю Шумахеру, который в 2004 году на Формульном болиде проехал полный круг GP за 1:29,468 мин.",
"фото_история": "nurburgring/history.jpg",
"video": "nurburgring/video.mp4", # Добавлено поле видео
"опасные_повороты": {
"Karussell": {
"описание": "Описание поворота Karussell — один из самых известных и сложных поворотов на Нюрбургринге. Требует максимальной концентрации и точности.",
"фото": "nurburgring/karussell.jpg"
},
"Brünnchen": {
"описание": "Поворот Brünnchen характеризуется высокой скоростью и требованием к точности траектории.",
"фото": "nurburgring/brunnchen.jpg"
},
"Flugplatz": {
"описание": "Поворот Flugplatz — это длинный и быстрый поворот, который требует отличного контроля над автомобилем.",
"фото": "nurburgring/flugplatz.jpg"
}
}
},
"Монца": {
"описание": "Трасса Монца известна своими длинными прямыми и высокой скоростью, что делает её одной из самых быстрых трасс в мире.",
"лидеры": "Баррикелло (Ferrari) | *1:21,046* |\n\n2. Хэмилтон (Mercedes) | *1:18,887* |\n\n",
"история": "Автодром Монца был открыт в 1922 году и является одной из старейших трасс Формулы-1. Первоначальная трасса проходила частично по общественным дорогам. Трасса неоднократно перестраивалась и модернизировалась, но сохранила свою скоростную сущность. Монца славится своей высокой скоростью и длинными прямыми, что делает её одной из самых быстрых трасс в мире. Она также является свидетелем многих знаменательных событий и трагических аварий в истории автоспорта.",
"фото_история": "monza/history.jpg",
"video": "monza/video.mp4", # Добавлено поле видео
"опасные_повороты": {
"Lesmo": {
"описание": "Секция Lesmo состоит из двух быстрых поворотов, которые требуют максимальной точности и контроля над автомобилем.",
"фото": "monza/lesmo.jpg"
},
"Curva Grande": {
"описание": "Большой поворот (Curva Grande) — быстрый и протяженный поворот, где ошибки могут привести к серьезным последствиям.",
"фото": "monza/curva_grande.jpg"
}
}
},
"Спа-Франкоршам": { # Новая трасса
"описание": "Трасса Спа-Франкоршам известна своим непредсказуемым погодным условиям и сложными поворотами, что делает её одной из самых технически сложных трасс в мире.",
"лидеры": "Льюис Хэмилтон | *6:47.285 минут* |\n\nСебастьян Феттель | *6:49.875 минут* |\n\nМакс Ферстаппен | *6:50.123 минут* |",
"история": "Автодром Спа-Франкоршам был открыт в 1921 году и является одним из старейших и самых уважаемых треков в мире автоспорта. Трасса проходит через живописные пейзажи Бельгии и известна своей историей и традициями. Спа-Франкоршам принимал множество чемпионатов мира, включая Формулу-1, и является любимой трассой многих гонщиков за её уникальную смесь высоких скоростей и технических сложностей.",
"фото_история": "spa/history.jpg",
"video": "spa/video.mp4", # Добавлено поле видео
"опасные_повороты": {
"Eau Rouge": {
"описание": "Eau Rouge — знаменитый быстрый подъем с крутым поворотом, требующий точности и смелости от пилотов.",
"фото": "spa/eau_rouge.jpg"
},
"Pouhon": {
"описание": "Pouhon — длинный и быстрый поворот, который требует отличной аэродинамики и устойчивости автомобиля.",
"фото": "spa/pouhon.jpg"
}
}
}
}

creators_info = "Этот бот был создан командой:\n\n1. Ратников Иван\n2. Хамраева Шукрона\n3. Новиков Елисей\n4. Елизарова Ксения\n5. Пискунов Кирилл\n6. Колосов Дмитрий."

# Функция для динамического создания клавиатур с несколькими кнопками в строке
def create_keyboard(buttons, buttons_per_row=2):
keyboard = []
for i in range(0, len(buttons), buttons_per_row):
keyboard.append(buttons[i:i + buttons_per_row])
return ReplyKeyboardMarkup(keyboard, resize_keyboard=True)

# Клавиатуры
def main_menu_keyboard():
buttons = ["Выбрать трассу", "Создатели"]
return create_keyboard(buttons, buttons_per_row=2)

def tracks_keyboard():
buttons = list(tracks.keys()) + ["Назад"]
return create_keyboard(buttons, buttons_per_row=2) # 2 кнопки в строке

def track_menu_keyboard():
buttons = ["Лидеры", "История о трассе", "Опасные повороты", "Видео трассы", "Назад"]
return create_keyboard(buttons, buttons_per_row=2) # 2 кнопки в строке, последняя строка с одной кнопкой

def dangerous_turns_keyboard(track_name):
dangerous_turns = tracks[track_name]["опасные_повороты"]
buttons = list(dangerous_turns.keys()) + ["Назад"]
return create_keyboard(buttons, buttons_per_row=2) # 2 кнопки в строке

# Функция анти-спама
async def anti_spam(update: Update, context: ContextTypes.DEFAULT_TYPE):
user_id = update.effective_user.id
current_time = time.time()

# Проверка на тайм-аута
timeout_until = context.user_data.get("spam_timeout_until", 0)
if current_time < timeout_until:
await update.message.reply_text("Вы слишком часто отправляете сообщения. Пожалуйста, подождите немного.")
return False

# Инициализация списка временных меток
timestamps = context.user_data.get("timestamps", [])

# Добавление текущей метки времени
timestamps.append(current_time)

# Очистка старых меток времени
timestamps = [t for t in timestamps if current_time - t <= TIME_WINDOW]
context.user_data["timestamps"] = timestamps

# Проверка количества сообщений за период
if len(timestamps) > MAX_MESSAGES:
context.user_data["spam_timeout_until"] = current_time + TIMEOUT_DURATION
await update.message.reply_text("Слишком много сообщений! Пожалуйста, подождите 10 секунд.")
return False

return True

# Вспомогательная функция для отправки фотографий с обработкой длинных подписей
async def send_photo(context, chat_id, photo_filename, caption=None, reply_markup=None):
# Полный путь к файлу
file_path = os.path.join(PHOTOS_DIR, photo_filename)

# Проверка расширения файла
_, ext = os.path.splitext(photo_filename)
if ext.lower() not in ALLOWED_PHOTO_EXTENSIONS:
logger.warning(f"Недопустимое расширение файла: {photo_filename}")
await context.bot.send_message(
chat_id=chat_id,
text="Невозможно отправить файл с недопустимым расширением.",
reply_markup=reply_markup
)
return

if os.path.isfile(file_path):
try:
with open(file_path, 'rb') as photo_file:
if caption:
if len(caption) <= 1024:
# Если длина подписи допустимая, отправляем фото с подписью
await context.bot.send_photo(
chat_id=chat_id,
photo=photo_file,
caption=caption,
parse_mode="Markdown",
reply_markup=reply_markup
)
else:
# Если подпись слишком длинная, отправляем фото без подписи
await context.bot.send_photo(
chat_id=chat_id,
photo=photo_file,
reply_markup=reply_markup
)
# Разбиваем длинный текст на части по 1024 символа
for i in range(0, len(caption), 1024):
part = caption[i:i+1024]
await context.bot.send_message(
chat_id=chat_id,
text=part,
parse_mode="Markdown",
reply_markup=reply_markup
)
else:
# Если подпись не требуется, просто отправляем фото
await context.bot.send_photo(
chat_id=chat_id,
photo=photo_file,
reply_markup=reply_markup
)
except Exception as e:
logger.error(f"Ошибка при отправке фото: {e}")
if caption:
await context.bot.send_message(
chat_id=chat_id,
text=caption,
parse_mode="Markdown",
reply_markup=reply_markup
)
else:
logger.warning(f"Файл фото не найден: {file_path}")
if caption:
await context.bot.send_message(
chat_id=chat_id,
text=f"{caption}\n\n*Фото не доступно.*",
parse_mode="Markdown",
reply_markup=reply_markup
)

# Вспомогательная функция для отправки видео из локальной папки
async def send_video(context, chat_id, video_filename, caption=None, reply_markup=None):
# Полный путь к файлу
file_path = os.path.join(VIDEOS_DIR, video_filename)

# Проверка расширения файла
_, ext = os.path.splitext(video_filename)
if ext.lower() not in ALLOWED_VIDEO_EXTENSIONS:
logger.warning(f"Недопустимое расширение видеофайла: {video_filename}")
await context.bot.send_message(
chat_id=chat_id,
text="Невозможно отправить видео с недопустимым расширением.",
reply_markup=reply_markup
)
return

if os.path.isfile(file_path):
try:
with open(file_path, 'rb') as video_file:
if caption:
await context.bot.send_video(
chat_id=chat_id,
video=video_file,
caption=caption,
parse_mode="Markdown",
reply_markup=reply_markup
)
else:
await context.bot.send_video(
chat_id=chat_id,
video=video_file,
reply_markup=reply_markup
)
except Exception as e:
logger.error(f"Ошибка при отправке видео: {e}")
if caption:
await context.bot.send_message(
chat_id=chat_id,
text=f"{caption}\n\n*Видео не доступно.*",
parse_mode="Markdown",
reply_markup=reply_markup
)
else:
logger.warning(f"Файл видео не найден: {file_path}")
if caption:
await context.bot.send_message(
chat_id=chat_id,
text=f"{caption}\n\n*Видео не доступно.*",
parse_mode="Markdown",
reply_markup=reply_markup
)

# Обработчики
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Сброс анти-спам таймера при старте
context.user_data.pop("spam_timeout_until", None)
context.user_data.pop("timestamps", None)
await update.message.reply_text(
"Добро пожаловать! Выберите опцию:",
reply_markup=main_menu_keyboard()
)
return CHOOSE_MAIN

async def main_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Проверка анти-спам
if not await anti_spam(update, context):
return CHOOSE_MAIN

text = update.message.text

if text == "Выбрать трассу":
await update.message.reply_text("Выберите трассу:", reply_markup=tracks_keyboard())
return CHOOSE_TRACK
elif text == "Создатели":
await update.message.reply_text(creators_info, reply_markup=main_menu_keyboard())
return CHOOSE_MAIN
else:
await update.message.reply_text("Неизвестная команда. Попробуйте снова.", reply_markup=main_menu_keyboard())
return CHOOSE_MAIN

async def track_choice(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Проверка анти-спам
if not await anti_spam(update, context):
return CHOOSE_TRACK

text = update.message.text

if text == "Назад":
# Возврат в главное меню
await update.message.reply_text("Главное меню:", reply_markup=main_menu_keyboard())
return CHOOSE_MAIN

if text in tracks:
context.user_data["current_track"] = text
track_info = tracks[text]
description = track_info["описание"]
response = f"**Трасса:** {text}\n\n**Описание:** {description}"
await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard())
return TRACK_MENU
else:
await update.message.reply_text("Пожалуйста, выберите доступную трассу или нажмите Назад.", reply_markup=tracks_keyboard())
return CHOOSE_TRACK

async def track_menu_choice(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Проверка анти-спам
if not await anti_spam(update, context):
return TRACK_MENU

text = update.message.text
current_track = context.user_data.get("current_track")

if text == "Назад":
# Возвращение к списку трасс
await update.message.reply_text("Выберите трассу:", reply_markup=tracks_keyboard())
return CHOOSE_TRACK

if current_track not in tracks:
await update.message.reply_text("Ошибка. Попробуйте снова.", reply_markup=main_menu_keyboard())
return CHOOSE_MAIN

track_info = tracks[current_track]

if text == "Лидеры":
leaders = track_info["лидеры"]
response = f"**Лидеры на трассе {current_track}:**\n{leaders}"
await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard())
elif text == "История о трассе":
history = track_info["история"]
response = f"**История трассы {current_track}:**\n{history}"

# Получение имени файла фото истории
photo_filename = track_info.get("фото_история")

if photo_filename:
# Отправка фото с текстом
await send_photo(
context,
chat_id=update.effective_chat.id,
photo_filename=photo_filename,
caption=response,
reply_markup=track_menu_keyboard()
)
else:
# Если фото не задано, отправляем только текст
await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard())
elif text == "Опасные повороты":
await update.message.reply_text("Выберите опасный поворот:", reply_markup=dangerous_turns_keyboard(current_track))
return CHOOSE_DANGEROUS_TURN
elif text == "Видео трассы": # Обработка новой опции
video_filename = track_info.get("video")
if video_filename:
response = f"**Видео трассы {current_track}:**"
await send_video(
context,
chat_id=update.effective_chat.id,
video_filename=video_filename,
caption=response,
reply_markup=track_menu_keyboard()
)
else:
await update.message.reply_text(
"Видео для этой трассы недоступно.",
parse_mode="Markdown",
reply_markup=track_menu_keyboard()
)
else:
response = "Неизвестная команда. Попробуйте снова."
await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard())

return TRACK_MENU

async def dangerous_turn_choice(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Проверка анти-спам
if not await anti_spam(update, context):
return CHOOSE_DANGEROUS_TURN

text = update.message.text
current_track = context.user_data.get("current_track")

if text == "Назад":
# Возвращение к меню трассы
await update.message.reply_text("Выберите опцию:", reply_markup=track_menu_keyboard())
return TRACK_MENU

dangerous_turns = tracks[current_track]["опасные_повороты"]

if text in dangerous_turns:
turn_info = dangerous_turns[text]
description = turn_info["описание"]
photo_filename = turn_info.get("фото")
response_text = f"**Опасный поворот {text} на трассе {current_track}:**\n{description}"

markup = create_keyboard(["Назад"], buttons_per_row=1)

if photo_filename:
await send_photo(
context, # Передача context
chat_id=update.effective_chat.id,
photo_filename=photo_filename,
caption=response_text,
reply_markup=markup
)
else:
await update.message.reply_text(
response_text,
parse_mode="Markdown",
reply_markup=markup
)
else:
response = "Неизвестный поворот. Пожалуйста, выберите доступный поворот или нажмите Назад."
markup = create_keyboard(["Назад"], buttons_per_row=1)
await update.message.reply_text(response, reply_markup=markup)

return CHOOSE_DANGEROUS_TURN

async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text("Используйте /start для начала.")

async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text("Действие отменено.", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END

def main():
TOKEN = "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() сделать так чтобы я мог более двух видео прикреплять

विचार

Erweiterung der Videooptionen

Der Benutzer hat einen Telegram-Bot-Code geteilt, der Informationen über Rennstrecken bietet. Der aktuelle Code kann bis zu zwei Videos pro Rennstrecke anhängen. Der Benutzer möchte die Möglichkeit, mehr als zwei Videos anzuhängen, möglicherweise pro Rennstrecke oder insgesamt erweitern.

Anpassen der Speicherung

Es scheint, dass der Code derzeit nur ein Video pro Rennstrecke unterstützt. Um mehrere Videos zu ermöglichen, könnte die 'tracks'-Datenstruktur aktualisiert und die 'send_video'-Funktion angepasst werden, um eine Liste von Videos zu handhaben.

Anpassen des Codes

Ich arbeite daran, dem Code die Möglichkeit zu geben, mehrere Videos zu senden, indem ich die Datenstruktur 'tracks' aktualisiere und die 'send_video'-Funktion anpasse, um jede Liste von Videos einzeln zu senden.

Anpassen des Codes

Ich aktualisiere die 'tracks'-Datenstruktur, um die Verwendung von 'videos' anstelle von 'video' zu ermöglichen. In der Funktion 'track_menu_choice' iteriere ich durch die Liste der Videos und sende jedes Video einzeln.

Gedanken für 4 Sekunden

उत्तर

Чтобы позволить вашему боту отправлять более двух видео для каждой трассы, необходимо внести следующие изменения:

  1. Изменить структуру данных трасс (tracks):

    • Вместо хранения одного видеофайла в поле "video", храните список видеофайлов в поле "videos".
  2. Обновить обработчик выбора видео:

    • Измените функцию track_menu_choice так, чтобы она могла обрабатывать и отправлять несколько видеофайлов.
  3. Обновить функцию отправки видео (если необходимо):

    • В текущей реализации функция send_video уже поддерживает отправку одного видео. Для отправки нескольких видео нужно вызывать эту функцию несколько раз.

Ниже приведены подробные шаги и кодовые примеры для реализации этих изменений.

1. Изменение структуры данных трасс (tracks)

Вместо использования поля "video" как строки, измените его на список "videos". Например:

tracks = { "Нюрбургринг-Нордшляйфе": { # ... другие поля ... "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". Это необходимо для единообразия и корректной работы последующего кода.

2. Обновление обработчика выбора видео

В функции track_menu_choice измените обработку опции "Видео трассы", чтобы она могла отправлять несколько видео из списка "videos".

Вот обновленный фрагмент кода для этой функции:

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

Объяснение изменений:

  • Получение списка видео: Используем track_info.get("videos", []), чтобы получить список видеофайлов. Если список пустой или поле отсутствует, вернётся пустой список.

  • Отправка заголовка: Отправляем заголовок "**Видео трассы {current_track}:**" отдельно перед отправкой видео.

  • Цикл по видеофайлам: Проходимся по каждому видеофайлу в списке videos и вызываем функцию send_video для отправки каждого из них.

  • Описание видео: В данном примере caption установлен в None, но вы можете модифицировать его, чтобы добавить описание или номер видео.

3. Обновление функции отправки видео (опционально)

Если вы хотите добавить описание для каждого видео или улучшить функцию отправки видео, вы можете внести дополнительные изменения в функцию send_video. Однако текущая реализация уже поддерживает отправку одного видео за раз, и, следовательно, достаточно вызывать её несколько раз в цикле для отправки нескольких видео.

Полный обновлённый код с внесёнными изменениями

Ниже представлен полный код с внесёнными изменениями для поддержки отправки более двух видео:

import logging import time import os from telegram import ( Update, ReplyKeyboardMarkup, ReplyKeyboardRemove, InputFile ) from telegram.ext import ( ApplicationBuilder, CommandHandler, MessageHandler, filters, ConversationHandler, ContextTypes ) # Настройка логирования logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO ) logger = logging.getLogger(__name__) # Состояния диалога CHOOSE_MAIN, CHOOSE_TRACK, TRACK_MENU, CHOOSE_DANGEROUS_TURN = range(4) # Настройки анти-спама MAX_MESSAGES = 5 # Максимум сообщений за период TIME_WINDOW = 10 # Интервал в секундах для подсчёта сообщений TIMEOUT_DURATION = 10 # Длительность тайм-аута в секундах # Папки с медиафайлами PHOTOS_DIR = "photos" # Убедитесь, что такая папка существует 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()

Дополнительные рекомендации

  1. Организация видеофайлов:

    • Убедитесь, что все видеофайлы правильно размещены в папке videos.
    • Названия файлов должны соответствовать тем, которые указаны в списках "videos" для каждой трассы.
  2. Проверка размеров видео:

    • Telegram имеет ограничения на размер отправляемых видео. Убедитесь, что ваши видео не превышают лимиты Telegram (обычно до 50 МБ для ботов).
  3. Оптимизация отправки:

    • Если у вас много видео, рассмотрите возможность добавления кнопок для просмотра видео в виде ссылок на YouTube или другой видеохостинг, чтобы снизить нагрузку на ваш сервер и сократить время загрузки.
  4. Добавление описаний к видео:

    • Если вы хотите добавить описания к каждому видео, вы можете расширить список "videos" для каждой трассы, используя словари вместо строк. Например:
    "videos": [ {"filename": "nurburgring/video1.mp4", "description": "Обзор трассы"}, {"filename": "nurburgring/video2.mp4", "description": "Гонки на трассе"} ]
    • Затем обновите функцию track_menu_choice для обработки этого формата.
  5. Обработка ошибок:

    • Добавьте дополнительную обработку ошибок для случаев, когда видео не могут быть отправлены, и информируйте пользователя соответствующим образом.

Следуя этим шагам, вы сможете расширить функциональность вашего бота, позволяя отправлять более двух видео для каждой трассы, что сделает взаимодействие с пользователями более информативным и увлекательным.

प्रश्न

__main__ - ERROR - Ошибка при отправке видео: Timed out

विचार

Entdecken von Ursachen
Zeitüberschreitung beim Video-Senden könnte an zu großer Dateigröße, Netzwerkproblen, falschem Pfad oder Überlastung durch viele Dateien gleichzeitig liegen.

Lösungen prüfen
Ich schaue mir die Videogröße und Pfadgenauigkeit an, setze erneute Versuche auf, optimiere den Versandprozess und empfehle Fehlerprotokolle.

Übermittlung von Videolinks
Versand via URL statt großer Dateien fehlerfrei. Verbesserung durch dateilinks, um Zeitüberschreitungen zu vermeiden.

Übertragung optimieren
Schritte zur Risikominimierung: Netzwerkordnungen prüfen, Codeanpassungen zur effizienten Handhabung von Videodateien und Streams zur Reduzierung von Speicherplatzverbrauch.

Prüfung der Dateigröße
Ich schaue mir die Dateigröße an und achte darauf, dass sie 50 MB nicht überschreitet, um die Anforderungen von Telegrams Bot-APIs zu erfüllen.

Gedanken für 5 Sekunden

उत्तर

Ошибка __main__ - ERROR - Ошибка при отправке видео: Timed out указывает на то, что ваш бот не смог отправить видео из-за превышения времени ожидания (тайм-аута). Это может происходить по нескольким причинам, наиболее вероятные из которых связаны с размером видеофайла или проблемами с сетью. Давайте рассмотрим возможные причины и способы их устранения.

Возможные причины ошибки

  1. Размер видеофайла превышает лимиты Telegram:

    • Лимит размера для ботов: Telegram ограничивает размер файлов, которые боты могут отправлять, до 50 МБ.
    • Большие видео: Если ваши видео превышают этот размер, попытка отправить их приведет к ошибке тайм-аута.
  2. Проблемы с сетью:

    • Низкая скорость интернета: Если у сервера, на котором работает ваш бот, медленное интернет-соединение, загрузка больших файлов может занимать слишком много времени.
    • Нестабильное соединение: Временные перебои в сети могут приводить к прерыванию передачи данных.
  3. Некорректный путь к файлу или отсутствующий файл:

    • Неверный путь: Если путь к видеофайлу указан неправильно, бот не сможет его найти и отправить.
    • Отсутствие файла: Видео может быть перемещено или удалено после указания пути.
  4. Перегрузка сервера или частые запросы:

    • Многократные отправки: Попытка отправить несколько больших видео одновременно может перегрузить бота и привести к тайм-аутам.

Шаги для устранения проблемы

1. Проверьте размер видеофайлов

Убедитесь, что размер каждого видеофайла не превышает 50 МБ. Вы можете проверить размер файла с помощью файлового менеджера или программно.

Пример проверки размера файла в Python:

import 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

Использование в вашем коде:

Перед отправкой видео добавьте проверку размера:

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"Ошибка при отправке видео: {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 )

2. Сократите размер видео

Если видео превышает лимит в 50 МБ, вам необходимо уменьшить его размер. Это можно сделать с помощью различных инструментов для сжатия видео, например:

  • FFmpeg: мощный инструмент для обработки видео из командной строки.

    Пример сжатия видео с помощью FFmpeg:

    ffmpeg -i input.mp4 -vcodec libx264 -crf 28 output.mp4

    Здесь -crf определяет качество видео. Чем выше значение, тем меньше размер файла и ниже качество. Значение по умолчанию — 23. Вы можете экспериментировать с этим параметром.

  • Онлайн-сервисы: существуют веб-сайты, позволяющие сжимать видео без установки дополнительного ПО.

3. Убедитесь в корректности путей к файлам

Проверьте, что все пути к видеофайлам указаны правильно и файлы действительно существуют по этим путям.

Пример проверки наличия файла:

if not os.path.isfile(file_path): logger.error(f"Видео файл не найден: {file_path}") # Дополнительная обработка, например, уведомление пользователя

4. Разбейте отправку видео на части

Если вы отправляете несколько видео одновременно, это может привести к перегрузке. Попробуйте добавить небольшую задержку между отправками, чтобы избежать тайм-аутов.

Пример добавления задержки:

import 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() )

5. Используйте ссылки на видео вместо загрузки

Если размер видео слишком большой и вы не можете его уменьшить, рассмотрите возможность размещения видео на внешнем сервисе (например, YouTube, Vimeo) и отправки ссылки пользователю.

Пример отправки ссылки на видео:

async 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() )

Измените структуру данных трасс:

tracks = { "Нюрбургринг-Нордшляйфе": { # ... другие поля ... "video_links": [ "https://www.youtube.com/watch?v=example1", "https://www.youtube.com/watch?v=example2" ], # ... остальные поля ... }, # ... другие трассы ... }

6. Улучшите обработку ошибок и логирование

Убедитесь, что вы логируете достаточную информацию для диагностики проблем. Это поможет быстрее находить и устранять ошибки.

Пример улучшенного логирования:

except Exception as e: logger.error(f"Ошибка при отправке видео '{video_filename}' на чат {chat_id}: {e}") # Дополнительные действия, например, уведомление администратора

7. Проверьте интернет-соединение сервера

Если ваш бот размещен на удаленном сервере, убедитесь, что у него стабильное и достаточно быстрое интернет-соединение для загрузки больших файлов.

8. Используйте асинхронные операции правильно

Убедитесь, что вы используете асинхронные функции и правильно обрабатываете задачи, чтобы избежать блокировки основного потока.

Пример обновленного кода с учетом вышеуказанных рекомендаций

Ниже приведен обновленный фрагмент вашего кода с добавленной проверкой размера видео и обработкой ошибок:

import 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

Дополнительные рекомендации

  1. Мониторинг и логирование:

    • Убедитесь, что логирование настроено правильно и содержит достаточно информации для диагностики проблем.
    • Рассмотрите возможность отправки уведомлений администраторам бота при возникновении критических ошибок.
  2. Использование внешних хостингов для видео:

    • Если вы планируете отправлять большое количество видео или видео больших размеров, подумайте о размещении их на сторонних сервисах (например, YouTube, Vimeo) и отправке ссылок пользователям. Это позволит избежать ограничений по размеру файлов и снизить нагрузку на сервер вашего бота.
  3. Обработка больших объемов данных:

    • Если пользователи часто запрашивают несколько видео, убедитесь, что ваш бот справляется с таким количеством запросов. Возможно, потребуется оптимизация или масштабирование инфраструктуры.
  4. Тестирование с разными видео:

    • Попробуйте отправить несколько разных видео с разными размерами и форматами, чтобы определить, при каких условиях возникает ошибка. Это поможет сузить круг поиска проблемы.
  5. Обновление библиотек и зависимостей:

    • Убедитесь, что вы используете актуальные версии библиотек python-telegram-bot и других зависимостей. Обновления могут содержать исправления ошибок и улучшения производительности.

Заключение

Ошибка тайм-аута при отправке видео через Telegram бота чаще всего связана с превышением лимита размера файла или проблемами с сетью. Следуя приведенным выше рекомендациям, вы сможете диагностировать и устранить проблему. Важно также обеспечить хорошую обработку ошибок и информировать пользователей о возможных проблемах, чтобы улучшить взаимодействие с ботом.

Если после выполнения всех шагов проблема сохраняется, пожалуйста, предоставьте дополнительную информацию, такую как:

  • Размер видеофайлов, которые вызывают ошибку.
  • Полный стек трейс ошибки, если он доступен.
  • Дополнительные логи, которые могут помочь в диагностике.

Это позволит более точно определить причину проблемы и найти оптимальное решение.

इस प्रश्नोत्तर को साझा करें