Привет! Продолжаем разбор темы разработки Telegram ботов с помощью Aiogram 3. В прошлой статье мы рассмотрели:
Магические фильтры (кратко)
Фильтры Command и CommandStart
Роутеры и диспетчер
Создание токена бота через BotFather
Выполнили первый запуск бота
Работали в рамках структуры, разработанной мной
Если вы новичок, предлагаю следовать моей структуре бота, но дальше – на ваше усмотрение.
О чём сегодня пойдёт речь:
Текстовые клавиатуры (markup)
ReplyKeyboardBuilder (генератор текстовых клавиатур)
Командное меню
Магические фильтры (в контексте клавиатур)
Бонус: Command object
Давайте обо всём по порядку.
Текстовая клавиатура
Текстовая клавиатура отображается под полем набора сообщения. Основная её особенность в том, что она не содержит никакой информации, кроме текста на кнопках (исключение – специальные кнопки, такие как геолокация о которых мы поговорим далее).
Другими словами, текст на кнопке отправляется боту, который реагирует на это сообщение, например, через F.text ==.
Начнем с кода. Реализуем его в файле all_kb.py, который находится в пакете keyboards (см. предыдущую статью для деталей).
Создадим простую клавиатуру главного меню. Для разнообразия сделаем так, чтоб у администраторов была дополнительная кнопка "Админ Панель" после выполнения простого фильтра.
Импорты в all_kb.py:
from aiogram.types import KeyboardButton, ReplyKeyboardMarkup from create_bot import admins
Создание клавиатуры "Главное меню":
def main_kb(user_telegram_id: int): kb_list = [ [KeyboardButton(text="📖 О нас"), KeyboardButton(text="👤 Профиль")], [KeyboardButton(text="📝 Заполнить анкету"), KeyboardButton(text="📚 Каталог")] ] if user_telegram_id in admins: kb_list.append([KeyboardButton(text="⚙️ Админ панель")]) keyboard = ReplyKeyboardMarkup(keyboard=kb_list, resize_keyboard=True, one_time_keyboard=True) return keyboard
Описание функции main_kb
Функция main_kb принимает один аргумент user_telegram_id типа int, который представляет собой ID пользователя в Telegram.
Создание списка кнопок:
kb_list = [ [KeyboardButton(text="📖 О нас"), KeyboardButton(text="👤 Профиль")], [KeyboardButton(text="📝 Заполнить анкету"), KeyboardButton(text="📚 Каталог")] ]
kb_list: список списков с объектамиKeyboardButton.Первая строка кнопок: "📖 О нас" и "👤 Профиль".
Вторая строка кнопок: "📝 Заполнить анкету" и "📚 Каталог".
Добавление кнопки для админов:
if user_telegram_id in admins: kb_list.append([KeyboardButton(text="⚙️ Админ панель")])
Если user_telegram_id присутствует в списке admins, добавляется строка с кнопкой "⚙️ Админ панель".
Создание и возврат объекта клавиатуры:
keyboard = ReplyKeyboardMarkup(keyboard=kb_list, resize_keyboard=True, one_time_keyboard=True) return keyboard
Функция возвращает созданную клавиатуру, которую мы затем привязываем к сообщению.
Привязка клавиатуры к сообщению (handlers/start.py):
from aiogram import Router, F from aiogram.filters import CommandStart from aiogram.types import Message from keyboards.all_kb import main_kb start_router = Router() @start_router.message(CommandStart()) async def cmd_start(message: Message): await message.answer('Запуск сообщения по команде /start используя фильтр CommandStart()', reply_markup=main_kb(message.from_user.id))
Для нашего фильтра мы вытянули телеграм айди пользователя из объекта message (message.from_user.id).
Импортируем клавиатуру из пакета keyboards и при помощи reply_markup привязываем её к сообщению. Давайте посмотрим что у нас получилось:

Так как у нас кнопок было достаточно много — они у нас получились более-менее обычного размера, но, если бы это была всего 1 кнопка, то тут бы вышло такое:

Улучшение клавиатуры
def main_kb(user_telegram_id: int): kb_list = [ [KeyboardButton(text="📖 О нас"), KeyboardButton(text="👤 Профиль")], [KeyboardButton(text="📝 Заполнить анкету"), KeyboardButton(text="📚 Каталог")] ] if user_telegram_id in admins: kb_list.append([KeyboardButton(text="⚙️ Админ панель")]) keyboard = ReplyKeyboardMarkup( keyboard=kb_list, resize_keyboard=True, one_time_keyboard=True, input_field_placeholder="Воспользуйтесь меню:" ) return keyboard
resize_keyboard=True: клавиатура будет автоматически изменять размер.one_time_keyboard=True: клавиатура скрывается после одного использования.input_field_placeholder: заменяет стандартную подпись «Написать сообщение...» на пользовательскую.
Смотрим что получилось:

