使用 python-telegram-bot 构建菜单的正确方法

问题描述 投票:0回答:4

我与

python-telegram-bot
一起工作,并尝试像 BotFather 机器人那样构建一个嵌套菜单系统。例如,您有一个通用机器人菜单

pic.1

您可以在其中选择“编辑机器人”并获得新的相应菜单

pic.2

可以选择返回上一个菜单。

我尝试用代码来实现这一点:

# 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))

这段代码可以工作,但我感觉它有点不合理——构建一棵长

elif
树。

此外,我不知道如何为用户提供从二级菜单返回主菜单的选项(因为主菜单位于另一个处理程序中,并且我无法通过

 的回调捕获它) CallbackQueryHandler
)。

所以问题是——构建这种菜单系统的最佳实践是什么?

python menu bots telegram python-telegram-bot
4个回答
30
投票

您应该在

pattern
中使用参数
CallbackQueryHandler
。使用键盘和消息的类或函数也是一件好事。
要返回主菜单,请在子菜单中添加具有特定回调模式的返回按钮。

请注意:您在菜单中使用

edit_message_text
。这意味着如果您从任何菜单中使用
start
方法调用
reply_text
函数,则不会发生任何事情。

带有函数的完整工作示例:

#!/usr/bin/env python3.8
from telegram.ext import Updater
from telegram.ext import CommandHandler, CallbackQueryHandler
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
############################### Bot ############################################
def start(bot, update):
  bot.message.reply_text(main_menu_message(),
                         reply_markup=main_menu_keyboard())

def main_menu(bot, update):
  bot.callback_query.message.edit_text(main_menu_message(),
                          reply_markup=main_menu_keyboard())

def first_menu(bot, update):
  bot.callback_query.message.edit_text(first_menu_message(),
                          reply_markup=first_menu_keyboard())

def second_menu(bot, update):
  bot.callback_query.message.edit_text(second_menu_message(),
                          reply_markup=second_menu_keyboard())

def first_submenu(bot, update):
  pass

def second_submenu(bot, update):
  pass

def error(update, context):
    print(f'Update {update} caused error {context.error}')

############################ Keyboards #########################################
def main_menu_keyboard():
  keyboard = [[InlineKeyboardButton('Menu 1', callback_data='m1')],
              [InlineKeyboardButton('Menu 2', callback_data='m2')],
              [InlineKeyboardButton('Menu 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('XXXXXXXXX:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', 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.dispatcher.add_error_handler(error)

updater.start_polling()
################################################################################

抱歉,我的选项卡中有两个空格。 :)

UPD:修复子菜单对象。


16
投票

@dzNET 的回答很好。但是在V12中不行所以我改变了一点

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()

再次感谢@dzNET


2
投票

我在 python-telegram-bot 之上编写了一个包装器来发送键盘和内联消息: https://github.com/mevellea/telegram_menu


from telegram_menu import TelegramMenuSession, BaseMessage, NavigationHandler, ButtonType


class BotMessage(BaseMessage):

    def __init__(self, navigation_handler: NavigationHandler, bot_name: str):
        super().__init__(navigation_handler, label="BotMessage", inlined=True, home_after=True)
        self.bot_name = bot_name

    def update(self):
        self.keyboard = []
        self.add_button("API token", callback=self.do_something, btype=ButtonType.MESSAGE)
        self.add_button(":door:", callback=self.do_something, btype=ButtonType.MESSAGE)
        return f"What do you want to do with {self.bot_name}?"

    def do_something(self) -> str:
        return f"[{self.bot_name}] do something"


class EntryPointMessage(BaseMessage):

    def __init__(self, navigation: NavigationHandler) -> None:
        super().__init__(navigation, label="StartMessage")

    def update(self):
        self.add_button(label="First bot", callback=BotMessage(self.navigation, bot_name="Bot1"))
        self.add_button(label="Second bot", callback=BotMessage(self.navigation, bot_name="Bot2"))
        return "App entry point"


session = TelegramMenuSession(API_TOKEN)
session.start(EntryPointMessage, idle=True)

我希望这有帮助。


0
投票

只是为了分享我在这篇文章中其他用户提供的所有帮助下制作的工作版本,因为原始代码在当前版本中不起作用,但这个版本可以:)

from telegram.ext import CommandHandler, CallbackQueryHandler
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram import ForceReply, Update
from telegram.ext import Updater
import configparser
from telegram.ext import *


config = configparser.ConfigParser()
config.read("config/config.ini")

bot_token=config["TelegramAPI"]["bot_token"]
admins=config["Users"]["admins"]

############################### Bot ############################################
async def start(update, context):
  await update.message.reply_text(await main_menu_message(),
                            reply_markup=await main_menu_keyboard())

async def main_menu(update,context):
  query = update.callback_query
  await query.answer()
  await query.edit_message_text(
                        text=await main_menu_message(),
                        reply_markup=await main_menu_keyboard())

async def first_menu(update,context):
  query = update.callback_query
  await query.answer()
  await query.edit_message_text(
                        text=await first_menu_message(),
                        reply_markup=await first_menu_keyboard())

async def second_menu(update,context):
  query = update.callback_query
  await query.answer()
  await query.edit_message_text(
                        text=await second_menu_message(),
                        reply_markup=await second_menu_keyboard())
  
async def third_menu(update,context):
  query = update.callback_query
  await query.answer()
  await query.edit_message_text(
                        text=await third_menu_message(),
                        reply_markup=await third_menu_keyboard())  

# and so on for every callback_data option
async def first_submenu(bot, update):
  pass

async def second_submenu(bot, update):
  pass

############################ Keyboards #########################################
async 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)

async 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)

async 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)

async def third_menu_keyboard():
  keyboard = [[InlineKeyboardButton('Submenu 3-1', callback_data='m2_1')],
              [InlineKeyboardButton('Submenu 3-2', callback_data='m2_2')],
              [InlineKeyboardButton('Main menu', callback_data='main')]]
  return InlineKeyboardMarkup(keyboard)

############################# Messages #########################################
async def main_menu_message():
  return 'Choose the option in main menu:'

async def first_menu_message():
  return 'Choose the submenu in first menu:'

async def second_menu_message():
  return 'Choose the submenu in second menu:'

async def third_menu_message():
  return 'Choose the submenu in second menu:'

############################# Handlers #########################################
# Create the Application and pass it your bot's token.
def main() -> None:
    application = Application.builder().token(bot_token).build()

    application.add_handler(CommandHandler('start', start))
    application.add_handler(CallbackQueryHandler(main_menu, pattern='main'))
    application.add_handler(CallbackQueryHandler(first_menu, pattern='m1'))
    application.add_handler(CallbackQueryHandler(second_menu, pattern='m2'))
    application.add_handler(CallbackQueryHandler(third_menu, pattern='m3'))

    application.add_handler(CallbackQueryHandler(first_submenu,
                                                        pattern='m1_1'))
    application.add_handler(CallbackQueryHandler(second_submenu,
                                                        pattern='m2_1'))

    application.run_polling(allowed_updates=Update.ALL_TYPES)

if __name__ == "__main__":
    main()

© www.soinside.com 2019 - 2024. All rights reserved.