урок 4 из 10

Ответы: текст, оформление, фото

Выбирать между answer, reply и send_message и понимать, когда нет входящего сообщения; оформлять ответы в режиме HTML и экранировать всё, что прислал человек; держаться пределов длины и не засыпать чат сообщениями, показывать работу над долгим ответом; отправлять фото по file_id и принимать присланн

10 мин чтения · 4 главы ·4 задания ·домашняя работа

Три способа ответить

До сих пор бот отвечал одним способом — message.answer: текст уходит в тот же чат, откуда пришло сообщение. Это самый частый ответ, но не единственный.

message.answerв тот же чатсамый частыйmessage.replyс цитатойпонятно, комуbot.send_messageв любой чатпо номерунет входящего сообщения — только send_message
answer пишет в тот же чат, reply — ответом с цитатой, send_message — в любой чат по номеру, даже когда никто ничего не присылал.

message.reply отправляет ответ с цитатой — над ним видно, на какое сообщение отвечают. В личке это почти не нужно, а в группе очень полезно: когда боту пишут пятеро, цитата показывает, кому именно он ответил. Правило: в группах — reply, в личке — answer.

Третий способ — bot.send_message(chat_id, text). Он отправляет текст в любой чат по номеру. Нужен он там, где входящего сообщения нет: бот напоминает о записи за час, сообщает владельцу о новой заявке, рассылает новость. Объект bot доступен в обработчике: достаточно добавить параметр с этим именем, и библиотека сама его подставит.

OWNER_ID = 58201937

@dp.message(Command("zapis"))
async def zapis(message: Message, bot: Bot):
    await message.answer("Вы записаны на завтра, 15:00.")
    await bot.send_message(OWNER_ID, f"Новая запись: {message.from_user.first_name}")

Здесь человек получает подтверждение в свой чат, а владелец — уведомление в свой. Помните правило первого урока: send_message сработает, только если владелец когда-то сам запустил бота. Иначе Телеграм откажет — бот не может писать первым.

Каждый способ возвращает отправленное сообщение. Его можно сохранить в переменную и потом изменить — например, заменить «Ищу…» на результат методом edit_text — или удалить методом delete. Так бот не засыпает чат промежуточными сообщениями.

Оформление и escape

Текст без оформления читается тяжело. Телеграм умеет жирный, курсив, моноширинный шрифт и ссылки — для этого у сообщения задают parse_mode. Самый надёжный режим — HTML: оформление пишется знакомыми тегами.

await message.answer(
    "<b>Запись подтверждена</b>\n"
    "Дата: <i>завтра, 15:00</i>\n"
    "Адрес: <a href='https://example.com/map'>на карте</a>",
    parse_mode="HTML",
)

Работают теги <b> (жирный), <i> (курсив), <u> (подчёркнутый), <s> (зачёркнутый), <code> (моноширинный — удобно для номеров заказов, которые копируют) и <a href> (ссылка). Абзацы, списки и заголовки, как на сайте, не работают: перенос строки — это просто «\n».

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

from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode

bot = Bot(token=TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML))

Теперь главное. Если в текст попадает то, что прислал человек — его имя, адрес, комментарий, — его нужно экранировать. Представьте, что человек назвался «Аня <3». Знак «<» Телеграм примет за начало тега, не найдёт его конца и откажется отправлять сообщение с ошибкой разбора. Бот, который падает на имени пользователя, выглядит нелепо, а иногда так ломают бота и нарочно.

from html import escape

name = escape(message.from_user.first_name)
await message.answer(f"Здравствуйте, <b>{name}</b>!")

escape превращает опасные знаки в безопасные записи, и в сообщении они отобразятся как есть. Правило простое: всё чужое, что вставляется в текст с оформлением, проходит через escape. Своё — заголовки и подписи, которые вы пишете сами, — экранировать не нужно.

Длина и частота

У сообщения есть предел длины — 4096 знаков. Текст длиннее Телеграм не примет, и бот получит ошибку. Обычно это всплывает внезапно: список заказов рос-рос и однажды не влез.

чтопредел
текст сообщения4096 знаков
подпись под фото или файлом1024 знака
текст команды32 знака

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

Обратная ошибка — много коротких сообщений подряд. Бот, который на один вопрос отвечает пятью сообщениями, раздражает, а в группах ещё и упирается в ограничения Телеграма на частоту (об этом девятый урок). Если ответ естественно делится на пункты, это один ответ со списком, а не пять ответов.

Когда ответ готовится долго — бот ищет в базе, обращается к сайту, — человек видит тишину и начинает писать снова. Показать, что бот работает, можно двумя способами. Первый — статус «печатает…» вверху чата:

await bot.send_chat_action(message.chat.id, "typing")

Статус держится около пяти секунд. Второй способ — сразу отправить короткое «Ищу…», а потом заменить его результатом через edit_text. Для ответов дольше нескольких секунд второй способ лучше: человек видит, что запрос принят, и не отправляет его повторно.

Фото и файлы

Картинка часто объясняет лучше текста: меню, схема проезда, карточка товара. Фото отправляют методом answer_photo. Взять его можно из файла на диске, по ссылке из интернета или по номеру файла в Телеграме.

from aiogram.types import FSInputFile

@dp.message(Command("menu"))
async def menu(message: Message):
    photo = FSInputFile("menu.jpg")
    await message.answer_photo(photo, caption="Меню на сегодня")

Когда фото отправлено один раз, Телеграм хранит его у себя и возвращает file_id — номер файла. Если отправлять то же фото снова по file_id, его не нужно загружать заново: отправка мгновенная, трафик не тратится. Для меню, которое бот показывает сто раз в день, это заметная экономия. Номер файла привязан к боту: другой бот по нему фото не отправит.

sent = await message.answer_photo(FSInputFile("menu.jpg"))
menu_id = sent.photo[-1].file_id     # сохранить и дальше слать по нему
await message.answer_photo(menu_id)

Фото, которое прислал человек, приходит в message.photo — и это не одно фото, а список размеров. Телеграм сам делает уменьшенные копии, от маленькой к большой.

90 px320 px800 px1280 pxphoto[-1]нужен оригинал — берите последний
Телеграм сам готовит уменьшенные копии присланного фото. В message.photo лежит список от маленькой к большой; последняя — самая крупная.

Для предпросмотра хватит маленькой, а если фото нужно сохранить или обработать — берут последнюю, photo[-1], самую крупную. Скачать её можно методом bot.download. Подпись, которую человек добавил к фото, лежит не в text, а в caption — ещё одна причина, почему у фото text пустой.

У файлов свои пределы: отправлять бот может файлы до 50 МБ, а скачивать присланные — до 20 МБ. Для большинства задач этого хватает; видео и большие архивы через обычного бота не передают.

Задания урока

Первые задание решается прямо здесь — без регистрации. Щёлкните по ответу.

  1. 1. Какой размер присланного фото самый крупный?

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

  1. Какой способ ответа подойдёт?средне
  2. Имя пользователя можно вставлять в HTML-сообщение без обработки: это же просто имя.средне
  3. Впишите число.средне

Практика и материалы по теме курса

Пройти урок с проверкой заданий Курс «Телеграм-боты на Python» открыт и бесплатен, прогресс сохраняется.
Открыть курс