Особые текстовые кнопки
Теперь создадим клавиатуру с "особыми кнопками". На примере будут кнопки:
Поделиться контактами
Поделиться локацией
Создать викторину/опрос
Создание специальной клавиатуры:
def create_spec_kb(): kb_list = [ [KeyboardButton(text="Отправить гео", request_location=True)], [KeyboardButton(text="Поделиться номером", request_contact=True)], [KeyboardButton(text="Отправить викторину/опрос", request_poll=KeyboardButtonPollType())] ] keyboard = ReplyKeyboardMarkup(keyboard=kb_list, resize_keyboard=True, one_time_keyboard=True, input_field_placeholder="Воспользуйтесь специальной клавиатурой:") return keyboard
request_location=True: позволяет пользователю отправить геолокацию.request_contact=True: позволяет отправить контактный номер.request_poll=KeyboardButtonPollType(): позволяет создать викторину или опрос. Может принимать один из параметров type = «quiz» (викторина) или «regular» (опрос). В нашем случае и то и то будет обработано.
Привязка специальной клавиатуры под обработчик /start_2:
@start_router.message(Command('start_2')) async def cmd_start(message: Message): await message.answer('Запуск сообщения по команде /start_2 используя фильтр Command()', reply_markup=create_spec_kb())
Смотрим что получилось:

Геолокацию можно отправить только со смартфона. При вызове этой опции через пк - получим такое сообщение:

Со смартфона данные передаются корректно, и теперь остается только обработать гео-данные. Как это сделать мы подробно обговорим в теме про FSM.
При клике на "Поделиться номером" (текст может быть любой). Пользователь увидит всплывающее окно:

После клика на "Поделиться" произойдет отправка номера телефона, который привязан к профилю телеграмм. Далее останется захватить ответ. Как это сделать мы тоже подробно обговорим в теме про FSM.


Штуку с викториной и опросником удобно использовать в группах и телеграмм каналах, которые будет администрировать ваш бот.
Использование ReplyKeyboardBuilder
Теперь давайте воспользуемся нововведением в aiogram 3, а именно строителем текстовых клавиатур. Для начала сделаем импорт:
from aiogram.utils.keyboard import ReplyKeyboardBuilder
Давайте сгенирируем некую шкалу голосования в котором результаты у нас записаны в виде баллов от 1 до 10. Пример кода:
def create_rat(): builder = ReplyKeyboardBuilder() for item in [str(i) for i in range(1, 11)]: builder.button(text=item) builder.button(text='Назад') builder.adjust(4, 4, 2, 1) return builder.as_markup(resize_keyboard=True)
Описание функции create_rat
def create_rat(): builder = ReplyKeyboardBuilder()
Создаем объект ReplyKeyboardBuilder, который будет использоваться для построения клавиатуры.
Добавление кнопок с оценками
for item in [str(i) for i in range(1, 11)]: builder.button(text=item)
Создаем список строк от '1' до '10' с помощью генератора списка.
Для каждого элемента в этом списке добавляем кнопку с текстом, равным этому элементу.
Добавление кнопки "Назад"
builder.button(text='Назад')
Настройка расположения кнопок
builder.adjust(4, 4, 2, 1)
Устанавливаем расположение кнопок на клавиатуре.
adjust(4, 4, 2, 1) указывает, что кнопки должны быть размещены в строках по 4, 4, 2 и 1 кнопке соответственно.
Первая строка будет содержать 4 кнопки.
Вторая строка будет содержать 4 кнопки.
Третья строка будет содержать 2 кнопки.
Четвертая строка будет содержать 1 кнопку ("Назад").
Возврат разметки клавиатуры
return builder.as_markup(resize_keyboard=True)
Преобразуем построенную клавиатуру в объект ReplyKeyboardMarkup с помощью метода as_markup.
Указываем параметр resize_keyboard=True, чтобы клавиатура автоматически изменяла размер в зависимости от количества и размера кнопок.
Привяжем клавиатуру к команде /start_3
@start_router.message(F.text == '/start_3') async def cmd_start(message: Message): await message.answer('Запуск сообщения по команде /start_3 используя магический фильтр F.text!', reply_markup=create_rat())
Смотрим что получилось:

Видим что все получилось как задумывали.
Мне кажется, что я раскрыл максимум из того что могло иметь отношение к текстовым кнопкам. Теперь поговорим про командное меню.
Работа с командным меню

Похожее меню мы можем создать двумя способами:
Через BotFather
Напрямую через код
Оба способа мы рассмотрим далее.
Настройка командного меню через BotFather





start - Главная страница start_3 - Вызов спец. клавиатуры
После этих действий в боте появится кнопка «Меню» с этими командами. Как вы понимаете, для того чтоб все работало необходимо в боте привязать к каждой команде нужное действие.

