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