1. Введение: От простого текста к богатому контенту
В предыдущей статье мы заложили прочный архитектурный фундамент нашего проекта, научившись грамотно организовывать код с помощью Роутеров и обрабатывать различные типы входящих сообщений. Наш бот стал структурированным и готовым к расширению, но его общение с пользователем все еще остается довольно примитивным — он отвечает только простым, неформатированным текстом.
В этой части руководства мы подробно разберем, как использовать HTML- и Markdown-разметку для выделения текста жирным шрифтом, курсивом или для вставки ссылок, используя параметр parse_mode
. После этого мы научим бота отправлять пользователям изображения и документы, хранящиеся локально на диске, с помощью специального класса FSInputFile
из aiogram
.
2. Теория форматирования: Как работает parse_mode
По умолчанию Telegram Bot API обрабатывает весь отправляемый текст как обычную строку, игнорируя любые спецсимволы, которые могли бы отвечать за форматирование. Чтобы активировать разметку, при отправке сообщения в таких методах, как message.answer()
или bot.send_message()
, необходимо явно указать параметр parse_mode
.
Библиотека aiogram
поддерживает два основных режима парсинга, предоставляемых Telegram: HTML
и MarkdownV2
.
Режим HTML
Этот режим использует для разметки теги, синтаксически схожие с теми, что применяются в веб-разработке. Это надежный и, как правило, более простой для новичков способ форматирования.
Основной синтаксис:
<b>жирный текст</b>
— жирный текст<i>курсивный текст</i>
— курсивный текст<u>подчеркнутый текст</u>
— подчеркнутый текст<s>зачеркнутый текст</s>
—зачеркнутый текст<code>моноширинный текст</code>
—моноширинный текст
(для кода, команд)<a href="URL">текст ссылки</a>
— текст ссылки<pre>предварительно отформатированный блок</pre>
— блок с сохранением пробелов и переносов строк.
Ключевое преимущество HTML-парсера заключается в его "терпимости": он не требует обязательного экранирования большинства специальных символов, которые могут встретиться в обычном тексте (таких как .
, !
, -
, +
), что значительно упрощает формирование строк.
Режим MarkdownV2
Это более современная версия Markdown, использующая интуитивно понятные символы для разметки, привычные по многим мессенджерам и текстовым редакторам.
Основной синтаксис:
*жирный текст*
_курсивный текст_
__подчеркнутый текст__
~зачеркнутый текст~
`моноширинный текст`
[текст ссылки](URL)
Важное замечание: Экранирование символов
Главная сложность MarkdownV2
заключается в том, что большое количество символов (.
, !
, -
, (
, )
, [
, ]
, *
, _
и другие) считаются служебными и являются частью самого синтаксиса. Если эти символы должны быть отображены в тексте как есть, их необходимо экранировать — поставить перед ними обратный слэш (\
).
Например, текст Ваш результат: 100.
должен быть отправлен как строка Ваш результат: 100\.
Иначе Telegram API вернет ошибку, так как не сможет корректно распарсить точку в конце предложения.
Наша рекомендация
В рамках данного руководства мы будем использовать HTML. Он прощает больше ошибок, не требует сложного экранирования и в целом более надежен при работе с динамически формируемыми текстами, что делает его идеальным выбором для старта.
3. Практика: Создание информационного меню с форматированием
Теперь давайте применим полученные знания на практике. Мы создадим новую команду /menu
, которая будет выводить приветственное сообщение с описанием возможностей бота, используя HTML-разметку.
Архитектурный шаг: новый роутер для меню
Чтобы не смешивать основную логику (обработку команд /start
и эхо) с демонстрационными или информационными командами, хорошей практикой будет вынести их в отдельный модуль. Это сохранит наш проект чистым и организованным.
Создайте в папке handlers
новый файл с именем menu_handlers.py
.
Реализация хэндлера
Откройте handlers/menu_handlers.py
и добавьте в него следующий код. Здесь мы создаем новый роутер и регистрируем в нем хэндлер для команды /menu
.
# handlers/menu_handlers.py
from aiogram import Router, types
from aiogram.filters.command import Command
router = Router()
@router.message(Command("menu"))
async def show_menu(message: types.Message):
# Используем HTML-разметку для формирования текста
text = (
"<b>Меню возможностей:</b>\n\n"
"<i>Этот бот демонстрирует:</i>\n"
" - Отправку форматированного текста\n"
" - Отправку изображений и документов\n\n"
"Воспользуйтесь командой /send_photo, чтобы получить картинку.\n\n"
"Больше информации о фреймворке вы найдете в "
"<a href='https://aiogram.dev/'>документации aiogram 3</a>."
)
# Отправляем сообщение, обязательно указав parse_mode
await message.answer(text, parse_mode="HTML")
Разбор кода:
Мы формируем многострочную текстовую переменную
text
, используя круглые скобки для удобства чтения.Теги
<b>
,<i>
и<a href="...">
используются для стилизации текста и добавления гиперссылки. Символ\n
отвечает за перенос строки.В методе
message.answer()
мы передаем наш текст и, что самое важное, указываемparse_mode="HTML"
. Без этого параметра Telegram отобразил бы пользователю весь текст вместе с тегами.
Интеграция нового роутера
Наш новый хэндлер готов, но диспетчер о нем еще не знает. Чтобы его "активировать", необходимо импортировать и зарегистрировать роутер из menu_handlers.py
в нашем главном файле main.py
, по аналогии с тем, как мы это делали в прошлом уроке.
Откройте main.py
и добавьте в него две строки:
# main.py
import asyncio
import logging
from aiogram import Bot, Dispatcher
# Импортируем роутеры из файлов
from handlers import basic_handlers, menu_handlers
# ... (инициализация бота и диспетчера) ...
async def main():
# Подключаем роутеры
dp.include_router(basic_handlers.router)
dp.include_router(menu_handlers.router) # Новый роутер
# ... (запуск поллинга) ...
# ... (точка входа) ...
Теперь запустите бота и отправьте ему команду /menu
. В ответ вы должны получить красиво отформатированное сообщение со ссылкой на документацию.
4. Теория отправки медиа: FSInputFile
Отправка медиафайлов — одна из ключевых функций, делающих ботов по-настоящему полезными. В Telegram Bot API существует два фундаментальных способа отправки файлов, и понимание их различий крайне важно для эффективной разработки.
1. Отправка по file_id
Каждый файл, который когда-либо был загружен на серверы Telegram (неважно, отправлен ли он пользователем боту или самим ботом), получает уникальный строковый идентификатор — file_id
. Этот идентификатор можно сохранить и в дальнейшем использовать для повторной отправки того же файла.
Этот способ является самым быстрым и эффективным, поскольку сам файл не загружается заново. Бот просто сообщает API file_id
, и Telegram мгновенно отправляет уже хранящийся у него файл получателю. Это экономит время и трафик.
2. Загрузка нового файла с диска
Этот способ используется, когда файл находится локально на том же сервере или компьютере, где запущен бот, и еще не был загружен в Telegram. В этом случае бот должен прочитать файл и отправить его содержимое (бинарные данные) в API-запросе.
Для реализации именно этого сценария в aiogram
предусмотрен специальный класс — FSInputFile
.
FSInputFile
(сокращение от FileSystem Input File) — это объект-обертка, который сообщает aiogram
, что по указанному пути нужно найти файл, прочитать его с файловой системы и прикрепить к API-запросу. Он является основным инструментом для отправки локальных файлов.
FSInputFile
импортируется из модуля aiogram.types
:
from aiogram.types import FSInputFile
Именно этот объект мы будем использовать на следующем шаге, чтобы научить нашего бота отправлять пользователям изображения и документы, хранящиеся в директории проекта.
5. Практика: Отправка фото и документов с форматированной подписью
Теперь, когда мы разобрались с теорией, научим нашего бота отправлять локальные файлы на примере изображения. Мы создадим новую команду /send_photo
, в ответ на которую бот загрузит картинку с диска и отправит ее пользователю вместе с красиво оформленной подписью.
Подготовка файла
Для начала необходимо подготовить сам файл.
Найдите любое изображение в формате JPG или PNG.
Переименуйте его в
cat.jpg
для соответствия нашему примеру.Положите этот файл в корневую директорию вашего проекта, то есть в ту же папку, где находится ваш файл
main.py
.
Дополнение handlers/menu_handlers.py
Откройте файл handlers/menu_handlers.py
и добавьте в него новый хэндлер. Не забудьте импортировать класс FSInputFile
в самом начале файла.
Приведите код файла к следующему виду:
# handlers/menu_handlers.py
from aiogram import Router, types
from aiogram.filters.command import Command
# 1. Импортируем FSInputFile
from aiogram.types import FSInputFile
router = Router()
# Хэндлер на команду /menu (уже был)
@router.message(Command("menu"))
async def show_menu(message: types.Message):
text = (
"<b>Меню возможностей:</b>\n\n"
"<i>Этот бот демонстрирует:</i>\n"
" - Отправку форматированного текста\n"
" - Отправку изображений и документов\n\n"
"Воспользуйтесь командой /send_photo, чтобы получить картинку.\n\n"
"Больше информации о фреймворке вы найдете в "
"<a href='https://aiogram.dev/'>документации aiogram 3</a>."
)
await message.answer(text, parse_mode="HTML")
# 2. Новый хэндлер для команды /send_photo
@router.message(Command("send_photo"))
async def send_photo_command(message: types.Message):
# Создаем объект файла для загрузки
image_from_pc = FSInputFile("cat.jpg")
# Отправляем фото с подписью и форматированием
await message.answer_photo(
photo=image_from_pc,
caption="<b>Вот ваш котик!</b> ᓚᘏᗢ\n\n"
"<em>Этот котик был загружен с компьютера.</em>",
parse_mode="HTML"
)
Разбор нового хэндлера
image_from_pc = FSInputFile("cat.jpg")
: Здесь мы создаем экземплярFSInputFile
, передавая в него относительный путь к нашему файлу. Поскольку скрипт запускается из корня проекта, достаточно указать просто имя файла.await message.answer_photo(...)
: Для отправки фотографий используется специальный методanswer_photo
. Аналогично существуютanswer_document
,answer_audio
,answer_video
и другие.photo=image_from_pc
: В этот именованный параметр мы передаем наш подготовленный объект файла.caption="..."
: Параметрcaption
отвечает за текстовую подпись, которая будет отправлена вместе с изображением.parse_mode="HTML"
: И, что самое важное, подписи тоже поддерживают форматирование! Указавparse_mode
, мы можем использовать все те же HTML-теги для оформления текста под картинкой.
Универсальность подхода
Подход с FSInputFile
универсален для разных типов файлов. Например, отправка документа или аудиофайла выглядела бы так:
# Отправка документа
# doc_from_pc = FSInputFile("document.pdf")
# await message.answer_document(doc_from_pc, caption="Вот инструкция.")
# Отправка аудио
# audio_from_pc = FSInputFile("music.mp3")
# await message.answer_audio(audio_from_pc, caption="Вот ваш трек.")
Теперь запустите бота (python main.py
) и протестируйте обе команды: /menu
и /send_photo
. Вы должны получить сначала отформатированное текстовое сообщение, а затем — изображение с красивой подписью.
6. Заключение и домашнее задание
В этом уроке мы сделали общение с ботом значительно более выразительным и профессиональным. Вы освоили два ключевых навыка: форматирование текста с помощью HTML и MarkdownV2 через параметр parse_mode
, и отправку медиафайлов с локального диска, используя класс FSInputFile
. Теперь ваш бот способен не только обмениваться информацией, но и представлять её в наглядном, структурированном и визуально привлекательном виде.
Домашнее задание к Уроку 3
Задание 1: "Красивая справка"
Что нужно сделать: Создайте или обновите команду /help
. Ответ на эту команду должен быть отформатирован с помощью HTML.
Используйте тег
<b>
, чтобы выделить названия команд жирным (/start
,/help
и т.д.).Используйте тег
<i>
, чтобы дать краткое описание каждой команде.Используйте тег
<code>
, чтобы показать пример использования, если это необходимо.
Пример ответа бота:
<b>Справка по командам бота:</b>
/start - <i>начало работы с ботом</i>
/help - <i>показать это сообщение</i>
/send_photo - <i>прислать тестовую картинку</i>
/menu - <i>показать меню возможностей</i>
Цель: Применить навыки HTML-форматирования для создания структурированного и читаемого текста.
Задание 2: "Бот-документовед"
Что нужно сделать: Создайте новую команду /send_doc
. По этой команде бот должен отправлять пользователю документ с вашего компьютера.
Найдите или создайте любой файл (например,
.txt
или.pdf
). Положите его в корень проекта.Напишите хэндлер для команды
/send_doc
.Используйте
FSInputFile
для указания пути к вашему файлу.Используйте метод
message.answer_document()
.Добавьте к документу подпись (
caption
) с HTML-форматированием. Например: "Вот инструкция по использованию бота в<code>.pdf</code>
формате."
Цель: Научиться отправлять не только фото, но и другие типы файлов, используя соответствующий метод answer_*
.
Задание 3: "Интерактивный промоушен"
Что нужно сделать: Создайте команду /promo
, которая отправляет пользователю два сообщения подряд:
Первое сообщение: Картинка (используйте
FSInputFile
) с короткой, броской подписью, например:<b>Наша новая акция!</b>
Второе сообщение (сразу после первого): Детальный текстовый пост об акции, который содержит:
Список преимуществ акции (просто строки, начинающиеся с дефиса или эмодзи).
Зачеркнутую старую цену (
<s>1000 руб</s>
).Жирную новую цену (
<b>500 руб</b>
).Встроенную ссылку на ваш сайт (
<a href="...">Узнать подробнее</a>
).
Цель: Закрепить навыки последовательной отправки разных типов сообщений и сложного форматирования в рамках одной команды.
Задание 4: "Технический паспорт (v2.0)"
Что нужно сделать: Усовершенствуйте хэндлер, который реагирует на отправленную пользователем фотографию. Теперь бот должен в ответ прислать не просто file_id
, а красиво оформленную "карточку" изображения, используя HTML-форматирование.
Пример ответа бота:
<b>Техническая информация о фото:</b>
- ID файла: <code>AgACAgIAAxkBAA...</code>
- Ширина: <i>1280 px</i>
- Высота: <i>720 px</i>
Подсказка: Когда пользователь отправляет фото, Telegram сохраняет его в нескольких разрешениях. Вся эта информация хранится в message.photo
в виде списка. Чтобы получить данные самого качественного изображения, нужно взять последний элемент этого списка (message.photo[-1]
).
ID файла:
message.photo[-1].file_id
Ширина:
message.photo[-1].width
Высота:
message.photo[-1].height
Цель: Научиться извлекать больше данных из объекта message
и динамически вставлять их в отформатированный HTML-ответ.
Задание 5 (со звездочкой): "Персональная открытка"*
Что нужно сделать: Создайте команду /card
, которая отправляет пользователю картинку (FSInputFile
) с персональной подписью. Имя пользователя должно быть взято из его профиля и вставлено в подпись.
Пример ответа бота:
Пользователь Василий Пупкин отправляет команду /card
(Бот присылает картинку-открытку)
Подпись к картинке: <b>Василий Пупкин</b>, эта открытка специально для вас! <i>Хорошего дня!</i>
Подсказка: Имя пользователя можно получить из message.from_user.full_name
. Вам нужно будет объединить f-строку и HTML-теги.
Цель: Практиковаться в создании динамических, персонализированных сообщений, комбинируя данные о пользователе и навыки форматирования.
Анонс новых статей, полезные материалы, а так же если в процессе решения возникнут сложности, обсудить их или задать вопрос по статье можно в моём Telegram-сообществе.
Уверен, у вас все получится. Вперед, к практике!