Настройка командного меню через код:
from aiogram.types import BotCommand, BotCommandScopeDefault async def set_commands(): commands = [BotCommand(command='start', description='Старт'), BotCommand(command='start_2', description='Старт 2'), BotCommand(command='start_3', description='Старт 3')] await bot.set_my_commands(commands, BotCommandScopeDefault())
Давайте разберем код функции set_commands построчно, чтобы понять, что он делает.
Импортируемые модули и объекты
from aiogram.types import BotCommand, BotCommandScopeDefault
BotCommand: объект, используемый для создания команд бота. Каждая команда имеет два атрибута: command (имя команды) и description (описание команды).BotCommandScopeDefault: объект, определяющий область действия команд. В данном случае используется область по умолчанию, что означает, что команды будут действовать для всех пользователей.
Описание функции set_commands
async def set_commands():
Объявляем асинхронную функцию set_commands, которая будет использоваться для установки команд бота. Асинхронная функция используется, потому что взаимодействие с API Telegram требует выполнения асинхронных запросов.
Создание списка команд
commands = [ BotCommand(command='start', description='Старт'), BotCommand(command='start_2', description='Старт 2'), BotCommand(command='start_3', description='Старт 3') ]
Создаем список commands, содержащий три команды:
BotCommand(command='start', description='Старт'):команда /start с описанием "Старт".BotCommand(command='start_2', description='Старт 2'):команда /start_2 с описанием "Старт 2".BotCommand(command='start_3', description='Старт 3'): команда /start_3 с описанием "Старт 3".
Установка команд бота
await bot.set_my_commands(commands, BotCommandScopeDefault())
Вызываем метод set_my_commands объекта bot для установки команд бота.
Передаем в метод два аргумента:
commands: список команд, который мы создали ранее.BotCommandScopeDefault(): область действия команд по умолчанию, которая устанавливает команды для всех пользователей.
и вызовем функцию в конце главной функции (то есть наш бот сначала будет запускаться, а после отправлять командное меню):
async def main(): # scheduler.add_job(send_time_msg, 'interval', seconds=10) # scheduler.start() dp.include_router(start_router) await bot.delete_webhook(drop_pending_updates=True) await dp.start_polling(bot) await set_commands()
Проверяем:

Использование Command Object для обработки аргументов команды
Вы, возможно, знали (если нет, то сейчас узнаете), что ссылки вида https://t.me/your_bot?start=12345 имеют особое значение для Telegram ботов. Бот может захватывать и обрабатывать информацию, следующую за start=. Это особенно полезно для реализации реферальных программ или отслеживания источника, откуда пришел пользователь.
Рассмотрим пример использования этой возможности на практическом примере. Писал бота клиники, который периодически рекламируется в различных Telegram-каналах и группах. Вместо простой ссылки на бота, такой как https://t.me/your_bot, менеджеры используют ссылки с метками, например, https://t.me/your_bot?start=habr, где habr - это метка источника.
Когда бот обнаруживает нового пользователя, он проверяет, была ли передана специальная информация в стартовой ссылке (метка). Если метка присутствует, бот фиксирует приход пользователя с конкретного источника. Эта информация затем отправляется на админ-панель для анализа маркетологами.
То же самое можно сделать для реферальной программы. Например, реферальная ссылка может содержать Telegram ID пользователя. Бот проверяет, является ли пользователь новым, и если это так, то пользователю, который поделился своей реферальной ссылкой, начисляются бонусы.
Для начала нам нужно выполнить новый импорт (CommandObject):
from aiogram.filters import CommandStart, Command, CommandObject
Изменение функции cmd_start:
@start_router.message(CommandStart()) async def cmd_start(message: Message, command: CommandObject): command_args: str = command.args if command_args: await message.answer( f'Запуск сообщения по команде /start используя фильтр CommandStart() с меткой <b>{command_args}</b>', reply_markup=main_kb(message.from_user.id)) else: await message.answer( f'Запуск сообщения по команде /start используя фильтр CommandStart() без метки', reply_markup=main_kb(message.from_user.id))
Обратите внимание, что в тройке больше не работает: message.get_args()!
Тут нас может заинтересовать только извлечение аргументов из команды:
command_args: str = cmd.args
Далее уже работаем с переменной command_args, как с обычным значением. Если метки не будет, то command_args будет равно None (этот случай я так же обработал в своем коде).
Тестируем:

Тот же результат мы получим если передадим свою метку в конструкцию такого вида:
/start метка

Мы видим, что бот успешно обработал метку. Ничто не мешает добавить такой же обработчик к любой другой команде. Это будет работать аналогично команде “/start”, за исключением того, что другие команды нельзя использовать в стартовой ссылке.
Заключение
Сегодня мы подробно разобрали темы командного меню и текстовых кнопок. Если у вас возникли вопросы по этой теме, пишите их в комментариях — я всегда стараюсь ответить каждому.
Если эта статья была для вас полезной, оставьте комментарий, подпишитесь и поставьте лайк. На создание таких туториалов уходит много времени и сил, и ваша поддержка очень важна для меня. Обратная связь от аудитории вдохновляет продолжать делиться знаниями и опытом.
Спасибо за внимание, и до новых встреч!
