2 часть статьи тут

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, в ответ на которую бот загрузит картинку с диска и отправит ее пользователю вместе с красиво оформленной подписью.

Подготовка файла

Для начала необходимо подготовить сам файл.

  1. Найдите любое изображение в формате JPG или PNG.

  2. Переименуйте его в cat.jpg для соответствия нашему примеру.

  3. Положите этот файл в корневую директорию вашего проекта, то есть в ту же папку, где находится ваш файл 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, которая отправляет пользователю два сообщения подряд:

  1. Первое сообщение: Картинка (используйте FSInputFile) с короткой, броской подписью, например: <b>Наша новая акция!</b>

  2. Второе сообщение (сразу после первого): Детальный текстовый пост об акции, который содержит:

    • Список преимуществ акции (просто строки, начинающиеся с дефиса или эмодзи).

    • Зачеркнутую старую цену (<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-сообществе.

Уверен, у вас все получится. Вперед, к практике!