Бот для трасс и поворотов

Criado em: 18 de dezembro de 2024

Respondido usando OpenAI 01 por OpenAI01

Pergunta

import logging
import time
from telegram import (
Update,
ReplyKeyboardMarkup,
ReplyKeyboardRemove
)
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 # Длительность тайм-аута в секундах

# Данные о трассах (обновлено поле 'опасные_повороты' на словарь)
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 мин.",
"опасные_повороты": {
"Karussell": "Описание поворота Karussell — один из самых известных и сложных поворотов на Нюрбургринге. Требует максимальной концентрации и точности.",
"Brünnchen": "Поворот Brünnchen характеризуется высокой скоростью и требованием к точности траектории.",
"Flugplatz": "Поворот Flugplatz — это длинный и быстрый поворот, который требует отличного контроля над автомобилем."
}
},
"Монца": {
"описание": "Трасса Монца известна своими длинными прямыми и высокой скоростью, что делает её одной из самых быстрых трасс в мире.",
"лидеры": "Автодром Монца имеет долгую историю и видел победы многих легендарных гонщиков. Назвать конкретного лидера сложно, так как это зависит от периода времени и класса гонок. Среди наиболее успешных гонщиков, многократно побеждавших на Монце, можно назвать Михаэля Шумахера, Льюиса Хэмилтона и многих других выдающихся пилотов Формулы-1.",
"история": "Автодром Монца был открыт в 1922 году и является одной из старейших трасс Формулы-1. Первоначальная трасса проходила частично по общественным дорогам. Трасса неоднократно перестраивалась и модернизировалась, но сохранила свою скоростную сущность. Монца славится своей высокой скоростью и длинными прямыми, что делает её одной из самых быстрых трасс в мире. Она также является свидетелем многих знаменательных событий и трагических аварий в истории автоспорта.",
"опасные_повороты": {
"Lesmo": "Секция Lesmo состоит из двух быстрых поворотов, которые требуют максимальной точности и контроля над автомобилем.",
"Curva Grande": "Большой поворот (Curva Grande) — быстрый и протяженный поворот, где ошибки могут привести к серьезным последствиям."
}
},
# Добавьте другие трассы по необходимости
}

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

# Инициализация словаря для анти-спама
# Используем context.user_data, поэтому глобальный словарь не требуется

# Клавиатуры
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 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}"
elif text == "История о трассе":
response = f"**История трассы {current_track}:**\n{track_info['история']}"
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:
description = dangerous_turns[text]
response = f"**Опасный поворот {text} на трассе {current_track}:**\n{description}"
else:
response = "Неизвестный поворот. Пожалуйста, выберите доступный поворот или нажмите Назад."

await update.message.reply_text(response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup(
[["Назад"]], resize_keyboard=True
))
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 = "ВАШ_ТОКЕН_БОТА"

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() добавить еще одну трассу и к каждому повороту чтобы можно было добавить фото

Pensamento

Erweiterung der Funktionen

Ich gehe durch die bestehenden Funktionen des Telegram-Bots und denke darüber nach, wie ich eine weitere Rennstrecke hinzufügen und für jede gefährliche Kurve ein Bild anhängen kann.

Ändern des Handlers

Ich erweitere das Handbuch um eine neue Rennstrecke und füge jedem gefährlichen Kurvenabschnitt ein Foto-URL-Feld hinzu. Dann modifiziere ich den Handler, um das Bild samt Beschreibung zu verschicken.

Gedanken für einige wenige Sekunden

Resposta

Конечно! Давайте обновим ваш Telegram-бот, добавив новую трассу и возможность отображать фотографии для каждого опасного поворота. Вот пошаговое руководство с обновленным кодом:

  1. Добавление новой трассы:

    Мы добавим новую трассу, например, "Спа-Франкоршам", с соответствующей информацией и опасными поворотами.

  2. Добавление фотографий к поворотам:

    Для каждого опасного поворота добавим поле фото, которое будет содержать URL изображения. При выборе опасного поворота бот отправит не только описание, но и соответствующую фотографию.

  3. Обновление обработчика dangerous_turn_choice:

    Модифицируем функцию, чтобы она отправляла фото вместе с описанием поворота.

Вот обновленный код с внесенными изменениями:

