Interactive Interfaces & Booking Systems
Simple text responses are often insufficient for professional services. To create a bot for ordering or booking, we must master custom keyboards, callback queries, and state-based logic, allowing users to navigate through menus and submit data step-by-step.
Learning Objectives
- 12.6.4.5 Implement complex decision trees using custom keyboards
- 12.6.4.6 Process callback events and manage interaction states
1. Telegram Keyboards: UI/UX Components
There are two primary ways to provide users with options in Telegram. Choosing the right one is critical for the "User Experience" (UX).
| Feature | Reply Keyboard (Menu) | Inline Keyboard (Buttons) |
|---|---|---|
| Location | Replaces the standard system keyboard at the bottom. | Attached directly to a specific message. |
| Function | Sends a predefined text message to the chat when pressed. | Triggers a "Callback Query" (hidden data sent to the server). |
| Best use case | Main persistent menus (e.g., "Main Menu", "Settings", "Help"). | Dynamic choices (e.g., "Select Size: S/M/L", "Confirm Order", "Select Date"). |
2. Modeling: Building a Booking Logic (Inline)
A booking bot follows a Decision Tree. We use types.InlineKeyboardMarkup to let users select options and callback_data to process their choice without cluttering the chat with text messages.
Live Code: Table Booking System
import telebot
from telebot import types
bot = telebot.TeleBot("YOUR_TOKEN")
# 1. Main Menu with Reply Keyboard
@bot.message_handler(commands=['start'])
def main_menu(message):
markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
btn1 = types.KeyboardButton("Order Coffee ☕")
btn2 = types.KeyboardButton("Book a Table 📅")
markup.add(btn1, btn2)
bot.send_message(message.chat.id, "Welcome to mr. TEA Cafe! How can I help you?", reply_markup=markup)
# 2. Interactive Selection with Inline Buttons
@bot.message_handler(func=lambda message: message.text == "Book a Table 📅")
def choose_time(message):
markup = types.InlineKeyboardMarkup(row_width=2)
# callback_data is what the bot "sees" behind the scenes (must be string < 64 bytes)
time1 = types.InlineKeyboardButton("18:00", callback_data="time_18")
time2 = types.InlineKeyboardButton("19:00", callback_data="time_19")
time3 = types.InlineKeyboardButton("20:00", callback_data="time_20")
markup.add(time1, time2, time3)
bot.send_message(message.chat.id, "Select your preferred time:", reply_markup=markup)
# 3. Processing the Choice (Callback Query)
@bot.callback_query_handler(func=lambda call: True)
def callback_inline(call):
if call.data.startswith("time_"):
selected_time = call.data.split("_")[1]
# Edit the original message to show confirmation
bot.edit_message_text(chat_id=call.message.chat.id,
message_id=call.message.message_id,
text=f"✅ Done! Table booked for {selected_time}:00. See you soon!")
# Answer the callback to remove the "loading" icon on the button
bot.answer_callback_query(call.id, "Booking Confirmed!")
bot.polling(none_stop=True)3. Structural Complexity: State Management
In a real ordering system (like a pizza delivery or event registration), clicking a button isn't enough. You need the user's typed input, such as their name, phone number, or address. We achieve this using State Management and the register_next_step_handler function.
Context Awareness
If a user sends the text "Alex", the bot needs to know if that is a name for a booking, an answer to a security question, or just random text. Without "States", the bot has no memory of the conversation history.
Live Code: Multi-step Data Collection
This method forces the bot to "wait" and direct the user's next message to a specific function.
# Global dictionary to act as a temporary database for user states
user_data = {}
@bot.message_handler(func=lambda message: message.text == "Order Coffee ☕")
def start_order(message):
chat_id = message.chat.id
user_data[chat_id] = {} # Initialize empty dict for this user
msg = bot.send_message(chat_id, "Great! Please type your Name:")
# Directs the next message from this user to the 'process_name' function
bot.register_next_step_handler(msg, process_name)
def process_name(message):
chat_id = message.chat.id
user_data[chat_id]['name'] = message.text # Save name
msg = bot.send_message(chat_id, "Nice to meet you! What is your phone number?")
bot.register_next_step_handler(msg, process_phone)
def process_phone(message):
chat_id = message.chat.id
user_data[chat_id]['phone'] = message.text # Save phone
# Final confirmation using the collected data
name = user_data[chat_id]['name']
phone = user_data[chat_id]['phone']
bot.send_message(chat_id, f"🧾 Order Profile Complete!\nName: {name}\nPhone: {phone}")
Common Pitfalls
Callback Data Size Limits
Telegram limits callback_data to 64 bytes. Avoid sending long sentences inside buttons; use short codes like "id_102" or "chk_val" and expand them in your Python code logic.
Blocking Operations
If your bot needs to check a slow external database, use bot.answer_callback_query(call.id, "Processing...") immediately. Otherwise, the button will stay in a "spinning" state and the user might press it multiple times causing duplicate bookings.
Practical Lab: Independent Projects
Choose one of the advanced projects below to build and demonstrate in PyCharm.
Goal: Build a multi-step registration bot for a school event.
1. Create a Main Menu (Reply Keyboard) with: "Registration" and "Schedule".
2. When "Schedule" is clicked, use Inline Buttons for "Day 1" and "Day 2".
3. When "Registration" is clicked, use register_next_step_handler to collect: Full Name, Grade, and Track (Robotics/Web/AI). Print a final confirmation ticket.
Goal: Write code where the bot, upon receiving a "Thank you" message, can respond in the user's selected language. Use a Reply Menu at the start of the chat to allow the user to select their preferred language (Russian, Kazakh, or English).
Goal: Create a chatbot that asks the user for a city name (using register_next_step_handler). Then, connect to api.openweathermap.org using the requests library to fetch and reply with the real-time temperature.
Self-Check
Q1: Which method should you use to update an existing message with buttons instead of sending a new one?
bot.edit_message_text()Q2: Why must we use bot.register_next_step_handler when asking a user for their name?
Q3: What is the purpose of bot.answer_callback_query()?