3

I'm trying to create a telegram conversation bot using python-telegram-bot package and I wondered what is the best practice for implementing a "back to main menu" button for each state in the conversation.

For some reason, I feels like i'm doing it the wrong way. The back to main menu button actually does nothing except to drawing the main menu buttons which should be immediate, but still it's usually takes 0.5-3 seconds and sometimes even longer (Only one user at a time talking to the bot).

I'm using CallbackQueryHandler(back_to_main_menu, pattern='^main_menu$') for each state basically which I'm quite sure it's not the right way to do it although I couldn't find any other better solution so far.

Notice: the example below is just a little example of how I use the CallbackQueryHandler. My state machine got 13 states, in 10 of them I got a back to main menu button.

A short example of what I need:

import os
from telegram.ext import Updater, ConversationHandler, CommandHandler, CallbackQueryHandler
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, ReplyKeyboardRemove


FIRST, SECOND, MAIN_MENU = range(3)


def get_main_menu():
    return [[InlineKeyboardButton("First", callback_data='first')],
            [InlineKeyboardButton("Second", callback_data='second')]]


def add_main_menu_button(bot, update, message, menu):
    query = update.callback_query
    menu.append([InlineKeyboardButton("Back to main menu", callback_data="main_menu")])
    reply_markup = InlineKeyboardMarkup(menu)

    bot.edit_message_text(message, chat_id=query.message.chat_id, message_id=query.message.message_id,
                          reply_markup=reply_markup)


def back_to_main_menu(bot, update):
    query = update.callback_query
    reply_markup = InlineKeyboardMarkup(get_main_menu())
    bot.edit_message_text("Example example 2", chat_id=query.message.chat_id, message_id=query.message.message_id, reply_markup=reply_markup)

    return MAIN_MENU


def second(bot, update):
    add_main_menu_button(bot, update, "E e", list())


def first(bot, update):
    add_main_menu_button(bot, update, "T t", list())


def start(bot, update):
    reply_markup = InlineKeyboardMarkup(get_main_menu())
    update.message.reply_text("Example example 1", reply_markup=reply_markup)

    return MAIN_MENU


def cancel(bot, update):
    update.message.reply_text('Bye! I hope we can talk again some day.', reply_markup=ReplyKeyboardRemove())

    return ConversationHandler.END


def main():
    updater = Updater(os.environ["BOT_TOKEN"])

    conversation_handler = ConversationHandler(entry_points=[CommandHandler('start', start)],

    states={FIRST: [CallbackQueryHandler(back_to_main_menu, pattern='^main_menu$')],
            SECOND: [CallbackQueryHandler(back_to_main_menu, pattern='^main_menu$')],
            MAIN_MENU: [CallbackQueryHandler(first, pattern='first'), 
                        CallbackQueryHandler(second)]},

    fallbacks=[CommandHandler('cancel', cancel)],
    allow_reentry=True
    )

    updater.dispatcher.add_handler(conversation_handler)

    updater.start_polling()
    updater.idle()

In addition, when I use multiple CallbackQueryHandler in a conversation I get the following warning:

WARNING:root:If 'per_message=False', 'CallbackQueryHandler' will not be tracked for every message

which makes me think I'm doing something wrong.

Thanks.

Ivan Vinogradov
  • 4,269
  • 6
  • 29
  • 39
OriK
  • 293
  • 1
  • 3
  • 13

1 Answers1

0

For each conversation you only need to specify one CallbackQueryHandler as a fallback. For example:

START = range(1)

start_conv_handler = ConversationHandler(entry_points=[CommandHandler('start', start)],
    states={
        START: [CommandHandler('start', start)]
    },
    fallbacks= CallbackQueryHandler(main_callback_handler)]
)

And then within that single CallbackQueryHandler you can plan all the neceesary actions you want your buttons to perform. Since you can add whatever information you need to the query that the handler receive, you can play with more complicated scenarios by putting return CallbackQueryHandler at the end of each function that describes a conversation step (start in my example).

So it would look like:

def start(bot, update):
  ... your code here
  return CallbackQueryHandler 
  • It won't help, first you need to return the next state in the start function and not the handler. second in my case, I need to have a state that listens both to text and buttons which won't work in your solution – OriK May 10 '18 at 08:58