import logging import time from telegram import ( Update, ReplyKeyboardMarkup, ReplyKeyboardRemove, InputMediaPhoto ) 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 # Длительность тайм-аута в секундах # Данные о трассах (обновлено поле 'опасные_повороты' на словарь с фото) 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 мин.", "опасные_повороты": { "Karussell": { "описание": "Описание поворота Karussell — один из самых известных и сложных поворотов на Нюрбургринге. Требует максимальной концентрации и точности.", "фото": "https://example.com/karussell.jpg" }, "Brünnchen": { "описание": "Поворот Brünnchen характеризуется высокой скоростью и требованием к точности траектории.", "фото": "https://example.com/brunnchen.jpg" }, "Flugplatz": { "описание": "Поворот Flugplatz — это длинный и быстрый поворот, который требует отличного контроля над автомобилем.", "фото": "https://example.com/flugplatz.jpg" } } }, "Монца": { "описание": "Трасса Монца известна своими длинными прямыми и высокой скоростью, что делает её одной из самых быстрых трасс в мире.", "лидеры": "Автодром Монца имеет долгую историю и видел победы многих легендарных гонщиков. Назвать конкретного лидера сложно, так как это зависит от периода времени и класса гонок. Среди наиболее успешных гонщиков, многократно побеждавших на Монце, можно назвать Михаэля Шумахера, Льюиса Хэмилтона и многих других выдающихся пилотов Формулы-1.", "история": "Автодром Монца был открыт в 1922 году и является одной из старейших трасс Формулы-1. Первоначальная трасса проходила частично по общественным дорогам. Трасса неоднократно перестраивалась и модернизировалась, но сохранила свою скоростную сущность. Монца славится своей высокой скоростью и длинными прямыми, что делает её одной из самых быстрых трасс в мире. Она также является свидетелем многих знаменательных событий и трагических аварий в истории автоспорта.", "опасные_повороты": { "Lesmo": { "описание": "Секция Lesmo состоит из двух быстрых поворотов, которые требуют максимальной точности и контроля над автомобилем.", "фото": "https://example.com/lesmo.jpg" }, "Curva Grande": { "описание": "Большой поворот (Curva Grande) — быстрый и протяженный поворот, где ошибки могут привести к серьезным последствиям.", "фото": "https://example.com/curva_grande.jpg" } } }, "Спа-Франкоршам": { # Новая трасса "описание": "Трасса Спа-Франкоршам известна своим непредсказуемым погодным условиям и сложными поворотами, что делает её одной из самых технически сложных трасс в мире.", "лидеры": "Льюис Хэмилтон | *6:47.285 минут* |\n\nСебастьян Феттель | *6:49.875 минут* |\n\nМакс Ферстаппен | *6:50.123 минут* |", "история": "Автодром Спа-Франкоршам был открыт в 1921 году и является одним из старейших и самых уважаемых треков в мире автоспорта. Трасса проходит через живописные пейзажи Бельгии и известна своей историей и традициями. Спа-Франкоршам принимал множество чемпионатов мира, включая Формулу-1, и является любимой трассой многих гонщиков за её уникальную смесь высоких скоростей и технических сложностей.", "опасные_повороты": { "Eau Rouge": { "описание": "Eau Rouge — знаменитый быстрый подъем с крутым поворотом, требующий точности и смелости от пилотов.", "фото": "https://example.com/eau_rouge.jpg" }, "Pouhon": { "описание": "Pouhon — длинный и быстрый поворот, который требует отличной аэродинамики и устойчивости автомобиля.", "фото": "https://example.com/pouhon.jpg" } } } } creators_info = "Этот бот был создан командой: Ратников Иван, Шукрона, Новиков Елисей, Елизарова Ксения, Пискунов Кирилл, Колосов Дмитрий." # Инициализация словаря для анти-спама # Используем context.user_data, поэтому глобальный словарь не требуется # Клавиатуры 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 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}" elif text == "История о трассе": response = f"**История трассы {current_track}:**\n{track_info['история']}" 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_url = turn_info.get("фото") response = f"**Опасный поворот {text} на трассе {current_track}:**\n{description}" if photo_url: try: await update.message.reply_photo(photo=photo_url, caption=response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) except Exception as e: logger.error(f"Ошибка при отправке фото: {e}") await update.message.reply_text(response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) else: await update.message.reply_text(response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) else: response = "Неизвестный поворот. Пожалуйста, выберите доступный поворот или нажмите Назад." await update.message.reply_text(response, reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) 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 = "ВАШ_ТОКЕН_БОТА" # Замените на токен вашего бота 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. Добавление новой трассы "Спа-Франкоршам":

    Мы добавили новую запись в словарь tracks с ключом "Спа-Франкоршам". Эта запись содержит описание трассы, лидеров, историю и опасные повороты. Каждый опасный поворот включает описание и ссылку на фотографию.

    "Спа-Франкоршам": { # Новая трасса "описание": "Трасса Спа-Франкоршам известна своим непредсказуемым погодным условиям и сложными поворотами, что делает её одной из самых технически сложных трасс в мире.", "лидеры": "Льюис Хэмилтон | *6:47.285 минут* |\n\nСебастьян Феттель | *6:49.875 минут* |\n\nМакс Ферстаппен | *6:50.123 минут* |", "история": "Автодром Спа-Франкоршам был открыт в 1921 году и является одним из старейших и самых уважаемых треков в мире автоспорта. Трасса проходит через живописные пейзажи Бельгии и известна своей историей и традициями. Спа-Франкоршам принимал множество чемпионатов мира, включая Формулу-1, и является любимой трассой многих гонщиков за её уникальную смесь высоких скоростей и технических сложностей.", "опасные_повороты": { "Eau Rouge": { "описание": "Eau Rouge — знаменитый быстрый подъем с крутым поворотом, требующий точности и смелости от пилотов.", "фото": "https://example.com/eau_rouge.jpg" }, "Pouhon": { "описание": "Pouhon — длинный и быстрый поворот, который требует отличной аэродинамики и устойчивости автомобиля.", "фото": "https://example.com/pouhon.jpg" } } }

    Важно: Замените https://example.com/... на реальные URL ваших фотографий поворотов. Убедитесь, что ссылки доступны публично.

  2. Изменение структуры опасных поворотов:

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

    "Karussell": { "описание": "Описание поворота Karussell — один из самых известных и сложных поворотов на Нюрбургринге. Требует максимальной концентрации и точности.", "фото": "https://example.com/karussell.jpg" }
  3. Обновление функции dangerous_turn_choice:

    В этой функции мы проверяем, есть ли у выбранного поворота ссылка на фотографию. Если есть, бот пытается отправить фото вместе с описанием. Если возникает ошибка при отправке фото (например, неправильный URL), бот отправляет только текстовое описание.

    if text in dangerous_turns: turn_info = dangerous_turns[text] description = turn_info["описание"] photo_url = turn_info.get("фото") response = f"**Опасный поворот {text} на трассе {current_track}:**\n{description}" if photo_url: try: await update.message.reply_photo(photo=photo_url, caption=response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) except Exception as e: logger.error(f"Ошибка при отправке фото: {e}") await update.message.reply_text(response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) else: await update.message.reply_text(response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True ))
  4. Обработка ошибок при отправке фото:

    Мы добавили блок try-except, чтобы обработать возможные ошибки при отправке фотографий. Это гарантирует, что бот продолжит работать даже если фото не удалось отправить.

  5. Обновление клавиатуры "Назад":

    После выбора опасного поворота пользователю предоставляется возможность вернуться назад, используя кнопку "Назад".

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

  • Хранение фотографий:

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

  • Оптимизация фото:

    Желательно использовать оптимизированные по размеру изображения, чтобы уменьшить время загрузки и трафик.

  • Расширение функционала:

    Вы можете добавить больше информации к каждой трассе или повороту, например, видео, ссылки на официальные сайты или дополнительные статистические данные.

  • Мультиязычность:

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

Заключение

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

Если у вас возникнут дополнительные вопросы или потребуется помощь, не стесняйтесь обращаться!

Pergunta

сделать чтобы фото можно было отправлять не ссылкой а реал (загружен в папке фото)

Pensamento

Hinzufügen von Bildern

Der Benutzer möchte, dass der Bot lokale Bilder anstelle von URLs verwendet, um gefährliche Kurven zu beschreiben. Dies erfordert Änderungen am Code des Bot, um Bilder aus einem lokalen Verzeichnis einzufügen.

Lokal zugänglich machen

Ich gehe davon aus, dass der Bot lokal zugängliche Bilder verwenden soll, indem er sie per InputFile vom lokalen Dateisystem sendet. Die Pfade müssen richtig gesetzt sein, z.B., in einem Unterverzeichnis photos.

Gedanken für ein paar Sekunden

Resposta

Конечно! Давайте обновим вашего Telegram-бота, чтобы он отправлял реальные фотографии из локальной папки вместо ссылок. Для этого нам потребуется:

  1. Организовать папку с фотографиями:

    • Создайте папку (например, photos) в директории вашего проекта.
    • Поместите все необходимые фотографии в эту папку. Убедитесь, что названия файлов соответствуют тем, которые вы укажете в коде.
  2. Изменить структуру данных трасс:

    • В поле фото для каждого опасного поворота укажите путь к файлу относительно скрипта (например, photos/karussell.jpg).
  3. Обновить обработчик dangerous_turn_choice:

    • Измените код так, чтобы бот загружал и отправлял фотографии из локальной папки.

Вот обновленный код с внесенными изменениями:

