I need to handle what happens when users press these buttons. For example, I have a collection of images and want to send a specific number of them based on which button gets pressed. What’s the proper way to set up callback handlers for these inline buttons?
Your callback handling looks good, but I’d suggest using pattern matching to keep things organized when you’ve got multiple buttons. The pattern parameter in CallbackQuery makes everything cleaner:
@client.on(events.CallbackQuery(pattern=b"btn_"))
async def handle_buttons(event):
callback_data = event.data.decode('utf-8')
if callback_data == "btn_a":
# Send 3 images for option A
for i in range(3):
await event.respond(file=f"path/to/image_{i}.jpg")
elif callback_data == "btn_b":
# Send 5 images for option B
for i in range(5):
await event.respond(file=f"path/to/image_{i}.jpg")
await event.answer()
This handler only fires for buttons with the “btn_” prefix. Quick tip: if you’re sending multiple files, use client.send_file() with a media group so you don’t spam the chat with separate messages.
quick tip: edit the original msg or ur chat gets flooded with menus. use event.edit() instead of event.respond() to replace the button msg with results. also check event.sender_id so only the person who triggered /menu can click buttons - otherwise anyone can mess with ur bot.
Also, add data validation to your button callbacks or they’ll crash your bot. Had users somehow trigger callbacks with broken data that killed my bot until I fixed it.
@client.on(events.CallbackQuery)
async def safe_callback(event):
try:
data = event.data.decode('utf-8')
# Your button logic here
if data.startswith('btn_'):
# Handle valid buttons
pass
else:
# Log unknown callback data
print(f"Unknown callback: {data}")
except Exception as e:
print(f"Callback error: {e}")
finally:
await event.answer() # Always answer to clear loading state
Btw, use event.client.send_file() with parse_mode='html' for images with formatted captions. Regular event.respond() strips formatting when sending media.
The Problem: You’re building a Telegram bot and want to handle user interactions with inline buttons efficiently, specifically triggering different actions (like sending a varying number of images) based on which button is pressed. The current approach of writing individual handlers for each button becomes unmanageable as the number of buttons increases.
Understanding the “Why” (The Root Cause):
Manually creating a callback handler for every single inline button leads to repetitive code and makes maintaining your bot increasingly difficult as it grows. This is a classic example of code duplication which violates the DRY (Don’t Repeat Yourself) principle. A more scalable solution is to automate the callback handling process. Instead of hardcoding responses for each button, you can define rules that map button clicks to specific actions.
Step-by-Step Guide:
Implement an Automated Callback System: Instead of individual @client.on(events.CallbackQuery(...)) handlers for each button, create a single, centralized handler that uses the callback data to determine the appropriate action. This requires a system that maps button data (e.g., “btn_a”, “btn_b”) to specific functions or actions. You can achieve this using a dictionary or a more sophisticated workflow engine (as mentioned in the original post, Latenode is a possible solution, but other methods exist depending on your preferences and setup).
Define Button-Action Mappings: Create a data structure (like a dictionary in Python) that maps your callback data to the corresponding actions. For example:
button_actions = {
"btn_a": lambda event: send_images(event, 3, "path/to/image_a_"), # Sends 3 images
"btn_b": lambda event: send_images(event, 5, "path/to/image_b_"), # Sends 5 images
# ... more mappings for other buttons
}
async def send_images(event, num_images, path_prefix):
for i in range(num_images):
await event.respond(file=f"{path_prefix}{i}.jpg")
await event.answer() #Always answer the callback
@client.on(events.CallbackQuery)
async def handle_callback(event):
data = event.data.decode('utf-8')
if data in button_actions:
await button_actions[data](event)
else:
await event.answer("Unknown button pressed.") # Handle invalid data gracefully
Refactor Button Creation: When creating your inline buttons, ensure the data values are consistent with your button_actions dictionary keys.
Error Handling and Validation: Always validate the callback data to prevent crashes if a user sends unexpected or malformed data. Include try-except blocks to gracefully handle potential errors and log them for debugging.
Common Pitfalls & What to Check Next:
Data Validation: Always validate the input received from the callback data to prevent crashes from unexpected input. Sanitize any data before using it in your bot’s logic.
Scalability: As your bot grows, consider using a more robust workflow engine or database to manage the mapping between buttons and actions, especially if you have a large number of buttons or complex actions.
Memory Management: If your automated system involves storing data per-user (session data, as mentioned in another reply), implement cleanup mechanisms to avoid memory leaks. Regularly delete or expire old sessions.
64-Byte Callback Data Limit: Remember that Telegram’s callback data has a 64-byte limit. For larger payloads, store the data server-side and pass a unique key through the button’s data.
Still running into issues? Share your (sanitized) config files, the exact command you ran, and any other relevant details. The community is here to help!
Use the CallbackQuery event handler to catch button clicks. Just match the callback data from your buttons. Here’s how I do it:
@client.on(events.CallbackQuery)
async def button_callback(event):
data = event.data
if data == b"btn_a":
await event.respond("You selected Option A")
# Send your images here
elif data == b"btn_b":
await event.respond("You selected Option B")
# Different action
elif data == b"btn_final":
await event.respond("Final choice selected")
# Important: answer the callback to remove loading state
await event.answer()
Don’t forget event.answer() at the end - skip this and your button gets stuck with a loading spinner forever. You can also do event.answer("Some text") to show a popup notification instead of sending a message.
Heads up - callback data has a 64-byte limit that’ll bite you hard if you try passing complex data through buttons. When you need more info than what fits, store it server-side with a unique key and just pass the key through the button.
# Store complex data separately
user_sessions = {}
@client.on(events.NewMessage(pattern="/menu"))
async def show_menu(msg):
session_key = f"user_{msg.sender_id}_{int(time.time())}"
user_sessions[session_key] = {
"images": ["img1.jpg", "img2.jpg", "img3.jpg"],
"user_prefs": some_complex_data
}
keyboard = [[Button.inline("Send Images", session_key.encode())]]
await client.send_message(msg.chat_id, "Menu:", buttons=keyboard)
@client.on(events.CallbackQuery)
async def handle_callback(event):
session_key = event.data.decode('utf-8')
if session_key in user_sessions:
data = user_sessions[session_key]
# Process with full data available
await event.answer()
Bonus: you can clean up expired sessions periodically to avoid memory leaks.