Современный Go-фреймворк для создания чат-ботов в мессенджере скаМ. Построен на базе исправленного max-bot-api-client-go и предоставляет мощный инструментарий для разработки сложных диалоговых сценариев.
Дальнейшая поддержка фреймворка возлагается на сообщество, в связи с тем что я больше не имею доступа к api ботов
- Flow-система — мощный инструмент для построения многошаговых диалогов с автоматическим управлением состоянием
- Управление сессиями — встроенная персистентность с поддержкой Memory и Redis
- Вложенные Flow — поддержка подфлоу с типизированной передачей данных между ними
- Фильтрация обновлений — точный контроль типов входящих данных на уровне каждого шага
- Dependency Injection — встроенный контейнер зависимостей для flow
- Middleware — гибкая система промежуточных обработчиков
- Type-safe API — строгая типизация для надежности кода
go get github.com/nlypage/maxigopackage main
import (
"log"
"os"
"github.com/nlypage/maxigo"
)
func main() {
bot, err := maxigo.New(os.Getenv("BOT_TOKEN"))
if err != nil {
log.Fatal(err)
}
// Обработка команды /start
bot.Handle(maxigo.OnStart, func(c maxigo.Context) error {
return c.Send("Привет! Я бот на Maxigo!")
})
// Обработка текстовых сообщений
bot.Handle(maxigo.OnText, func(c maxigo.Context) error {
return c.Reply("Вы написали: " + c.Text())
})
log.Println("Бот запущен!")
bot.Start()
}bot.Handle("/menu", func(c maxigo.Context) error {
kb := maxigo.NewKeyboard()
kb.Row(
kb.Data("Подтвердить", "btn_confirm", "").Positive(),
kb.Data("Отменить", "btn_cancel", "").Negative(),
)
kb.Row(
kb.URL("Открыть сайт", "https://example.com"),
)
return c.Send(
"Выберите действие:",
maxigo.WithKeyboard(kb),
)
})
// Обработка callback
bot.Handle(maxigo.OnCallback("btn_confirm"), func(c maxigo.Context) error {
_ = c.AnswerCallback("Подтверждено!")
return c.Edit("Действие подтверждено")
})Flow — это мощный инструмент для построения сложных многошаговых диалогов с пользователем. Он автоматически управляет состоянием сессии, обрабатывает переходы между шагами и поддерживает персистентность.
| Компонент | Описание |
|---|---|
| Flow | Последовательность шагов диалога |
| Step | Отдельный шаг с обработчиками показа и завершения |
| Session | Состояние пользователя в flow |
| Storage | Хранилище данных сессии |
| Controller | Интерфейс управления flow из обработчиков |
| Bus | Центральный координатор всех flow |
package main
import (
"log"
"os"
"time"
"github.com/nlypage/maxigo"
"github.com/nlypage/maxigo/flow"
"github.com/nlypage/maxigo/flow/storage"
)
func main() {
bot, _ := maxigo.New(os.Getenv("BOT_TOKEN"))
// Создаем Bus для управления flow
flowBus := flow.NewBus(bot, storage.NewMemoryStorage())
// Регистрируем middleware для восстановления сессий
bot.Use(flowBus.Restore())
// Регистрируем обработчики для flow
bot.Handle(maxigo.OnText, flowBus.Handle)
bot.Handle(maxigo.OnAnyCallback, flowBus.Handle)
// Создаем flow регистрации
registrationFlow, _ := flowBus.NewFlow("registration").
Steps(
// Шаг 1: Запрос имени
flow.NewStep("ask_name", func(c maxigo.Context, ctrl flow.Controller) error {
return c.Send("Как вас зовут?")
}).OnComplete(func(c maxigo.Context, ctrl flow.Controller) error {
name := c.Text()
ctrl.Storage().Set("name", name)
return ctrl.Jump("ask_age")
}),
// Шаг 2: Запрос возраста
flow.NewStep("ask_age", func(c maxigo.Context, ctrl flow.Controller) error {
name, _ := ctrl.Storage().GetString("name")
return c.Send("Приятно познакомиться, " + name + "! Сколько вам лет?")
}).OnComplete(func(c maxigo.Context, ctrl flow.Controller) error {
age := c.Text()
ctrl.Storage().Set("age", age)
return ctrl.Complete()
}),
).
WithTimeout(5 * time.Minute).
OnComplete(func(c maxigo.Context, ctrl flow.Controller) error {
name, _ := ctrl.Storage().GetString("name")
age, _ := ctrl.Storage().GetString("age")
return c.Send("Регистрация завершена!\nИмя: " + name + "\nВозраст: " + age)
}).
Build()
// Запуск flow по команде
bot.Handle("/register", registrationFlow.Start)
bot.Start()
}// Переход на другой шаг
ctrl.Jump("step_id")
// Завершение flow
ctrl.Complete()
// Отмена flow
ctrl.Cancel()// Сохранение данных
ctrl.Storage().Set("key", "value")
ctrl.Storage().Set("count", 42)
// Получение данных
name, ok := ctrl.Storage().GetString("name")
count, ok := ctrl.Storage().GetInt("count")
value, ok := ctrl.Storage().Get("key")
// Проверка наличия
if ctrl.Storage().Has("key") {
// ...
}
// Удаление
ctrl.Storage().Delete("key")
// Очистка всех данных
ctrl.Storage().Clear()Flow позволяет точно контролировать, какие типы обновлений обрабатывает каждый шаг:
flow.NewStep("ask_photo", func(c maxigo.Context, ctrl flow.Controller) error {
return c.Send("Отправьте фото")
}).
// Принимаем ТОЛЬКО фото, все остальное игнорируется
AcceptUpdates(maxigo.OnPhoto).
OnComplete(func(c maxigo.Context, ctrl flow.Controller) error {
photo := c.Message().Image()
ctrl.Storage().Set("photo_url", photo.Payload.Url)
return ctrl.Jump("next_step")
})maxigo.OnText— текстовые сообщенияmaxigo.OnPhoto— фотоmaxigo.OnVideo— видеоmaxigo.OnAudio— аудиоmaxigo.OnFile— файлыmaxigo.OnContact— контактыmaxigo.OnLocation— геолокацияmaxigo.OnSticker— стикерыmaxigo.OnAnyCallback— любые callback"btn_unique"— конкретный callback по unique ID
Исключения позволяют определенным обновлениям проходить через фильтр шага к глобальным обработчикам:
// Шаг принимает только текст, но кнопка "Помощь" обрабатывается глобально
flow.NewStep("ask_name", handler).
AcceptUpdates(maxigo.OnText).
AllowUpdateExceptions("btn_help") // Кнопка помощи пройдет к глобальному обработчику
// Глобальные исключения для всего flow (например, команда /cancel)
flowBus.NewFlow("my_flow").
WithGlobalExceptions(maxigo.OnText). // /cancel всегда работает
Steps(...)MessageCollector автоматически собирает и удаляет сообщения в рамках flow:
flow.NewStep("ask_name", func(c maxigo.Context, ctrl flow.Controller) error {
// Отправляем сообщение и автоматически добавляем в коллектор
return ctrl.MessageCollector().Send("Как вас зовут?")
}).OnComplete(func(c maxigo.Context, ctrl flow.Controller) error {
// Собираем сообщение пользователя для последующего удаления
_ = ctrl.MessageCollector().Collect(c.Message())
name := c.Text()
ctrl.Storage().Set("name", name)
return ctrl.Jump("next_step")
})
// При завершении flow удаляем все собранные сообщения
OnComplete(func(c maxigo.Context, ctrl flow.Controller) error {
_ = ctrl.MessageCollector().Clear(flow.ClearOptions{
IgnoreErrors: true, // Игнорировать ошибки удаления
})
return c.Send("Готово!")
})Maxigo поддерживает запуск одного flow из другого с автоматическим сохранением контекста и передачей данных:
// Вложенный flow для ввода адреса
addressFlow, _ := flowBus.NewFlow("address_input").
Steps(
flow.NewStep("ask_street", func(c maxigo.Context, ctrl flow.Controller) error {
return c.Send("Введите улицу:")
}).OnComplete(func(c maxigo.Context, ctrl flow.Controller) error {
ctrl.Storage().Set("street", c.Text())
return ctrl.Jump("ask_building")
}),
flow.NewStep("ask_building", func(c maxigo.Context, ctrl flow.Controller) error {
return c.Send("Введите номер дома:")
}).OnComplete(func(c maxigo.Context, ctrl flow.Controller) error {
ctrl.Storage().Set("building", c.Text())
return ctrl.Jump("finish")
}),
flow.NewStep("finish", func(c maxigo.Context, ctrl flow.Controller) error {
street, _ := ctrl.Storage().GetString("street")
building, _ := ctrl.Storage().GetString("building")
// Возвращаем результат в родительский flow
return ctrl.CompleteWithResult(map[string]interface{}{
"full_address": street + ", " + building,
"street": street,
"building": building,
})
}),
).
Build()
// Главный flow
mainFlow, _ := flowBus.NewFlow("registration").
Steps(
flow.NewStep("ask_address", func(c maxigo.Context, ctrl flow.Controller) error {
kb := maxigo.NewKeyboard()
kb.Row(kb.Data("Ввести адрес", "enter_address", ""))
return c.Send("Нужен адрес?", maxigo.WithKeyboard(kb))
}).OnComplete(func(c maxigo.Context, ctrl flow.Controller) error {
if cb := c.Callback(); cb != nil {
// Запускаем вложенный flow
return ctrl.StartFlow("address_input")
}
// После возврата из вложенного flow получаем результат
var address map[string]interface{}
if result, ok := ctrl.Storage().Get("nested_result:address_input"); ok {
address = result.(map[string]interface{})
fullAddr := address["full_address"].(string)
ctrl.Storage().Set("address", fullAddr)
}
return ctrl.Complete()
}),
).
Build()Для удобства можно использовать структуры:
type AddressResult struct {
FullAddress string `json:"full_address"`
Street string `json:"street"`
Building string `json:"building"`
}
// В вложенном flow
ctrl.CompleteWithResult(AddressResult{
FullAddress: fullAddr,
Street: street,
Building: building,
})
// В родительском flow
var address AddressResult
if err := ctrl.GetNestedResult("address_input", &address); err == nil {
ctrl.Storage().Set("address", address.FullAddress)
}Flow поддерживает внедрение зависимостей через контейнер:
// Определяем интерфейс сервиса
type UserService interface {
SaveUser(name string, age int) error
}
// Создаем контейнер и регистрируем зависимости
container := flow.NewContainer()
container.Set("userService", myUserService)
// Создаем flow с контейнером
myFlow, _ := flowBus.NewFlow("registration").
WithContainer(container).
Steps(
flow.NewStep("save_user", func(c maxigo.Context, ctrl flow.Controller) error {
// Получаем сервис из контейнера
service, err := flow.GetTyped[UserService](ctrl.Container(), "userService")
if err != nil {
return err
}
name, _ := ctrl.Storage().GetString("name")
age, _ := ctrl.Storage().GetInt("age")
return service.SaveUser(name, age)
}),
).
Build()Flow поддерживает middleware на уровне flow и шагов:
// Middleware для логирования
func LoggingMiddleware() flow.Middleware {
return func(next flow.StepHandler) flow.StepHandler {
return func(c maxigo.Context, ctrl flow.Controller) error {
log.Printf("Executing step: %s", ctrl.Storage().Get("current_step"))
return next(c, ctrl)
}
}
}
// Применение middleware
myFlow, _ := flowBus.NewFlow("my_flow").
WithMiddleware(LoggingMiddleware()).
Steps(...).
Build()flowBus.NewFlow("my_flow").
Steps(...).
OnComplete(func(c maxigo.Context, ctrl flow.Controller) error {
// Вызывается при успешном завершении flow
return c.Send("Завершено!")
}).
OnCancel(func(c maxigo.Context, ctrl flow.Controller) error {
// Вызывается при отмене flow
return c.Send("Отменено")
}).
OnTimeout(func(c maxigo.Context, ctrl flow.Controller) error {
// Вызывается при истечении времени сессии
return c.Send("Время истекло")
}).
OnError(func(c maxigo.Context, ctrl flow.Controller, err error) error {
// Обработка ошибок
log.Printf("Flow error: %v", err)
return c.Send("Произошла ошибка")
}).
Build()storage := storage.NewMemoryStorage()
flowBus := flow.NewBus(bot, storage)import "github.com/nlypage/maxigo/flow/storage"
redisStorage := storage.NewRedisStorage(&storage.RedisConfig{
Addr: "localhost:6379",
Password: "",
DB: 0,
})
flowBus := flow.NewBus(bot, redisStorage)import "github.com/nlypage/maxigo/middleware"
// Логирование
bot.Use(middleware.Logger())
// Автоматический индикатор "печатает"
bot.Use(middleware.AutoRespond())
// Rate limiting (1 запрос в секунду на пользователя)
bot.Use(middleware.PerUserRateLimit(1, 1))func MyMiddleware() maxigo.MiddlewareFunc {
return func(next maxigo.HandlerFunc) maxigo.HandlerFunc {
return func(c maxigo.Context) error {
// Код до обработчика
log.Println("Before handler")
// Вызов следующего обработчика
err := next(c)
// Код после обработчика
log.Println("After handler")
return err
}
}
}
bot.Use(MyMiddleware())// Простая отправка
c.Send("Привет!")
// С опциями
c.Send(
"**Жирный текст**",
maxigo.WithFormat(maxigo.Markdown),
maxigo.WithKeyboard(kb),
)
// Ответ на сообщение
c.Reply("Ответ на ваше сообщение")
// Редактирование сообщения
c.Edit("Обновленный текст")
// Удаление сообщения
c.Delete()// Отправка фото
photo := maxigo.FromURL("https://example.com/image.jpg")
// или
photo := maxigo.FromDisk("/path/to/photo.jpg")
c.Send("Вот фото:", maxigo.WithAttachment(photo))
// Отправка файла
file := maxigo.FromDisk("/path/to/document.pdf")
c.Send("Документ:", maxigo.WithAttachment(file))
// Отправка альбома (несколько фото)
photos := []*maxigo.File{
maxigo.FromURL("https://example.com/photo1.jpg"),
maxigo.FromURL("https://example.com/photo2.jpg"),
}
c.SendAlbum(photos)// Показать "печатает..."
c.Notify(maxigo.Typing)
// Показать "отправляет фото..."
c.Notify(maxigo.SendingPhoto)
// Отметить сообщение как прочитанное
c.Notify(maxigo.MarkSeen)kb := maxigo.NewKeyboard()
// Callback кнопка
kb.Row(kb.Data("Текст", "unique_id", "optional_data"))
// URL кнопка
kb.Row(kb.URL("Открыть сайт", "https://example.com"))
// Запрос контакта
kb.Row(kb.Contact("Поделиться контактом"))
// Запрос геолокации
kb.Row(kb.Location("Отправить локацию", false))
// Отправка текста от имени пользователя
kb.Row(kb.Message("/start"))
// Открытие мини-приложения
kb.Row(kb.OpenApp("Открыть приложение", "bot_username", 0, "payload"))В репозитории доступны полные примеры:
- examples/basic — базовый бот с обработкой команд и медиа
- examples/flow — демонстрация flow-системы с фильтрацией
- examples/nested_flow — вложенные flow с передачей данных
maxigo/
├── bot.go # Основной класс бота
├── context.go # Контекст обработчика
├── keyboard.go # Билдер клавиатур
├── message.go # Работа с сообщениями
├── middleware.go # Система middleware
├── flow/ # Flow-система
│ ├── bus.go # Координатор flow
│ ├── flow.go # Логика flow
│ ├── builder.go # Билдер flow
│ ├── step.go # Шаги flow
│ ├── session.go # Управление сессиями
│ ├── controller.go # Контроллер flow
│ ├── container.go # DI контейнер
│ └── storage/ # Хранилища
│ ├── memory.go # In-memory storage
│ └── redis.go # Redis storage
├── middleware/ # Встроенные middleware
└── pkg/ # max-bot-api-client-go
bot, err := maxigo.New(
token,
maxigo.WithLogger(customLogger), // Кастомный логгер
maxigo.WithSynchronous(true), // Синхронная обработка
maxigo.WithUpdatesBuffer(200), // Размер буфера обновлений
maxigo.WithWorkerCount(10), // Количество воркеров
maxigo.WithPoller(customPoller), // Кастомный поллер
maxigo.WithOnError(errorHandler), // Обработчик ошибок
)// Глобальный обработчик ошибок
bot, _ := maxigo.New(
token,
maxigo.WithOnError(func(err error, c maxigo.Context) {
log.Printf("Error: %v", err)
if c != nil {
c.Send("Произошла ошибка: " + err.Error())
}
}),
)
// Обработка ошибок в flow
flowBus.NewFlow("my_flow").
OnError(func(c maxigo.Context, ctrl flow.Controller, err error) error {
log.Printf("Flow error: %v", err)
return c.Send("Ошибка в flow")
}).
Build()// Создание группы с общим middleware
admin := bot.Group()
admin.Use(AdminOnlyMiddleware())
admin.Handle("/ban", banHandler)
admin.Handle("/unban", unbanHandler)// Прямой доступ к Max API клиенту
api := bot.API()
// Использование методов API
botInfo, err := api.Bots.GetBot(context.Background())Полная документация API доступна на pkg.go.dev.
Приветствуются pull request'ы! Для крупных изменений сначала откройте issue для обсуждения.
Этот проект распространяется под лицензией, указанной в файле LICENSE.
Если у вас возникли вопросы или проблемы, создайте issue в репозитории.
Основан на исправленном max-bot-api-client-go. Вдохновлен популярными bot-фреймворками.