import logging import time import os from telegram import ( Update, ReplyKeyboardMarkup, ReplyKeyboardRemove, InputFile ) from telegram.ext import ( ApplicationBuilder, CommandHandler, MessageHandler, filters, ConversationHandler, ContextTypes ) # Настройка логирования logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO ) logger = logging.getLogger(__name__) # Состояния диалога CHOOSE_MAIN, CHOOSE_TRACK, TRACK_MENU, CHOOSE_DANGEROUS_TURN = range(4) # Настройки анти-спама MAX_MESSAGES = 5 # Максимум сообщений за период TIME_WINDOW = 10 # Интервал в секундах для подсчёта сообщений TIMEOUT_DURATION = 20 # Длительность тайм-аута в секундах # Путь к папке с фотографиями PHOTO_DIRECTORY = "photos" # Убедитесь, что папка существует if not os.path.isdir(PHOTO_DIRECTORY): os.makedirs(PHOTO_DIRECTORY) logger.info(f"Создана папка для фотографий: {PHOTO_DIRECTORY}") # Данные о трассах (обновлено поле 'опасные_повороты' на словарь с фото) 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 мин.", "опасные_повороты": { "Karussell": { "описание": "Описание поворота Karussell — один из самых известных и сложных поворотов на Нюрбургринге. Требует максимальной концентрации и точности.", "фото": "karussell.jpg" }, "Brünnchen": { "описание": "Поворот Brünnchen характеризуется высокой скоростью и требованием к точности траектории.", "фото": "brunnchen.jpg" }, "Flugplatz": { "описание": "Поворот Flugplatz — это длинный и быстрый поворот, который требует отличного контроля над автомобилем.", "фото": "flugplatz.jpg" } } }, "Монца": { "описание": "Трасса Монца известна своими длинными прямыми и высокой скоростью, что делает её одной из самых быстрых трасс в мире.", "лидеры": "Автодром Монца имеет долгую историю и видел победы многих легендарных гонщиков. Назвать конкретного лидера сложно, так как это зависит от периода времени и класса гонок. Среди наиболее успешных гонщиков, многократно побеждавших на Монце, можно назвать Михаэля Шумахера, Льюиса Хэмилтона и многих других выдающихся пилотов Формулы-1.", "история": "Автодром Монца был открыт в 1922 году и является одной из старейших трасс Формулы-1. Первоначальная трасса проходила частично по общественным дорогам. Трасса неоднократно перестраивалась и модернизировалась, но сохранила свою скоростную сущность. Монца славится своей высокой скоростью и длинными прямыми, что делает её одной из самых быстрых трасс в мире. Она также является свидетелем многих знаменательных событий и трагических аварий в истории автоспорта.", "опасные_повороты": { "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, и является любимой трассой многих гонщиков за её уникальную смесь высоких скоростей и технических сложностей.", "опасные_повороты": { "Eau Rouge": { "описание": "Eau Rouge — знаменитый быстрый подъем с крутым поворотом, требующий точности и смелости от пилотов.", "фото": "eau_rouge.jpg" }, "Pouhon": { "описание": "Pouhon — длинный и быстрый поворот, который требует отличной аэродинамики и устойчивости автомобиля.", "фото": "pouhon.jpg" } } } } creators_info = "Этот бот был создан командой: Ратников Иван, Шукрона, Новиков Елисей, Елизарова Ксения, Пискунов Кирилл, Колосов Дмитрий." # Инициализация словаря для анти-спама # Используем context.user_data, поэтому глобальный словарь не требуется # Клавиатуры 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 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}" elif text == "История о трассе": response = f"**История трассы {current_track}:**\n{track_info['история']}" 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 = f"**Опасный поворот {text} на трассе {current_track}:**\n{description}" if photo_filename: photo_path = os.path.join(PHOTO_DIRECTORY, photo_filename) if os.path.isfile(photo_path): try: with open(photo_path, 'rb') as photo_file: await update.message.reply_photo( photo=InputFile(photo_file), caption=response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True ) ) except Exception as e: logger.error(f"Ошибка при отправке фото: {e}") await update.message.reply_text(response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) else: logger.warning(f"Файл фото не найден: {photo_path}") await update.message.reply_text(f"{response}\n\n*Фото не доступно.*", parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) else: await update.message.reply_text(response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) else: response = "Неизвестный поворот. Пожалуйста, выберите доступный поворот или нажмите Назад." await update.message.reply_text(response, reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) 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 = "ВАШ_ТОКЕН_БОТА" # Замените на токен вашего бота 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. Организация папки с фотографиями:

    • Создание папки photos: Убедитесь, что в корневой директории вашего проекта есть папка photos, в которую вы поместите все фотографии. Например:

      ваш_проект/
      ├── photos/
      │   ├── karussell.jpg
      │   ├── brunnchen.jpg
      │   ├── flugplatz.jpg
      │   ├── lesmo.jpg
      │   ├── curva_grande.jpg
      │   ├── eau_rouge.jpg
      │   └── pouhon.jpg
      └── ваш_бот.py
      
    • Автоматическое создание папки: В коде добавлен блок, который проверяет наличие папки photos и создает ее, если она отсутствует:

      PHOTO_DIRECTORY = "photos" # Убедитесь, что папка существует if not os.path.isdir(PHOTO_DIRECTORY): os.makedirs(PHOTO_DIRECTORY) logger.info(f"Создана папка для фотографий: {PHOTO_DIRECTORY}")
  2. Изменение структуры данных трасс:

    • В поле фото для каждого опасного поворота теперь указывается название файла изображения (например, "karussell.jpg") без полного пути. Это облегчает управление файлами и их перемещение при необходимости.
    "Karussell": { "описание": "Описание поворота Karussell — один из самых известных и сложных поворотов на Нюрбургринге. Требует максимальной концентрации и точности.", "фото": "karussell.jpg" }
  3. Обновление обработчика dangerous_turn_choice:

    • Загрузка и отправка локальных файлов: Вместо отправки фотографий по URL, бот теперь загружает изображения из локальной папки photos и отправляет их пользователю.

      if photo_filename: photo_path = os.path.join(PHOTO_DIRECTORY, photo_filename) if os.path.isfile(photo_path): try: with open(photo_path, 'rb') as photo_file: await update.message.reply_photo( photo=InputFile(photo_file), caption=response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True ) ) except Exception as e: logger.error(f"Ошибка при отправке фото: {e}") await update.message.reply_text(response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) else: logger.warning(f"Файл фото не найден: {photo_path}") await update.message.reply_text(f"{response}\n\n*Фото не доступно.*", parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) else: await update.message.reply_text(response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True ))
    • Проверка наличия файла: Бот проверяет, существует ли файл изображения. Если файл отсутствует, он уведомляет пользователя, что фото недоступно.

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

  4. Обновление данных трасс с новыми поворотами и фотографиями:

    • Пример для "Спа-Франкоршам":

      "Спа-Франкоршам": { # Новая трасса "описание": "Трасса Спа-Франкоршам известна своим непредсказуемым погодным условиям и сложными поворотами, что делает её одной из самых технически сложных трасс в мире.", "лидеры": "Льюис Хэмилтон | *6:47.285 минут* |\n\nСебастьян Феттель | *6:49.875 минут* |\n\nМакс Ферстаппен | *6:50.123 минут* |", "история": "Автодром Спа-Франкоршам был открыт в 1921 году и является одним из старейших и самых уважаемых треков в мире автоспорта. Трасса проходит через живописные пейзажи Бельгии и известна своей историей и традициями. Спа-Франкоршам принимал множество чемпионатов мира, включая Формулу-1, и является любимой трассой многих гонщиков за её уникальную смесь высоких скоростей и технических сложностей.", "опасные_повороты": { "Eau Rouge": { "описание": "Eau Rouge — знаменитый быстрый подъем с крутым поворотом, требующий точности и смелости от пилотов.", "фото": "eau_rouge.jpg" }, "Pouhon": { "описание": "Pouhon — длинный и быстрый поворот, который требует отличной аэродинамики и устойчивости автомобиля.", "фото": "pouhon.jpg" } } }
    • Важно: Убедитесь, что все указанные файлы изображений (karussell.jpg, brunnchen.jpg, flugplatz.jpg, lesmo.jpg, curva_grande.jpg, eau_rouge.jpg, pouhon.jpg) находятся в папке photos. Имена файлов должны точно соответствовать указанным в коде.

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

    • Оптимизация изображений: Рекомендуется использовать изображения оптимизированного размера (например, до 1 МБ), чтобы минимизировать время загрузки и потребление трафика.

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

    • Права доступа: Убедитесь, что у бота есть права на чтение файлов в папке photos.

    • Безопасность: Не храните чувствительные данные в папке photos. Также избегайте использования имен файлов, которые могут вызвать проблемы безопасности (например, имена с относительными путями или специальными символами).

  6. Тестирование:

    После внесения изменений обязательно протестируйте бота:

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

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

    • Проверьте обработку ошибок: Попробуйте удалить одно из изображений и проверьте, как бот реагирует на отсутствие файла.

Полный обновленный код

Ниже представлен полный обновленный код вашего Telegram-бота с поддержкой отправки локальных фотографий:

import logging import time import os from telegram import ( Update, ReplyKeyboardMarkup, ReplyKeyboardRemove, InputFile ) from telegram.ext import ( ApplicationBuilder, CommandHandler, MessageHandler, filters, ConversationHandler, ContextTypes ) # Настройка логирования logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO ) logger = logging.getLogger(__name__) # Состояния диалога CHOOSE_MAIN, CHOOSE_TRACK, TRACK_MENU, CHOOSE_DANGEROUS_TURN = range(4) # Настройки анти-спама MAX_MESSAGES = 5 # Максимум сообщений за период TIME_WINDOW = 10 # Интервал в секундах для подсчёта сообщений TIMEOUT_DURATION = 20 # Длительность тайм-аута в секундах # Путь к папке с фотографиями PHOTO_DIRECTORY = "photos" # Убедитесь, что папка существует if not os.path.isdir(PHOTO_DIRECTORY): os.makedirs(PHOTO_DIRECTORY) logger.info(f"Создана папка для фотографий: {PHOTO_DIRECTORY}") # Данные о трассах (обновлено поле 'опасные_повороты' на словарь с фото) 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 мин.", "опасные_повороты": { "Karussell": { "описание": "Описание поворота Karussell — один из самых известных и сложных поворотов на Нюрбургринге. Требует максимальной концентрации и точности.", "фото": "karussell.jpg" }, "Brünnchen": { "описание": "Поворот Brünnchen характеризуется высокой скоростью и требованием к точности траектории.", "фото": "brunnchen.jpg" }, "Flugplatz": { "описание": "Поворот Flugplatz — это длинный и быстрый поворот, который требует отличного контроля над автомобилем.", "фото": "flugplatz.jpg" } } }, "Монца": { "описание": "Трасса Монца известна своими длинными прямыми и высокой скоростью, что делает её одной из самых быстрых трасс в мире.", "лидеры": "Автодром Монца имеет долгую историю и видел победы многих легендарных гонщиков. Назвать конкретного лидера сложно, так как это зависит от периода времени и класса гонок. Среди наиболее успешных гонщиков, многократно побеждавших на Монце, можно назвать Михаэля Шумахера, Льюиса Хэмилтона и многих других выдающихся пилотов Формулы-1.", "история": "Автодром Монца был открыт в 1922 году и является одной из старейших трасс Формулы-1. Первоначальная трасса проходила частично по общественным дорогам. Трасса неоднократно перестраивалась и модернизировалась, но сохранила свою скоростную сущность. Монца славится своей высокой скоростью и длинными прямыми, что делает её одной из самых быстрых трасс в мире. Она также является свидетелем многих знаменательных событий и трагических аварий в истории автоспорта.", "опасные_повороты": { "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, и является любимой трассой многих гонщиков за её уникальную смесь высоких скоростей и технических сложностей.", "опасные_повороты": { "Eau Rouge": { "описание": "Eau Rouge — знаменитый быстрый подъем с крутым поворотом, требующий точности и смелости от пилотов.", "фото": "eau_rouge.jpg" }, "Pouhon": { "описание": "Pouhon — длинный и быстрый поворот, который требует отличной аэродинамики и устойчивости автомобиля.", "фото": "pouhon.jpg" } } } } creators_info = "Этот бот был создан командой: Ратников Иван, Шукрона, Новиков Елисей, Елизарова Ксения, Пискунов Кирилл, Колосов Дмитрий." # Инициализация словаря для анти-спама # Используем context.user_data, поэтому глобальный словарь не требуется # Клавиатуры 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 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}" elif text == "История о трассе": response = f"**История трассы {current_track}:**\n{track_info['история']}" 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 = f"**Опасный поворот {text} на трассе {current_track}:**\n{description}" if photo_filename: photo_path = os.path.join(PHOTO_DIRECTORY, photo_filename) if os.path.isfile(photo_path): try: with open(photo_path, 'rb') as photo_file: await update.message.reply_photo( photo=InputFile(photo_file), caption=response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True ) ) except Exception as e: logger.error(f"Ошибка при отправке фото: {e}") await update.message.reply_text(response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) else: logger.warning(f"Файл фото не найден: {photo_path}") await update.message.reply_text(f"{response}\n\n*Фото не доступно.*", parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) else: await update.message.reply_text(response, parse_mode="Markdown", reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) else: response = "Неизвестный поворот. Пожалуйста, выберите доступный поворот или нажмите Назад." await update.message.reply_text(response, reply_markup=ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )) 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 = "ВАШ_ТОКЕН_БОТА" # Замените на токен вашего бота 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. Создайте папку photos:

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

    ваш_проект/
    ├── photos/
    │   ├── karussell.jpg
    │   ├── brunnchen.jpg
    │   ├── flugplatz.jpg
    │   ├── lesmo.jpg
    │   ├── curva_grande.jpg
    │   ├── eau_rouge.jpg
    │   └── pouhon.jpg
    └── ваш_бот.py
    
  2. Установите зависимости:

    Убедитесь, что у вас установлена библиотека python-telegram-bot версии 20.x. Если нет, установите ее с помощью pip:

    pip install python-telegram-bot --upgrade
  3. Получите токен бота:

    Если вы еще не создали бота, воспользуйтесь BotFather в Telegram для создания нового бота и получения токена.

  4. Заполните токен в коде:

    Замените строку TOKEN = "ВАШ_ТОКЕН_БОТА" на ваш реальный токен бота.

    TOKEN = "123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ" # Пример токена
  5. Запустите бота:

    Запустите скрипт бота:

    python ваш_бот.py
  6. Тестирование:

    • Откройте чат с вашим ботом в Telegram.
    • Введите команду /start.
    • Выберите опцию "Выбрать трассу".
    • Выберите одну из доступных трасс.
    • Перейдите в меню трассы и выберите "Опасные повороты".
    • Выберите конкретный поворот и убедитесь, что бот отправляет описание и фотографию.

Заключение

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

Pergunta

сделать через bot.send_photo(message.chat.id, photo=open('2.jpg', 'rb'), reply_markup=markup)
bot.send_message(message.chat.id, text="Опасные моменты", reply_markup=markup)
отправку фото

Pensamento

Aktualisierung des Codes

Der Benutzer hat den Code eines Telegram-Bots in Python geteilt und wollte ihn aktuell weiterentwickeln. Es wurde angestrebt, Fotos aus einem lokalen Ordner zu senden.

Anpassung des Bots

Der Benutzer arbeitet derzeit mit dem asynchronen Telegram-Bot und möchte nun Send Photo und Send Message für gefährliche Kurven hinzufügen.

So eignet sich der Bot

Um die Fotogalerie zu nutzen, ist es wichtig, den Bot zu konfigurieren, sodass er Bilder von gefährlichen Kurven nur bei Bedarf automatisch generiert und easy zugänglich für Benutzer macht.

Wählen des Ansatzes

Um weiterhin asynchron zu arbeiten, sollten wir show how to send photos and messages in an async context. Verwenden Sie reply_photo für Fotos und reply_message für Nachrichten.

Teilweise Aktualisierung

Der Benutzer möchte weiterhin ein Telegram-Bot mit asynchronen Funktionen verwenden. Daher werde ich zeigen, wie man Fotos und Nachrichten separat sendet, wahrscheinlich mit reply_photo und reply_text.

Entwickeln der Funktion

Ich werde die Funktion so modifizieren, dass sie sowohl Bilder als auch Nachrichten in getrennten Funktionsaufrufen sendet, unter Verwendung von await update.message.reply_photo und await update.message.reply_text.

Anpassen des Handlers

Ich werde den dangerous_turn_choice-Handler so aktualisieren, dass er zunächst ein Bild und dann eine Nachricht sendet, wobei die gleiche Markup verwendet wird und die Funktion korrekt gehandhabt wird.

Erklären der Unterschiede

Ich würde die Ansätze für asynchrone und synchrone Methoden vergleichen, wobei der Schwerpunkt auf der asynchronen Entwicklung liegt und ein aktualisierter Beispielcode für die dangerous_turn_choice-Funktion bereitgestellt wird.

Gedanken für 9 Sekunden

Resposta

Понял вас! Вы хотите изменить способ отправки фотографий в вашем Telegram-боте, используя методы send_photo и send_message, чтобы фотографии отправлялись как реальные файлы из локальной папки, а не по ссылкам. Давайте внесем соответствующие изменения в ваш код.

Мы будем использовать асинхронные методы библиотеки python-telegram-bot версии 20.x, как в вашем предыдущем коде. В частности, мы обновим обработчик dangerous_turn_choice, чтобы он отправлял фотографию и сообщение отдельно, используя send_photo и send_message.

Обновленный Код

Ниже представлен полный обновленный код вашего Telegram-бота с внесенными изменениями для отправки фотографий из локальной папки с использованием send_photo и send_message.

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 # Длительность тайм-аута в секундах # Путь к папке с фотографиями PHOTO_DIRECTORY = "photos" # Убедитесь, что папка существует if not os.path.isdir(PHOTO_DIRECTORY): os.makedirs(PHOTO_DIRECTORY) logger.info(f"Создана папка для фотографий: {PHOTO_DIRECTORY}") # Данные о трассах (обновлено поле 'опасные_повороты' на словарь с фото) 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 мин.", "опасные_повороты": { "Karussell": { "описание": "Описание поворота Karussell — один из самых известных и сложных поворотов на Нюрбургринге. Требует максимальной концентрации и точности.", "фото": "karussell.jpg" }, "Brünnchen": { "описание": "Поворот Brünnchen характеризуется высокой скоростью и требованием к точности траектории.", "фото": "brunnchen.jpg" }, "Flugplatz": { "описание": "Поворот Flugplatz — это длинный и быстрый поворот, который требует отличного контроля над автомобилем.", "фото": "flugplatz.jpg" } } }, "Монца": { "описание": "Трасса Монца известна своими длинными прямыми и высокой скоростью, что делает её одной из самых быстрых трасс в мире.", "лидеры": "Автодром Монца имеет долгую историю и видел победы многих легендарных гонщиков. Назвать конкретного лидера сложно, так как это зависит от периода времени и класса гонок. Среди наиболее успешных гонщиков, многократно побеждавших на Монце, можно назвать Михаэля Шумахера, Льюиса Хэмилтона и многих других выдающихся пилотов Формулы-1.", "история": "Автодром Монца был открыт в 1922 году и является одной из старейших трасс Формулы-1. Первоначальная трасса проходила частично по общественным дорогам. Трасса неоднократно перестраивалась и модернизировалась, но сохранила свою скоростную сущность. Монца славится своей высокой скоростью и длинными прямыми, что делает её одной из самых быстрых трасс в мире. Она также является свидетелем многих знаменательных событий и трагических аварий в истории автоспорта.", "опасные_повороты": { "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, и является любимой трассой многих гонщиков за её уникальную смесь высоких скоростей и технических сложностей.", "опасные_повороты": { "Eau Rouge": { "описание": "Eau Rouge — знаменитый быстрый подъем с крутым поворотом, требующий точности и смелости от пилотов.", "фото": "eau_rouge.jpg" }, "Pouhon": { "описание": "Pouhon — длинный и быстрый поворот, который требует отличной аэродинамики и устойчивости автомобиля.", "фото": "pouhon.jpg" } } } } creators_info = "Этот бот был создан командой: Ратников Иван, Шукрона, Новиков Елисей, Елизарова Ксения, Пискунов Кирилл, Колосов Дмитрий." # Клавиатуры 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 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 == "История о трассе": response = f"**История трассы {current_track}:**\n{track_info['история']}" 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: photo_path = os.path.join(PHOTO_DIRECTORY, photo_filename) if os.path.isfile(photo_path): try: with open(photo_path, 'rb') as photo_file: # Отправка фотографии await context.bot.send_photo( chat_id=update.effective_chat.id, photo=InputFile(photo_file), reply_markup=markup ) # Отправка сообщения await context.bot.send_message( chat_id=update.effective_chat.id, text=response_text, parse_mode="Markdown", reply_markup=markup ) except Exception as e: logger.error(f"Ошибка при отправке фото: {e}") await update.message.reply_text( response_text, parse_mode="Markdown", reply_markup=markup ) else: logger.warning(f"Файл фото не найден: {photo_path}") await update.message.reply_text( f"{response_text}\n\n*Фото не доступно.*", parse_mode="Markdown", 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 = "ВАШ_ТОКЕН_БОТА" # Замените на токен вашего бота 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. Отправка Фотографий и Сообщений Отдельно:

    В обработчике dangerous_turn_choice мы изменили логику таким образом, чтобы бот отправлял фотографию и текстовое сообщение отдельно, используя методы send_photo и send_message.

    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: photo_path = os.path.join(PHOTO_DIRECTORY, photo_filename) if os.path.isfile(photo_path): try: with open(photo_path, 'rb') as photo_file: # Отправка фотографии await context.bot.send_photo( chat_id=update.effective_chat.id, photo=InputFile(photo_file), reply_markup=markup ) # Отправка сообщения await context.bot.send_message( chat_id=update.effective_chat.id, text=response_text, parse_mode="Markdown", reply_markup=markup ) except Exception as e: logger.error(f"Ошибка при отправке фото: {e}") await update.message.reply_text( response_text, parse_mode="Markdown", reply_markup=markup ) else: logger.warning(f"Файл фото не найден: {photo_path}") await update.message.reply_text( f"{response_text}\n\n*Фото не доступно.*", parse_mode="Markdown", reply_markup=markup ) else: await update.message.reply_text( response_text, parse_mode="Markdown", reply_markup=markup )

    Что происходит здесь:

    • Проверка Наличия Файла Фото:
      • Бот проверяет, существует ли файл фотографии в указанной папке photos.
      • Если файл найден, бот открывает его и отправляет с помощью send_photo.
      • Затем отправляет текстовое сообщение с описанием поворота.
    • Обработка Ошибок:
      • Если при отправке фото возникает ошибка (например, файл поврежден или недоступен), бот логирует ошибку и отправляет только текстовое сообщение.
      • Если файл фото не найден, бот уведомляет пользователя, что фото недоступно.
    • Отправка Только Текста:
      • Если для поворота не указано фото, бот просто отправляет текстовое описание.
  2. Организация Папки с Фотографиями:

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

      ваш_проект/
      ├── photos/
      │   ├── karussell.jpg
      │   ├── brunnchen.jpg
      │   ├── flugplatz.jpg
      │   ├── lesmo.jpg
      │   ├── curva_grande.jpg
      │   ├── eau_rouge.jpg
      │   └── pouhon.jpg
      └── ваш_бот.py
      
    • Автоматическое Создание Папки: В коде добавлен блок, который проверяет наличие папки photos и создает ее, если она отсутствует:

      PHOTO_DIRECTORY = "photos" # Убедитесь, что папка существует if not os.path.isdir(PHOTO_DIRECTORY): os.makedirs(PHOTO_DIRECTORY) logger.info(f"Создана папка для фотографий: {PHOTO_DIRECTORY}")
  3. Использование InputFile для Отправки Файлов:

    Для отправки локальных файлов используется InputFile. Это позволяет загружать файлы непосредственно с вашего сервера или локальной машины.

    from telegram import InputFile # Пример отправки фото with open(photo_path, 'rb') as photo_file: await context.bot.send_photo( chat_id=update.effective_chat.id, photo=InputFile(photo_file), reply_markup=markup )
  4. Использование ReplyKeyboardMarkup для Кнопок "Назад":

    После отправки фотографии и сообщения, пользователю предоставляется возможность вернуться назад, используя кнопку "Назад".

    markup = ReplyKeyboardMarkup( [["Назад"]], resize_keyboard=True )
  5. Обработка Отсутствующих Фотографий:

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

    if os.path.isfile(photo_path): # Отправка фото и текста else: logger.warning(f"Файл фото не найден: {photo_path}") await update.message.reply_text( f"{response_text}\n\n*Фото не доступно.*", parse_mode="Markdown", reply_markup=markup )

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

  1. Проверка Названий Файлов:

    Убедитесь, что названия файлов изображений в папке photos точно соответствуют тем, которые указаны в вашем словаре tracks. Например, для поворота "Karussell" должно быть изображение с именем karussell.jpg.

  2. Оптимизация Изображений:

    Рекомендуется использовать изображения оптимизированного размера (например, до 1 МБ), чтобы минимизировать время загрузки и потребление трафика.

  3. Права Доступа:

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

  4. Тестирование:

    После внесения изменений обязательно протестируйте бота:

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

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

  5. Логирование:

    Используйте логирование для отслеживания возможных ошибок при отправке фотографий. Это поможет быстро выявлять и исправлять проблемы.

Заключение

С этими изменениями ваш Telegram-бот сможет отправлять реальные фотографии опасных поворотов из локальной папки, а также текстовые сообщения отдельно, используя методы send_photo и send_message. Это делает взаимодействие с пользователями более информативным и визуально привлекательным. Убедитесь, что все изображения находятся в правильной директории и имеют корректные имена файлов. Если у вас возникнут дополнительные вопросы или потребуется помощь, не стесняйтесь обращаться!

Pergunta

Если положил в папку photos фото, но все равно не показывает фото? в чем связанно ?

Pensamento

Ensuring file access

I’m checking if the code sends an image from a local file system. The user needs to verify the relative path or environment, ensure the image's correct naming and reading permissions, and confirm the working directory or absolute path are accurate.

Navigating file and path issues

I’m identifying potential reasons like mismatched file names or permissions, checking the working directory, and confirming the use of context.bot for asynchronous code.

Thought about photo display issue für 15 Sekunden

Resposta

Если вы поместили изображения в папку photos, но бот по-прежнему не отображает фото, это может быть связано с рядом причин. Рассмотрим наиболее распространенные:

  1. Несоответствие имен файлов:
    Убедитесь, что название изображения в папке photos точно соответствует тому, что указано в словаре tracks.
    Например, если в коде указано "фото": "karussell.jpg", то файл должен называться именно karussell.jpg (регистрозависимость имен файлов важна, особенно на Unix-подобных системах).

  2. Неправильное расположение папки:
    Убедитесь, что папка photos находится в той же директории, откуда вы запускаете скрипт.
    Например, если ваш бот запускается из директории bot/, то структура должна выглядеть так:

    bot/
    ├── bot.py (ваш код бота)
    ├── photos/
    │   ├── karussell.jpg
    │   ├── ...
    
  3. Отсутствие прав на чтение файлов:
    Убедитесь, что у пользователя, под которым вы запускаете бота, есть права на чтение файлов в папке photos.

  4. Путь к файлу:
    В коде путь к файлу формируется так:

    photo_path = os.path.join(PHOTO_DIRECTORY, photo_filename)

    Убедитесь, что PHOTO_DIRECTORY указывает на папку photos, а photo_filename — правильное имя файла. Вы можете добавить в код отладочный вывод:

    print("Trying to open file:", photo_path)

    чтобы убедиться, что путь формируется верно.

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

    cd /путь/к/директории/где/находится/бот.py python бот.py
  6. Логирование и проверка ошибок:
    В коде присутствует логирование. Если фотография не найдена, в логах (или в консоли) будет предупреждение:

    Файл фото не найден: ...
    

    Или ошибка при отправке фото. Посмотрите логи для получения дополнительной информации. Логи могут подсказать, в чем именно проблема (например, неправильный путь, отсутствие файла или проблемы с доступом).

Если ни одно из вышеуказанных решений не помогло, попробуйте:

  • Указать абсолютный путь к файлу, например:

    PHOTO_DIRECTORY = "/полный/путь/к/photos"

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

  • Проверить регистр имени файла и расширение (например, .JPG vs .jpg).

  • Убедиться, что вы используете правильный синтаксис для отправки фото. С вашей стороны было показано использование:

    bot.send_photo(chat_id=..., photo=open('путь/к/фото.jpg', 'rb'))

    Если вы используете асинхронный код из примера, стоит использовать context.bot.send_photo, а не bot.send_photo, и убедиться, что указаны правильные параметры.

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

Pergunta

сделать отправку фото без директории

Pensamento

Thought about sending photos without directory für ein paar Sekunden

Resposta

Если вы хотите отправлять фото без использования отдельной директории (например, все фото будут лежать в той же папке, что и скрипт), вы можете просто указать имя файла напрямую. Для этого:

  1. Удалите или закомментируйте использование переменной PHOTO_DIRECTORY и логику, связанную с проверкой существования папки.
  2. В словаре для поворотов в поле "фото" укажите просто имя файла, например "karussell.jpg".
  3. При отправке фото используйте open('karussell.jpg', 'rb') без дополнительных путей.

Ниже приведен пример обновленного кода фрагмента, в котором отправляется фото. В данном примере мы предполагаем, что все изображения лежат в той же директории, что и сам скрипт бота.

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: # Предполагаем, что фото лежит в той же папке, где и скрипт if os.path.isfile(photo_filename): try: with open(photo_filename, 'rb') as photo_file: # Отправка фотографии await context.bot.send_photo( chat_id=update.effective_chat.id, photo=photo_file, reply_markup=markup ) # Отправка сообщения await context.bot.send_message( chat_id=update.effective_chat.id, text=response_text, parse_mode="Markdown", reply_markup=markup ) except Exception as e: logger.error(f"Ошибка при отправке фото: {e}") await update.message.reply_text( response_text, parse_mode="Markdown", reply_markup=markup ) else: logger.warning(f"Файл фото не найден: {photo_filename}") await update.message.reply_text( f"{response_text}\n\n*Фото не доступно.*", parse_mode="Markdown", 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

Основные изменения:

  1. Удаление переменной PHOTO_DIRECTORY:
    Ранее мы формировали путь к файлу через os.path.join(PHOTO_DIRECTORY, photo_filename). Теперь мы просто используем photo_filename напрямую, предполагая, что фото лежат в той же директории.

  2. Прямая проверка наличия файла:
    if os.path.isfile(photo_filename): проверяет, есть ли указанный файл в текущей директории. Если да, мы открываем его через open(photo_filename, 'rb').

  3. Отправка фото без дополнительного пути:
    await context.bot.send_photo(chat_id=..., photo=photo_file) отправляет фото, лежащее в текущей директории.

Важно убедиться, что файл karussell.jpg (или другой указанный в коде) действительно лежит в той же папке, откуда вы запускаете скрипт. Если вы запускаете скрипт из другой директории или у вас сложная структура проекта, убедитесь, что вы находитесь именно в директории со скриптом и файлами, или используйте абсолютный путь к файлу.

Pergunta

изменить основной код и добавить изменение выше

Pensamento

Ladeorte für Bilder

Zunächst warnte ich vor gefährlichen Kurven, was ich schnell ändern musste, um lokale Bilder direkt zu senden. Das brachte einige Herausforderungen mit sich.

Anpassung des Codes

Ich schaue mir die Integration von send_photo und send_message an, um Fotos mit Dateinamen ohne Verzeichnisstrukturen zu senden, und gebe Tipps zur Fehlerbehebung bei Problemen mit dem Senden von Fotos.

Anpassungen vornehmen

Ich füge eine vollständige Code-Sample hinzu, die aktuelle Änderungen integriert, und hebe die relevanten Teile hervor. Ich prüfe auch, ob der Pfad korrekt ist und why Fotos nicht gesendet werden.

Gedanken für 7 Sekunden

Resposta

Понял вас. Давайте обновим ваш основной код Telegram-бота, чтобы он отправлял фотографии напрямую из той же директории, где находится скрипт, без использования отдельной папки photos. Мы будем использовать методы send_photo и send_message для отправки фотографий и сообщений отдельно.

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

Полный Обновленный Код Бота

Ниже представлен полный обновленный код вашего Telegram-бота с учетом всех изменений:

import logging import time import os from telegram import ( Update, ReplyKeyboardMarkup, ReplyKeyboardRemove, InputFile ) from telegram.ext import ( ApplicationBuilder, CommandHandler, MessageHandler, filters, ConversationHandler, ContextTypes ) # Настройка логирования logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO ) logger = logging.getLogger(__name__) # Состояния диалога CHOOSE_MAIN, CHOOSE_TRACK, TRACK_MENU, CHOOSE_DANGEROUS_TURN = range(4) # Настройки анти-спама MAX_MESSAGES = 5 # Максимум сообщений за период TIME_WINDOW = 10 # Интервал в секундах для подсчёта сообщений TIMEOUT_DURATION = 20 # Длительность тайм-аута в секундах # Данные о трассах (обновлено поле 'опасные_повороты' на словарь с фото) 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 мин.", "опасные_повороты": { "Karussell": { "описание": "Описание поворота Karussell — один из самых известных и сложных поворотов на Нюрбургринге. Требует максимальной концентрации и точности.", "фото": "karussell.jpg" }, "Brünnchen": { "описание": "Поворот Brünnchen характеризуется высокой скоростью и требованием к точности траектории.", "фото": "brunnchen.jpg" }, "Flugplatz": { "описание": "Поворот Flugplatz — это длинный и быстрый поворот, который требует отличного контроля над автомобилем.", "фото": "flugplatz.jpg" } } }, "Монца": { "описание": "Трасса Монца известна своими длинными прямыми и высокой скоростью, что делает её одной из самых быстрых трасс в мире.", "лидеры": "Автодром Монца имеет долгую историю и видел победы многих легендарных гонщиков. Назвать конкретного лидера сложно, так как это зависит от периода времени и класса гонок. Среди наиболее успешных гонщиков, многократно побеждавших на Монце, можно назвать Михаэля Шумахера, Льюиса Хэмилтона и многих других выдающихся пилотов Формулы-1.", "история": "Автодром Монца был открыт в 1922 году и является одной из старейших трасс Формулы-1. Первоначальная трасса проходила частично по общественным дорогам. Трасса неоднократно перестраивалась и модернизировалась, но сохранила свою скоростную сущность. Монца славится своей высокой скоростью и длинными прямыми, что делает её одной из самых быстрых трасс в мире. Она также является свидетелем многих знаменательных событий и трагических аварий в истории автоспорта.", "опасные_повороты": { "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, и является любимой трассой многих гонщиков за её уникальную смесь высоких скоростей и технических сложностей.", "опасные_повороты": { "Eau Rouge": { "описание": "Eau Rouge — знаменитый быстрый подъем с крутым поворотом, требующий точности и смелости от пилотов.", "фото": "eau_rouge.jpg" }, "Pouhon": { "описание": "Pouhon — длинный и быстрый поворот, который требует отличной аэродинамики и устойчивости автомобиля.", "фото": "pouhon.jpg" } } } } creators_info = "Этот бот был создан командой: Ратников Иван, Шукрона, Новиков Елисей, Елизарова Ксения, Пискунов Кирилл, Колосов Дмитрий." # Клавиатуры 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 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 == "История о трассе": response = f"**История трассы {current_track}:**\n{track_info['история']}" 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: # Предполагаем, что фото лежит в той же папке, где и скрипт if os.path.isfile(photo_filename): try: with open(photo_filename, 'rb') as photo_file: # Отправка фотографии await context.bot.send_photo( chat_id=update.effective_chat.id, photo=photo_file, reply_markup=markup ) # Отправка сообщения await context.bot.send_message( chat_id=update.effective_chat.id, text=response_text, parse_mode="Markdown", reply_markup=markup ) except Exception as e: logger.error(f"Ошибка при отправке фото: {e}") await update.message.reply_text( response_text, parse_mode="Markdown", reply_markup=markup ) else: logger.warning(f"Файл фото не найден: {photo_filename}") await update.message.reply_text( f"{response_text}\n\n*Фото не доступно.*", parse_mode="Markdown", 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 = "ВАШ_ТОКЕН_БОТА" # Замените на токен вашего бота 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. Отправка Фотографий без Использования Директории:

    • Удалены ссылки на папку photos: Мы предполагаем, что все фотографии находятся в той же директории, что и скрипт бота. Поэтому теперь мы проверяем наличие файла непосредственно по его имени.

    • Изменения в Обработчике dangerous_turn_choice: Вместо использования os.path.join(PHOTO_DIRECTORY, photo_filename), мы используем только photo_filename. Это означает, что файл должен находиться в той же папке, откуда запускается скрипт.

      if os.path.isfile(photo_filename): with open(photo_filename, 'rb') as photo_file: await context.bot.send_photo( chat_id=update.effective_chat.id, photo=photo_file, reply_markup=markup )
  2. Отправка Фотографий и Сообщений Отдельно:

    • Сначала отправляется фотография с помощью send_photo.
    • Затем отправляется текстовое сообщение с описанием поворота с помощью send_message.
    await context.bot.send_photo( chat_id=update.effective_chat.id, photo=photo_file, reply_markup=markup ) await context.bot.send_message( chat_id=update.effective_chat.id, text=response_text, parse_mode="Markdown", reply_markup=markup )
  3. Добавлены Отладочные Сообщения:

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

      else: logger.warning(f"Файл фото не найден: {photo_filename}") await update.message.reply_text( f"{response_text}\n\n*Фото не доступно.*", parse_mode="Markdown", reply_markup=markup )
    • Логирование ошибок при отправке фото: Если при отправке фото возникает ошибка, она логируется, и бот отправляет только текстовое сообщение.

      except Exception as e: logger.error(f"Ошибка при отправке фото: {e}") await update.message.reply_text( response_text, parse_mode="Markdown", reply_markup=markup )
  4. Удаление Проверки Папки photos: Поскольку теперь фотографии находятся в той же директории, что и скрипт, проверка и создание папки photos больше не требуется.

    Удалены следующие строки:

    # Путь к папке с фотографиями PHOTO_DIRECTORY = "photos" # Убедитесь, что папка существует if not os.path.isdir(PHOTO_DIRECTORY): os.makedirs(PHOTO_DIRECTORY) logger.info(f"Создана папка для фотографий: {PHOTO_DIRECTORY}")
  5. Убедитесь, что Файлы Фотографий Находятся в Правильной Директории:

    • Все файлы изображений (например, karussell.jpg, brunnchen.jpg, и т.д.) должны находиться в той же папке, что и скрипт бот.py.

    • Структура проекта должна выглядеть примерно так:

      ваш_проект/
      ├── karussell.jpg
      ├── brunnchen.jpg
      ├── flugplatz.jpg
      ├── lesmo.jpg
      ├── curva_grande.jpg
      ├── eau_rouge.jpg
      ├── pouhon.jpg
      └── бот.py
      
  6. Убедитесь, что Имена Файлов Совпадают:

    • Имена файлов должны точно соответствовать тем, что указаны в словаре tracks. Обратите внимание на регистр букв и расширение файлов.

    Например, для поворота "Karussell" должно быть изображение с именем karussell.jpg.

  7. Проверка Запуска Скрипта из Правильной Директории:

    • Убедитесь, что вы запускаете скрипт из той директории, где находятся фотографии и сам скрипт.

    • Например, если ваш проект находится в папке bot/, перейдите в эту папку перед запуском:

      cd /путь/к/вашему_проекту/bot/ python бот.py
  8. Убедитесь, что Библиотека python-telegram-bot Обновлена:

    • Используйте версию 20.x или выше для совместимости с асинхронными методами.

      pip install python-telegram-bot --upgrade

Дополнительные Шаги для Диагностики

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

  1. Проверьте Логи:

    • Запустите скрипт и следите за выводом логов в консоли. Бот будет логировать предупреждения и ошибки при попытке отправить фотографии.
    • Например, если файл не найден, вы увидите сообщение:
      WARNING:__main__:Файл фото не найден: karussell.jpg
      
  2. Добавьте Дополнительные Отладочные Выводы:

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

    Например, в обработчике dangerous_turn_choice добавьте:

    logger.info(f"Отправка фото: {photo_filename}")
  3. Проверьте Права Доступа:

    • Убедитесь, что у пользователя, под которым запускается бот, есть права на чтение файлов изображений.
  4. Проверьте Формат Изображений:

    • Убедитесь, что файлы изображений не повреждены и имеют поддерживаемые форматы (например, .jpg, .png).
  5. Тестируйте Отдельно:

    • Попробуйте создать минимальный скрипт для отправки одного изображения, чтобы проверить, работает ли отправка фотографий вообще.
    import logging from telegram import Update, InputFile from telegram.ext import ApplicationBuilder, CommandHandler, ContextTypes # Настройка логирования logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO ) logger = logging.getLogger(__name__) async def send_test_photo(update: Update, context: ContextTypes.DEFAULT_TYPE): try: with open('karussell.jpg', 'rb') as photo_file: await context.bot.send_photo( chat_id=update.effective_chat.id, photo=photo_file ) await context.bot.send_message( chat_id=update.effective_chat.id, text="Тестовое фото отправлено!", ) except Exception as e: logger.error(f"Ошибка при отправке тестового фото: {e}") await context.bot.send_message( chat_id=update.effective_chat.id, text="Не удалось отправить тестовое фото.", ) def main(): TOKEN = "ВАШ_ТОКЕН_БОТА" # Замените на токен вашего бота application = ApplicationBuilder().token(TOKEN).build() application.add_handler(CommandHandler("testphoto", send_test_photo)) application.run_polling() if __name__ == '__main__': main()
    • Запустите этот скрипт и отправьте боту команду /testphoto, чтобы проверить, отправляется ли фото.

Заключение

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

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

Если у вас возникнут дополнительные вопросы или потребуется дальнейшая помощь, пожалуйста, сообщите об этом!

Compartilhar esta Q&A