Фреймворк для ботов Max мессенджера. Роутер, middleware, контекст, группы — вдохновлён Echo и telebot.
go get github.com/maxigo-bot/maxigo-botТребуется Go 1.25+. Построен на maxigo-client — без внешних транзитивных зависимостей.
package main
import (
"log"
maxigobot "github.com/maxigo-bot/maxigo-bot"
)
func main() {
b, err := maxigobot.New("YOUR_BOT_TOKEN")
if err != nil {
log.Fatal(err)
}
b.Handle("/start", func(c maxigobot.Context) error {
return c.Send("Привет, " + c.Sender().FirstName + "!")
})
b.Handle(maxigobot.OnText, func(c maxigobot.Context) error {
return c.Reply("Вы написали: " + c.Text())
})
b.Start() // блокирует до вызова Stop()
}Бот настраивается через функциональные опции:
b, err := maxigobot.New("TOKEN",
maxigobot.WithLongPolling(30), // long polling с таймаутом 30с (по умолчанию)
maxigobot.WithClient(preConfiguredClient), // инжектировать готовый maxigo-client
maxigobot.WithUpdateTypes( // фильтр типов обновлений
"message_created",
"message_callback",
),
)| Опция | Описание |
|---|---|
WithLongPolling(timeout) |
Таймаут long polling в секундах (по умолчанию: 30) |
WithClient(client) |
Инжектировать готовый *maxigo.Client (полезно для тестов) |
WithUpdateTypes(types...) |
Фильтровать типы обновлений, которые получает поллер |
Для прямых API-вызовов:
client := b.Client() // *maxigo.Client
bot, err := client.GetBot(ctx)Max использует : как разделитель в командах (не пробел как в Telegram): /start:payload.
b.Handle("/start", func(c maxigobot.Context) error {
name := c.Sender().FirstName
payload := c.Payload() // текст после ":"
args := c.Args() // payload разбитый по пробелам
return c.Send("Добро пожаловать, " + name + "!")
})
b.Handle("/help", func(c maxigobot.Context) error {
return c.Send("Доступные команды: /start, /help")
})// Текстовые сообщения (не команды)
b.Handle(maxigobot.OnText, func(c maxigobot.Context) error {
return c.Reply("Вы написали: " + c.Text())
})
// Любое сообщение (catch-all для message_created)
b.Handle(maxigobot.OnMessage, func(c maxigobot.Context) error {
return c.Send("Получено сообщение")
})
// Хуки жизненного цикла
b.Handle(maxigobot.OnBotStarted, func(c maxigobot.Context) error {
return c.Send("Добро пожаловать! Используйте /help для списка команд.")
})
b.Handle(maxigobot.OnBotAdded, func(c maxigobot.Context) error {
return c.Send("Спасибо, что добавили меня!")
})
b.Handle(maxigobot.OnEdited, func(c maxigobot.Context) error {
log.Printf("Сообщение отредактировано: %s", c.Text())
return nil
})// Отправляем сообщение с инлайн-клавиатурой
b.Handle("/menu", func(c maxigobot.Context) error {
return c.Send("Выберите действие:",
maxigobot.WithAttachments(
maxigo.NewInlineKeyboardAttachment([][]maxigo.Button{
{
maxigo.NewCallbackButtonWithIntent("Подтвердить", "confirm", maxigo.IntentPositive),
maxigo.NewCallbackButtonWithIntent("Отмена", "cancel", maxigo.IntentNegative),
},
}),
),
)
})
// Обработка конкретного callback
b.Handle(maxigobot.OnCallback("confirm"), func(c maxigobot.Context) error {
return c.Respond("Подтверждено!")
})
b.Handle(maxigobot.OnCallback("cancel"), func(c maxigobot.Context) error {
return c.Respond("Отменено.")
})
// Catch-all обработчик (пустая строка — любой неподходящий callback)
b.Handle(maxigobot.OnCallback(""), func(c maxigobot.Context) error {
log.Printf("Неизвестный callback: %s", c.Data())
return c.Respond("Неизвестное действие")
})Для обновлений message_created роутер ищет обработчики в таком порядке:
- Точная команда (
/start,/help, ...) — совпадает первой OnText— fallback для текстовых сообщений (включая ненайденные команды)OnMessage— catch-all для любых сообщений (фото, стикеры и т.д.)
Для callback:
- Точный payload (
OnCallback("confirm")) — совпадает первым OnCallback("")— catch-all для неподходящих callback
| Константа | Тип обновления | Описание |
|---|---|---|
OnText |
message_created |
Текстовое сообщение (не команда) |
OnMessage |
message_created |
Любое сообщение (catch-all) |
OnEdited |
message_edited |
Сообщение отредактировано |
OnRemoved |
message_removed |
Сообщение удалено |
OnBotStarted |
bot_started |
Пользователь нажал Start |
OnBotStopped |
bot_stopped |
Пользователь остановил бота |
OnBotAdded |
bot_added |
Бот добавлен в чат |
OnBotRemoved |
bot_removed |
Бот удалён из чата |
OnUserAdded |
user_added |
Пользователь добавлен в чат |
OnUserRemoved |
user_removed |
Пользователь удалён из чата |
OnChatTitleChanged |
chat_title_changed |
Название чата изменено |
OnChatCreated |
message_chat_created |
Чат создан через кнопку |
OnDialogMuted |
dialog_muted |
Диалог замьючен |
OnDialogUnmuted |
dialog_unmuted |
Диалог размьючен |
OnDialogCleared |
dialog_cleared |
История диалога очищена |
OnDialogRemoved |
dialog_removed |
Диалог удалён |
OnCallback("id") |
message_callback |
Callback с конкретным payload |
Middleware оборачивает обработчики для добавления сквозной логики (логирование, авторизация, recovery и т.д.).
type HandlerFunc func(c Context) error
type MiddlewareFunc func(next HandlerFunc) HandlerFuncPre-middleware — выполняется для ВСЕХ обновлений, до роутинга:
b.Pre(recoverMiddleware)
b.Pre(requestIDMiddleware)Use-middleware — выполняется только для совпавших обработчиков, после роутинга:
b.Use(loggerMiddleware)
b.Use(authMiddleware)Обновление → Pre-middleware → Роутинг → Use-middleware → Group middleware → Per-handler middleware → Обработчик
Если обработчик не найден, Use-middleware и далее не выполняются.
// Logger — логирует каждый обработанный апдейт
func Logger() maxigobot.MiddlewareFunc {
return func(next maxigobot.HandlerFunc) maxigobot.HandlerFunc {
return func(c maxigobot.Context) error {
start := time.Now()
err := next(c)
log.Printf("[%d] %s (%v)", c.Chat(), c.Text(), time.Since(start))
return err
}
}
}
// Recover — перехватывает паники в обработчиках
func Recover() maxigobot.MiddlewareFunc {
return func(next maxigobot.HandlerFunc) maxigobot.HandlerFunc {
return func(c maxigobot.Context) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v", r)
}
}()
return next(c)
}
}
}
// Whitelist — разрешает только определённым пользователям
func Whitelist(allowed ...int64) maxigobot.MiddlewareFunc {
set := make(map[int64]bool, len(allowed))
for _, id := range allowed {
set[id] = true
}
return func(next maxigobot.HandlerFunc) maxigobot.HandlerFunc {
return func(c maxigobot.Context) error {
if sender := c.Sender(); sender != nil && set[sender.UserID] {
return next(c)
}
return c.Send("Доступ запрещён.")
}
}
}Можно привязать middleware к конкретному обработчику:
b.Handle("/admin", adminHandler, adminOnlyMiddleware, auditMiddleware)Они выполняются после глобальных и групповых middleware.
Группы предоставляют изолированные стеки middleware для подмножества обработчиков:
// Группа для администраторов
admin := b.Group()
admin.Use(Whitelist(123456, 789012))
admin.Handle("/ban", banHandler)
admin.Handle("/mute", muteHandler)
admin.Handle("/stats", statsHandler)
// Публичные обработчики — без admin middleware
b.Handle("/start", startHandler)
b.Handle("/help", helpHandler)Группы наследуют глобальные Use-middleware и добавляют свои поверх:
Обновление → Pre → Роутинг → глобальные Use → group middleware → per-handler middleware → Обработчик
Context предоставляет обработчику доступ к текущему обновлению и API бота. Новый контекст создаётся для каждого обновления.
b.Handle("/info", func(c maxigobot.Context) error {
// Пользователь, инициировавший обновление (nil для некоторых событий)
sender := c.Sender() // *maxigo.User
// ID чата, где произошло обновление (0, если недоступен)
chatID := c.Chat() // int64
// Исходное сообщение (nil для хуков жизненного цикла)
msg := c.Message() // *maxigo.Message
// Полный текст сообщения (пустая строка, если не текстовое)
text := c.Text() // string
// Сырое обновление из maxigo-client
upd := c.Update() // maxigo.Update
// context.Context с привязкой к запросу
ctx := c.Ctx() // context.Context
})// Для /greet:Иван Петров
b.Handle("/greet", func(c maxigobot.Context) error {
c.Command() // "greet"
c.Payload() // "Иван Петров"
c.Args() // ["Иван", "Петров"]
return c.Send("Привет, " + c.Payload() + "!")
})
// BotStartedUpdate тоже имеет payload
b.Handle(maxigobot.OnBotStarted, func(c maxigobot.Context) error {
ref := c.Payload() // start payload (deep link)
return c.Send("Добро пожаловать! Ref: " + ref)
})b.Handle(maxigobot.OnCallback("action"), func(c maxigobot.Context) error {
cb := c.Callback() // *maxigo.Callback
data := c.Data() // строка payload callback
// Ответить уведомлением
return c.Respond("Действие получено: " + data)
})// Отправить в текущий чат
c.Send("Привет!")
// Ответить на текущее сообщение
c.Reply("Понял!")
// Отредактировать текущее сообщение
c.Edit("Обновлённый текст")
// Удалить текущее сообщение
c.Delete()
// Отправить фото
c.SendPhoto(&maxigo.PhotoAttachmentRequestPayload{
Photos: tokens.Photos,
})
// Отправить индикатор набора текста
c.Notify(maxigo.ActionTypingOn)c.Send("Привет!",
maxigobot.WithReplyTo(messageID), // ответ на конкретное сообщение
maxigobot.WithNotify(false), // отключить уведомление
maxigobot.WithFormat(maxigo.FormatMarkdown), // форматирование markdown
maxigobot.WithAttachments( // инлайн-клавиатура
maxigo.NewInlineKeyboardAttachment(buttons),
),
maxigobot.WithDisableLinkPreview(), // без превью ссылок
)| Опция | Описание |
|---|---|
WithReplyTo(msgID) |
Ответить на конкретное сообщение |
WithNotify(bool) |
Включить/отключить уведомление для участников чата |
WithFormat(format) |
Формат текста: maxigo.FormatMarkdown или maxigo.FormatHTML |
WithAttachments(att...) |
Прикрепить файлы, клавиатуры, локации и т.д. |
WithDisableLinkPreview() |
Отключить генерацию превью ссылок |
// Уведомление (небольшой toast сверху)
c.Respond("Готово!")
// Алерт (диалог, который нужно закрыть)
c.RespondAlert("Вы уверены?")Контекст предоставляет потокобезопасное хранилище для передачи данных между middleware и обработчиками:
// В middleware
func AuthMiddleware() maxigobot.MiddlewareFunc {
return func(next maxigobot.HandlerFunc) maxigobot.HandlerFunc {
return func(c maxigobot.Context) error {
c.Set("user_role", "admin")
return next(c)
}
}
}
// В обработчике
b.Handle("/dashboard", func(c maxigobot.Context) error {
role := c.Get("user_role").(string)
return c.Send("Ваша роль: " + role)
})Для операций, не покрытых Context, используйте maxigo-client напрямую:
b.Handle("/members", func(c maxigobot.Context) error {
members, err := c.API().GetMembers(c.Ctx(), c.Chat(), maxigo.GetMembersOpts{Count: 100})
if err != nil {
return err
}
return c.Send(fmt.Sprintf("В чате %d участников", len(members.Members)))
})b.OnError = func(err error, c maxigobot.Context) {
log.Printf("Ошибка: %v", err)
if c != nil {
c.Send("Произошла ошибка. Попробуйте ещё раз.")
}
}Если OnError равен nil, ошибки логируются в stderr.
Методы контекста возвращают *BotError, когда операция не может быть выполнена:
var botErr *maxigobot.BotError
if errors.As(err, &botErr) {
log.Printf("Endpoint: %s, Err: %v", botErr.Endpoint, botErr.Err)
}| Ошибка | Причина |
|---|---|
ErrNoChatID |
В обновлении нет ID чата (попытка Send из события без чата) |
ErrNoMessage |
В обновлении нет сообщения (попытка Edit из хука жизненного цикла) |
ErrNoCallback |
Обновление не является callback (попытка Respond из текстового сообщения) |
ErrNilPhoto |
SendPhoto вызван с nil payload |
ErrAlreadyStarted |
Start() вызван более одного раза |
Бот автоматически восстанавливается после паник в обработчиках и в поллере. Перехваченные паники передаются в OnError (или логируются, если nil).
b, _ := maxigobot.New("TOKEN")
// Регистрация обработчиков и middleware...
// Start блокирует до вызова Stop()
go b.Start()
// Graceful shutdown.
// Сигнализирует поллеру остановиться, ждёт завершения текущих обработчиков.
b.Stop() // безопасно вызывать несколько разStart() паникует, если вызван более одного раза.
Используйте WithClient для инжекции мок-клиента:
func TestBot(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Мок ответов API
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(maxigo.SimpleQueryResult{Success: true})
}))
defer srv.Close()
client, _ := maxigo.New("test-token", maxigo.WithBaseURL(srv.URL))
b, _ := maxigobot.New("test-token", maxigobot.WithClient(client))
// Регистрация обработчиков, затем тестирование через processUpdate
// или отправку обновлений через канал поллера.
}Если вы переходите с telebot, API покажется знакомым. Вот полная таблица соответствия.
| Действие | Telebot v3 | maxigo-bot |
|---|---|---|
| Отправка | c.Send("text") |
c.Send("text") |
| Ответ | c.Reply("text") |
c.Reply("text") |
| Редактирование | c.Edit("text") |
c.Edit("text") |
| Удаление | c.Delete() |
c.Delete() |
| Уведомление | c.Notify(tele.Typing) |
c.Notify(maxigo.ActionTypingOn) |
| Отправитель | c.Sender() → *User |
c.Sender() → *maxigo.User |
| Чат | c.Chat() → *Chat |
c.Chat() → int64 |
| Текст | c.Text() |
c.Text() |
| Аргументы | c.Args() |
c.Args() |
| Данные callback | c.Data() |
c.Data() |
| Ответ на callback | c.Respond() |
c.Respond("") |
| Алерт | c.RespondAlert("text") |
c.RespondAlert("text") |
| Хранилище | c.Get(k) / c.Set(k, v) |
c.Get(k) / c.Set(k, v) |
| Бот | c.Bot() |
c.Bot() |
| Прямой API | b.Raw(method, params) |
c.API() → *maxigo.Client |
| Действие | Telebot v3 | maxigo-bot |
|---|---|---|
| До роутинга | нет | b.Pre(mw) |
| После роутинга | b.Use(mw) |
b.Use(mw) |
| Группа | g := b.Group() |
g := b.Group() |
| Per-handler | b.Handle(ep, h, mw) |
b.Handle(ep, h, mw) |
| Recover | middleware.Recover() |
middleware.Recover() |
| Logger | middleware.Logger() |
middleware.Logger() |
| AutoRespond | middleware.AutoRespond() |
middleware.AutoRespond() |
| Whitelist | middleware.Whitelist(ids...) |
middleware.Whitelist(ids...) |
| Blacklist | middleware.Blacklist(ids...) |
middleware.Blacklist(ids...) |
| Skipper | нет | все middleware поддерживают Skipper |
| Действие | Telebot v3 | maxigo-bot |
|---|---|---|
| Форматирование | c.Send("text", tele.ModeHTML) |
c.Send("text", WithFormat(maxigo.FormatHTML)) |
| Клавиатура | c.Send("text", &markup) |
c.Send("text", WithKeyboard(rows...)) |
| Без превью | c.Send("text", tele.NoPreview) |
c.Send("text", WithDisableLinkPreview()) |
| Ответ на | &tele.SendOptions{ReplyTo: msg} |
WithReplyTo(msgID) |
| Вложения | встроено в what interface{} |
WithAttachments(att...) |
| Telebot v3 | maxigo-bot | |
|---|---|---|
Pre() middleware |
нет (всё через Use) |
есть — до роутинга |
| Роутинг callback | &InlineButton{Unique} |
OnCallback("unique") |
| Send options | interface{} |
типизированные SendOption функции |
| Payload команд | /start payload (пробел) |
/start:payload (двоеточие, Max API) |
| Config pattern | нет | Echo-style WithConfig для middleware |
| Пакет | Описание |
|---|---|
| maxigo-client | Идиоматичный Go HTTP-клиент для Max Bot API (без внешних зависимостей) |
| maxigo-bot | Фреймворк для ботов с роутером, middleware и контекстом |
MIT