Интерактивные кнопки⚓︎
Inline-кнопки отображаются под сообщением и помогают пользователю взаимодействовать с ботом без ручного ввода: выбрать команду, открыть ссылку, отправить подготовленный текст или скопировать данные.
Начнём с одной кнопки, а затем соберём из них меню и добавим обработку нажатий.
Требования
- TrueConf Server 5.5.6 или новее;
- TrueConf для Windows, macOS и Linux 8.6.0 или новее;
- TrueConf для Android 3.3.0 или новее;
- TrueConf для iOS 4.2.0 или новее.
Caution
Кнопки можно прикреплять только к обычным текстовым сообщениям. Отправлять сообщения с кнопками могут только чат-боты.
Полное описание формата кнопок на уровне WebSocket API приведено в документации TrueConf Server API.
Первая кнопка⚓︎
Кнопка создаётся с помощью InlineKeyboardButton, а клавиатура — с помощью InlineKeyboardMarkup:
from trueconf import InlineKeyboardButton, InlineKeyboardMarkup
keyboard = InlineKeyboardMarkup(
buttons=[
[InlineKeyboardButton(text="Помощь", command="menu:help")],
]
)
Здесь text — надпись на кнопке, а command — команда, которую бот получит после нажатия.
Параметр buttons содержит список рядов. Поэтому даже для одной кнопки нужны два вложенных списка: внешний задаёт клавиатуру, внутренний — первый ряд.
Передайте готовую клавиатуру в параметр buttons метода отправки сообщения:
Клавиатуру также можно передать в message.answer(...) и message.reply(...).
Tip
Если одна клавиатура используется в нескольких обработчиках, создайте её один раз на уровне модуля и передавайте в нужные методы.
Действия кнопок⚓︎
В предыдущем примере использовалась кнопка command, которая отправляет команду боту. Всего библиотека поддерживает четыре действия. Для каждой кнопки нужно указать ровно одно из них:
| Параметр | Что происходит при нажатии |
|---|---|
command | Боту приходит событие CallbackQuery; сообщение в чат не отправляется |
url | Открывается ссылка с протоколом http, https, mailto или trueconf |
message | От имени пользователя отправляется заданное сообщение |
copy_data | Заданные данные копируются в буфер обмена |
Кнопки можно объединять в несколько рядов:
from trueconf import ButtonStyle, InlineKeyboardButton, InlineKeyboardMarkup
keyboard = InlineKeyboardMarkup(
buttons=[
[
InlineKeyboardButton(
text="Помощь",
command="menu:help",
style=ButtonStyle.PRIMARY,
),
InlineKeyboardButton(
text="Открыть сайт",
url="https://trueconf.com",
),
],
[
InlineKeyboardButton(
text="Подготовить вопрос",
message="Расскажите подробнее",
draft=True,
),
InlineKeyboardButton(
text="Скопировать код",
copy_data="code-42",
),
],
]
)
В этом примере:
Помощьотправляет боту командуmenu:help;Открыть сайтоткрывает ссылку;Подготовить вопроспомещает текст в поле ввода благодаряdraft=True;Скопировать кодкопирует строкуcode-42.
Если у кнопки message не указывать draft=True, подготовленный текст будет сразу отправлен в чат от имени пользователя.
Tip
Параметр text обязателен для всех действий, кроме url. Если не задать надпись кнопки-ссылки, клиент отобразит саму ссылку.
Caution
Только кнопка command отправляет событие боту. Нажатия кнопок url, message и copy_data обрабатываются клиентским приложением и не создают CallbackQuery.
Дополнительные параметры⚓︎
Когда базового действия недостаточно, поведение и внешний вид кнопки можно уточнить дополнительными параметрами:
| Параметр | Для каких кнопок | Назначение |
|---|---|---|
style | Для всех | Задаёт стиль ButtonStyle.DEFAULT, ButtonStyle.PRIMARY, ButtonStyle.DANGER или ButtonStyle.SUCCESS |
button_id | Для всех | Задаёт идентификатор кнопки; для command он приходит в событии нажатия |
draft | Только message | Помещает текст в поле ввода вместо немедленной отправки |
custom_data | Только command | Передаёт боту дополнительные данные вместе с командой |
to | Только command | Направляет команду другому боту |
Например, в custom_data можно передать идентификатор выбранного объекта отдельно от команды:
button = InlineKeyboardButton(
text="Добавить в корзину",
command="cart:add",
custom_data="product-42",
button_id="add-product",
)
Warning
Параметр wait_reply=True и метод CallbackQuery.answer() пока нельзя использовать, поскольку клиентские приложения TrueConf ещё не поддерживают этот сценарий. Поэтому при их использовании библиотека вызывает NotImplementedError.
Обработка команды⚓︎
После нажатия кнопки command бот получает объект CallbackQuery. Для обработки события зарегистрируйте @router.callback_query() и добавьте фильтр по значению команды:
from trueconf import CallbackQuery, F, Router
router = Router()
@router.callback_query(F.command == "menu:help")
async def show_help(callback: CallbackQuery):
await callback.edit_message(text="Раздел помощи", buttons=help_keyboard)
Фильтр F.command == "menu:help" позволяет обработчику реагировать только на нужную кнопку. Для группы связанных команд удобно использовать общий префикс и фильтр F.command.startswith(...):
@router.callback_query(F.command.startswith("menu:"))
async def handle_menu(callback: CallbackQuery):
...
Данные о нажатии доступны через свойства объекта CallbackQuery:
callback.command— команда нажатой кнопки;callback.custom_data— данные изcustom_data;callback.message— сообщение с кнопкой;callback.chat— чат, в котором нажали кнопку;callback.command_source.button_id— значениеbutton_id, если оно было задано.
Изменение меню после нажатия⚓︎
Кнопки часто используются для навигации: пользователь открывает раздел, а бот изменяет текст сообщения и показывает новый набор действий. Из обработчика для этого удобно вызывать callback.edit_message(...):
@router.callback_query(F.command == "menu:catalog")
async def show_catalog(callback: CallbackQuery):
await callback.edit_message(
text="Выберите категорию:",
buttons=catalog_keyboard,
)
Текст и клавиатуру можно изменять независимо друг от друга:
# Изменить только текст и сохранить текущие кнопки
await callback.edit_message(text="Новый текст")
# Изменить только кнопки и сохранить текущий текст
await callback.edit_message(buttons=new_keyboard)
# Изменить текст и кнопки одновременно
await callback.edit_message(
text="Новый текст",
buttons=new_keyboard,
)
# Удалить кнопки и сохранить текущий текст
await callback.edit_message(buttons=None)
Если параметр buttons не указан, текущие кнопки сохраняются. Чтобы удалить их, нужно явно передать buttons=None.
Если сообщение изменяется не из обработчика CallbackQuery, используйте bot.edit_message(...). В этом случае нужно обязательно передать текст сообщения:
Получение клавиатуры сообщения⚓︎
Клавиатура полученного текстового сообщения доступна через TextContent.buttons:
message = await bot.get_message_by_id(message_id)
if message.content.buttons is not None:
keyboard = message.content.buttons
Ограничения⚓︎
При создании клавиатуры соблюдайте следующие ограничения:
- не более 8 рядов и 8 кнопок в каждом ряду;
- не более 64 кнопок во всей клавиатуре;
text— от 1 до 32 символов, если надпись задана;command— от 1 до 255 ASCII-символов;custom_data— от 1 до 4096 ASCII-символов;copy_data— от 1 до 4095 символов Unicode;url— от 8 до 8095 ASCII-символов.- HTML- и Markdown-разметка в тексте кнопки не обрабатывается, а переносы строк заменяются пробелами.
Полный пример⚓︎
Готовые боты
Больше готовых сценариев использования кнопок доступно в примерах на GitHub.
Этот бот отправляет меню в ответ на команду /menu, а затем обрабатывает кнопку Поздороваться:
import asyncio
from trueconf import (
Bot,
CallbackQuery,
Dispatcher,
F,
InlineKeyboardButton,
InlineKeyboardMarkup,
Message,
Router,
)
from trueconf.filters import Command
router = Router()
dispatcher = Dispatcher()
MAIN_MENU = InlineKeyboardMarkup(
buttons=[
[
InlineKeyboardButton(text="Поздороваться", command="menu:hello"),
InlineKeyboardButton(text="Открыть сайт", url="https://trueconf.com"),
],
[InlineKeyboardButton(text="Скопировать", copy_data="trueconf")],
]
)
@router.message(Command("menu"))
async def show_menu(message: Message):
await message.answer("Выберите действие:", buttons=MAIN_MENU)
@router.callback_query(F.command == "menu:hello")
async def say_hello(callback: CallbackQuery):
await callback.edit_message(text="Здравствуйте!", buttons=MAIN_MENU)
async def main():
dispatcher.include_router(router)
bot = Bot.from_credentials(
username="echo_bot",
password="123tr",
server="video.example.com",
dispatcher=dispatcher,
)
await bot.run()
if __name__ == "__main__":
asyncio.run(main())