Как стать автором
Обновить

Telegram Боты на Aiogram 3.x: Текстовая клавиатура и Командное меню

Уровень сложностиСредний
Время на прочтение9 мин
Количество просмотров16K

Привет! Продолжаем разбор темы разработки 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

Похожее меню мы можем создать двумя способами:

  1. Через BotFather

  2. Напрямую через код

Оба способа мы рассмотрим далее.

Настройка командного меню через BotFather

Вводим /mybots (или выбираем через командное меню)
Вводим /mybots (или выбираем через командное меню)
Выбираем нашего бота
Выбираем нашего бота
Выбираем Edit bot
Выбираем Edit bot
Выбираем Edit command
Выбираем Edit command
Отправляем боту сообщение такого вида как на скрине
Отправляем боту сообщение такого вида как на скрине
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”, за исключением того, что другие команды нельзя использовать в стартовой ссылке.

Заключение

Сегодня мы подробно разобрали темы командного меню и текстовых кнопок. Если у вас возникли вопросы по этой теме, пишите их в комментариях — я всегда стараюсь ответить каждому.

Если эта статья была для вас полезной, оставьте комментарий, подпишитесь и поставьте лайк. На создание таких туториалов уходит много времени и сил, и ваша поддержка очень важна для меня. Обратная связь от аудитории вдохновляет продолжать делиться знаниями и опытом.

Спасибо за внимание, и до новых встреч!

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Продолжим?
96.08% Да, жду новую статью!49
3.92% Нет, достаточно2
Проголосовал 51 пользователь. Воздержался 1 пользователь.
Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
Всего голосов 10: ↑9 и ↓1+8
Комментарии12

Публикации

Истории

Работа

Data Scientist
70 вакансий

Ближайшие события