1. Введение: От статики к интерактиву
В предыдущих частях нашего руководства мы последовательно построили архитектурно грамотный бот, который умеет отправлять отформатированный текст и медиафайлы. Однако наше взаимодействие с ним по-прежнему остается "статичным": пользователь вынужден запоминать и вводить все команды вручную.
Следующий ключевой этап в разработке любого удобного бота — это внедрение интерактивных элементов. Кнопки не только значительно улучшают пользовательский опыт, делая навигацию интуитивно понятной, но и позволяют создавать сложные, управляемые меню.
В Telegram существует два принципиально разных типа клавиатур, каждый из которых служит своей цели:
ReplyKeyboardMarkup
: Постоянные кнопки, заменяющие стандартную клавиатуру.InlineKeyboardMarkup
: Встроенные в сообщение кнопки, которые отправляют боту "сигналы" (callback
) и позволяют редактировать уже отправленный контент.
2. ReplyKeyboardMarkup: Создаем "пульт управления" ботом
Теория
Reply-клавиатура — это интерактивная панель с кнопками, которая появляется внизу экрана вместо стандартной клавиатуры для набора текста. Её главная особенность и принцип действия заключаются в том, что нажатие на любую из её кнопок эквивалентно отправке обычного текстового сообщения в чат.
Это делает её идеальным инструментом для создания главного меню и предоставления доступа к постоянным, часто используемым командам, таким как «Профиль», «Помощь», «Каталог» и т.д.
Практика: Реализация главного меню
Давайте реализуем команду /start
, которая будет не только приветствовать пользователя, но и сразу предлагать ему главное меню с кнопками.
1. Архитектурный шаг: новый файл для обработчиков
Для поддержания порядка в проекте, всю логику, связанную с клавиатурами и интерактивными элементами, мы вынесем в отдельный файл. Создайте в папке handlers
новый файл keyboard_handlers.py
.
2. Реализация кода
Откройте этот файл и добавьте в него следующий код. Мы создадим роутер, хэндлер для команды /start
и хэндлеры, которые будут реагировать на нажатия наших будущих кнопок.
# handlers/keyboard_handlers.py
from aiogram import Router, types, F
from aiogram.filters.command import Command
from aiogram.utils.keyboard import ReplyKeyboardBuilder
router = Router()
# Хэндлер на команду /start
@router.message(Command("start"))
async def cmd_start(message: types.message):
# Создаем объект билдера для Reply-клавиатуры
builder = ReplyKeyboardBuilder()
# Добавляем кнопки
builder.button(text="Помощь")
builder.button(text="Показать картинку")
# Указываем, сколько кнопок будет в одном ряду (в данном случае 2)
builder.adjust(2)
await message.answer(
"Привет! Я бот с клавиатурами. Выбери опцию:",
reply_markup=builder.as_markup(resize_keyboard=True)
)
# Хэндлер для обработки нажатия на кнопку "Помощь"
@router.message(F.text == "Помощь")
async def get_help(message: types.Message):
await message.answer("Это справка. Здесь пока ничего нет.")
# Хэндлер для обработки нажатия на кнопку "Показать картинку"
@router.message(F.text == "Показать картинку")
async def send_image_by_url(message: types.Message):
image_url = "https://picsum.photos/seed/aiogram/800/600"
await message.answer_photo(
photo=image_url,
caption="Вот картинка, загруженная из интернета!"
)
Разбор кода
ReplyKeyboardBuilder
: Для создания клавиатуры мы используем удобный класс-конструктор. Он позволяет гибко добавлять кнопки и настраивать их расположение..button(text=...)
и.adjust(...)
: Метод.button()
добавляет новую кнопку с указанным текстом. Метод.adjust()
"расставляет" уже добавленные кнопки по рядам.adjust(2)
означает, что в каждом ряду будет по две кнопки.reply_markup
: В методеmessage.answer()
мы используем параметрreply_markup
, чтобы прикрепить нашу клавиатуру к сообщению.builder.as_markup()
преобразует объект билдера в готовую клавиатуру.resize_keyboard=True
: Этот важный параметр делает кнопки компактными и удобными, подгоняя их размер под содержимое, а не растягивая на всю высоту экрана.
Новый подход: Отправка медиа по URL
Обратите внимание на хэндлер send_image_by_url
. Вместо использования FSInputFile
для отправки файла с диска, мы просто передаем в параметр photo
строку с URL-адресом изображения. aiogram
достаточно умен, чтобы определить, что это ссылка, самостоятельно скачать файл "на лету" и отправить его пользователю. Это чрезвычайно удобный способ для работы с динамическим контентом из интернета.
3. Интеграция роутера
Наконец, не забудьте зарегистрировать наш новый роутер в файле main.py
, чтобы диспетчер знал о его существовании.
# main.py
# ... (импорты)
from handlers import basic_handlers, menu_handlers, keyboard_handlers
# ... (инициализация)
async def main():
dp.include_router(basic_handlers.router)
dp.include_router(menu_handlers.router)
dp.include_router(keyboard_handlers.router) # <-- Наш новый роутер
# ... (запуск)
Теперь запустите бота и отправьте ему команду /start
. Вы увидите приветственное сообщение и две удобные кнопки внизу экрана.
3. InlineKeyboardMarkup: Интерактивность внутри сообщения
Теория
В отличие от Reply-клавиатуры, которая является элементом интерфейса чата, Inline-клавиатура — это неотъемлемая часть самого сообщения. Она прикрепляется непосредственно к тексту, фото или другому контенту и "путешествует" вместе с ним (например, при пересылке).
Ключевое отличие в принципе действия: нажатие на Inline-кнопку не отправляет новое текстовое сообщение в чат. Вместо этого оно посылает боту фоновый запрос-сигнал, называемый CallbackQuery
. Это позволяет создавать сложные интерактивные меню, проводить опросы, реализовывать навигацию и запрашивать подтверждения действий, не засоряя историю чата.
Практика: Два типа Inline-кнопок
Давайте добавим логику для Inline-клавиатур в наш файл handlers/keyboard_handlers.py
.
Тип 1: Кнопки-ссылки (url
)
Это простейший вид Inline-кнопок. Их единственная задача — перенаправить пользователя по указанному URL-адресу. Они не отправляют боту никаких callback
-сигналов.
Добавим в наш файл новую команду /links
:
# handlers/keyboard_handlers.py
# ... (предыдущий код) ...
# Не забываем импортировать InlineKeyboardBuilder
from aiogram.utils.keyboard import InlineKeyboardBuilder
# ...
@router.message(Command("links"))
async def cmd_links(message: types.Message):
# Создаем объект билдера для Inline-клавиатуры
builder = InlineKeyboardBuilder()
# Добавляем кнопки-ссылки
builder.button(text="Документация aiogram", url="https://aiogram.dev/")
builder.button(text="Мой GitHub", url="https://github.com/")
# Расставляем кнопки в один ряд
builder.adjust(1)
await message.answer(
"Вот полезные ссылки:",
reply_markup=builder.as_markup()
)
Здесь мы используем InlineKeyboardBuilder
и его метод .button()
, но вместо параметра text
указываем url
.
Тип 2: Callback-кнопки (callback_data
)
Это самый мощный и часто используемый тип кнопок. Параметр callback_data
содержит строку — "секретный код", который будет отправлен боту при нажатии. Мы сами придумываем этот код и используем его для идентификации того, какая именно кнопка была нажата.
Процесс работы с ними состоит из двух шагов: создание кнопок и обработка нажатий.
Шаг 1: Создание кнопок
Добавим команду /actions
, которая будет отправлять сообщение с двумя callback-кнопками.
# handlers/keyboard_handlers.py
# ... (предыдущий код) ...
@router.message(Command("actions"))
async def cmd_actions(message: types.Message):
builder = InlineKeyboardBuilder()
builder.button(text="Показать уведомление", callback_data="show_alert")
builder.button(text="Сменить это сообщение", callback_data="edit_message")
await message.answer(
"Нажми на кнопку, чтобы выполнить действие:",
reply_markup=builder.as_markup()
)
Шаг 2: Обработка нажатий
Чтобы "поймать" callback_data
, нам нужен новый тип хэндлера, который регистрируется с помощью декоратора @router.callback_query()
. Он слушает не сообщения в чате, а именно те самые CallbackQuery
-сигналы.
Добавим в конец файла keyboard_handlers.py
два таких хэндлера:
# handlers/keyboard_handlers.py
# ... (предыдущий код) ...
# Хэндлер для обработки нажатия на кнопку "Показать уведомление"
@router.callback_query(F.data == "show_alert")
async def handle_show_alert(callback: types.CallbackQuery):
await callback.answer(
"Это всплывающее уведомление!",
show_alert=True # Делает уведомление модальным окном
)
# Хэндлер для обработки нажатия на кнопку "Сменить это сообщение"
@router.callback_query(F.data == "edit_message")
async def handle_edit_message(callback: types.CallbackQuery):
# Редактируем текст исходного сообщения
await callback.message.edit_text("Сообщение было изменено!")
# Отвечаем на callback, чтобы убрать "часики" на кнопке
await callback.answer()
Разбор нового кода:
@router.callback_query(...)
: Декоратор для регистрации обработчикаCallbackQuery
.F.data == "..."
: Магический фильтр, который проверяет содержимое поляdata
в пришедшемCallbackQuery
. Так мы различаем, какая кнопка была нажата.callback: types.CallbackQuery
: В хэндлер передается объектCallbackQuery
, содержащий всю информацию о нажатии, включая ссылку на исходное сообщение (callback.message
) и на пользователя (callback.from_user
).await callback.answer()
: Это обязательный метод! Его вызов сообщает клиенту Telegram, что нажатие обработано. Если этого не сделать, на кнопке будут вечно висеть "часики". Можно передать в него текст для всплывающего уведомления, а параметрshow_alert=True
превратит его в модальное окно.await callback.message.edit_text(...)
: Один из самых мощных методов. Он позволяет боту редактировать то самое сообщение, к которому была прикреплена нажатая кнопка, создавая динамический интерфейс без отправки новых сообщений.
5. Заключение и домашнее задание
В этом уроке мы сделали огромный шаг вперед, превратив нашего бота из простого исполнителя команд в интерактивного собеседника. Вы освоили один из самых важных аспектов в разработке ботов — создание и обработку клавиатур. Теперь вы умеете создавать главное меню с помощью ReplyKeyboardMarkup
, а также сложные интерактивные элементы, кнопки-ссылки, и даже редактировать сообщения "на лету" с помощью InlineKeyboardMarkup
и CallbackQuery
.
Домашнее задание к Уроку "Все виды клавиатур"
Задание 1: "Главное меню v2.0"
Что нужно сделать: Усовершенствуйте Reply-клавиатуру, которая появляется по команде /start
.
Добавьте в нее третью кнопку: "Скрыть клавиатуру".
Напишите текстовый хэндлер, который будет реагировать на сообщение "Скрыть клавиатуру".
В этом хэндлере отправьте пользователю сообщение (например, "Клавиатура скрыта.") и в
reply_markup
передайте специальный объектReplyKeyboardRemove
.
Подсказка: from aiogram.types import ReplyKeyboardRemove
Ожидаемый результат: При нажатии на новую кнопку Reply-клавиатура должна исчезнуть.
Цель: Научиться не только показывать, но и убирать Reply-клавиатуру, а также добавлять кнопки в существующий ReplyKeyboardBuilder
.
Задание 2: "Бот-визитка"
Что нужно сделать: Создайте новую команду /socials
, которая будет отправлять Inline-клавиатуру с кнопками-ссылками на различные ресурсы.
Напишите хэндлер для команды
/socials
.Внутри создайте
InlineKeyboardBuilder
.Добавьте как минимум 3 кнопки-ссылки (например, на канал в Telegram, на YouTube, на ваш сайт). Каждая кнопка должна вести на свой URL.
Используйте
builder.adjust(1)
, чтобы каждая кнопка была на новой строке.
Ожидаемый результат: По команде /socials
бот присылает сообщение с тремя вертикально расположенными кнопками, каждая из которых открывает свою ссылку.
Цель: Закрепить навык создания Inline-клавиатур с url
-кнопками.
Задание 3: "Запрос подтверждения"
Что нужно сделать: Создайте команду /confirm
, которая запрашивает у пользователя подтверждение действия с помощью Inline-кнопок.
Напишите хэндлер для команды
/confirm
. Он должен отправлять сообщение "Вы уверены, что хотите продолжить?".К этому сообщению прикрепите Inline-клавиатуру с двумя кнопками: "Да" и "Нет".
Для кнопки "Да" установите
callback_data="confirm_yes"
.Для кнопки "Нет" установите
callback_data="confirm_no"
.Напишите два
callback_query
хэндлера, которые будут ловитьconfirm_yes
иconfirm_no
.В каждом хэндлере используйте
callback.answer()
с параметромshow_alert=True
, чтобы показать пользователю всплывающее окно с его выбором (например,await callback.answer("Вы подтвердили действие!", show_alert=True)
).
Цель: Научиться создавать Inline-кнопки с callback_data
и обрабатывать их с помощью callback_query
хэндлеров, используя модальные окна.
Задание 4: "Динамическое сообщение"
Что нужно сделать: Усовершенствуйте задание 3. Теперь, после нажатия на кнопку "Да" или "Нет", исходное сообщение должно изменяться.
Возьмите код из предыдущего задания.
В
callback_query
хэндлере дляconfirm_yes
, послеcallback.answer()
, добавьте редактирование исходного сообщения:await callback.message.edit_text("Действие успешно подтверждено! ✅")
.Аналогично, в хэндлере для
confirm_no
измените сообщение на:await callback.message.edit_text("Действие отменено. ❌")
.
Ожидаемый результат: После нажатия на кнопку Inline-клавиатура исчезает, а текст сообщения "Вы уверены...?" заменяется на результат выбора.
Цель: Освоить ключевой метод callback.message.edit_text()
для создания интерактивных интерфейсов.
Задание 5 (со звездочкой): "Интерактивный счётчик"*
Что нужно сделать: Создайте команду /counter
, которая отправляет сообщение с числом "0" и двумя Inline-кнопками: "+1" и "-1". При нажатии на кнопки число в сообщении должно изменяться.
Напишите хэндлер для команды
/counter
, который отправляет сообщение "Текущее значение: 0" и Inline-клавиатуру с кнопками "+1" (callback_data="increment"
) и "-1" (callback_data="decrement"
).Создайте один или два
callback_query
хэндлера для обработки этих нажатий.Внутри хэндлера вам нужно:
Получить текущее число из текста сообщения (
callback.message.text
).Преобразовать его в целое число (
int
).Увеличить или уменьшить его на 1.
С помощью
callback.message.edit_text()
обновить текст сообщения новым значением.
Подсказка: Чтобы из строки "Текущее значение: 5" получить число 5, можно использовать int(callback.message.text.split()[-1])
.
Цель: Решить нестандартную задачу, комбинируя чтение данных из сообщения (callback.message.text
) и его редактирование (edit_text
), чтобы создать полноценный интерактивный элемент.
Анонс новых статей, полезные материалы, а так же если в процессе решения возникнут сложности, обсудить их или задать вопрос по статье можно в моём Telegram-сообществе.
Уверен, у вас все получится. Вперед, к практике!