Unit 12.4 · Term 4

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

Lesson Presentation

chatbot_lesson_3.pptx · Slides for classroom use

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.

Project 1: "Startup NIS" Registration Bot

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.

Project 2: Multilingual Bot

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).

Project 3: External API Weather Bot

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?

Chatbots are asynchronous. Without it, the bot will treat the user's next message as a standard command rather than the specific answer to the name question.

Q3: What is the purpose of bot.answer_callback_query()?

To signal Telegram that the inline button press was successfully handled by the server and stop the "loading" animation on the user's screen.