pythonclassbotstelegraminstance-methods

Python Telegram Bot. How to use ConversationQueryCallback with instance method as callback?


I'm setting up a wrapper around the library python-telegram-bot, just because i don't like to build a bot writing 1000k lines of spare functions and dispatcher adds.

class TelegramBot:
    _types = {
        "_command_callback": CommandHandler,
        "_message_callback": MessageHandler,
        "_conversation_callback": ConversationHandler
    }

    def __init__(self, token=token):
        self.updater = Updater(token, use_context=True)
        self.dispatcher = self.updater.dispatcher

        self.add_dispatchers()

    def add_dispatchers(self):
        for _type, _class in self._types.items():
            handler_functions = list(filter(lambda x: x.endswith(_type), dir(self)))
            for handler in handler_functions:
                attr_name = handler.replace('callback', "handler")
                if _type == '_message_callback':
                    setattr(self, attr_name, _class(Filters.text, getattr(self, handler)))
                elif _type == '_command_callback':
                    setattr(self, attr_name, _class(handler.replace(_type, ''), getattr(self, handler)))
                elif _type == '_conversation_callback':
                    setattr(self, attr_name, _class(*getattr(self, handler)))
                self.dispatcher.add_handler(getattr(self, attr_name))

    def get_handler(self, name, type):
        _name = f"{name}_{type}_handler"
        return getattr(self, _name)

    def start_polling(self):
        self.updater.start_polling()
        self.updater.idle()

With this simple class, now I can simply do something like:

class MyCustomBot(TelegramBot):

    def test_command_callback(self, update, context):
        update.message.reply_text("Testing command")

    def echo_message_callback(self, update, context):
        update.message.reply_text(update.message.text)

my_bot = MyCustomBot()
my_bot.start_polling()

With these 7 lines above I define a command handler and a simple message handler. Pretty clean.

Now I'd like to add a conversation handler. Outside the class, everything works fine, but if I define MyCustomBot like this:

class MyCustomBot(TelegramBot):

    def test_command_callback(self, update, context):
        update.message.reply_text("Testing command")

    def echo_message_callback(self, update, context):
        update.message.reply_text(update.message.text)

    @property
    def test_conversation_callback(self):
        entry_points = [self.get_handler('search', 'command')]
        states = {
            1: [CallbackQueryHandler(self.test_command_callback)]
        }
        fallbacks = entry_points
        return entry_points, states, fallbacks

    def search_command_callback(self, update, context):
        keyboard = [
            [InlineKeyboardButton("1", callback_data=str(1)),
             InlineKeyboardButton("2", callback_data=str(2))]
        ]
        reply_markup = InlineKeyboardMarkup(keyboard)
        # Send message with text and appended InlineKeyboard
        update.message.reply_text(
            "Start handler, Choose a route",
            reply_markup=reply_markup
        )

        return 1


bot = MyCustomBot()
bot.start_polling()

It doesn't work. If i fire /search, it sends me the keyboard correctly, but then, if I press a button I see a warning passing trough the logs saying "conversation returned status None".

I think that the problem might be here maybe?

states = {
            1: [CallbackQueryHandler(self.test_command_callback)]
        }

self.test_command_callback is an instance method, can CallbackQueryHandler use it as callback?

Or maybe I'm missing something else?


Solution

  • Finally I found what I was doing wrong.

    I registered search_command_callback to dispatcher as pure CommandHandler and also into entry_points of ConversationHandler.

    This does not work. Entry points can't be also registered CommandHandlers.

    I don't know if this is documented. In a couple of hours of debugging I didn't found this reported in documentation.