Добрый день, сегодня я поделюсь с вами, на мой взгляд, довольно удачным шаблоном для телеграмм ботов на go
В нем заложена большая часть популярных сценариев работы и его расширение не должно вызывать проблем.
Функционал шаблона
-
Выполнение запросов в Горутинах
-
Выполнение команд из консоли
-
Хранение контекста во время выполнения
-
Вывод клавиатур и сообщений с локализацией
-
Возвращение к предыдущему меню
-
Заполняемые формы
-
Постраничные списки
Для тех кому не терпеться взглянуть добро пожаловать в GitHub.
Заранее прошу меня простить если покорёжит от стиля кода, шаблон я составил на 2й день изучения языка и ещё не успел впитать в себя его дух.
Шаблон основан на идеи единой архитектуры системы обработки сообщений, где функционал можно добавлять виде модулей.
Все возможные действия которые может выполнять бот заключены в одну абстрактную функцию Run (Run go ! Run !).
Для хранения контекста запуска этих функций создана структура CallStack
type Run func(CallStack) CallStack type CallStack struct { ChatID int64 Bot *tgBotAPI.BotAPI Update *tgBotAPI.Update Action Run IsPrint bool Parent *CallStack Data string } var userRuns = map[int64]CallStack{} stack := userRuns[ID] if stack.Action != nil { stack.Update = &update userRuns[ID] = userRuns[ID].Action(stack) } else { if update.Message != nil { userRuns[ID] = RunTemplate(CallStack{ ChatID: ID, Bot: bot, Update: &update, IsPrint: true, }) } }
В BotLoop реализован готовый цикл обработки сообщений.
Для разделения стеков контекстов выполнения используется map, где ключ это ID чата, но Вы можете выбрать свой, например логин пользователя.
Если в хранилище есть готовый стек контекста, передаем ему обновление и запускаем Run, который вернёт контекст для следующей обработки.
Если действие не задано возвращаем пользователя в начальную точку.
RunTemplate это шаблон реализации Run, он заключает в себе сразу 3 вещи:
-
Вывод интереса для пользователя.
-
Обработка сообщений.
-
Инициализация нового контекста.
Это делает его самодостаточным модулем для бота, который можно переносить из проекта в проект.
Далее повествование уходит в код:
Это общий шаблон для всех Run func RunTemplate(stack CallStack) CallStack { Когда Run получает свой контекст он выставляет в нём себя в качетве выполняемого действия. Далее практически всё происходит на его основе контекста stack.Action = RunTemplate Информация о пользователе data := userDatas[stack.ChatID] if stack.IsPrint { Если нужно что-то передать пользователю пишем это тут Если передачу нужно повторять при повсторном заходе в Run Уберите это снятие флага, но обработку сообщений тогда нужно будет вынести из else stack.IsPrint = false Тут пример вывода локализованного форматированого сообщения с прикреплёнными кнопками Не пишите текст прямо в Run, заносите шаблоны для вывода в MessageTemplates msg := tgBotAPI.NewMessage(stack.ChatID, fmt.Sprintf(SelectTemplate("RunTemplate", data.languageСode), data.firstName, )) mainMenuInlineKeyboard := tgBotAPI.NewInlineKeyboardMarkup( tgBotAPI.NewInlineKeyboardRow( tgBotAPI.NewInlineKeyboardButtonData(SelectTemplate("back", data.languageСode), "back"), ), ) msg.ReplyMarkup = mainMenuInlineKeyboard _, _ = stack.Bot.Send(msg) Тут отчищается прошлый набор кнопок Также можно задать свой набор для отображания mainMenuKeyboard := tgBotAPI.NewRemoveKeyboard(true) msg = tgBotAPI.NewMessage(stack.ChatID, "") msg.ReplyMarkup = mainMenuKeyboard _, _ = stack.Bot.Send(msg) И так Run подготовил интерфейс и возвратил контекст выполнения для его сохранения Исходя из этого, следующеt сообщение пользователя будет обработанно в этом же Run return stack } else { И так, пользователь ввел сообщение, вызвал комманду или другим образом попытался сломать бота ) Сообщения if stack.Update.Message != nil { switch stack.Update.Message.Text { case "back": { return ReturnOnParent(stack) } } } Кнопки с inline if stack.Update != nil { // Processing a message if stack.Update.CallbackQuery != nil { switch stack.Update.CallbackQuery.Data { case "back": { stack.Data = stack.Update.CallbackQuery.Data return ReturnOnParent(stack) } } } } Комманды if update.Message.IsCommand() { switch update.Message.Command() { case "back": { return ReturnOnParent(stack) } } } и т.д. } Если произошло что-то не предвиденное и код пришел сюда можно просто отправить этот же контекст для следующей обработки return stack Или применить Затычку return Chop(stack) } func Chop(stack CallStack) CallStack { Затычка выведет сообшение ввиде картинки, которая сообщает что пользователь достиг не проработанной конечной точки photo := tgBotAPI.NewPhoto(stack.ChatID, chopFile) photo.ReplyMarkup = tgBotAPI.NewRemoveKeyboard(true) _, _ = stack.Bot.Send(photo) И попытается вернуть пользователя назад return ReturnOnParent(stack) } func ReturnOnParent(stack CallStack) CallStack { if stack.Parent != nil { stack.Parent.IsPrint = true return stack.Parent.Action(*stack.Parent) } return RunTemplate(CallStack{ IsPrint: true, ChatID: stack.ChatID, Bot: stack.Bot, }) }
Это достаточно гибкая архитектура, изменяя Run можно добиться множество интересных эффектов.
-
Сохранение стека контекста при остановке и восстановление при включении.
-
Если добавить рефлексии можно сделать бота полностью конфигурируемого внешними файлами.
-
Изменение логики работы во время работы.
Ниже пример работы бота простроенного на данном шаблоне
ссылка на оригинал статьи https://habr.com/ru/articles/794098/
Добавить комментарий