如何基于go-telegram-bot-api实现Telegram多层菜单导航
Telegram多层菜单导航实现方案(基于go-telegram-bot-api/v5)
问题描述
基于github.com/go-telegram-bot-api/telegram-bot-api/v5实现Telegram多层菜单导航时,遇到两个核心问题:
- 无法在
Page变量中添加Callback,避免循环引用问题 - 需要保存用户当前所在页面及点击的按钮状态
目前未找到合适的GitHub示例及有效解决方案。
现有代码
package main import ( "log" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" ) type Page struct { Name string Buttons [][]tgbotapi.KeyboardButton Parent *Page Children []*Page Callback func(update tgbotapi.Update, page *Page, bot *tgbotapi.BotAPI) } func sendPage(ChatID int64, page *Page, bot *tgbotapi.BotAPI) { buttons := tgbotapi.NewReplyKeyboard(page.Buttons...) message := tgbotapi.NewMessage(ChatID, page.Name) message.ReplyMarkup = buttons _, err := bot.Send(message) if err != nil { log.Println("Error sending message: ", err) } } var ( mainMenuPage = &Page{ Name: "Главное меню", Buttons: [][]tgbotapi.KeyboardButton{ { tgbotapi.NewKeyboardButton("Профиль"), tgbotapi.NewKeyboardButton("Кошелек"), }, { tgbotapi.NewKeyboardButton("Пустая кнопка"), tgbotapi.NewKeyboardButton("Фидбек"), }, }, Parent: nil, Children: []*Page{}, Callback: nil, } ) var ( profileMenuPage = &Page{ Name: "Профиль", Buttons: [][]tgbotapi.KeyboardButton{ { tgbotapi.NewKeyboardButton("Мой профиль"), tgbotapi.NewKeyboardButton("Статистика"), }, { tgbotapi.NewKeyboardButton("Добавить кошелек"), tgbotapi.NewKeyboardButton("Назад"), }, }, Parent: nil, Children: []*Page{}, Callback: nil, } ) func main() { // 初始化机器人并创建更新接收通道 bot, err := tgbotapi.NewBotAPI("token") if err != nil { log.Panic(err) } bot.Debug = true log.Printf("Authorized on account %s", bot.Self.UserName) u := tgbotapi.NewUpdate(0) u.Timeout = 60 updates := bot.GetUpdatesChan(u) sentMainMenu := false // 更新处理器 for update := range updates { if update.Message != nil { // 回复用户消息 msg := tgbotapi.NewMessage(update.Message.Chat.ID, update.Message.Text) bot.Send(msg) // 首次发送主菜单 if !sentMainMenu { sendPage(update.Message.Chat.ID, mainMenuPage, bot) sentMainMenu = true } } } }
解决方案
1. 解决Callback循环引用问题
不在Page结构体中直接绑定Callback,改用按钮文本-处理函数映射的方式,在更新处理器中动态匹配执行逻辑,彻底避免循环引用。
示例:
// 按钮文本对应的处理函数映射 var buttonHandlers = map[string]func(update tgbotapi.Update, bot *tgbotapi.BotAPI){ "Профиль": func(update tgbotapi.Update, bot *tgbotapi.BotAPI) { sendPage(update.Message.Chat.ID, profileMenuPage, bot) }, "Назад": func(update tgbotapi.Update, bot *tgbotapi.BotAPI) { chatID := update.Message.Chat.ID currentPage := userPages[chatID] // 返回父页面,无父页面则回到主菜单 if currentPage.Parent != nil { sendPage(chatID, currentPage.Parent, bot) } else { sendPage(chatID, mainMenuPage, bot) } }, "Мой профиль": func(update tgbotapi.Update, bot *tgbotapi.BotAPI) { msg := tgbotapi.NewMessage(update.Message.Chat.ID, "这里是你的个人信息展示区域") bot.Send(msg) }, }
2. 保存用户页面状态
使用map[int64]*Page存储每个用户(以ChatID为标识)当前所在页面,每次发送页面时更新该映射。
示例:
// 存储用户当前页面,生产环境建议改用数据库或缓存 var userPages = make(map[int64]*Page) // 改造sendPage函数,添加页面状态更新 func sendPage(ChatID int64, page *Page, bot *tgbotapi.BotAPI) { buttons := tgbotapi.NewReplyKeyboard(page.Buttons...) message := tgbotapi.NewMessage(ChatID, page.Name) message.ReplyMarkup = buttons _, err := bot.Send(message) if err != nil { log.Println("Error sending message: ", err) return } // 更新用户当前页面 userPages[ChatID] = page }
3. 完整更新处理器改造
func main() { bot, err := tgbotapi.NewBotAPI("token") if err != nil { log.Panic(err) } bot.Debug = true log.Printf("Authorized on account %s", bot.Self.UserName) u := tgbotapi.NewUpdate(0) u.Timeout = 60 updates := bot.GetUpdatesChan(u) for update := range updates { if update.Message != nil { chatID := update.Message.Chat.ID text := update.Message.Text // 用户首次交互时发送主菜单 if _, exists := userPages[chatID]; !exists { sendPage(chatID, mainMenuPage, bot) continue } // 匹配按钮处理逻辑 if handler, exists := buttonHandlers[text]; exists { handler(update, bot) } else { // 处理未知输入 msg := tgbotapi.NewMessage(chatID, "未知命令,请使用菜单按钮操作") bot.Send(msg) } } } }
4. 页面层级关联优化
通过init函数完成页面父子关系绑定,让层级结构更清晰:
func init() { mainMenuPage.Children = append(mainMenuPage.Children, profileMenuPage) profileMenuPage.Parent = mainMenuPage }
以上改造既解决了循环引用问题,又能稳定保存用户页面状态,实现完整的多层菜单导航逻辑。
内容的提问来源于stack exchange,提问作者hey
相关产品推荐
相关产品推荐

