Skip to content

nlypage/maxigo

Repository files navigation

Maxigo

Современный Go-фреймворк для создания чат-ботов в мессенджере скаМ. Построен на базе исправленного max-bot-api-client-go и предоставляет мощный инструментарий для разработки сложных диалоговых сценариев.

Дальнейшая поддержка фреймворка возлагается на сообщество, в связи с тем что я больше не имею доступа к api ботов

Go Reference Go Report Card

Возможности

  • Flow-система — мощный инструмент для построения многошаговых диалогов с автоматическим управлением состоянием
  • Управление сессиями — встроенная персистентность с поддержкой Memory и Redis
  • Вложенные Flow — поддержка подфлоу с типизированной передачей данных между ними
  • Фильтрация обновлений — точный контроль типов входящих данных на уровне каждого шага
  • Dependency Injection — встроенный контейнер зависимостей для flow
  • Middleware — гибкая система промежуточных обработчиков
  • Type-safe API — строгая типизация для надежности кода

Установка

go get github.com/nlypage/maxigo

Быстрый старт

Простой бот

package 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 — это мощный инструмент для построения сложных многошаговых диалогов с пользователем. Он автоматически управляет состоянием сессии, обрабатывает переходы между шагами и поддерживает персистентность.

Основные концепции

Компонент Описание
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()
}

Управление Flow

Переходы между шагами

// Переход на другой шаг
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

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("Готово!")
})

Вложенные Flow (Nested Flows)

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)
}

Dependency Injection

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()

Middleware для Flow

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()

Обработка событий Flow

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()

Хранилища

Memory Storage (для разработки)

storage := storage.NewMemoryStorage()
flowBus := flow.NewBus(bot, storage)

Redis Storage (для продакшена)

import "github.com/nlypage/maxigo/flow/storage"

redisStorage := storage.NewRedisStorage(&storage.RedisConfig{
    Addr:     "localhost:6379",
    Password: "",
    DB:       0,
})

flowBus := flow.NewBus(bot, redisStorage)

Middleware

Встроенные middleware

import "github.com/nlypage/maxigo/middleware"

// Логирование
bot.Use(middleware.Logger())

// Автоматический индикатор "печатает"
bot.Use(middleware.AutoRespond())

// Rate limiting (1 запрос в секунду на пользователя)
bot.Use(middleware.PerUserRateLimit(1, 1))

Создание собственного middleware

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)

Доступ к API

// Прямой доступ к 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-фреймворками.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages