Ошибка подписи Telegram

생성일: 2024년 12월 18일

답변에 사용된 모델 OpenAI 01 by OpenAI01

질문

__main__ - ERROR - Ошибка при отправке фото: Message caption is too long фикс в коде 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 = 20 # Длительность тайм-аута в секундах

# Папка с фотографиями
PHOTOS_DIR = "photos" # Убедитесь, что такая папка существует

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

# Данные о трассах (добавлено поле 'фото_история' для каждой трассы)
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",
"опасные_повороты": {
"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\n3. [Добавьте третьего лидера]",
"история": "Автодром Монца был открыт в 1922 году и является одной из старейших трасс Формулы-1. Первоначальная трасса проходила частично по общественным дорогам. Трасса неоднократно перестраивалась и модернизировалась, но сохранила свою скоростную сущность. Монца славится своей высокой скоростью и длинными прямыми, что делает её одной из самых быстрых трасс в мире. Она также является свидетелем многих знаменательных событий и трагических аварий в истории автоспорта.",
"фото_история": "monza_history.jpg", # Добавлено поле с фото истории
"опасные_повороты": {
"Lesmo": {
"описание": "Секция Lesmo состоит из двух быстрых поворотов, которые требуют максимальной точности и контроля над автомобилем.",
"фото": "lesmo.jpg"
},
"Curva Grande": {
"описание": "Большой поворот (Curva Grande) — быстрый и протяженный поворот, где ошибки могут привести к серьезным последствиям.",
"фото": "curva_grande.jpg"
}
}
},
"Спа-Франкоршам": { # Новая трасса
"описание": "Трасса Спа-Франкоршам известна своим непредсказуемым погодным условиям и сложными поворотами, что делает её одной из самых технически сложных трасс в мире.",
"лидеры": "Льюис Хэмилтон | *6:47.285 минут* |\n\nСебастьян Феттель | *6:49.875 минут* |\n\nМакс Ферстаппен | *6:50.123 минут* |",
"история": "Автодром Спа-Франкоршам был открыт в 1921 году и является одним из старейших и самых уважаемых треков в мире автоспорта. Трасса проходит через живописные пейзажи Бельгии и известна своей историей и традициями. Спа-Франкоршам принимал множество чемпионатов мира, включая Формулу-1, и является любимой трассой многих гонщиков за её уникальную смесь высоких скоростей и технических сложностей.",
"фото_история": "spa_history.jpg", # Добавлено поле с фото истории
"опасные_повороты": {
"Eau Rouge": {
"описание": "Eau Rouge — знаменитый быстрый подъем с крутым поворотом, требующий точности и смелости от пилотов.",
"фото": "eau_rouge.jpg"
},
"Pouhon": {
"описание": "Pouhon — длинный и быстрый поворот, который требует отличной аэродинамики и устойчивости автомобиля.",
"фото": "pouhon.jpg"
}
}
}
}

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

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

def tracks_keyboard():
track_buttons = [[name] for name in tracks.keys()]
track_buttons.append(["Назад"])
return ReplyKeyboardMarkup(track_buttons, resize_keyboard=True)

def track_menu_keyboard():
return ReplyKeyboardMarkup(
[
["Лидеры"],
["История о трассе"],
["Опасные повороты"],
["Назад"]
],
resize_keyboard=True
)

def dangerous_turns_keyboard(track_name):
dangerous_turns = tracks[track_name]["опасные_повороты"]
buttons = [[turn] for turn in dangerous_turns.keys()]
buttons.append(["Назад"])
return ReplyKeyboardMarkup(buttons, resize_keyboard=True)

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

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

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

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

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

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

return True

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

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

if os.path.isfile(file_path):
try:
with open(file_path, 'rb') as photo_file:
await context.bot.send_photo(
chat_id=chat_id,
photo=photo_file,
caption=caption,
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,
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
else:
response = "Неизвестная команда. Попробуйте снова."
await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard())

return TRACK_MENU

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

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

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

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

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

markup = ReplyKeyboardMarkup(
[["Назад"]], resize_keyboard=True
)

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

return CHOOSE_DANGEROUS_TURN

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

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

def main():
TOKEN = "8166041667:AAHsjC8QSxSsqIt3zxGUm6YeGyEcp9QRr08" # tokens
# Убедитесь, что папка с фотографиями существует
if not os.path.isdir(PHOTOS_DIR):
os.makedirs(PHOTOS_DIR)
logger.info(f"Создана папка для фотографий: {PHOTOS_DIR}")

application = ApplicationBuilder().token(TOKEN).build()

conv_handler = ConversationHandler(
entry_points=[CommandHandler("start", start)],
states={
CHOOSE_MAIN: [MessageHandler(filters.TEXT & ~filters.COMMAND, main_menu_choice)],
CHOOSE_TRACK: [MessageHandler(filters.TEXT & ~filters.COMMAND, track_choice)],
TRACK_MENU: [MessageHandler(filters.TEXT & ~filters.COMMAND, track_menu_choice)],
CHOOSE_DANGEROUS_TURN: [MessageHandler(filters.TEXT & ~filters.COMMAND, dangerous_turn_choice)]
},
fallbacks=[CommandHandler("cancel", cancel), CommandHandler("help", help_command)]
)

application.add_handler(conv_handler)
application.run_polling()

if __name__ == '__main__':
main()

사고

Überschreitung des Limits

Beim Versenden eines Fotos auf Telegram stoße ich auf eine Fehlermeldung, die anzeigt, dass der Nachrichtenbeschreibungstext zu lang ist. Telegram erlaubt maximal 1024 Zeichen.

Lösungsansätze zur Fehlerbehebung

Ich denke darüber nach, alternative Methoden zu verwenden, um lange Beschreibungen für Fotos auf Telegram zu bewältigen. Optionen umfassen das Kürzen der Beschreibungen oder die separate Versendung des Fotos und Textes.

Überlegenheit der Strategie

Ich schaue mir die 'send_photo' Funktion an und denke darüber nach, wie sie eingesetzt wird, wenn die Beschreibungen länger als 1024 Zeichen sind.

Vier Tipps einpacken

Ich denke darüber nach, wie die 'send_photo' Funktion Beschreibungen über 1024 Zeichen vermeiden kann, indem sie den Text separat oder thematisch gruppiert geschickt übermittelt.

Längenüberprüfung integrieren

Ich schlage vor, die 'send_photo' Funktion so anzupassen, dass sie die Länge der Beschreibung überprüft und entsprechend aufteilt, um die Grenze von 1024 Zeichen nicht zu überschreiten.

Überlängen vermeiden

Ich möchte sicherstellen, dass die 'send_photo' Funktion die Länge der Beschreibung prüft und bei Überschreitung von 1024 Zeichen den Text separat sendet, um eine Überlänge zu vermeiden.

Anpassen der 'send_photo' Funktion

Ich bin dabei, die 'send_photo' Funktion zu aktualisieren, um die Länge der Beschreibung zu überprüfen und den Text separat zu senden, wenn die Grenze von 1024 Zeichen überschritten wird.

Gedanken für 7 Sekunden

답변

Ошибка Message caption is too long возникает потому, что длина подписи (caption), которую вы пытаетесь отправить вместе с фотографией, превышает допустимый лимит Telegram — 1024 символа. В вашем случае, описание истории трассы слишком длинное и вызывает эту ошибку.

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

Шаги для решения проблемы:

  1. Проверка длины подписи: Перед отправкой фотографии проверьте, не превышает ли длина подписи 1024 символа.
  2. Разделение текста: Если подпись слишком длинная, разделите её на части, каждая из которых не превышает 1024 символа.
  3. Отправка текста отдельно: Отправьте фотографию без подписи, а длинный текст — как отдельные сообщения.

Обновленный код функции send_photo:

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

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

  1. Проверка длины подписи:

    • Если длина caption меньше или равна 1024 символам, фотография отправляется с подписью как обычно.
    • Если длина превышает 1024 символа, фотография отправляется без подписи, а длинный текст разбивается на части и отправляется как отдельные сообщения.
  2. Разбиение текста:

    • Используется цикл for с шагом 1024 символа, чтобы разделить длинный текст на допустимые части.
  3. Отправка сообщений:

    • Фото отправляется без подписи.
    • Каждая часть длинного текста отправляется как отдельное сообщение с использованием parse_mode="Markdown" для сохранения форматирования.

Применение изменений в обработчике:

В функции track_menu_choice, когда пользователь выбирает опцию "История о трассе", вызывается send_photo с длинной подписью. Благодаря обновленной функции send_photo, длинный текст будет корректно обработан и отправлен без превышения лимита Telegram.

Полный пример обновленного обработчика:

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 else: response = "Неизвестная команда. Попробуйте снова." await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) return TRACK_MENU

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

  • Оптимизация текстов: Проверьте и, при необходимости, сократите тексты описаний, чтобы они были более краткими и информативными.
  • Использование форматирования: Используйте форматирование Markdown или HTML для улучшения читаемости сообщений.
  • Тестирование: После внесения изменений протестируйте бот, отправляя длинные описания, чтобы убедиться, что все работает корректно.

Заключение

Следуя приведённым выше шагам, вы сможете избежать ошибки Message caption is too long и обеспечить корректную отправку длинных текстов вместе с фотографиями в вашем Telegram-боте.

질문

фикс именно в полном коде написать

사고

Lösung finden: Hinweis zur Tatsache, dass der Fehler durch Überschreiten der Telegram-Beschriftungsgrenze verursacht wird. Ich erwäge, die send_photo-Funktion zu ändern, um dieses Problem zu beheben.

Anpassungen vornehmen

Ich integriere den modifizierten send_photo-Code, um die Telegram-Beschriftungsgrenzen zu berücksichtigen. Dies soll die Fehlerbehebung unterstützen und die Anpassungen klar aufzeigen.

Gedanken für einige wenige Sekunden

답변

Понимаю, что вы хотите получить полный исправленный код вашего Telegram-бота с учетом обработки ошибки Message caption is too long. Ниже представлен полный код с внесенными изменениями, которые предотвращают превышение лимита длины подписи (caption) при отправке фотографий.

