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-сообществе.
Уверен, у вас все получится. Вперед, к практике!
sashocq
Исправьте: у вас ссылка в тексте не на статью, а на её редактирование :-)
enamored_poc Автор
исправил