Interactive Buttons⚓︎
Inline buttons appear below a message and let users interact with a bot without typing: they can choose a command, open a link, send prepared text, or copy data.
We will start with one button, then build a menu and handle button clicks.
Requirements
- TrueConf Server 5.5.6 or later;
- TrueConf for Windows, macOS, and Linux 8.6.0 or later;
- TrueConf for Android 3.3.0 or later;
- TrueConf for iOS 4.2.0 or later.
Caution
Buttons can only be attached to plain text messages, and only chatbots can send messages with buttons.
For a complete description of the button format at the WebSocket API level, see the TrueConf Server API documentation.
Your First Button⚓︎
Create a button with InlineKeyboardButton and a keyboard with InlineKeyboardMarkup:
from trueconf import InlineKeyboardButton, InlineKeyboardMarkup
keyboard = InlineKeyboardMarkup(
buttons=[
[InlineKeyboardButton(text="Help", command="menu:help")],
]
)
Here, text is the button label and command is the command the bot receives when the button is clicked.
The buttons parameter contains a list of rows. This means that even a single button requires two nested lists: the outer list defines the keyboard, and the inner list defines its first row.
Pass the keyboard to the buttons parameter when sending a message:
You can also pass a keyboard to message.answer(...) and message.reply(...).
Tip
If several handlers use the same keyboard, create it once at module level and pass it to the methods that need it.
Button Actions⚓︎
The previous example used a command button, which sends a command to the bot. The library supports four actions, and each button must specify exactly one of them:
| Parameter | What happens when the button is clicked |
|---|---|
command | The bot receives a CallbackQuery event; no message is posted to the chat |
url | Opens a link using the http, https, mailto, or trueconf scheme |
message | Sends the prepared message on behalf of the user |
copy_data | Copies the specified data to the clipboard |
You can arrange buttons in multiple rows:
from trueconf import ButtonStyle, InlineKeyboardButton, InlineKeyboardMarkup
keyboard = InlineKeyboardMarkup(
buttons=[
[
InlineKeyboardButton(
text="Help",
command="menu:help",
style=ButtonStyle.PRIMARY,
),
InlineKeyboardButton(
text="Open website",
url="https://trueconf.com",
),
],
[
InlineKeyboardButton(
text="Prepare a question",
message="Tell me more",
draft=True,
),
InlineKeyboardButton(
text="Copy code",
copy_data="code-42",
),
],
]
)
In this example:
Helpsends themenu:helpcommand to the bot;Open websiteopens a link;Prepare a questionplaces text in the input field becausedraft=True;Copy codecopies thecode-42string.
If draft=True is omitted from a message button, the prepared text is sent to the chat immediately on behalf of the user.
Tip
The text parameter is required for every action except url. If a URL button has no label, the client displays the URL itself.
Caution
Only a command button sends an event to the bot. The client application handles url, message, and copy_data buttons, so clicking them does not create a CallbackQuery.
Additional Parameters⚓︎
When the basic action is not enough, use additional parameters to customize the button's behavior and appearance:
| Parameter | Available for | Purpose |
|---|---|---|
style | All buttons | Sets ButtonStyle.DEFAULT, ButtonStyle.PRIMARY, ButtonStyle.DANGER, or ButtonStyle.SUCCESS |
button_id | All buttons | Sets the button identifier; for a command button, it is included in the click event |
draft | message only | Places text in the input field instead of sending it immediately |
custom_data | command only | Sends additional data to the bot together with the command |
to | command only | Directs the command to another bot |
For example, custom_data can carry the identifier of a selected object separately from the command:
button = InlineKeyboardButton(
text="Add to cart",
command="cart:add",
custom_data="product-42",
button_id="add-product",
)
Warning
The wait_reply=True parameter and the CallbackQuery.answer() method cannot be used yet because TrueConf client applications do not currently support this scenario. The library therefore raises NotImplementedError when they are used.
Handling a Command⚓︎
After the user clicks a command button, the bot receives a CallbackQuery object. Register a @router.callback_query() handler and add a filter for the command value:
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="Help", buttons=help_keyboard)
The F.command == "menu:help" filter ensures that the handler responds only to the intended button. For a group of related commands, use a common prefix and an F.command.startswith(...) filter:
@router.callback_query(F.command.startswith("menu:"))
async def handle_menu(callback: CallbackQuery):
...
The click data is available through the properties of the CallbackQuery object:
callback.command— the command of the clicked button;callback.custom_data— the data fromcustom_data;callback.message— the message containing the button;callback.chat— the chat in which the button was clicked;callback.command_source.button_id— thebutton_idvalue, if specified.
Updating a Menu After a Click⚓︎
Buttons are often used for navigation: the user opens a section, and the bot changes the message text and displays a new set of actions. From a callback handler, use callback.edit_message(...):
@router.callback_query(F.command == "menu:catalog")
async def show_catalog(callback: CallbackQuery):
await callback.edit_message(
text="Choose a category:",
buttons=catalog_keyboard,
)
You can update the text and keyboard independently:
# Update only the text and keep the current buttons
await callback.edit_message(text="New text")
# Update only the buttons and keep the current text
await callback.edit_message(buttons=new_keyboard)
# Update the text and buttons together
await callback.edit_message(
text="New text",
buttons=new_keyboard,
)
# Remove the buttons and keep the current text
await callback.edit_message(buttons=None)
If the buttons parameter is omitted, the current buttons are preserved. To remove them, explicitly pass buttons=None.
To edit a message outside a CallbackQuery handler, use bot.edit_message(...). In this case, you must provide the message text:
Getting a Message Keyboard⚓︎
The keyboard of a received text message is available through TextContent.buttons:
message = await bot.get_message_by_id(message_id)
if message.content.buttons is not None:
keyboard = message.content.buttons
Restrictions⚓︎
Observe the following restrictions when creating a keyboard:
- no more than 8 rows and 8 buttons per row;
- no more than 64 buttons in a keyboard;
textmust contain 1 to 32 characters when a label is provided;commandmust contain 1 to 255 ASCII characters;custom_datamust contain 1 to 4096 ASCII characters;copy_datamust contain 1 to 4095 Unicode characters;urlmust contain 8 to 8095 ASCII characters;- HTML and Markdown in button labels are not processed, and line breaks are replaced with spaces.
Complete Example⚓︎
Ready-to-use bots
More ready-to-use button scenarios are available in the examples on GitHub.
This bot sends a menu in response to the /menu command and then handles the Say hello button:
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="Say hello", command="menu:hello"),
InlineKeyboardButton(text="Open website", url="https://trueconf.com"),
],
[InlineKeyboardButton(text="Copy", copy_data="trueconf")],
]
)
@router.message(Command("menu"))
async def show_menu(message: Message):
await message.answer("Choose an action:", buttons=MAIN_MENU)
@router.callback_query(F.command == "menu:hello")
async def say_hello(callback: CallbackQuery):
await callback.edit_message(text="Hello!", 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())