Шаблон телеграмм бота на go

от автора

Добрый день, сегодня я поделюсь с вами, на мой взгляд, довольно удачным шаблоном для телеграмм ботов на 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *