Go telegram bot template
Good afternoon, today I will share with you, in my opinion, a rather successful template for telegram bots on go
It contains most of the popular work scenarios and its expansion should not cause problems.
Template functionality
Executing requests in Goroutines
Executing commands from the console
Storing context at runtime
Display keyboards and messages with localization
Return to previous menu
For those who can’t wait to take a look, welcome to GitHub
https://github.com/AnderKot/Go-TG-Bot-Template
I ask you to forgive me in advance if I get confused by the style of the code; I created the template on the 2nd day of learning the language and have not yet had time to absorb its spirit.
The template is based on the idea of a unified message processing system architecture, where functionality can be added in the form of modules.
All possible actions that a bot can perform are enclosed in one abstract function Run (Run go! Run!).
A CallStack structure was created to store the launch context of these functions.
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 implements a ready-made message processing loop.
To separate stacks of execution contexts, map is used, where the key is the chat ID, but you can choose your own, for example, a user login.
If the repository has a ready-made context stack, we pass the update to it and run Run, which will return the context for the next processing.
If the action is not specified, we return the user to the starting point.
RunTemplate is a Run implementation template, it contains 3 things at once
Displaying interest for the user
Message processing
Initializing a new context
This makes it a self-contained bot module that can be transferred from project to project.
Then the narrative goes into code
Это общий шаблон для всех 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 {
Это или предыдущий Run из стека
if stack.Parent != nil {
stack.Parent.IsPrint = true
return *stack.Parent
}
Или начальная точка
return RunTemplate(CallStack{
IsPrint: true,
ChatID: stack.ChatID,
Bot: stack.Bot,
})
}
This is a fairly flexible architecture; by changing Run you can achieve many interesting effects.
Input menu with information returning to the previous Run
Run Sequence Passthrough
Saving the context stack when stopped and restored when turned on
If you add reflection, you can make the bot completely configurable with external files
Changing the execution sequence of Runs during operation