Привет! Продолжаем разбор темы разработки 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”, за исключением того, что другие команды нельзя использовать в стартовой ссылке.
Заключение
Сегодня мы подробно разобрали темы командного меню и текстовых кнопок. Если у вас возникли вопросы по этой теме, пишите их в комментариях — я всегда стараюсь ответить каждому.
Если эта статья была для вас полезной, оставьте комментарий, подпишитесь и поставьте лайк. На создание таких туториалов уходит много времени и сил, и ваша поддержка очень важна для меня. Обратная связь от аудитории вдохновляет продолжать делиться знаниями и опытом.
Спасибо за внимание, и до новых встреч!
Комментарии (8)
yailya
11.06.2024 07:12вопрос не ради разжигания холивара на национальной почве, а скорее из соображений безопасности. Aiogram создан украинцем и им же модерируется. документация доступна только на двух языках - украинском и английском, при том, что, очевидно, у библиотеки есть огромное количество русскоязычных пользователей.
нет ли тут потенциальной угрозы отключения всего и вся или неочевидного поведения для русскоязычных пользователей?
markoni
11.06.2024 07:12Никто не мешает клонировать-проверить, а также пользоваться англ. документацией. Тем более, что автор оперативно и адекватно отвечает на вопросы.
yailya
11.06.2024 07:12да. я понимаю, что всегда можно смотреть все обновления библиотеки. но ведь никто так не делает. и 90% пользователей не будут, потому что это требует очень глубокого погружения и опыта исследований кода.
про документацию никаких вопросов. мне и англ хватит.
yakvenalex Автор
11.06.2024 07:12Думаю, что в современном мире заблокировать доступ к чему-либо по территориальному признаку невозможно. Яркий тому пример ВК в Украине, Instagram, FB, Openai, RuTracker в РФ и так далее. Кроме установки библиотеки и копирования (клонирования) - всегда можно воспользоваться ВПН (писал недавно как свой ВПН за 5 минут развернуть на ВПС). Так что причин для беспокойств тут не вижу)
yailya
11.06.2024 07:12пример: вы развернули бота на сервере в РФ. библиотека по ip узнает, где произошло развёртывание и дальше делает что-то (скачивает доп скрипты какие-то и тд)
да, можно установить VPN. но тогда на этом серваке будут недоступны какие-то российские сервисы...
Shopech
Жду искренне в 3 части разбор мидлварей aiogram и их настройки
yakvenalex Автор
К сожалению или к счатью, но в третьей части у меня разбор CallBack (если время будет сегодня напишу и завтра утром выложу). А после фильтры, так как это более "попсовая" штука в разработке ботов, но до всего дойдем) Скажу честно, примерно в половине своих проектов я не использую мидлвари, но, конечно, в будущих статьях и это обсудим)