Search code examples
pythonmenubotstelegrampython-telegram-bot

Proper way to build menus with python-telegram-bot


I work with python-telegram-bot and try to build a system of nested menus as BotFather bot does. For instance, you have a general bot menu

pic.1

where you can choose "Edit Bot" and get the new corresponding menu

pic.2

with an option to get back to the previous menu.

I try to achieve that with code:

# main menu
def start(bot, update):
    menu_main = [[InlineKeyboardButton('Option 1', callback_data='m1')],
                 [InlineKeyboardButton('Option 2', callback_data='m2')],
                 [InlineKeyboardButton('Option 3', callback_data='m3')]]
    reply_markup = InlineKeyboardMarkup(menu_main)
    update.message.reply_text('Choose the option:', reply_markup=reply_markup)

# all other menus
def menu_actions(bot, update):
    query = update.callback_query

    if query.data == 'm1':
        # first submenu
        menu_1 = [[InlineKeyboardButton('Submenu 1-1', callback_data='m1_1')],
                  [InlineKeyboardButton('Submenu 1-2', callback_data='m1_2')]]
        reply_markup = InlineKeyboardMarkup(menu_1)
        bot.edit_message_text(chat_id=query.message.chat_id,
                              message_id=query.message.message_id,
                              text='Choose the option:',
                              reply_markup=reply_markup)
    elif query.data == 'm2':
        # second submenu
        # first submenu
        menu_2 = [[InlineKeyboardButton('Submenu 2-1', callback_data='m2_1')],
                  [InlineKeyboardButton('Submenu 2-2', callback_data='m2_2')]]
        reply_markup = InlineKeyboardMarkup(menu_2)
        bot.edit_message_text(chat_id=query.message.chat_id,
                              message_id=query.message.message_id,
                              text='Choose the option:',
                              reply_markup=reply_markup)
    elif query.data == 'm1_1':
        ...
    elif query.data == 'm1_2':
        ...
    # and so on for every callback_data option

...

# handlers
dispatcher.add_handler(CommandHandler('start', start))
dispatcher.add_handler(CallbackQueryHandler(menu_actions))

This code works but I have a feeling that it is kind of irrational — to build a long elif tree.

Moreover, I can't figure out how to give to the user an option to get back to the main menu from second level menus (since the main menu is located in another handler and I can't catch it with a callback from CallbackQueryHandler).

So the question is — what is the best practice to build that kind of menu systems?


Solution

  • great answer by @dzNET. But it won't work in V12 so I changed a little bit

    from telegram.ext import CommandHandler, CallbackQueryHandler
    from telegram import InlineKeyboardButton, InlineKeyboardMarkup
    ############################### Bot ############################################
    def start(update, context):
      update.message.reply_text(main_menu_message(),
                                reply_markup=main_menu_keyboard())
    
    def main_menu(update,context):
      query = update.callback_query
      query.answer()
      query.edit_message_text(
                            text=main_menu_message(),
                            reply_markup=main_menu_keyboard())
    
    def first_menu(update,context):
      query = update.callback_query
      query.answer()
      query.edit_message_text(
                            text=first_menu_message(),
                            reply_markup=first_menu_keyboard())
    
    def second_menu(update,context):
      query = update.callback_query
      query.answer()
      query.edit_message_text(
                            text=second_menu_message(),
                            reply_markup=second_menu_keyboard())
    
    # and so on for every callback_data option
    def first_submenu(bot, update):
      pass
    
    def second_submenu(bot, update):
      pass
    
    ############################ Keyboards #########################################
    def main_menu_keyboard():
      keyboard = [[InlineKeyboardButton('Option 1', callback_data='m1')],
                  [InlineKeyboardButton('Option 2', callback_data='m2')],
                  [InlineKeyboardButton('Option 3', callback_data='m3')]]
      return InlineKeyboardMarkup(keyboard)
    
    def first_menu_keyboard():
      keyboard = [[InlineKeyboardButton('Submenu 1-1', callback_data='m1_1')],
                  [InlineKeyboardButton('Submenu 1-2', callback_data='m1_2')],
                  [InlineKeyboardButton('Main menu', callback_data='main')]]
      return InlineKeyboardMarkup(keyboard)
    
    def second_menu_keyboard():
      keyboard = [[InlineKeyboardButton('Submenu 2-1', callback_data='m2_1')],
                  [InlineKeyboardButton('Submenu 2-2', callback_data='m2_2')],
                  [InlineKeyboardButton('Main menu', callback_data='main')]]
      return InlineKeyboardMarkup(keyboard)
    
    ############################# Messages #########################################
    def main_menu_message():
      return 'Choose the option in main menu:'
    
    def first_menu_message():
      return 'Choose the submenu in first menu:'
    
    def second_menu_message():
      return 'Choose the submenu in second menu:'
    
    ############################# Handlers #########################################
    updater = Updater('YOUR_TOKEN_HERE', use_context=True)
    
    updater.dispatcher.add_handler(CommandHandler('start', start))
    updater.dispatcher.add_handler(CallbackQueryHandler(main_menu, pattern='main'))
    updater.dispatcher.add_handler(CallbackQueryHandler(first_menu, pattern='m1'))
    updater.dispatcher.add_handler(CallbackQueryHandler(second_menu, pattern='m2'))
    updater.dispatcher.add_handler(CallbackQueryHandler(first_submenu,
                                                        pattern='m1_1'))
    updater.dispatcher.add_handler(CallbackQueryHandler(second_submenu,
                                                        pattern='m2_1'))
    
    updater.start_polling()
    

    Again thanks to @dzNET