Изменения, внесенные в код:

  1. Обновленная функция send_photo: Теперь перед отправкой фотографии проверяется длина подписи. Если она превышает 1024 символа, фотография отправляется без подписи, а длинный текст разбивается и отправляется отдельными сообщениями.

  2. Добавлено использование parse_mode="Markdown" при отправке длинных частей текста для сохранения форматирования.

  3. Обработка случаев, когда caption отсутствует или файл фотографии не найден.

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

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 = 20 # Длительность тайм-аута в секундах # Папка с фотографиями PHOTOS_DIR = "photos" # Убедитесь, что такая папка существует # Разрешённые расширения файлов ALLOWED_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.bmp'} # Данные о трассах (добавлено поле 'фото_история' для каждой трассы) 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", "опасные_повороты": { "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\n3. [Добавьте третьего лидера]", "история": "Автодром Монца был открыт в 1922 году и является одной из старейших трасс Формулы-1. Первоначальная трасса проходила частично по общественным дорогам. Трасса неоднократно перестраивалась и модернизировалась, но сохранила свою скоростную сущность. Монца славится своей высокой скоростью и длинными прямыми, что делает её одной из самых быстрых трасс в мире. Она также является свидетелем многих знаменательных событий и трагических аварий в истории автоспорта.", "фото_история": "monza_history.jpg", # Добавлено поле с фото истории "опасные_повороты": { "Lesmo": { "описание": "Секция Lesmo состоит из двух быстрых поворотов, которые требуют максимальной точности и контроля над автомобилем.", "фото": "lesmo.jpg" }, "Curva Grande": { "описание": "Большой поворот (Curva Grande) — быстрый и протяженный поворот, где ошибки могут привести к серьезным последствиям.", "фото": "curva_grande.jpg" } } }, "Спа-Франкоршам": { # Новая трасса "описание": "Трасса Спа-Франкоршам известна своим непредсказуемым погодным условиям и сложными поворотами, что делает её одной из самых технически сложных трасс в мире.", "лидеры": "Льюис Хэмилтон | *6:47.285 минут* |\n\nСебастьян Феттель | *6:49.875 минут* |\n\nМакс Ферстаппен | *6:50.123 минут* |", "история": "Автодром Спа-Франкоршам был открыт в 1921 году и является одним из старейших и самых уважаемых треков в мире автоспорта. Трасса проходит через живописные пейзажи Бельгии и известна своей историей и традициями. Спа-Франкоршам принимал множество чемпионатов мира, включая Формулу-1, и является любимой трассой многих гонщиков за её уникальную смесь высоких скоростей и технических сложностей.", "фото_история": "spa_history.jpg", # Добавлено поле с фото истории "опасные_повороты": { "Eau Rouge": { "описание": "Eau Rouge — знаменитый быстрый подъем с крутым поворотом, требующий точности и смелости от пилотов.", "фото": "eau_rouge.jpg" }, "Pouhon": { "описание": "Pouhon — длинный и быстрый поворот, который требует отличной аэродинамики и устойчивости автомобиля.", "фото": "pouhon.jpg" } } } } creators_info = "Этот бот был создан командой:\n\n1 Ратников Иван,\n\n2 Шукрона,\n\n3 Новиков Елисей,\n\n4 Елизарова Ксения,\n\n5 Пискунов Кирилл,\n\n6 Колосов Дмитрий." # Клавиатуры def main_menu_keyboard(): return ReplyKeyboardMarkup( [["Выбрать трассу", "Создатели"]], resize_keyboard=True ) def tracks_keyboard(): track_buttons = [[name] for name in tracks.keys()] track_buttons.append(["Назад"]) return ReplyKeyboardMarkup(track_buttons, resize_keyboard=True) def track_menu_keyboard(): return ReplyKeyboardMarkup( [ ["Лидеры"], ["История о трассе"], ["Опасные повороты"], ["Назад"] ], resize_keyboard=True ) def dangerous_turns_keyboard(track_name): dangerous_turns = tracks[track_name]["опасные_повороты"] buttons = [[turn] for turn in dangerous_turns.keys()] buttons.append(["Назад"]) return ReplyKeyboardMarkup(buttons, resize_keyboard=True) # Функция анти-спама async def anti_spam(update: Update, context: ContextTypes.DEFAULT_TYPE): user_id = update.effective_user.id current_time = time.time() # Проверка на тайм-аута timeout_until = context.user_data.get("spam_timeout_until", 0) if current_time < timeout_until: await update.message.reply_text("Вы слишком часто отправляете сообщения. Пожалуйста, подождите немного.") return False # Инициализация списка временных меток timestamps = context.user_data.get("timestamps", []) # Добавление текущей метки времени timestamps.append(current_time) # Очистка старых меток времени timestamps = [t for t in timestamps if current_time - t <= TIME_WINDOW] context.user_data["timestamps"] = timestamps # Проверка количества сообщений за период if len(timestamps) > MAX_MESSAGES: context.user_data["spam_timeout_until"] = current_time + TIMEOUT_DURATION await update.message.reply_text("Слишком много сообщений! Пожалуйста, подождите 20 секунд.") return False return True # Вспомогательная функция для отправки фотографий с обработкой длинных подписей async def send_photo(context, chat_id, photo_filename, caption=None, reply_markup=None): # Полный путь к файлу file_path = os.path.join(PHOTOS_DIR, photo_filename) # Проверка расширения файла _, ext = os.path.splitext(photo_filename) if ext.lower() not in ALLOWED_EXTENSIONS: logger.warning(f"Недопустимое расширение файла: {photo_filename}") await context.bot.send_message( chat_id=chat_id, text="Невозможно отправить файл с недопустимым расширением.", reply_markup=reply_markup ) return if os.path.isfile(file_path): try: with open(file_path, 'rb') as photo_file: if caption: if len(caption) <= 1024: # Если длина подписи допустимая, отправляем фото с подписью await context.bot.send_photo( chat_id=chat_id, photo=photo_file, caption=caption, parse_mode="Markdown", reply_markup=reply_markup ) else: # Если подпись слишком длинная, отправляем фото без подписи await context.bot.send_photo( chat_id=chat_id, photo=photo_file, reply_markup=reply_markup ) # Разбиваем длинный текст на части по 1024 символа for i in range(0, len(caption), 1024): part = caption[i:i+1024] await context.bot.send_message( chat_id=chat_id, text=part, parse_mode="Markdown", reply_markup=reply_markup ) else: # Если подпись не требуется, просто отправляем фото await context.bot.send_photo( chat_id=chat_id, photo=photo_file, reply_markup=reply_markup ) except Exception as e: logger.error(f"Ошибка при отправке фото: {e}") if caption: await context.bot.send_message( chat_id=chat_id, text=caption, parse_mode="Markdown", reply_markup=reply_markup ) else: logger.warning(f"Файл фото не найден: {file_path}") if caption: await context.bot.send_message( chat_id=chat_id, text=f"{caption}\n\n*Фото не доступно.*", parse_mode="Markdown", reply_markup=reply_markup ) # Обработчики async def 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 else: response = "Неизвестная команда. Попробуйте снова." await update.message.reply_text(response, parse_mode="Markdown", reply_markup=track_menu_keyboard()) return TRACK_MENU async def dangerous_turn_choice(update: Update, context: ContextTypes.DEFAULT_TYPE): # Проверка анти-спама if not await anti_spam(update, context): return CHOOSE_DANGEROUS_TURN text = update.message.text current_track = context.user_data.get("current_track") if text == "Назад": # Возвращение к меню трассы await update.message.reply_text("Выберите опцию:", reply_markup=track_menu_keyboard()) return TRACK_MENU dangerous_turns = tracks[current_track]["опасные_повороты"] if text in dangerous_turns: turn_info = dangerous_turns[text] description = turn_info["описание"] photo_filename = turn_info.get("фото") response_text = f"**Опасный поворот {text} на трассе {current_track}:**\n{description}" markup = ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True ) if photo_filename: await send_photo( context, # Передача context chat_id=update.effective_chat.id, photo_filename=photo_filename, caption=response_text, reply_markup=markup ) else: await update.message.reply_text( response_text, parse_mode="Markdown", reply_markup=markup ) else: response = "Неизвестный поворот. Пожалуйста, выберите доступный поворот или нажмите Назад." markup = ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True ) await update.message.reply_text(response, reply_markup=markup) return CHOOSE_DANGEROUS_TURN async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text("Используйте /start для начала.") async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text("Действие отменено.", reply_markup=ReplyKeyboardRemove()) return ConversationHandler.END def main(): TOKEN = "8166041667:AAHsjC8QSxSsqIt3zxGUm6YeGyEcp9QRr08" # Замените на ваш действительный токен # Убедитесь, что папка с фотографиями существует if not os.path.isdir(PHOTOS_DIR): os.makedirs(PHOTOS_DIR) logger.info(f"Создана папка для фотографий: {PHOTOS_DIR}") application = ApplicationBuilder().token(TOKEN).build() conv_handler = ConversationHandler( entry_points=[CommandHandler("start", start)], states={ CHOOSE_MAIN: [MessageHandler(filters.TEXT & ~filters.COMMAND, main_menu_choice)], CHOOSE_TRACK: [MessageHandler(filters.TEXT & ~filters.COMMAND, track_choice)], TRACK_MENU: [MessageHandler(filters.TEXT & ~filters.COMMAND, track_menu_choice)], CHOOSE_DANGEROUS_TURN: [MessageHandler(filters.TEXT & ~filters.COMMAND, dangerous_turn_choice)] }, fallbacks=[CommandHandler("cancel", cancel), CommandHandler("help", help_command)] ) application.add_handler(conv_handler) application.run_polling() if __name__ == '__main__': main()

Пояснения к изменениям:

  1. Функция send_photo:

    • Проверка длины подписи: Добавлена проверка длины caption. Если она превышает 1024 символа, фотография отправляется без подписи.
    • Разбиение длинного текста: Если подпись слишком длинная, она разбивается на части по 1024 символа и отправляется отдельными сообщениями.
    • Использование parse_mode="Markdown": Это позволяет сохранять форматирование текста при отправке.
  2. Обработчик track_menu_choice:

    • При выборе опции "История о трассе" вызывается обновленная функция send_photo, которая теперь обрабатывает длинные подписи корректно.
  3. Обработчик dangerous_turn_choice:

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

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

Рекомендации:

  • Безопасность токена: Никогда не публикуйте свой токен бота в открытых источниках. Убедитесь, что токен в коде действителен и не скомпрометирован. Если вы случайно опубликовали его, рекомендуется немедленно сгенерировать новый токен через BotFather.

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

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

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

Заключение

С внесенными изменениями ваш бот теперь сможет корректно обрабатывать длинные подписи при отправке фотографий, избегая ошибки Message caption is too long. Это улучшит пользовательский опыт и обеспечит стабильную работу бота даже при отправке больших объемов текста.

이 Q&A 공유하기