Отправка текста и форматирование
Учимся не просто отправлять текст из бота, а делать его красивым: жирным, курсивным, со ссылками и моноширинным кодом.
Форматирование — это разметка внутри текста сообщения, которая говорит Telegram: «вот это слово сделай жирным, а это — ссылкой». Сам текст ты пишешь как обычно, а оформление добавляешь специальными тегами.
В прошлом уроке про хэндлеры сообщений наш «Цыплёнок-помощник» научился отвечать на текст. Но отвечал он скучно — серой простынёй букв. Сегодня превратим эти ответы в аккуратные, живые сообщения, на которые приятно смотреть.
Зачем вообще форматировать текст
Представь, что ты пишешь другу в личку важное расписание: «завтра матч в 18:00 не опаздывай форма синяя». Каша, да? А теперь представь то же самое, но время — жирным, «не опаздывай» — тоже жирным, а «форма синяя» — отдельной строкой. Сразу понятно, что главное, а что нет.
С ботом ровно так же. Когда «Цыплёнок-помощник» отвечает тебе про погоду или про домашку, ему нужно выделять важное: температуру, дедлайн, название предмета. Иначе пользователь просто пробежит глазами и ничего не запомнит.
Вот к чему мы придём к концу урока — бот будет отвечать так:
@chick_helper_bot
Привет! Я Цыплёнок-помощник 🐤
Сегодня по плану:
• Математика — стр. 45, № 12
• История — параграф 7
Подробности: открой дневник
Результат: в чате бот ответит сообщением, где «Цыплёнок-помощник» выделен жирным, список предметов идёт пунктами, слово «дневник» — кликабельной ссылкой, а номер задания — моноширинным шрифтом, как код.
Главный инструмент: message.answer
Когда боту приходит сообщение, aiogram передаёт его в хэндлер в виде объекта message. У этого объекта есть метод answer — он отправляет ответ в тот же чат, откуда пришло сообщение. Думай о нём как о кнопке «Ответить» в мессенджере: тебе не надо помнить, кому и куда писать, метод сам отправит туда, откуда пришёл вопрос.
from aiogram import Bot, Dispatcher
from aiogram.filters import CommandStart
from aiogram.types import Message
@dp.message(CommandStart())
async def start_handler(message: Message):
await message.answer("Привет! Я Цыплёнок-помощник 🐤")
Результат: в чате бот ответит обычным текстом «Привет! Я Цыплёнок-помощник 🐤» без какого-либо оформления.
Обрати внимание на await перед message.answer. Отправка сообщения — это поход к серверам Telegram, и он занимает время. Слово await говорит: «подожди, пока ответ дойдёт, и только потом иди дальше». Без него бот попытается отправить сообщение и тут же забудет об этом — текст до пользователя не долетит. Это та самая асинхронность, которой не надо бояться: просто ставь await перед всем, что общается с Telegram.
Разметка: как сказать «сделай жирным»
Сам по себе message.answer ничего не форматирует — он шлёт текст как есть. Чтобы Telegram понял разметку, нужно две вещи: расставить специальные теги в тексте и сказать боту, на каком «языке разметки» этот текст написан. Этот язык задаётся параметром parse_mode.
Есть два таких языка: HTML и MarkdownV2. Они делают одно и то же, просто пишутся по-разному. Мы будем использовать HTML — он почти такой же, как теги на сайтах, и его проще читать.
HTML-разметка по полочкам
Принцип простой: оборачиваешь кусок текста в парный тег. Открывающий тег — <b>, закрывающий — </b> со слешем. Всё, что между ними, оформится.
| Что хотим | Тег | Пример |
| Жирный | <b>…</b> | <b>важно</b> |
| Курсив | <i>…</i> | <i>тихо</i> |
| Зачёркнутый | <s>…</s> | <s>отменено</s> |
| Моноширинный (код) | <code>…</code> | <code>№ 12</code> |
| Ссылка | <a href="…">…</a> | <a href="https://t.me">тут</a> |
Теперь подключим это к боту. Задать parse_mode можно один раз для всего бота — тогда не придётся повторять его в каждом сообщении.
from aiogram import Bot, Dispatcher
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from aiogram.types import Message
bot = Bot(
token=TOKEN,
default=DefaultBotProperties(parse_mode=ParseMode.HTML),
)
dp = Dispatcher()
@dp.message()
async def schedule_handler(message: Message):
await message.answer(
"Я <b>Цыплёнок-помощник</b> 🐤\n"
"Сегодня: <i>математика</i> и <i>история</i>\n"
"Задание: <code>№ 12"
)
Результат: в чате бот ответит сообщением, где «Цыплёнок-помощник» будет жирным, слова «математика» и «история» — курсивом, а «№ 12» — моноширинным шрифтом, будто кусочек кода.
Здесь DefaultBotProperties(parse_mode=ParseMode.HTML) — это как настройка по умолчанию для объекта bot: «весь текст, который я отправляю, читай как HTML». Один раз настроил — и забыл. Если же тебе нужно в одном конкретном сообщении отключить разметку, можно передать parse_mode=None прямо в message.answer.
Многострочные сообщения и переносы
Чтобы сообщение шло несколькими строками, используется \n — это символ перевода строки. Каждый \n — новая строка в чате. А ещё подряд идущие строки в Python склеиваются, если просто поставить их рядом в скобках, как в примере выше: "...\n" и следующая строка автоматически приклеятся друг к другу. Это удобно, когда сообщение длинное.
Ссылки: спрятать длинный URL за словом
Голый адрес https://t.me/durov в чате выглядит громоздко. С тегом <a> можно спрятать его за обычным словом.
@dp.message()
async def link_handler(message: Message):
await message.answer(
"Открой свой <a href=\"https://dnevnik.ru\">дневник</a>, "
"чтобы увидеть оценки."
)
Результат: в чате бот ответит «Открой свой дневник, чтобы увидеть оценки», где слово «дневник» будет синей кликабельной ссылкой, ведущей на dnevnik.ru.
Экранирование: когда символы ломают разметку
Вот где новички спотыкаются чаще всего. Раз ты сказал боту «читай текст как HTML», то символы <, > и & Telegram воспринимает как часть разметки, а не как обычные буквы. Если пользователь спросит у бота «сколько будет 5 < 7?», и ты вставишь этот текст прямо в сообщение, Telegram решит, что < 7? — это начало какого-то тега, запутается и вернёт ошибку.
Решение — экранирование: заменить опасные символы на их безопасные «псевдонимы». Telegram понимает три замены:
<заменяем на<>заменяем на>&заменяем на&
В aiogram для этого есть готовая функция, но полезно понимать, как замена работает под капотом. Вот маленький пример на чистом Python, который ты можешь запустить прямо тут — он показывает суть экранирования:
def escape_html(text):
text = text.replace("&", "&")
text = text.replace("<", "<")
text = text.replace(">", ">")
return text
user_text = "5 < 7 & это правда"
print(escape_html(user_text))
Вывод:
5 < 7 & это правда
Заметь важную деталь: & заменяем первым. Если сделать наоборот, то после замены < на < мы получим амперсанд, который потом сами же испортим. Порядок важен.
В реальном боте не надо писать эту функцию руками — aiogram уже умеет это. Импортируешь хелпер и оборачиваешь им любой текст от пользователя:
from aiogram.utils.markdown import html_decoration as hd
from aiogram.types import Message
@dp.message()
async def math_handler(message: Message):
safe = hd.quote(message.text)
await message.answer(f"Ты написал: <b>{safe}</b>")
Результат: в чате бот безопасно повторит твоё сообщение жирным шрифтом, даже если в нём были символы <, > или & — они отобразятся как обычные знаки, а не сломают сообщение.
Частые ошибки и подводные камни
Через эти грабли проходят почти все. Давай пройдёмся, чтобы ты их обошёл.
1. Забыл parse_mode — теги видны как текст
Ты написал <b>Привет</b>, а в чате так и появилось «<b>Привет</b>» со скобками. Значит, ты не сказал боту, что текст — это HTML. Проверь, что задал parse_mode=ParseMode.HTML в настройках бота или в самом message.answer.
2. Незакрытый тег — ошибка от Telegram
Открыл <b>, а закрыть </b> забыл — и бот молчит, а в консоли красная ошибка вроде «can't parse entities». Telegram строго требует, чтобы каждый открывающий тег имел свою пару. Считай теги как скобки в коде: сколько открыл, столько закрой.
3. Вставил текст пользователя без экранирования
Самая коварная. Бот работает месяцами, а потом кто-то присылает сообщение с символом <, и бот падает с ошибкой. Правило простое: любой текст, пришедший от пользователя (его имя, его сообщение), перед вставкой в HTML-разметку прогоняй через hd.quote(...). Свой собственный текст экранировать не надо.
4. Спутал HTML и MarkdownV2
Если ты выбрал ParseMode.HTML, то звёздочки *жирный* работать не будут — это синтаксис Markdown. И наоборот. Выбери один язык разметки и держись его во всём боте, чтобы не путаться.
5. Сообщение длиннее 4096 символов
Telegram не пропустит сообщение длиннее 4096 символов — вернёт ошибку. Если бот шлёт что-то огромное (например, длинный список), разбивай его на несколько message.answer или сокращай.
Мини-практика: бот-эхо с подсветкой
Теперь твоя очередь. Допиши «Цыплёнка-помощника» так, чтобы на любое текстовое сообщение он отвечал «карточкой»:
- Первая строка — жирным: «Ты написал:».
- Вторая строка — само сообщение пользователя, но моноширинным шрифтом (тег
<code>), и обязательно экранированное черезhd.quote. - Третья строка — курсивом: сколько в сообщении символов (подсказка:
len(message.text)).
Каркас, который надо дополнить:
from aiogram.utils.markdown import html_decoration as hd
from aiogram.types import Message
@dp.message()
async def echo_handler(message: Message):
safe = hd.quote(message.text)
length = len(message.text)
# допиши message.answer с тремя строками:
# жирный заголовок, моноширинный текст, курсивный счётчик
await message.answer(...)
Результат: когда ты пришлёшь боту «привет <3», он ответит карточкой: жирное «Ты написал:», на новой строке моноширинное «привет <3» (символы не сломают сообщение), и курсивом «Символов: 9».
Если справился — поздравляю, ты уже умеешь оформлять сообщения лучше, чем половина учебных ботов в интернете.
Итоги
Сегодня «Цыплёнок-помощник» научился говорить красиво. Коротко, что ты теперь знаешь:
message.answerотправляет ответ в тот же чат — не забывайawaitперед ним.parse_mode=ParseMode.HTMLв настройках бота включает HTML-разметку для всех сообщений.- Теги
<b>,<i>,<code>,<a>делают текст жирным, курсивным, моноширинным и ссылкой. - Текст от пользователя обязательно экранируй через
hd.quote(...), иначе символы< > &сломают сообщение.
В следующем уроке мы дадим Цыплёнку настоящие кнопки — те самые, что появляются под сообщением и под полем ввода. Текст — это здорово, но когда пользователю не надо ничего печатать, а можно просто нажать «Погода» или «Домашка», бот сразу становится в разы удобнее. До встречи!