REPOSITORY / ScuroNeko/Laniakea
Wiki
Rewrite scene docs and rebalance wiki backlog
Refresh Scenes pages to match current API and behavior Split framework backlog into priority tiers and clean up RU terminology
+13
-13
@@ -6,7 +6,7 @@ English version: [[Auto-Generated-Commands]]
|
|||||||
|
|
||||||
## Что делает эта функция
|
## Что делает эта функция
|
||||||
|
|
||||||
Laniakea может пройтись по уже зарегистрированным plugin commands, собрать подходящие команды и отправить их в Telegram через `setMyCommands`.
|
Laniakea может пройтись по уже зарегистрированным командам плагинов, собрать подходящие команды и отправить их в Telegram через `setMyCommands`.
|
||||||
|
|
||||||
Для этого используются:
|
Для этого используются:
|
||||||
- `AutoGenerateCommands()`
|
- `AutoGenerateCommands()`
|
||||||
@@ -16,16 +16,16 @@ Laniakea может пройтись по уже зарегистрирован
|
|||||||
|
|
||||||
## Откуда берутся команды
|
## Откуда берутся команды
|
||||||
|
|
||||||
Автогенерация работает только по уже зарегистрированным commands.
|
Автогенерация работает только по уже зарегистрированным командам.
|
||||||
|
|
||||||
Учитывается:
|
Учитывается:
|
||||||
- имя команды;
|
- имя команды;
|
||||||
- описание команды;
|
- описание команды;
|
||||||
- skip-флаги на command или plugin уровне.
|
- skip-флаги на уровне команды или плагина.
|
||||||
|
|
||||||
Не участвуют:
|
Не участвуют:
|
||||||
- payload handlers;
|
- обработчики данных callback;
|
||||||
- update handlers;
|
- обработчики обновлений;
|
||||||
- команды, которые ты явно исключил из автогенерации.
|
- команды, которые ты явно исключил из автогенерации.
|
||||||
|
|
||||||
## Когда вызывать
|
## Когда вызывать
|
||||||
@@ -33,13 +33,13 @@ Laniakea может пройтись по уже зарегистрирован
|
|||||||
Обычный порядок такой:
|
Обычный порядок такой:
|
||||||
|
|
||||||
1. создать bot;
|
1. создать bot;
|
||||||
2. создать plugins;
|
2. создать плагины;
|
||||||
3. зарегистрировать commands;
|
3. зарегистрировать команды;
|
||||||
4. добавить plugins через `AddPlugins(...)`;
|
4. добавить плагины через `AddPlugins(...)`;
|
||||||
5. вызвать `AutoGenerateCommands()` или `AutoGenerateCommandsForScope(...)`;
|
5. вызвать `AutoGenerateCommands()` или `AutoGenerateCommandsForScope(...)`;
|
||||||
6. запустить bot.
|
6. запустить bot.
|
||||||
|
|
||||||
Важно: `AddPlugins(...)` — это snapshot point. Если ты меняешь plugin после регистрации, автогенерация не обязана увидеть эти изменения.
|
Важно: `AddPlugins(...)` — это точка фиксации. Если ты меняешь плагин после регистрации, автогенерация не обязана увидеть эти изменения.
|
||||||
|
|
||||||
## Ограничения Telegram
|
## Ограничения Telegram
|
||||||
|
|
||||||
@@ -65,14 +65,14 @@ Laniakea может пройтись по уже зарегистрирован
|
|||||||
|
|
||||||
Это полезно для:
|
Это полезно для:
|
||||||
- внутренних технических commands;
|
- внутренних технических commands;
|
||||||
- переходных migration commands;
|
- переходных миграционных команд;
|
||||||
- редко используемых maintenance commands.
|
- редко используемых служебных команд.
|
||||||
|
|
||||||
## Рекомендации
|
## Рекомендации
|
||||||
|
|
||||||
- Вызывай автогенерацию после полной регистрации plugins.
|
- Вызывай автогенерацию после полной регистрации плагинов.
|
||||||
- Следи, чтобы descriptions были короткими и понятными.
|
- Следи, чтобы descriptions были короткими и понятными.
|
||||||
- Не путай payload handlers с обычными slash-командами.
|
- Не путай обработчики данных callback с обычными slash-командами.
|
||||||
|
|
||||||
## Что читать дальше
|
## Что читать дальше
|
||||||
|
|
||||||
|
|||||||
+16
-16
@@ -2,19 +2,19 @@
|
|||||||
|
|
||||||
English version: [[Bot-Lifecycle]]
|
English version: [[Bot-Lifecycle]]
|
||||||
|
|
||||||
Это краткая русскоязычная версия страницы про lifecycle `Bot`. Полная и наиболее актуальная страница: [[Bot-Lifecycle]].
|
Это краткая русскоязычная версия страницы про жизненный цикл `Bot`. Полная и наиболее актуальная страница: [[Bot-Lifecycle]].
|
||||||
|
|
||||||
## Жизненный цикл в одном списке
|
## Жизненный цикл в одном списке
|
||||||
|
|
||||||
1. Собрать `BotOpts`.
|
1. Собрать `BotOpts`.
|
||||||
2. Создать `Bot` через `NewBot[T](opts)`.
|
2. Создать `Bot` через `NewBot[T](opts)`.
|
||||||
3. Полностью настроить bot: plugins, middleware, runners, payload policy, l10n, db context.
|
3. Полностью настроить бот: плагины, middleware, фоновые задачи, политику данных callback, l10n, контекст базы данных.
|
||||||
4. Запустить через `Run()` или `RunWithContext(...)`.
|
4. Запустить через `Run()` или `RunWithContext(...)`.
|
||||||
5. Остановить runtime через завершение `Run()` или cancel context.
|
5. Остановить выполнение через завершение `Run()` или отмену context.
|
||||||
6. Освободить локальные ресурсы через `Close()`.
|
6. Освободить локальные ресурсы через `Close()`.
|
||||||
7. Для следующего запуска создать новый `Bot`.
|
7. Для следующего запуска создать новый `Bot`.
|
||||||
|
|
||||||
Главное правило: `Bot` single-use.
|
Главное правило: `Bot` используется только один раз.
|
||||||
|
|
||||||
## Что делает `NewBot(...)`
|
## Что делает `NewBot(...)`
|
||||||
|
|
||||||
@@ -42,14 +42,14 @@ English version: [[Bot-Lifecycle]]
|
|||||||
- `SetStrictPayloadType(...)`
|
- `SetStrictPayloadType(...)`
|
||||||
- `SetDraftProvider(...)`
|
- `SetDraftProvider(...)`
|
||||||
|
|
||||||
Это важно, потому что runtime не рассчитан на модель “запустили, а потом продолжаем собирать конфигурацию на лету”.
|
Это важно, потому что механизм выполнения не рассчитан на модель “запустили, а потом продолжаем собирать конфигурацию на лету”.
|
||||||
|
|
||||||
## Почему `AddPlugins(...)` так важен
|
## Почему `AddPlugins(...)` так важен
|
||||||
|
|
||||||
`AddPlugins(...)` копирует конфигурацию plugin внутрь bot.
|
`AddPlugins(...)` копирует конфигурацию плагина внутрь бота.
|
||||||
|
|
||||||
Практически это значит:
|
Практически это значит:
|
||||||
- сначала закончи настройку plugin;
|
- сначала закончи настройку плагина;
|
||||||
- потом регистрируй его;
|
- потом регистрируй его;
|
||||||
- не рассчитывай, что дальнейшая мутация исходного `*Plugin` будет официально поддерживаемой частью API.
|
- не рассчитывай, что дальнейшая мутация исходного `*Plugin` будет официально поддерживаемой частью API.
|
||||||
|
|
||||||
@@ -58,33 +58,33 @@ English version: [[Bot-Lifecycle]]
|
|||||||
`Run()` — это короткая форма для простых случаев.
|
`Run()` — это короткая форма для простых случаев.
|
||||||
|
|
||||||
`RunWithContext(...)` — основной production-вариант, потому что он:
|
`RunWithContext(...)` — основной production-вариант, потому что он:
|
||||||
- умеет graceful shutdown через `ctx.Done()`;
|
- умеет корректно завершаться через `ctx.Done()`;
|
||||||
- ждет завершения queued updates;
|
- ждет завершения queued updates;
|
||||||
- корректно дожидается runners.
|
- корректно дожидается фоновых задач.
|
||||||
|
|
||||||
Если bot уже был запущен раньше, повторный запуск вернет `ErrBotAlreadyRun`.
|
Если bot уже был запущен раньше, повторный запуск вернет `ErrBotAlreadyRun`.
|
||||||
|
|
||||||
## Что происходит во время runtime
|
## Что происходит во время выполнения
|
||||||
|
|
||||||
Во время работы bot делает три вещи:
|
Во время работы бот делает три вещи:
|
||||||
- long-polling `getUpdates`;
|
- long-polling `getUpdates`;
|
||||||
- складывает updates во внутреннюю очередь;
|
- складывает updates во внутреннюю очередь;
|
||||||
- обрабатывает их через worker pool.
|
- обрабатывает их через worker pool.
|
||||||
|
|
||||||
Полезно помнить:
|
Полезно помнить:
|
||||||
- размер worker pool управляется через `MaxWorkers`;
|
- размер worker pool управляется через `MaxWorkers`;
|
||||||
- polling при ошибках использует exponential backoff;
|
- polling при ошибках использует экспоненциальный backoff;
|
||||||
- после cancel сначала прекращается polling, потом дренируется очередь, потом дожидаются runners.
|
- после отмены context сначала прекращается polling, потом дренируется очередь, потом дожидаются фоновые задачи.
|
||||||
|
|
||||||
## `Close()` и `CloseRemote()`
|
## `Close()` и `CloseRemote()`
|
||||||
|
|
||||||
Это разные вещи.
|
Это разные вещи.
|
||||||
|
|
||||||
`Close()`:
|
`Close()`:
|
||||||
- закрывает plugins через `Plugin.Close()`;
|
- закрывает плагины через `Plugin.Close()`;
|
||||||
- закрывает uploader;
|
- закрывает uploader;
|
||||||
- закрывает локальный API client;
|
- закрывает локальный API client;
|
||||||
- закрывает request logger и main logger.
|
- закрывает логгер запросов и основной логгер.
|
||||||
|
|
||||||
`CloseRemote(ctx)`:
|
`CloseRemote(ctx)`:
|
||||||
- отправляет Telegram Bot API метод `close`;
|
- отправляет Telegram Bot API метод `close`;
|
||||||
@@ -96,7 +96,7 @@ English version: [[Bot-Lifecycle]]
|
|||||||
|
|
||||||
- Пытаться повторно использовать тот же `Bot`.
|
- Пытаться повторно использовать тот же `Bot`.
|
||||||
- Забывать `Close()` после завершения `RunWithContext(...)`.
|
- Забывать `Close()` после завершения `RunWithContext(...)`.
|
||||||
- Менять plugins после `AddPlugins(...)` и ждать, что bot это гарантированно увидит.
|
- Менять плагины после `AddPlugins(...)` и ждать, что бот это гарантированно увидит.
|
||||||
- Регистрировать repeating runner без timeout.
|
- Регистрировать repeating runner без timeout.
|
||||||
|
|
||||||
## Что читать дальше
|
## Что читать дальше
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ English version: [[Bot-Options-and-Configuration]]
|
|||||||
Через него настраиваются:
|
Через него настраиваются:
|
||||||
- токен и API endpoint;
|
- токен и API endpoint;
|
||||||
- update types и command prefixes;
|
- update types и command prefixes;
|
||||||
- logging behavior;
|
- поведение логирования;
|
||||||
- rate limiting;
|
- ограничение частоты;
|
||||||
- strict payload decoding;
|
- строгое декодирование данных callback;
|
||||||
- размер worker pool.
|
- размер worker pool.
|
||||||
|
|
||||||
Обычный flow:
|
Обычный поток:
|
||||||
1. собрать `BotOpts` вручную или через `LoadOptsFromEnv()`;
|
1. собрать `BotOpts` вручную или через `LoadOptsFromEnv()`;
|
||||||
2. при необходимости донастроить setter methods;
|
2. при необходимости донастроить setter methods;
|
||||||
3. передать в `NewBot(...)`.
|
3. передать в `NewBot(...)`.
|
||||||
@@ -53,8 +53,8 @@ opts := laniakea.LoadOptsFromEnv()
|
|||||||
- `RateLimit` по умолчанию `30`
|
- `RateLimit` по умолчанию `30`
|
||||||
- `MaxWorkers` по умолчанию `32`
|
- `MaxWorkers` по умолчанию `32`
|
||||||
- `ErrorTemplate` по умолчанию `"%s"`
|
- `ErrorTemplate` по умолчанию `"%s"`
|
||||||
- request logging и file logging выключены
|
- журналирование запросов и логирование в файл выключены
|
||||||
- strict payload decoding выключен
|
- строгое декодирование данных callback выключено
|
||||||
|
|
||||||
## Важные поля
|
## Важные поля
|
||||||
|
|
||||||
@@ -70,11 +70,11 @@ opts := laniakea.LoadOptsFromEnv()
|
|||||||
|
|
||||||
### `ErrorTemplate`
|
### `ErrorTemplate`
|
||||||
|
|
||||||
Определяет, как пользователю показываются returned handler errors.
|
Определяет, как пользователю показываются ошибки, возвращённые обработчиком.
|
||||||
|
|
||||||
### `Debug`
|
### `Debug`
|
||||||
|
|
||||||
Включает debug logging.
|
Включает отладочное логирование.
|
||||||
|
|
||||||
### `UseRequestLogger`
|
### `UseRequestLogger`
|
||||||
|
|
||||||
@@ -86,27 +86,27 @@ opts := laniakea.LoadOptsFromEnv()
|
|||||||
|
|
||||||
### `UseTestServer` и `APIUrl`
|
### `UseTestServer` и `APIUrl`
|
||||||
|
|
||||||
Полезны для test environment, proxy или custom Telegram gateway.
|
Полезны для тестового окружения, proxy или собственного Telegram gateway.
|
||||||
|
|
||||||
### `RateLimit` и `DropRLOverflow`
|
### `RateLimit` и `DropRLOverflow`
|
||||||
|
|
||||||
Управляют политикой limiter:
|
Управляют политикой limiter:
|
||||||
- ждать и доставлять надежнее;
|
- ждать и доставлять надежнее;
|
||||||
- или дропать overflow ради отзывчивости.
|
- или сбрасывать лишние запросы ради отзывчивости.
|
||||||
|
|
||||||
### `StrictPayloadType`
|
### `StrictPayloadType`
|
||||||
|
|
||||||
Включает строгую политику декодирования callback payloads без fallback между JSON и Base64.
|
Включает строгую политику декодирования данных callback без запасного варианта между JSON и Base64.
|
||||||
|
|
||||||
### `MaxWorkers`
|
### `MaxWorkers`
|
||||||
|
|
||||||
Определяет максимальное количество concurrent update handlers.
|
Определяет максимальное количество параллельных обработчиков обновлений.
|
||||||
|
|
||||||
## Когда выбирать маленький или большой `MaxWorkers`
|
## Когда выбирать маленький или большой `MaxWorkers`
|
||||||
|
|
||||||
Меньше:
|
Меньше:
|
||||||
- если handlers CPU-bound;
|
- если обработчики CPU-bound;
|
||||||
- если downstream services не выдержат много параллелизма.
|
- если нижележащие сервисы не выдержат много параллелизма.
|
||||||
|
|
||||||
Больше:
|
Больше:
|
||||||
- если handlers в основном I/O-bound;
|
- если handlers в основном I/O-bound;
|
||||||
|
|||||||
+47
-47
@@ -2,31 +2,31 @@
|
|||||||
|
|
||||||
English version: [[Commands-and-Plugins]]
|
English version: [[Commands-and-Plugins]]
|
||||||
|
|
||||||
Это краткая русскоязычная версия страницы про архитектуру `Bot`, `Plugin`, commands, payloads и update handlers. Полная и наиболее актуальная страница: [[Commands-and-Plugins]].
|
Это краткая русскоязычная версия страницы про архитектуру `Bot`, `Plugin`, команды, данные callback и обработчики обновлений. Полная и наиболее актуальная страница: [[Commands-and-Plugins]].
|
||||||
|
|
||||||
## Главное сначала
|
## Главное сначала
|
||||||
|
|
||||||
Обычная модель в Laniakea такая:
|
Обычная модель в Laniakea такая:
|
||||||
- `Bot` владеет runtime, polling, логированием и API-клиентами;
|
- `Bot` управляет выполнением, polling, логированием и API-клиентами;
|
||||||
- `Plugin` группирует связанную функциональность;
|
- `Plugin` группирует связанную функциональность;
|
||||||
- commands обрабатывают текстовые команды вроде `/start`;
|
- команды обрабатывают текстовые команды вроде `/start`;
|
||||||
- payload handlers обрабатывают callback data от inline-кнопок;
|
- обработчики данных callback обрабатывают callback data от inline-кнопок;
|
||||||
- update handlers обрабатывают остальные update types вне обычного command/payload flow.
|
- обработчики обновлений обрабатывают остальные типы обновлений вне обычного потока команд и callback.
|
||||||
|
|
||||||
Для большинства ботов стартовая структура выглядит так:
|
Для большинства ботов стартовая структура выглядит так:
|
||||||
- один или несколько плагинов;
|
- один или несколько плагинов;
|
||||||
- несколько команд;
|
- несколько команд;
|
||||||
- plugin middleware для общих проверок;
|
- middleware плагина для общих проверок;
|
||||||
- payload handlers, когда появляются inline-кнопки.
|
- обработчики данных callback, когда появляются inline-кнопки.
|
||||||
|
|
||||||
## Что такое `Plugin`
|
## Что такое `Plugin`
|
||||||
|
|
||||||
Плагин — это именованная группа:
|
Плагин — это именованная группа:
|
||||||
- commands;
|
- команд;
|
||||||
- payload handlers;
|
- обработчиков данных callback;
|
||||||
- update handlers;
|
- обработчиков обновлений;
|
||||||
- общих middleware;
|
- общих middleware;
|
||||||
- optional logger и `OnClose` hook.
|
- необязательного логгера и `OnClose`-хука.
|
||||||
|
|
||||||
Пример:
|
Пример:
|
||||||
|
|
||||||
@@ -42,9 +42,9 @@ plugin := laniakea.NewPlugin[laniakea.NoDB]("admin")
|
|||||||
|
|
||||||
Так проще держать границы ответственности и не превращать весь бот в один большой registry-файл.
|
Так проще держать границы ответственности и не превращать весь бот в один большой registry-файл.
|
||||||
|
|
||||||
## Command handlers
|
## Обработчики команд
|
||||||
|
|
||||||
Сигнатура command handler такая:
|
Сигнатура обработчика команды такая:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func(ctx *laniakea.MsgContext, db T) error
|
func(ctx *laniakea.MsgContext, db T) error
|
||||||
@@ -56,7 +56,7 @@ func(ctx *laniakea.MsgContext, db T) error
|
|||||||
|
|
||||||
Возвращай:
|
Возвращай:
|
||||||
- `nil`, если все прошло успешно;
|
- `nil`, если все прошло успешно;
|
||||||
- `error`, если хочешь отдать ошибку в централизованный error flow.
|
- `error`, если хочешь отдать ошибку в централизованный поток обработки ошибок.
|
||||||
|
|
||||||
Пример:
|
Пример:
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ plugin.AddCommand(plugin.NewCommand(start, "start"))
|
|||||||
/echo hello world
|
/echo hello world
|
||||||
```
|
```
|
||||||
|
|
||||||
в handler'е будет:
|
в обработчике будет:
|
||||||
- `ctx.Text == "hello world"`
|
- `ctx.Text == "hello world"`
|
||||||
- `ctx.Args == []string{"hello", "world"}`
|
- `ctx.Args == []string{"hello", "world"}`
|
||||||
|
|
||||||
@@ -116,11 +116,11 @@ plugin.AddCommand(
|
|||||||
- базовый тип вроде `int` или `string`;
|
- базовый тип вроде `int` или `string`;
|
||||||
- regex-ограничения через конфигурацию аргумента.
|
- regex-ограничения через конфигурацию аргумента.
|
||||||
|
|
||||||
Если валидация не проходит, handler не запускается, а ошибка идет в обычный error flow.
|
Если валидация не проходит, обработчик не запускается, а ошибка идет в обычный поток обработки ошибок.
|
||||||
|
|
||||||
## Payload handlers
|
## Обработчики данных callback
|
||||||
|
|
||||||
Payload handlers нужны для callback data от inline-кнопок.
|
Обработчики данных callback нужны для callback data от inline-кнопок.
|
||||||
|
|
||||||
Пример:
|
Пример:
|
||||||
|
|
||||||
@@ -134,15 +134,15 @@ plugin.AddPayload(plugin.NewPayload(confirmDelete, "delete.confirm"))
|
|||||||
```
|
```
|
||||||
|
|
||||||
Важно помнить:
|
Важно помнить:
|
||||||
- payload handler использует ту же сигнатуру, что и command handler;
|
- обработчик данных callback использует ту же сигнатуру, что и обработчик команды;
|
||||||
- аргументы decoded payload попадают в `ctx.Args`;
|
- аргументы разобранных данных callback попадают в `ctx.Args`;
|
||||||
- payload — это не текстовая команда, а callback from button.
|
- это не текстовая команда, а callback от кнопки.
|
||||||
|
|
||||||
Подробности: [[Inline-Keyboards-and-Payloads]]
|
Подробности: [[Inline-Keyboards-and-Payloads]]
|
||||||
|
|
||||||
## Update handlers
|
## Обработчики обновлений
|
||||||
|
|
||||||
Update handlers нужны для update types вне обычного command/payload flow.
|
Обработчики обновлений нужны для типов обновлений вне обычного потока команд и callback.
|
||||||
|
|
||||||
Пример:
|
Пример:
|
||||||
|
|
||||||
@@ -163,29 +163,29 @@ plugin.AddUpdateHandler(tgapi.UpdateTypeInlineQuery, func(ctx *laniakea.MsgConte
|
|||||||
- `channel_post`
|
- `channel_post`
|
||||||
- `callback_query`
|
- `callback_query`
|
||||||
|
|
||||||
не должны идти через `AddUpdateHandler(...)`, потому что они уже обслуживаются обычным command/payload pipeline.
|
не должны идти через `AddUpdateHandler(...)`, потому что они уже обслуживаются обычным потоком команд и callback.
|
||||||
|
|
||||||
## Как выглядит runtime flow
|
## Как выглядит поток выполнения
|
||||||
|
|
||||||
Для текстовой команды поток примерно такой:
|
Для текстовой команды поток примерно такой:
|
||||||
|
|
||||||
1. Приходит Telegram update.
|
1. Приходит Telegram update.
|
||||||
2. `Bot` готовит `MsgContext`.
|
2. `Bot` готовит `MsgContext`.
|
||||||
3. Выполняется bot middleware.
|
3. Выполняется middleware бота.
|
||||||
4. Находится подходящий plugin.
|
4. Находится подходящий плагин.
|
||||||
5. Выполняется plugin middleware.
|
5. Выполняется middleware плагина.
|
||||||
6. Выполняется валидация аргументов.
|
6. Выполняется валидация аргументов.
|
||||||
7. Выполняется command-specific middleware.
|
7. Выполняется middleware команды.
|
||||||
8. Запускается handler.
|
8. Запускается обработчик.
|
||||||
9. Если handler вернул ошибку, она идет в централизованный error flow.
|
9. Если обработчик вернул ошибку, она идет в централизованный поток обработки ошибок.
|
||||||
|
|
||||||
Для payload flow идея та же самая, только trigger приходит не из текста сообщения, а из decoded callback data.
|
Для потока данных callback идея та же самая, только запуск происходит не из текста сообщения, а из разобранных callback data.
|
||||||
|
|
||||||
## Где использовать middleware
|
## Где использовать middleware
|
||||||
|
|
||||||
Есть два основных уровня:
|
Есть два основных уровня:
|
||||||
|
|
||||||
### Plugin middleware
|
### Middleware плагина
|
||||||
|
|
||||||
Добавляется через:
|
Добавляется через:
|
||||||
|
|
||||||
@@ -194,12 +194,12 @@ plugin.AddMiddleware(...)
|
|||||||
```
|
```
|
||||||
|
|
||||||
Подходит для общей логики внутри одного плагина:
|
Подходит для общей логики внутри одного плагина:
|
||||||
- auth checks;
|
- проверки доступа;
|
||||||
- rate limiting;
|
- ограничение частоты;
|
||||||
- shared logging;
|
- общее логирование;
|
||||||
- common preconditions.
|
- общие предусловия.
|
||||||
|
|
||||||
### Command-specific middleware
|
### Middleware команды
|
||||||
|
|
||||||
Добавляется через:
|
Добавляется через:
|
||||||
|
|
||||||
@@ -207,7 +207,7 @@ plugin.AddMiddleware(...)
|
|||||||
plugin.NewCommand(handler, "name").Use(middleware)
|
plugin.NewCommand(handler, "name").Use(middleware)
|
||||||
```
|
```
|
||||||
|
|
||||||
Подходит, когда проверка нужна только одной команде или одному payload handler.
|
Подходит, когда проверка нужна только одной команде или одному обработчику данных callback.
|
||||||
|
|
||||||
Подробности: [[Middleware]]
|
Подробности: [[Middleware]]
|
||||||
|
|
||||||
@@ -227,13 +227,13 @@ plugin.NewCommand(start, "/start")
|
|||||||
plugin.NewCommand(start, "start")
|
plugin.NewCommand(start, "start")
|
||||||
```
|
```
|
||||||
|
|
||||||
### Считать payload обычной командой
|
### Считать данные callback обычной командой
|
||||||
|
|
||||||
Payload handler вызывается не из текста сообщения, а из callback data кнопки.
|
Обработчик данных callback вызывается не из текста сообщения, а из callback data кнопки.
|
||||||
|
|
||||||
### Использовать `AddUpdateHandler(...)` для `message` или `callback_query`
|
### Использовать `AddUpdateHandler(...)` для `message` или `callback_query`
|
||||||
|
|
||||||
Эти update types относятся к обычному command/payload pipeline.
|
Эти типы обновлений относятся к обычному потоку команд и callback.
|
||||||
|
|
||||||
### Возвращать `error` там, где это просто обычная ветка UX
|
### Возвращать `error` там, где это просто обычная ветка UX
|
||||||
|
|
||||||
@@ -247,11 +247,11 @@ return nil
|
|||||||
## Когда использовать что
|
## Когда использовать что
|
||||||
|
|
||||||
Используй:
|
Используй:
|
||||||
- commands для slash-команд;
|
- команды для slash-команд;
|
||||||
- payload handlers для inline button callbacks;
|
- обработчики данных callback для callback кнопок;
|
||||||
- update handlers для остальных Telegram updates;
|
- обработчики обновлений для остальных Telegram updates;
|
||||||
- plugin middleware для общих проверок;
|
- middleware плагина для общих проверок;
|
||||||
- command middleware для локальных, узких проверок.
|
- middleware команды для локальных, узких проверок.
|
||||||
|
|
||||||
## Что читать дальше
|
## Что читать дальше
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
English version: [[Drafts]]
|
English version: [[Drafts]]
|
||||||
|
|
||||||
Это краткая русскоязычная версия страницы про drafts. Полная и наиболее актуальная страница: [[Drafts]].
|
Это краткая русскоязычная версия страницы про черновики. Полная и наиболее актуальная страница: [[Drafts]].
|
||||||
|
|
||||||
## Когда нужны drafts
|
## Когда нужны черновики
|
||||||
|
|
||||||
Drafts полезны, когда ответ:
|
Drafts полезны, когда ответ:
|
||||||
- собирается постепенно;
|
- собирается постепенно;
|
||||||
@@ -36,7 +36,7 @@ draft.Flush()
|
|||||||
|
|
||||||
## Lifecycle draft'а
|
## Lifecycle draft'а
|
||||||
|
|
||||||
Типичный flow такой:
|
Типичный поток такой:
|
||||||
1. создать draft;
|
1. создать draft;
|
||||||
2. добавлять текст и настройки;
|
2. добавлять текст и настройки;
|
||||||
3. при необходимости делать `Push(...)` как промежуточную отправку;
|
3. при необходимости делать `Push(...)` как промежуточную отправку;
|
||||||
@@ -56,7 +56,7 @@ draft.Flush()
|
|||||||
У provider есть `FlushAll()`.
|
У provider есть `FlushAll()`.
|
||||||
|
|
||||||
Это best-effort операция:
|
Это best-effort операция:
|
||||||
- provider пытается отправить все pending drafts;
|
- провайдер пытается отправить все ожидающие черновики;
|
||||||
- ошибки одного draft не отменяют попытки для остальных.
|
- ошибки одного draft не отменяют попытки для остальных.
|
||||||
|
|
||||||
## IDs и стратегии генерации
|
## IDs и стратегии генерации
|
||||||
@@ -71,7 +71,7 @@ draft.Flush()
|
|||||||
|
|
||||||
`NewDraftMarkdown()` включает `MarkdownV2`.
|
`NewDraftMarkdown()` включает `MarkdownV2`.
|
||||||
|
|
||||||
Как и в остальных Markdown helper methods, пользовательский ввод надо экранировать отдельно.
|
Как и в остальных Markdown-вспомогательных методах, пользовательский ввод надо экранировать отдельно.
|
||||||
|
|
||||||
## Что читать дальше
|
## Что читать дальше
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -2,16 +2,16 @@
|
|||||||
|
|
||||||
English version: [[Error-Handling]]
|
English version: [[Error-Handling]]
|
||||||
|
|
||||||
Это краткая русскоязычная версия страницы про centralized error flow. Полная и наиболее актуальная страница: [[Error-Handling]].
|
Это краткая русскоязычная версия страницы про централизованный поток обработки ошибок. Полная и наиболее актуальная страница: [[Error-Handling]].
|
||||||
|
|
||||||
## Базовая идея
|
## Базовая идея
|
||||||
|
|
||||||
В Laniakea handlers возвращают `error`.
|
В Laniakea handlers возвращают `error`.
|
||||||
|
|
||||||
Это относится к:
|
Это относится к:
|
||||||
- command handlers;
|
- обработчики команд;
|
||||||
- payload handlers;
|
- обработчики данных callback;
|
||||||
- update handlers.
|
- обработчики обновлений.
|
||||||
|
|
||||||
Если handler возвращает ошибку, bot:
|
Если handler возвращает ошибку, bot:
|
||||||
- форматирует user-facing текст через `ErrorTemplate(...)`;
|
- форматирует user-facing текст через `ErrorTemplate(...)`;
|
||||||
@@ -60,7 +60,7 @@ if !allowed {
|
|||||||
|
|
||||||
## Callback-specific поведение
|
## Callback-specific поведение
|
||||||
|
|
||||||
Для callback flow returned error превращается не в обычное сообщение в чат, а в ответ на callback query.
|
Для callback-потока возвращённая ошибка превращается не в обычное сообщение в чат, а в ответ на callback query.
|
||||||
|
|
||||||
Если нужен другой UX, лучше:
|
Если нужен другой UX, лучше:
|
||||||
- вызвать `AnswerCbQueryText(...)` или `AnswerCbQueryAlert(...)`;
|
- вызвать `AnswerCbQueryText(...)` или `AnswerCbQueryAlert(...)`;
|
||||||
|
|||||||
+26
-26
@@ -6,12 +6,12 @@ English version: [[FAQ]]
|
|||||||
|
|
||||||
## Почему хендлеры возвращают `error`?
|
## Почему хендлеры возвращают `error`?
|
||||||
|
|
||||||
Чтобы ошибки проходили через единый, централизованный flow, а не обрабатывались вручную в каждом command или payload handler.
|
Чтобы ошибки проходили через единый, централизованный поток, а не обрабатывались вручную в каждом обработчике команды или callback.
|
||||||
|
|
||||||
Это дает несколько плюсов:
|
Это дает несколько плюсов:
|
||||||
- хендлеры остаются проще;
|
- хендлеры остаются проще;
|
||||||
- user-facing error format можно контролировать через `ErrorTemplate(...)`;
|
- формат пользовательских ошибок можно контролировать через `ErrorTemplate(...)`;
|
||||||
- command, payload и non-command update handlers используют один и тот же контракт.
|
- команды, callback и обработчики обновлений вне команд используют один и тот же контракт.
|
||||||
|
|
||||||
Если тебе нужен полностью ручной ответ пользователю, ты все еще можешь ответить сам и вернуть `nil`.
|
Если тебе нужен полностью ручной ответ пользователю, ты все еще можешь ответить сам и вернуть `nil`.
|
||||||
|
|
||||||
@@ -19,12 +19,12 @@ English version: [[FAQ]]
|
|||||||
|
|
||||||
## Почему `Bot` single-use?
|
## Почему `Bot` single-use?
|
||||||
|
|
||||||
Потому что один run владеет реальным runtime state:
|
Потому что один запуск владеет реальным состоянием выполнения:
|
||||||
- polling lifecycle;
|
- жизненным циклом polling;
|
||||||
- worker pool;
|
- worker pool;
|
||||||
- update offsets;
|
- update offsets;
|
||||||
- runner execution;
|
- выполнением фоновых задач;
|
||||||
- API и logger resources.
|
- API и ресурсами логгеров.
|
||||||
|
|
||||||
Из-за этого модель “создал -> настроил -> запустил -> закрыл -> создал новый” безопаснее и проще для понимания, чем попытка перезапускать один и тот же `Bot`.
|
Из-за этого модель “создал -> настроил -> запустил -> закрыл -> создал новый” безопаснее и проще для понимания, чем попытка перезапускать один и тот же `Bot`.
|
||||||
|
|
||||||
@@ -39,31 +39,31 @@ English version: [[FAQ]]
|
|||||||
- возможен partial success;
|
- возможен partial success;
|
||||||
- клавиатура в `KeyboardLong(...)` вешается только на последний chunk.
|
- клавиатура в `KeyboardLong(...)` вешается только на последний chunk.
|
||||||
|
|
||||||
Такой split сделан специально, чтобы длинные ответы не меняли поведение обычных helper methods неявно.
|
Такое разделение сделано специально, чтобы длинные ответы не меняли поведение обычных вспомогательных методов неявно.
|
||||||
|
|
||||||
## Зачем есть и JSON, и Base64 payload formats?
|
## Зачем есть и JSON, и Base64 форматы данных callback?
|
||||||
|
|
||||||
Они решают разные задачи:
|
Они решают разные задачи:
|
||||||
|
|
||||||
- `BotPayloadJson` удобен для читаемости, логов и тестов
|
- `BotPayloadJson` удобен для читаемости, логов и тестов
|
||||||
- `BotPayloadBase64` удобен как более компактная и “непрозрачная” transport-форма того же payload
|
- `BotPayloadBase64` удобен как более компактная и “непрозрачная” транспортная форма тех же данных callback
|
||||||
|
|
||||||
Логическая структура callback payload при этом одна и та же: меняется только encoding.
|
Логическая структура данных callback при этом одна и та же: меняется только кодирование.
|
||||||
|
|
||||||
Подробности: [[Inline-Keyboards-and-Payloads]]
|
Подробности: [[Inline-Keyboards-and-Payloads]]
|
||||||
|
|
||||||
## Когда использовать `MsgContext`, а когда `tgapi`?
|
## Когда использовать `MsgContext`, а когда `tgapi`?
|
||||||
|
|
||||||
Используй `MsgContext`, когда ты уже внутри хендлера и тебе нужен удобный reply/edit/delete flow с текущим chat, message и logger.
|
Используй `MsgContext`, когда ты уже внутри хендлера и тебе нужен удобный поток reply/edit/delete с текущими chat, message и logger.
|
||||||
|
|
||||||
Используй `tgapi`, когда:
|
Используй `tgapi`, когда:
|
||||||
- нужного helper method нет в `MsgContext`
|
- нужного вспомогательного метода нет в `MsgContext`
|
||||||
- ты работаешь вне handler flow
|
- ты работаешь вне потока обработчика
|
||||||
- нужен lower-level control над Telegram methods, uploads или downloads
|
- нужен более низкоуровневый контроль над Telegram methods, uploads или downloads
|
||||||
|
|
||||||
Коротко:
|
Коротко:
|
||||||
- `MsgContext` — ergonomic default
|
- `MsgContext` — удобный вариант по умолчанию
|
||||||
- `tgapi` — lower-level escape hatch
|
- `tgapi` — низкоуровневый запасной путь
|
||||||
|
|
||||||
Подробности: [[tgapi-Overview]]
|
Подробности: [[tgapi-Overview]]
|
||||||
|
|
||||||
@@ -71,30 +71,30 @@ English version: [[FAQ]]
|
|||||||
|
|
||||||
Потому что это две разные ответственности:
|
Потому что это две разные ответственности:
|
||||||
|
|
||||||
- `RunWithContext(...)` управляет run loop, graceful stop и ожиданием runner'ов
|
- `RunWithContext(...)` управляет циклом выполнения, корректной остановкой и ожиданием фоновых задач
|
||||||
- `Close()` освобождает API, uploader, plugin shutdown hooks и loggers
|
- `Close()` освобождает API, uploader, хуки остановки плагинов и логгеры
|
||||||
|
|
||||||
Поэтому `Close()` все равно нужен.
|
Поэтому `Close()` все равно нужен.
|
||||||
|
|
||||||
## Почему `Plugin` нужно полностью настроить до `AddPlugins(...)`?
|
## Почему `Plugin` нужно полностью настроить до `AddPlugins(...)`?
|
||||||
|
|
||||||
Потому что `AddPlugins(...)` — это configuration snapshot point.
|
Потому что `AddPlugins(...)` — это точка фиксации конфигурации.
|
||||||
|
|
||||||
После регистрации `Bot` хранит внутреннюю копию состояния плагина, и изменения исходного `*Plugin` уже не считаются поддерживаемым API.
|
После регистрации `Bot` хранит внутреннюю копию состояния плагина, и изменения исходного `*Plugin` уже не считаются поддерживаемым API.
|
||||||
|
|
||||||
До `AddPlugins(...)` стоит завершить:
|
До `AddPlugins(...)` стоит завершить:
|
||||||
- commands
|
- commands
|
||||||
- payloads
|
- данные callback
|
||||||
- update handlers
|
- обработчики обновлений
|
||||||
- plugin middleware
|
- middleware плагина
|
||||||
- logger choice
|
- выбор логгера
|
||||||
- `OnClose`
|
- `OnClose`
|
||||||
|
|
||||||
## Почему async middleware игнорирует `false`?
|
## Почему async middleware игнорирует `false`?
|
||||||
|
|
||||||
Потому что async middleware задуман как side-effect path, а не как механизм flow control.
|
Потому что async middleware задуман как путь для побочных действий, а не как механизм управления потоком.
|
||||||
|
|
||||||
Когда middleware уходит в goroutine, он уже не может надежно остановить основной execution path. Поэтому для блокировки и отказов нужно использовать обычный synchronous middleware.
|
Когда middleware уходит в goroutine, он уже не может надежно остановить основной путь выполнения. Поэтому для блокировки и отказов нужно использовать обычный synchronous middleware.
|
||||||
|
|
||||||
Подробности: [[Middleware]]
|
Подробности: [[Middleware]]
|
||||||
|
|
||||||
|
|||||||
+86
-82
@@ -1,97 +1,101 @@
|
|||||||
# Framework Backlog
|
# Framework Backlog
|
||||||
|
|
||||||
Эта страница отслеживает backlog framework-level задач, связанных с отсутствующими концепциями в библиотеке, а не просто с нехваткой документации.
|
Эта страница отслеживает список задач уровня фреймворка, связанных с отсутствующими концепциями в библиотеке, а не просто с нехваткой документации.
|
||||||
|
|
||||||
## High-Priority Core Concepts
|
## Приоритет 1 — Срочно
|
||||||
|
|
||||||
### 1. Conversation / Scene Model
|
- Контракт схемы обновлений: обработка обновлений уже есть, но нет формального понятия уровня фреймворка, описывающего, какие поля `MsgContext` гарантированы для каких видов обновлений.
|
||||||
|
- Модель пользовательских и внутренних ошибок: у фреймворка есть единый поток обработки ошибок, но он ещё плохо различает пользовательские, внутренние, повторяемые и тихие ошибки.
|
||||||
|
- Модель фиксации конфигурации: у фреймворка уже есть реальные точки фиксации вроде `AddPlugins(...)`, но пока это скорее факт реализации, чем явная верхнеуровневая концепция.
|
||||||
|
|
||||||
Current state:
|
## Приоритет 2 — Важно
|
||||||
|
|
||||||
- Фреймворк хорошо обрабатывает один update через commands, payloads, middleware и update handlers.
|
- Модель выполнения webhook: у библиотеки есть хорошая polling-модель, но нет полноценной модели выполнения webhook на уровне фреймворка.
|
||||||
- В нём уже есть полезные низкоуровневые строительные блоки: `MsgContext`, drafts, payload routing, plugins и update handlers.
|
- Модель авторизации и политик: middleware могут реализовать аутентификацию и права доступа, но нет явной модели уровня фреймворка для политик доступа, ролей или проверок возможностей.
|
||||||
- Теперь в нём уже есть work-in-progress skeleton для долгоживущих interaction flows: сцены можно регистрировать в plugins, запускать через `MsgContext`, сохранять через `SessionStore` и маршрутизировать раньше обычной обработки команд.
|
- Модель наблюдаемости: логирование уже сильное, но метрики, трассировка и структурированные хуки фреймворка пока не являются полноценной частью API.
|
||||||
|
|
||||||
Why this matters:
|
## Приоритет 3 — Стратегически
|
||||||
|
|
||||||
- Многие Telegram-боты быстро перерастают из изолированных команд в stateful multi-step flows.
|
- Модель сервисного слоя и графа зависимостей: `DatabaseContext(T)` намеренно минималистичен, но нет более сильной концепции уровня фреймворка для сервисов приложения или зависимостей с ограниченной областью действия.
|
||||||
|
- Контракт композиции плагинов: плагины — хороший способ группировки, но нет явной модели зависимостей плагинов, общих возможностей или контрактов композиции.
|
||||||
|
|
||||||
|
## Done
|
||||||
|
|
||||||
|
### [1.0.0-rc.12] Conversation / Scene Model
|
||||||
|
|
||||||
|
Текущее состояние:
|
||||||
|
|
||||||
|
- Фреймворк хорошо обрабатывает одно обновление через команды, данные callback, middleware и обработчики обновлений.
|
||||||
|
- В нём уже есть полезные низкоуровневые строительные блоки: `MsgContext`, черновики, маршрутизация данных callback, плагины и обработчики обновлений.
|
||||||
|
- Теперь в нём уже есть реализованная начальная модель сцен для долгоживущих интерактивных сценариев: сцены можно регистрировать в плагинах, запускать через `MsgContext`, сохранять через `SessionStore` и маршрутизировать раньше обычной обработки команд.
|
||||||
|
|
||||||
|
Почему это важно:
|
||||||
|
|
||||||
|
- Многие Telegram-боты быстро перерастают из изолированных команд в многошаговые сценарии с сохранением состояния.
|
||||||
- Реальным ботам часто нужны концепции вроде "подождать следующее сообщение пользователя", "пользователь сейчас на шаге 3 из 5" или "нажатие кнопки переводит пользователя в следующее состояние сцены".
|
- Реальным ботам часто нужны концепции вроде "подождать следующее сообщение пользователя", "пользователь сейчас на шаге 3 из 5" или "нажатие кнопки переводит пользователя в следующее состояние сцены".
|
||||||
- Без scene model пользователи библиотеки начинают строить свой mini-framework поверх Laniakea.
|
- Без модели сцен пользователи библиотеки начинают строить свой мини-фреймворк поверх Laniakea.
|
||||||
|
|
||||||
What is already present:
|
Что уже есть:
|
||||||
|
|
||||||
- Маршрутизация активной сцены раньше обычного command flow.
|
- Маршрутизация активной сцены раньше обычной маршрутизации команд.
|
||||||
- Session scopes на пользователя, чат и пару пользователь-чат.
|
- Области действия сессии на пользователя, чат и пару пользователь-чат.
|
||||||
- Явный вход и выход через `MsgContext`.
|
- Явный вход и выход через `MsgContext`.
|
||||||
- Step handlers, scene-local commands и `OnMessage(...)`.
|
- Обработчики шагов, локальные команды сцены и `OnMessage(...)`.
|
||||||
- In-memory session storage по умолчанию плюс интерфейс `SessionStore` для кастомного persistence.
|
- Встроенное in-memory-хранилище по умолчанию и интерфейс `SessionStore` для собственного постоянного хранения.
|
||||||
|
|
||||||
What is still missing or not yet settled:
|
Что ещё отсутствует или не до конца определено:
|
||||||
|
|
||||||
- Scene-local payload routing.
|
- Локальная маршрутизация данных callback внутри сцены.
|
||||||
- Ясное решение о том, нужен ли вообще дополнительный публичный API для inspection сцен.
|
- Ясное решение о том, нужен ли вообще дополнительный публичный API для просмотра состояния сцен.
|
||||||
- Более широкая стабилизация и документация вокруг helper-методов для scene state.
|
- Дальнейшие расширения поверх текущей модели сцен, ориентированной на сообщения.
|
||||||
|
|
||||||
Current API direction:
|
Текущее направление API:
|
||||||
|
|
||||||
- `Scene`, `SceneContext`, `SceneSession` и `SessionStore`.
|
- `Scene`, `SceneContext`, `SceneSession` и `SessionStore`.
|
||||||
- `Plugin.NewScene(...)` и `Plugin.AddScene(...)`.
|
- `Plugin.NewScene(...)` и `Plugin.AddScene(...)`.
|
||||||
- `MsgContext.EnterScene(...)`, `EnterSceneStep(...)` и `ExitScene(...)`.
|
- `MsgContext.EnterScene(...)`, `EnterSceneStep(...)` и `ExitScene(...)`.
|
||||||
- `SceneContext.Stay()`, `Next(...)`, `Exit()`, `Pass()`, `BindData(...)` и `SaveData(...)`.
|
- `SceneContext.Stay()`, `Next(...)`, `Exit()`, `Pass()`, `BindData(...)` и `SaveData(...)`.
|
||||||
- Storage-backed состояние на пользователя или чат с чистым интерфейсом для кастомного persistence.
|
- Состояние на пользователя или чат с хранением в `SessionStore` и чистым интерфейсом для собственного постоянного хранения.
|
||||||
|
|
||||||
Important design constraints:
|
Важные ограничения дизайна:
|
||||||
|
|
||||||
- Это должно быть опциональным и additive.
|
- Это должно быть опциональным и расширяющим текущую модель.
|
||||||
- Это не должно заменять plugins, commands или handlers как обычные точки входа во фреймворк.
|
- Это не должно заменять плагины, команды или обработчики как обычные точки входа во фреймворк.
|
||||||
- Это должно работать поверх существующих middleware и `MsgContext`, а не вводить вторую несовместимую модель выполнения.
|
- Это должно работать поверх существующих middleware и `MsgContext`, а не вводить вторую несовместимую модель выполнения.
|
||||||
|
|
||||||
Practical target:
|
Практическая цель:
|
||||||
|
|
||||||
- Достабилизировать текущий scene skeleton до состояния first-class framework-supported паттерна.
|
- Расширять текущую полноценную модель сцен только там, где это реально добавляет ценность.
|
||||||
- Покрыть и step-based формы, и mode-based chat flows без необходимости строить пользовательские routing layers вокруг активных sessions.
|
- Сохранять поддержку и пошаговых форм, и модальных чат-сценариев без необходимости строить пользовательские слои маршрутизации вокруг активных сессий.
|
||||||
|
|
||||||
## Secondary Backlog
|
|
||||||
|
|
||||||
- Webhook runtime model: у библиотеки есть хороший polling model, но нет first-class webhook execution model на уровне framework.
|
|
||||||
- Service layer and dependency graph model: `DatabaseContext(T)` намеренно минималистичен, но нет более сильной framework-level концепции для application services или scoped dependencies.
|
|
||||||
- User-facing vs internal error model: у framework есть unified error flow, но он ещё плохо различает user-visible, internal-only, retryable и silent errors.
|
|
||||||
- Authorization and policy model: middleware могут реализовать auth и permissions, но нет явной framework-level модели для access policies, roles или capability checks.
|
|
||||||
- Observability model: logging уже сильный, но metrics, tracing и structured framework hooks пока не first-class.
|
|
||||||
- Plugin composition contract: plugins — хороший grouping unit, но нет явной модели plugin dependencies, shared capabilities или composition contracts.
|
|
||||||
- Update schema contract: update handling уже есть, но нет formal framework-level понятия, описывающего, какие поля `MsgContext` гарантированы для каких update kinds.
|
|
||||||
- Configuration freeze model: у framework уже есть реальные commit points вроде `AddPlugins(...)`, но пока это скорее implementation truth, чем явная top-level концепция.
|
|
||||||
|
|
||||||
## Done
|
|
||||||
|
|
||||||
### [1.0.0-rc.12] Typed Handler Input Model
|
### [1.0.0-rc.12] Typed Handler Input Model
|
||||||
|
|
||||||
Current state:
|
Текущее состояние:
|
||||||
|
|
||||||
- Commands и payloads сейчас отдают parsed text через `ctx.Text` и `ctx.Args`.
|
- Команды и данные callback сейчас отдают разобранный текст через `ctx.Text` и `ctx.Args`.
|
||||||
- `CommandArg` даёт базовую проверку аргументов и их формы.
|
- `CommandArg` даёт базовую проверку аргументов и их формы.
|
||||||
- Handlers всё ещё делают большую часть нетривиального парсинга вручную.
|
- Обработчики всё ещё делают большую часть нетривиального разбора вручную.
|
||||||
|
|
||||||
Why this matters:
|
Почему это важно:
|
||||||
|
|
||||||
- По мере роста бота handlers часто начинают с повторяющегося boilerplate для разбора `ctx.Args`.
|
- По мере роста бота обработчики часто начинают с повторяющегося шаблонного кода для разбора `ctx.Args`.
|
||||||
- Validation logic расползается по handlers вместо того, чтобы жить в одном предсказуемом binding layer.
|
- Логика валидации расползается по обработчикам вместо того, чтобы жить в одном предсказуемом слое привязки.
|
||||||
- Текущая модель проста и честна, но помогает недостаточно, когда команды становятся более структурированными.
|
- Текущая модель проста и честна, но помогает недостаточно, когда команды становятся более структурированными.
|
||||||
|
|
||||||
What is missing:
|
Чего не хватает:
|
||||||
|
|
||||||
- First-class способ bind'ить command или payload arguments в typed Go value.
|
- Полноценный способ привязывать аргументы команды или данные callback к типизированному Go-значению.
|
||||||
- Framework-level паттерн для conversion errors и validation errors вместо чистой работы со строками.
|
- Паттерн уровня фреймворка для ошибок преобразования и ошибок валидации вместо чистой работы со строками.
|
||||||
- Low-friction путь перехода от positional arguments к structured input object.
|
- Низкопороговый путь перехода от позиционных аргументов к структурированному входному объекту.
|
||||||
|
|
||||||
Possible API direction:
|
Возможное направление API:
|
||||||
|
|
||||||
- Lightweight binding API вроде `ctx.BindArgs(&input)`.
|
- Лёгкий API привязки вроде `ctx.BindArgs(&input)`.
|
||||||
- Или explicit typed command registration вроде `NewCommandTyped(...)`.
|
- Или явная регистрация типизированных команд вроде `NewCommandTyped(...)`.
|
||||||
- Positional mapping в structs, optional fields, basic conversion support и интеграция с текущим validation flow.
|
- Позиционное отображение в структуры, optional-поля, базовая поддержка преобразований и интеграция с текущим потоком валидации.
|
||||||
- Unified binding и validation failures, которые идут через существующий centralized error path.
|
- Единые ошибки привязки и валидации, которые идут через существующий централизованный путь обработки ошибок.
|
||||||
|
|
||||||
Example of the kind of user code this should enable:
|
Пример пользовательского кода, который это должно сделать удобным:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type BanInput struct {
|
type BanInput struct {
|
||||||
@@ -109,50 +113,50 @@ func ban(ctx *laniakea.MsgContext, db *App) error {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Important design constraints:
|
Важные ограничения дизайна:
|
||||||
|
|
||||||
- Избегать reflection-heavy и слишком "магической" подсистемы.
|
- Избегать тяжёлой зависимости от reflection и слишком "магической" подсистемы.
|
||||||
- Сохранять текущую модель `ctx.Args` как минимальный baseline.
|
- Сохранять текущую модель `ctx.Args` как минимальную базовую модель.
|
||||||
- Относиться к typed binding как к ergonomic layer поверх текущей command model, а не как к её замене.
|
- Относиться к типизированной привязке как к удобному слою поверх текущей модели команд, а не как к её замене.
|
||||||
|
|
||||||
Practical target:
|
Практическая цель:
|
||||||
|
|
||||||
- Убрать повторяющийся parsing boilerplate, сохранив явный и Go-like характер framework.
|
- Убрать повторяющийся код разбора, сохранив явный и Go-подобный характер фреймворка.
|
||||||
|
|
||||||
### [1.0.0-rc.12] Request Context / Cancellation Model
|
### [1.0.0-rc.12] Request Context / Cancellation Model
|
||||||
|
|
||||||
Current state:
|
Текущее состояние:
|
||||||
|
|
||||||
- `RunWithContext(...)` управляет runtime lifecycle бота и graceful shutdown.
|
- `RunWithContext(...)` управляет жизненным циклом выполнения бота и корректным завершением.
|
||||||
- `tgapi` уже поддерживает context-aware методы.
|
- `tgapi` уже поддерживает методы, принимающие `context.Context`.
|
||||||
- Обычные handlers не получают first-class request-scoped `context.Context`.
|
- Обычные обработчики не получают полноценный `context.Context`, привязанный к обработке конкретного запроса.
|
||||||
|
|
||||||
Why this matters:
|
Почему это важно:
|
||||||
|
|
||||||
- Handler business logic часто требует cancellation-aware database calls, HTTP calls или обращения к downstream services.
|
- Бизнес-логика обработчиков часто требует вызовов базы данных, HTTP-вызовов или обращений к нижележащим сервисам с поддержкой отмены.
|
||||||
- У framework уже есть хорошая runtime cancellation story, но она пока не доходит естественным образом до пользовательского кода внутри handlers.
|
- У фреймворка уже есть хорошая история с отменой выполнения на уровне механизма выполнения, но она пока не доходит естественным образом до пользовательского кода внутри обработчиков.
|
||||||
- В современном Go API `context.Context` — стандартная часть operational correctness.
|
- В современном Go API `context.Context` — стандартная часть корректного управления выполнением.
|
||||||
|
|
||||||
What is missing:
|
Чего не хватает:
|
||||||
|
|
||||||
- Чистый request-scoped context, который сопровождает каждый update через всё выполнение handler.
|
- Чистый `context.Context`, который сопровождает каждое обновление через всё выполнение обработчика.
|
||||||
- Стандартный способ для application code остановить работу, когда бот shutting down или update processing context отменён.
|
- Стандартный способ для кода приложения остановить работу, когда бот завершает работу или когда контекст обработки обновления отменён.
|
||||||
- Прямой мост между bot lifecycle control и service-layer cancellation.
|
- Прямой мост между управлением жизненным циклом бота и отменой в сервисном слое.
|
||||||
|
|
||||||
Possible API direction:
|
Возможное направление API:
|
||||||
|
|
||||||
- Предпочесть non-breaking подход и выдавать context через `MsgContext`, например `ctx.Context()`.
|
- Предпочесть non-breaking подход и выдавать context через `MsgContext`, например `ctx.Context()`.
|
||||||
- Строить context из update-processing lifecycle, чтобы он был meaningful во время graceful shutdown.
|
- Строить context из жизненного цикла обработки обновления, чтобы он оставался полезным во время корректного завершения.
|
||||||
- Сделать естественным передачу этого context в database methods, HTTP clients и `tgapi.WithContext(...)`.
|
- Сделать естественной передачу этого context в методы базы данных, HTTP-клиенты и `tgapi.WithContext(...)`.
|
||||||
|
|
||||||
Why this should probably not be a signature change:
|
Почему это, скорее всего, не должно быть изменением сигнатуры:
|
||||||
|
|
||||||
- Изменение handler signatures на прямой `context.Context` было бы public breaking change.
|
- Изменение сигнатур обработчиков на прямой `context.Context` было бы публичным ломающим изменением.
|
||||||
- Accessor на `MsgContext` сохраняет совместимость и при этом даёт handlers idiomatic Go path для cancellation.
|
- Аксессор на `MsgContext` сохраняет совместимость и при этом даёт обработчикам идиоматичный Go-путь для отмены выполнения.
|
||||||
|
|
||||||
Practical target:
|
Практическая цель:
|
||||||
|
|
||||||
- Позволить handler code естественно участвовать в cancellation и graceful shutdown, не заставляя пользователей строить собственный context plumbing.
|
- Позволить коду обработчиков естественно участвовать в отмене выполнения и корректном завершении, не заставляя пользователей строить собственную передачу `context`.
|
||||||
|
|
||||||
Связанные страницы:
|
Связанные страницы:
|
||||||
|
|
||||||
|
|||||||
+23
-19
@@ -2,15 +2,32 @@
|
|||||||
|
|
||||||
This page tracks framework-level backlog items that are about missing concepts in the library itself, not just missing documentation.
|
This page tracks framework-level backlog items that are about missing concepts in the library itself, not just missing documentation.
|
||||||
|
|
||||||
## High-Priority Core Concepts
|
## Priority 1 — Urgent
|
||||||
|
|
||||||
### 1. Conversation / Scene Model
|
- Update schema contract: update handling exists, but there is no formal framework-level concept describing which `MsgContext` fields are guaranteed in which update kinds.
|
||||||
|
- User-facing vs internal error model: the framework has a unified error flow, but it does not yet distinguish well between user-visible, internal-only, retryable, or silent errors.
|
||||||
|
- Configuration freeze model: the framework already has real commit points like `AddPlugins(...)`, but this is still more of an implementation truth than an explicit top-level concept.
|
||||||
|
|
||||||
|
## Priority 2 — Important
|
||||||
|
|
||||||
|
- Webhook runtime model: the library has a solid polling model, but no first-class webhook execution model at the framework level.
|
||||||
|
- Authorization and policy model: middleware can implement auth and permissions, but there is no explicit framework concept for access policies, roles, or capability checks.
|
||||||
|
- Observability model: logging is strong, but metrics, tracing, and structured framework hooks are still missing as first-class concepts.
|
||||||
|
|
||||||
|
## Priority 3 — Strategic
|
||||||
|
|
||||||
|
- Service layer and dependency graph model: `DatabaseContext(T)` is intentionally minimal, but there is no stronger framework concept for application services or scoped dependencies.
|
||||||
|
- Plugin composition contract: plugins are a good grouping unit, but there is no explicit model for plugin dependencies, shared capabilities, or composition contracts.
|
||||||
|
|
||||||
|
## Done
|
||||||
|
|
||||||
|
### [1.0.0-rc.12] Conversation / Scene Model
|
||||||
|
|
||||||
Current state:
|
Current state:
|
||||||
|
|
||||||
- The framework is strong at handling a single update through commands, payloads, middleware, and update handlers.
|
- The framework is strong at handling a single update through commands, payloads, middleware, and update handlers.
|
||||||
- It already has useful lower-level building blocks such as `MsgContext`, drafts, payload routing, plugins, and update handlers.
|
- It already has useful lower-level building blocks such as `MsgContext`, drafts, payload routing, plugins, and update handlers.
|
||||||
- It now provides a work-in-progress scene skeleton for long-lived user interaction flows: scenes can be registered in plugins, entered through `MsgContext`, persisted through `SessionStore`, and routed before normal command handling.
|
- It now provides an implemented initial scene model for long-lived user interaction flows: scenes can be registered in plugins, entered through `MsgContext`, persisted through `SessionStore`, and routed before normal command handling.
|
||||||
|
|
||||||
Why this matters:
|
Why this matters:
|
||||||
|
|
||||||
@@ -30,7 +47,7 @@ What is still missing or not yet settled:
|
|||||||
|
|
||||||
- Scene-local payload routing.
|
- Scene-local payload routing.
|
||||||
- A clear decision on whether any additional public scene-inspection API is needed.
|
- A clear decision on whether any additional public scene-inspection API is needed.
|
||||||
- Broader stabilization and documentation around the scene-state helper surface.
|
- Further extensions beyond the current message-driven scene model.
|
||||||
|
|
||||||
Current API direction:
|
Current API direction:
|
||||||
|
|
||||||
@@ -48,21 +65,8 @@ Important design constraints:
|
|||||||
|
|
||||||
Practical target:
|
Practical target:
|
||||||
|
|
||||||
- Stabilize the current scene skeleton into a first-class framework-supported pattern.
|
- Extend the current first-class scene model where it adds clear value.
|
||||||
- Cover both step-based forms and mode-based chat flows without forcing users to build custom routing layers around active sessions.
|
- Keep both step-based forms and mode-based chat flows supported without forcing users to build custom routing layers around active sessions.
|
||||||
|
|
||||||
## Secondary Backlog
|
|
||||||
|
|
||||||
- Webhook runtime model: the library has a solid polling model, but no first-class webhook execution model at the framework level.
|
|
||||||
- Service layer and dependency graph model: `DatabaseContext(T)` is intentionally minimal, but there is no stronger framework concept for application services or scoped dependencies.
|
|
||||||
- User-facing vs internal error model: the framework has a unified error flow, but it does not yet distinguish well between user-visible, internal-only, retryable, or silent errors.
|
|
||||||
- Authorization and policy model: middleware can implement auth and permissions, but there is no explicit framework concept for access policies, roles, or capability checks.
|
|
||||||
- Observability model: logging is strong, but metrics, tracing, and structured framework hooks are still missing as first-class concepts.
|
|
||||||
- Plugin composition contract: plugins are a good grouping unit, but there is no explicit model for plugin dependencies, shared capabilities, or composition contracts.
|
|
||||||
- Update schema contract: update handling exists, but there is no formal framework-level concept describing which `MsgContext` fields are guaranteed in which update kinds.
|
|
||||||
- Configuration freeze model: the framework already has real commit points like `AddPlugins(...)`, but this is still more of an implementation truth than an explicit top-level concept.
|
|
||||||
|
|
||||||
## Done
|
|
||||||
|
|
||||||
### [1.0.0-rc.12] Typed Handler Input Model
|
### [1.0.0-rc.12] Typed Handler Input Model
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -116,7 +116,7 @@ func(ctx *laniakea.MsgContext, db T) error
|
|||||||
|
|
||||||
То есть:
|
То есть:
|
||||||
- на успехе возвращай `nil`
|
- на успехе возвращай `nil`
|
||||||
- если хочешь централизованный flow обработки ошибок, возвращай `error`
|
- если хочешь централизованный поток обработки ошибок, возвращай `error`
|
||||||
|
|
||||||
Пример:
|
Пример:
|
||||||
|
|
||||||
@@ -181,7 +181,7 @@ defer bot.Close()
|
|||||||
|
|
||||||
### Неожидание, что `ctx.Text` уже очищен от команды
|
### Неожидание, что `ctx.Text` уже очищен от команды
|
||||||
|
|
||||||
Для обычного command flow:
|
Для обычного потока команд:
|
||||||
- сообщение: `/echo hello world`
|
- сообщение: `/echo hello world`
|
||||||
- имя команды: `echo`
|
- имя команды: `echo`
|
||||||
- `ctx.Text`: `hello world`
|
- `ctx.Text`: `hello world`
|
||||||
|
|||||||
+21
-20
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
English version: [[Home]]
|
English version: [[Home]]
|
||||||
|
|
||||||
Это краткая русскоязычная точка входа в wiki Laniakea. Подробная и наиболее полная документация остается в англоязычной части wiki, поэтому за полным API-справочником лучше переходить по ссылкам на соответствующие английские страницы.
|
Это краткая русскоязычная точка входа в wiki Laniakea. Подробная и наиболее полная документация остаётся в англоязычной части wiki, поэтому за полным API-справочником лучше переходить по ссылкам на соответствующие английские страницы.
|
||||||
|
|
||||||
## Старт здесь
|
## Старт здесь
|
||||||
|
|
||||||
@@ -12,13 +12,14 @@ English version: [[Home]]
|
|||||||
- [[MsgContext-RU]]
|
- [[MsgContext-RU]]
|
||||||
- [[FAQ-RU]]
|
- [[FAQ-RU]]
|
||||||
|
|
||||||
## Runtime и архитектура
|
## Выполнение и архитектура
|
||||||
|
|
||||||
- [[Bot-Lifecycle-RU]]
|
- [[Bot-Lifecycle-RU]]
|
||||||
- [[Middleware-RU]]
|
- [[Middleware-RU]]
|
||||||
- [[Runners-RU]]
|
- [[Runners-RU]]
|
||||||
- [[Error-Handling-RU]]
|
- [[Error-Handling-RU]]
|
||||||
- [[Logging-RU]]
|
- [[Logging-RU]]
|
||||||
|
- [[Scenes-RU]]
|
||||||
|
|
||||||
## Telegram API и взаимодействие
|
## Telegram API и взаимодействие
|
||||||
|
|
||||||
@@ -34,47 +35,47 @@ English version: [[Home]]
|
|||||||
- [[Recipes-RU]]
|
- [[Recipes-RU]]
|
||||||
- [[Testing-Bots-with-Laniakea-RU]]
|
- [[Testing-Bots-with-Laniakea-RU]]
|
||||||
|
|
||||||
## Чего еще не хватает в core concepts
|
## Чего ещё не хватает в основных концепциях
|
||||||
|
|
||||||
Сейчас wiki уже покрывает почти весь основной surface фреймворка: `Bot`, `BotOpts`, lifecycle, plugins и commands, `MsgContext`, middleware, payloads, `tgapi`, drafts, localization, runners, errors, logging, rate limiting, testing и migration.
|
Сейчас wiki уже покрывает почти всю основную поверхность фреймворка: `Bot`, `BotOpts`, жизненный цикл, плагины и команды, `MsgContext`, middleware, данные callback, `tgapi`, черновики, локализацию, фоновые задачи, ошибки, логирование, ограничение частоты, тестирование и миграцию.
|
||||||
|
|
||||||
Но если смотреть именно на концептуальные дыры, а не просто на наличие страниц, то все еще выделяются такие темы:
|
Но если смотреть именно на концептуальные дыры, а не просто на наличие страниц, то все еще выделяются такие темы:
|
||||||
|
|
||||||
1. `Update-Routing-Model`
|
1. `Update-Routing-Model`
|
||||||
Почему это важно:
|
Почему это важно:
|
||||||
Сейчас routing объяснен кусками в `Commands-and-Plugins`, `Bot-Lifecycle` и `Middleware`, но нет одной страницы, которая последовательно показывает, как update проходит через систему.
|
Сейчас маршрутизация объяснена кусками в `Commands-and-Plugins`, `Bot-Lifecycle` и `Middleware`, но нет одной страницы, которая последовательно показывает, как обновление проходит через систему.
|
||||||
Что туда войдет:
|
Что туда войдет:
|
||||||
`prepareUpdateCtx`, bot middleware, command flow, payload flow, update handlers, cloned context для non-command updates и first-match behavior.
|
`prepareUpdateCtx`, middleware бота, поток команд, поток данных callback, обработчики обновлений, клонированный контекст для обновлений вне командного потока и поведение первого совпадения.
|
||||||
|
|
||||||
2. `Context-and-State-Model`
|
2. `Context-and-State-Model`
|
||||||
Почему это важно:
|
Почему это важно:
|
||||||
Страница про `MsgContext` уже есть, но нет отдельной концептуальной страницы про shared state, copied state, поведение `DatabaseContext(T)` и про то, почему pointer types чаще всего являются правильным default choice.
|
Страница про `MsgContext` уже есть, но нет отдельной концептуальной страницы про разделяемое состояние, копируемое состояние, поведение `DatabaseContext(T)` и про то, почему pointer types чаще всего являются правильным выбором по умолчанию.
|
||||||
Что туда войдет:
|
Что туда войдет:
|
||||||
Shared dependencies, copied context values, runtime expectations и места, где легко ошибиться с race assumptions.
|
Разделяемые зависимости, копируемые значения context, ожидания во время выполнения и места, где легко ошибиться с предположениями о гонках.
|
||||||
|
|
||||||
3. `Plugin-Boundaries-and-Composition`
|
3. `Plugin-Boundaries-and-Composition`
|
||||||
Почему это важно:
|
Почему это важно:
|
||||||
Wiki уже объясняет, как plugins использовать на практике, но почти не говорит о том, как о них думать архитектурно.
|
Wiki уже объясняет, как использовать плагины на практике, но почти не говорит о том, как о них думать архитектурно.
|
||||||
Что туда войдет:
|
Что туда войдет:
|
||||||
Как резать бот на plugins, что должно жить в plugin middleware, когда выделять новый plugin и как не прийти к giant-plugin design.
|
Как делить бот на плагины, что должно жить в middleware плагина, когда выделять новый плагин и как не прийти к дизайну одного гигантского плагина.
|
||||||
|
|
||||||
4. `Handler-Design-Guidelines`
|
4. `Handler-Design-Guidelines`
|
||||||
Почему это важно:
|
Почему это важно:
|
||||||
Это будет страница не столько про API, сколько про стиль и idiomatic use framework'а.
|
Это будет страница не столько про API, сколько про стиль и идиоматичное использование фреймворка.
|
||||||
Что туда войдет:
|
Что туда войдет:
|
||||||
Когда возвращать `error`, когда отвечать вручную, как держать handlers thin, когда выносить логику в сервисный слой и как не смешивать `tgapi` и high-level helpers без необходимости.
|
Когда возвращать `error`, когда отвечать вручную, как держать обработчики тонкими, когда выносить логику в сервисный слой и как не смешивать `tgapi` и высокоуровневые вспомогательные методы без необходимости.
|
||||||
|
|
||||||
5. `Update-Types-and-Coverage`
|
5. `Update-Types-and-Coverage`
|
||||||
Почему это важно:
|
Почему это важно:
|
||||||
Сейчас update handlers уже задокументированы, но нет одной карты того, какие update types идут через commands и payloads, какие через `AddUpdateHandler(...)`, и какие поля `MsgContext` разумно ожидать в каждом flow.
|
Сейчас обработчики обновлений уже задокументированы, но нет одной карты того, какие типы обновлений идут через команды и данные callback, какие через `AddUpdateHandler(...)`, и какие поля `MsgContext` разумно ожидать в каждом потоке.
|
||||||
Что туда войдет:
|
Что туда войдет:
|
||||||
Routing categories, update-specific context guarantees и влияние формы update на handler design.
|
Категории маршрутизации, гарантии context для конкретных видов обновлений и влияние формы обновления на дизайн обработчика.
|
||||||
|
|
||||||
6. `Telegram-Limits-and-Validation`
|
6. `Telegram-Limits-and-Validation`
|
||||||
Почему это важно:
|
Почему это важно:
|
||||||
Часть этой информации уже разбросана по страницам про rate limiting, payloads и errors, но нет одной общей mental-model страницы.
|
Часть этой информации уже разбросана по страницам про ограничение частоты, данные callback и ошибки, но нет одной общей страницы с целостной моделью.
|
||||||
Что туда войдет:
|
Что туда войдет:
|
||||||
Лимиты на message text, captions и callback data, validation before send, long replies, Markdown caveats и upload-related ограничения.
|
Лимиты на текст сообщения, подписи и callback data, валидация до отправки, длинные ответы, особенности Markdown и ограничения, связанные с загрузкой файлов.
|
||||||
|
|
||||||
Менее срочные, но тоже полезные темы:
|
Менее срочные, но тоже полезные темы:
|
||||||
- `Concurrency-Model`
|
- `Concurrency-Model`
|
||||||
@@ -94,9 +95,9 @@ Routing categories, update-specific context guarantees и влияние фор
|
|||||||
- [[Page-Priority-RU]]
|
- [[Page-Priority-RU]]
|
||||||
- [[FAQ-RU]]
|
- [[FAQ-RU]]
|
||||||
|
|
||||||
## Полный английский reference
|
## Полный английский справочник
|
||||||
|
|
||||||
Если нужен полный и наиболее свежий reference, смотри исходные англоязычные страницы:
|
Если нужен полный и наиболее свежий справочник, смотри исходные англоязычные страницы:
|
||||||
|
|
||||||
- [[Getting-Started]]
|
- [[Getting-Started]]
|
||||||
- [[Bot-Options-and-Configuration]]
|
- [[Bot-Options-and-Configuration]]
|
||||||
@@ -112,8 +113,8 @@ Routing categories, update-specific context guarantees и влияние фор
|
|||||||
|
|
||||||
1. Прочитать [[Getting-Started-RU]].
|
1. Прочитать [[Getting-Started-RU]].
|
||||||
2. Прочитать [[Commands-and-Plugins-RU]] и [[MsgContext-RU]].
|
2. Прочитать [[Commands-and-Plugins-RU]] и [[MsgContext-RU]].
|
||||||
3. Перейти в runtime-страницы вроде [[Bot-Lifecycle-RU]] и [[Middleware-RU]].
|
3. Перейти в страницы про выполнение и поведение во время работы, например [[Bot-Lifecycle-RU]] и [[Middleware-RU]].
|
||||||
4. При необходимости открыть специализированные страницы вроде [[Drafts-RU]], [[Rate-Limiting-RU]] или [[tgapi-Overview-RU]].
|
4. При необходимости открыть специализированные страницы вроде [[Drafts-RU]], [[Rate-Limiting-RU]] или [[tgapi-Overview-RU]].
|
||||||
5. Для максимальной точности переходить в соответствующую англоязычную страницу.
|
5. Для максимальной точности переходить в соответствующую англоязычную страницу.
|
||||||
|
|
||||||
Такой подход позволяет пользоваться русской wiki как полноценным слоем документации, но при этом при желании всегда уходить в англоязычный source of truth.
|
Такой подход позволяет пользоваться русской wiki как полноценным слоем документации, но при этом при желании всегда уходить в англоязычный основной источник.
|
||||||
|
|||||||
@@ -2,17 +2,17 @@
|
|||||||
|
|
||||||
English version: [[Inline-Keyboards-and-Payloads]]
|
English version: [[Inline-Keyboards-and-Payloads]]
|
||||||
|
|
||||||
Это краткая русскоязычная версия страницы про inline keyboards и callback payloads. Полная и наиболее актуальная страница: [[Inline-Keyboards-and-Payloads]].
|
Это краткая русскоязычная версия страницы про inline-клавиатуры и данные callback. Полная и наиболее актуальная страница: [[Inline-Keyboards-and-Payloads]].
|
||||||
|
|
||||||
## Главное сначала
|
## Главное сначала
|
||||||
|
|
||||||
- `InlineKeyboard` строит inline keyboard row by row;
|
- `InlineKeyboard` строит inline-клавиатуру ряд за рядом;
|
||||||
- callback button хранит `CallbackData` с command name и args;
|
- callback-кнопка хранит `CallbackData` с именем команды и аргументами;
|
||||||
- payload может кодироваться как JSON или Base64;
|
- данные callback могут кодироваться как JSON или Base64;
|
||||||
- у bot есть default payload type;
|
- у бота есть тип данных callback по умолчанию;
|
||||||
- конкретная keyboard может переопределить его локально.
|
- конкретная клавиатура может переопределить его локально.
|
||||||
|
|
||||||
## Как строить keyboard
|
## Как строить клавиатуру
|
||||||
|
|
||||||
Основные конструкторы:
|
Основные конструкторы:
|
||||||
- `NewInlineKeyboardJson(maxRow)`
|
- `NewInlineKeyboardJson(maxRow)`
|
||||||
@@ -30,46 +30,46 @@ kb := laniakea.NewInlineKeyboardJson(2).
|
|||||||
|
|
||||||
`maxRow` определяет, сколько кнопок автоматически помещается в один ряд.
|
`maxRow` определяет, сколько кнопок автоматически помещается в один ряд.
|
||||||
|
|
||||||
## Что такое payload
|
## Что такое данные callback
|
||||||
|
|
||||||
Callback payload логически содержит:
|
Данные callback логически содержат:
|
||||||
- command name;
|
- имя команды;
|
||||||
- список string arguments.
|
- список строковых аргументов.
|
||||||
|
|
||||||
Ты обычно не собираешь JSON вручную. Вместо этого используешь:
|
Ты обычно не собираешь JSON вручную. Вместо этого используешь:
|
||||||
- `AddCallbackButton(...)`
|
- `AddCallbackButton(...)`
|
||||||
- `AddCallbackButtonStyle(...)`
|
- `AddCallbackButtonStyle(...)`
|
||||||
- `NewCallbackData(...)`
|
- `NewCallbackData(...)`
|
||||||
|
|
||||||
Все аргументы через `fmt.Sprint` превращаются в строки, поэтому в handler'е ты читаешь их через `ctx.Args`.
|
Все аргументы через `fmt.Sprint` превращаются в строки, поэтому в обработчике ты читаешь их через `ctx.Args`.
|
||||||
|
|
||||||
## `ctx.Args`, а не `ctx.Payload`
|
## `ctx.Args`, а не `ctx.Payload`
|
||||||
|
|
||||||
В текущем API нет отдельного `ctx.Payload`.
|
В текущем API нет отдельного `ctx.Payload`.
|
||||||
|
|
||||||
В payload handler'е decoded args доступны через:
|
В обработчике данных callback разобранные аргументы доступны через:
|
||||||
- `ctx.Args`
|
- `ctx.Args`
|
||||||
|
|
||||||
А выбор handler'а происходит по имени payload command.
|
А выбор обработчика происходит по имени callback-команды.
|
||||||
|
|
||||||
## JSON vs Base64
|
## JSON vs Base64
|
||||||
|
|
||||||
`BotPayloadJson`:
|
`BotPayloadJson`:
|
||||||
- удобнее читать в логах и тестах;
|
- удобнее читать в логах и тестах;
|
||||||
- проще дебажить.
|
- проще отлаживать.
|
||||||
|
|
||||||
`BotPayloadBase64`:
|
`BotPayloadBase64`:
|
||||||
- более компактный transport form;
|
- более компактная транспортная форма;
|
||||||
- выглядит более “непрозрачно” в callback data.
|
- выглядит более “непрозрачно” в callback data.
|
||||||
|
|
||||||
Логическая структура payload при этом одна и та же.
|
Логическая структура данных callback при этом одна и та же.
|
||||||
|
|
||||||
## Strict vs tolerant decoding
|
## Строгое и терпимое декодирование
|
||||||
|
|
||||||
По умолчанию bot tolerant:
|
По умолчанию бот работает в терпимом режиме:
|
||||||
- если основной decoder не сработал, может попробовать второй формат.
|
- если основной декодер не сработал, может попробовать второй формат.
|
||||||
|
|
||||||
Strict mode отключает этот fallback:
|
Строгий режим отключает этот запасной вариант:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
bot.SetStrictPayloadType(true)
|
bot.SetStrictPayloadType(true)
|
||||||
@@ -77,24 +77,24 @@ bot.SetStrictPayloadType(true)
|
|||||||
|
|
||||||
или через `BotOpts`.
|
или через `BotOpts`.
|
||||||
|
|
||||||
Это полезно, если payload-format drift нужно считать настоящей ошибкой.
|
Это полезно, если расхождение формата данных callback нужно считать настоящей ошибкой.
|
||||||
|
|
||||||
## Keyboard-local override
|
## Локальное переопределение для клавиатуры
|
||||||
|
|
||||||
Даже если у bot есть default payload type, keyboard можно переопределить локально:
|
Даже если у бота есть тип данных callback по умолчанию, клавиатуру можно переопределить локально:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
kb := ctx.NewInlineKeyboard(2).
|
kb := ctx.NewInlineKeyboard(2).
|
||||||
SetPayloadType(laniakea.BotPayloadJson)
|
SetPayloadType(laniakea.BotPayloadJson)
|
||||||
```
|
```
|
||||||
|
|
||||||
Это удобно для migration или debugging-сценариев.
|
Это удобно для сценариев миграции или отладки.
|
||||||
|
|
||||||
## `KeyboardLong(...)`
|
## `KeyboardLong(...)`
|
||||||
|
|
||||||
Если текст длинный, используй `KeyboardLong(...)`.
|
Если текст длинный, используй `KeyboardLong(...)`.
|
||||||
|
|
||||||
Keyboard прикрепляется только к последнему chunk, чтобы не дублировать кнопки на каждой части.
|
Клавиатура прикрепляется только к последней части, чтобы не дублировать кнопки на каждом фрагменте.
|
||||||
|
|
||||||
## Что читать дальше
|
## Что читать дальше
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -56,11 +56,11 @@ bot.AddL10n(l10n)
|
|||||||
- `bot.L10n(lang, key)`
|
- `bot.L10n(lang, key)`
|
||||||
- `ctx.Translate(key)`
|
- `ctx.Translate(key)`
|
||||||
|
|
||||||
Если вызвать `AddL10n(nil)`, bot залогирует warning и оставит текущий provider без изменений.
|
Если вызвать `AddL10n(nil)`, бот залогирует предупреждение и оставит текущий provider без изменений.
|
||||||
|
|
||||||
## `ctx.Translate(...)`
|
## `ctx.Translate(...)`
|
||||||
|
|
||||||
Это основной ergonomic helper внутри handler'ов.
|
Это основной удобный вспомогательный метод внутри обработчиков.
|
||||||
|
|
||||||
Он:
|
Он:
|
||||||
- берет language code из `ctx.From`, если он есть;
|
- берет language code из `ctx.From`, если он есть;
|
||||||
|
|||||||
+32
-32
@@ -2,30 +2,30 @@
|
|||||||
|
|
||||||
English version: [[Logging]]
|
English version: [[Logging]]
|
||||||
|
|
||||||
Это краткая русскоязычная версия страницы про logging. Полная и наиболее актуальная страница: [[Logging]].
|
Это краткая русскоязычная версия страницы про логирование. Полная и наиболее актуальная страница: [[Logging]].
|
||||||
|
|
||||||
## Какие logger layers есть
|
## Какие уровни логирования есть
|
||||||
|
|
||||||
У Laniakea обычно есть несколько уровней логирования:
|
У Laniakea обычно есть несколько уровней логирования:
|
||||||
- main bot logger;
|
- основной логгер бота;
|
||||||
- optional request logger;
|
- необязательный логгер запросов;
|
||||||
- plugin loggers;
|
- логгеры плагинов;
|
||||||
- внутренние loggers у `tgapi.API` и `tgapi.Uploader`.
|
- внутренние логгеры у `tgapi.API` и `tgapi.Uploader`.
|
||||||
|
|
||||||
Они нужны для разных задач:
|
Они нужны для разных задач:
|
||||||
- bot logger покрывает lifecycle и routing;
|
- логгер бота покрывает жизненный цикл и маршрутизацию;
|
||||||
- request logger пишет сырые updates;
|
- логгер запросов пишет сырые обновления;
|
||||||
- plugin loggers помогают разделять логи по модулям;
|
- логгеры плагинов помогают разделять логи по модулям;
|
||||||
- API/uploader loggers полезны для low-level tracing.
|
- логгеры API/uploader полезны для низкоуровневой трассировки.
|
||||||
|
|
||||||
## Main bot logger
|
## Основной логгер бота
|
||||||
|
|
||||||
Создается при `NewBot(...)`.
|
Создается при `NewBot(...)`.
|
||||||
|
|
||||||
Используется для:
|
Используется для:
|
||||||
- startup/shutdown сообщений;
|
- сообщений о запуске и остановке;
|
||||||
- runner и middleware warnings;
|
- предупреждений от фоновых задач и middleware;
|
||||||
- bot-level operational logs.
|
- операционных логов уровня бота.
|
||||||
|
|
||||||
Получить можно через:
|
Получить можно через:
|
||||||
|
|
||||||
@@ -33,36 +33,36 @@ English version: [[Logging]]
|
|||||||
logger := bot.GetLogger()
|
logger := bot.GetLogger()
|
||||||
```
|
```
|
||||||
|
|
||||||
## Request logger
|
## Логгер запросов
|
||||||
|
|
||||||
Включается через `BotOpts.UseRequestLogger`.
|
Включается через `BotOpts.UseRequestLogger`.
|
||||||
|
|
||||||
Он логирует raw updates после успешного `getUpdates`.
|
Он логирует сырые обновления после успешного `getUpdates`.
|
||||||
|
|
||||||
Это особенно полезно для:
|
Это особенно полезно для:
|
||||||
- debugging routing;
|
- отладки маршрутизации;
|
||||||
- изучения реальной формы Telegram updates;
|
- изучения реальной формы Telegram updates;
|
||||||
- подготовки тестовых samples.
|
- подготовки тестовых примеров.
|
||||||
|
|
||||||
## Plugin loggers и `ctx.Logger`
|
## Логгеры плагинов и `ctx.Logger`
|
||||||
|
|
||||||
У plugin может быть свой logger.
|
У плагина может быть свой логгер.
|
||||||
|
|
||||||
Если он есть, в handler'е `ctx.Logger` обычно указывает именно на него.
|
Если он есть, в handler'е `ctx.Logger` обычно указывает именно на него.
|
||||||
|
|
||||||
Если нет, `ctx.Logger` падает обратно на bot logger.
|
Если нет, `ctx.Logger` переключается обратно на логгер бота.
|
||||||
|
|
||||||
Из-за этого внутри handler'ов чаще всего правильнее использовать именно `ctx.Logger`.
|
Из-за этого внутри обработчиков чаще всего правильнее использовать именно `ctx.Logger`.
|
||||||
|
|
||||||
## Debug mode
|
## Debug mode
|
||||||
|
|
||||||
`Bot.Debug(true)` или `BotOpts.Debug`:
|
`Bot.Debug(true)` или `BotOpts.Debug`:
|
||||||
- поднимает уровень логирования;
|
- поднимает уровень логирования;
|
||||||
- влияет на bot logger;
|
- влияет на bot logger;
|
||||||
- влияет на request logger;
|
- влияет на логгер запросов;
|
||||||
- влияет на уже зарегистрированные plugin loggers.
|
- влияет на уже зарегистрированные логгеры плагинов.
|
||||||
|
|
||||||
## File logging
|
## Логирование в файл
|
||||||
|
|
||||||
Если включить:
|
Если включить:
|
||||||
- `WriteToFile`
|
- `WriteToFile`
|
||||||
@@ -72,21 +72,21 @@ logger := bot.GetLogger()
|
|||||||
- `main.log`
|
- `main.log`
|
||||||
- `requests.log`
|
- `requests.log`
|
||||||
|
|
||||||
Если создание file logger не удалось, bot не падает, а остается на stdout logging.
|
Если создание файлового логгера не удалось, бот не падает, а остается на логировании в stdout.
|
||||||
|
|
||||||
## `AddDatabaseLoggerWriter(...)`
|
## `AddDatabaseLoggerWriter(...)`
|
||||||
|
|
||||||
Этот метод позволяет прикрепить writer, полученный из DB/shared context, сразу к нескольким logger layers.
|
Этот метод позволяет прикрепить writer, полученный из DB/shared context, сразу к нескольким уровням логирования.
|
||||||
|
|
||||||
Он добавляется в:
|
Он добавляется в:
|
||||||
- bot logger;
|
- логгер бота;
|
||||||
- request logger;
|
- логгер запросов;
|
||||||
- API/uploader loggers;
|
- логгеры API/uploader;
|
||||||
- уже зарегистрированные plugin loggers.
|
- уже зарегистрированные логгеры плагинов.
|
||||||
|
|
||||||
Важно:
|
Важно:
|
||||||
- сначала должен быть задан `DatabaseContext(...)`;
|
- сначала должен быть задан `DatabaseContext(...)`;
|
||||||
- если хочешь, чтобы plugin loggers точно получили writer, вызывай метод после `AddPlugins(...)`.
|
- если хочешь, чтобы логгеры плагинов точно получили writer, вызывай метод после `AddPlugins(...)`.
|
||||||
|
|
||||||
## Что читать дальше
|
## Что читать дальше
|
||||||
|
|
||||||
|
|||||||
+39
-39
@@ -6,11 +6,11 @@ English version: [[Middleware]]
|
|||||||
|
|
||||||
## Зачем нужен middleware
|
## Зачем нужен middleware
|
||||||
|
|
||||||
Middleware позволяет запускать логику до command handlers, payload handlers и update handlers.
|
Middleware позволяет запускать логику до обработчиков команд, данных callback и обновлений.
|
||||||
|
|
||||||
Это главное место для cross-cutting concerns:
|
Это главное место для сквозной логики:
|
||||||
- auth checks;
|
- проверки доступа;
|
||||||
- request logging;
|
- журналирование запросов;
|
||||||
- feature flags;
|
- feature flags;
|
||||||
- простая подготовка контекста;
|
- простая подготовка контекста;
|
||||||
- side effects вроде analytics.
|
- side effects вроде analytics.
|
||||||
@@ -18,27 +18,27 @@ Middleware позволяет запускать логику до command handl
|
|||||||
## Уровни middleware
|
## Уровни middleware
|
||||||
|
|
||||||
Есть три уровня:
|
Есть три уровня:
|
||||||
- bot-level через `Bot.AddMiddleware(...)`;
|
- на уровне бота через `Bot.AddMiddleware(...)`;
|
||||||
- plugin-level через `Plugin.AddMiddleware(...)`;
|
- на уровне плагина через `Plugin.AddMiddleware(...)`;
|
||||||
- command/payload-level через `Command.Use(...)`.
|
- на уровне команды или callback через `Command.Use(...)`.
|
||||||
|
|
||||||
Их удобно понимать так:
|
Их удобно понимать так:
|
||||||
- bot-level действует на весь bot;
|
- уровень бота действует на весь бот;
|
||||||
- plugin-level на один plugin;
|
- уровень плагина на один плагин;
|
||||||
- command-level на одну конкретную command или payload.
|
- уровень команды на одну конкретную команду или данные callback.
|
||||||
|
|
||||||
## Порядок выполнения
|
## Порядок выполнения
|
||||||
|
|
||||||
Для commands и payloads порядок такой:
|
Для команд и данных callback порядок такой:
|
||||||
1. bot middleware;
|
1. middleware бота;
|
||||||
2. plugin middleware;
|
2. middleware плагина;
|
||||||
3. command/payload middleware;
|
3. middleware команды или callback;
|
||||||
4. final handler.
|
4. финальный обработчик.
|
||||||
|
|
||||||
Для update handlers:
|
Для обработчиков обновлений:
|
||||||
1. bot middleware;
|
1. middleware бота;
|
||||||
2. plugin middleware для каждого подходящего plugin;
|
2. middleware плагина для каждого подходящего плагина;
|
||||||
3. update handler.
|
3. обработчик обновления.
|
||||||
|
|
||||||
## Synchronous middleware
|
## Synchronous middleware
|
||||||
|
|
||||||
@@ -53,53 +53,53 @@ func(ctx *laniakea.MsgContext, db T) bool
|
|||||||
- `false` — остановить текущую цепочку.
|
- `false` — остановить текущую цепочку.
|
||||||
|
|
||||||
Это правильный вариант для:
|
Это правильный вариант для:
|
||||||
- access control;
|
- контроля доступа;
|
||||||
- required validation;
|
- обязательной валидации;
|
||||||
- rate limiting gates;
|
- ограничителей частоты;
|
||||||
- любой логики, которая должна уметь реально остановить handler path.
|
- любой логики, которая должна уметь реально остановить цепочку обработки.
|
||||||
|
|
||||||
## Asynchronous middleware
|
## Asynchronous middleware
|
||||||
|
|
||||||
Можно включить `SetAsync(true)`.
|
Можно включить `SetAsync(true)`.
|
||||||
|
|
||||||
Но это меняет semantics:
|
Но это меняет семантику:
|
||||||
- middleware идет в goroutine;
|
- middleware идет в goroutine;
|
||||||
- выполнение цепочки продолжается сразу;
|
- выполнение цепочки продолжается сразу;
|
||||||
- `bool` return value игнорируется;
|
- возвращаемое значение `bool` игнорируется;
|
||||||
- middleware получает копию `MsgContext`.
|
- middleware получает копию `MsgContext`.
|
||||||
|
|
||||||
Поэтому async middleware подходит только для:
|
Поэтому async middleware подходит только для:
|
||||||
- telemetry;
|
- телеметрии;
|
||||||
- audit logs;
|
- аудиторских логов;
|
||||||
- fire-and-forget notifications;
|
- уведомлений без ожидания результата;
|
||||||
- best-effort side effects.
|
- best-effort side effects.
|
||||||
|
|
||||||
Он не подходит для:
|
Он не подходит для:
|
||||||
- auth checks;
|
- проверок доступа;
|
||||||
- required validation;
|
- обязательной валидации;
|
||||||
- логики, которая должна переписать `ctx` и повлиять на handler.
|
- логики, которая должна переписать `ctx` и повлиять на обработчик.
|
||||||
|
|
||||||
## Почему async middleware получает копию `MsgContext`
|
## Почему async middleware получает копию `MsgContext`
|
||||||
|
|
||||||
Так библиотека избегает очевидных data races между goroutine middleware и основной handler chain.
|
Так библиотека избегает очевидных гонок данных между goroutine middleware и основной цепочкой обработки.
|
||||||
|
|
||||||
Практический вывод:
|
Практический вывод:
|
||||||
- изменения `ctx` внутри async middleware не становятся canonical context для handler'а;
|
- изменения `ctx` внутри async middleware не становятся каноническим контекстом для обработчика;
|
||||||
- async middleware не может надежно остановить flow.
|
- async middleware не может надежно остановить поток выполнения.
|
||||||
|
|
||||||
## Ordering
|
## Порядок
|
||||||
|
|
||||||
Только bot-level middleware имеет explicit sorting:
|
Только middleware уровня бота имеет явную сортировку:
|
||||||
- сначала по `order`;
|
- сначала по `order`;
|
||||||
- потом по `name`.
|
- потом по `name`.
|
||||||
|
|
||||||
Plugin-level и command-level middleware сохраняют insertion order.
|
Middleware уровня плагина и команды сохраняют порядок добавления.
|
||||||
|
|
||||||
## Частые ошибки
|
## Частые ошибки
|
||||||
|
|
||||||
- Использовать async middleware для блокировки доступа.
|
- Использовать async middleware для блокировки доступа.
|
||||||
- Надеяться, что async middleware сможет изменить `ctx.Text` для handler'а.
|
- Надеяться, что async middleware сможет изменить `ctx.Text` для обработчика.
|
||||||
- Настраивать plugin middleware после `AddPlugins(...)`.
|
- Настраивать middleware плагина после `AddPlugins(...)`.
|
||||||
- Оставлять middleware без внятного имени.
|
- Оставлять middleware без внятного имени.
|
||||||
|
|
||||||
## Что читать дальше
|
## Что читать дальше
|
||||||
|
|||||||
+14
-14
@@ -17,51 +17,51 @@ English version: [[Migration]]
|
|||||||
## Что важно помнить при апгрейде
|
## Что важно помнить при апгрейде
|
||||||
|
|
||||||
- Сначала прочитай `CHANGELOG.md`.
|
- Сначала прочитай `CHANGELOG.md`.
|
||||||
- Потом проверь runtime model, если затрагивались `Bot`, plugins или handlers.
|
- Потом проверь модель выполнения, если затрагивались `Bot`, плагины или обработчики.
|
||||||
- После этого отдельно пройди по payloads, drafts и command generation, если используешь эти части API.
|
- После этого отдельно пройди по данным callback, черновикам и генерации команд, если используешь эти части API.
|
||||||
|
|
||||||
## Ключевые изменения последних `rc`
|
## Ключевые изменения последних `rc`
|
||||||
|
|
||||||
### `rc.12`
|
### `rc.12`
|
||||||
|
|
||||||
Главный смысл релиза:
|
Главный смысл релиза:
|
||||||
- handlers стали возвращать `error`;
|
- обработчики стали возвращать `error`;
|
||||||
- появились long reply helpers;
|
- появились вспомогательные методы для длинных ответов;
|
||||||
- появился strict payload mode.
|
- появился строгий режим обработки данных callback.
|
||||||
|
|
||||||
Что обычно нужно менять:
|
Что обычно нужно менять:
|
||||||
- перевести handlers на `error`-return signature;
|
- перевести обработчики на сигнатуру с возвратом `error`;
|
||||||
- если длинные ответы могли выходить за Telegram limit, перейти на `AnswerLong(...)` или `KeyboardLong(...)`;
|
- если длинные ответы могли выходить за Telegram limit, перейти на `AnswerLong(...)` или `KeyboardLong(...)`;
|
||||||
- если payload policy важна, решить, нужен ли strict mode.
|
- если политика данных callback важна, решить, нужен ли строгий режим.
|
||||||
|
|
||||||
### `rc.10`
|
### `rc.10`
|
||||||
|
|
||||||
Главные изменения:
|
Главные изменения:
|
||||||
- `NewBot[T](opts)` теперь возвращает `(*Bot[T], error)`;
|
- `NewBot[T](opts)` теперь возвращает `(*Bot[T], error)`;
|
||||||
- `Run()` и `RunWithContext(...)` возвращают `error`;
|
- `Run()` и `RunWithContext(...)` возвращают `error`;
|
||||||
- `AddPlugins(...)` стал configuration snapshot point.
|
- `AddPlugins(...)` стал точкой фиксации конфигурации.
|
||||||
|
|
||||||
Что обычно нужно менять:
|
Что обычно нужно менять:
|
||||||
- добавить error handling после `NewBot`, `Run`, `RunWithContext`;
|
- добавить error handling после `NewBot`, `Run`, `RunWithContext`;
|
||||||
- перестать мутировать plugins после регистрации.
|
- перестать менять плагины после регистрации.
|
||||||
|
|
||||||
### `rc.7`
|
### `rc.7`
|
||||||
|
|
||||||
На этой стадии заметно укрепился plugin lifecycle:
|
На этой стадии заметно укрепился жизненный цикл плагинов:
|
||||||
- logger APIs;
|
- logger APIs;
|
||||||
- shutdown hooks;
|
- хуки остановки;
|
||||||
- `Plugin.Close()`.
|
- `Plugin.Close()`.
|
||||||
|
|
||||||
### `rc.4`
|
### `rc.4`
|
||||||
|
|
||||||
Ранние изменения в основном затрагивали общую структуру API и helper surface.
|
Ранние изменения в основном затрагивали общую структуру API и набор вспомогательных методов.
|
||||||
|
|
||||||
## Практическая стратегия миграции
|
## Практическая стратегия миграции
|
||||||
|
|
||||||
1. Сначала собери проект.
|
1. Сначала собери проект.
|
||||||
2. Исправь signature-level ошибки.
|
2. Исправь signature-level ошибки.
|
||||||
3. Проверь runtime behavior: startup, shutdown, plugins.
|
3. Проверь поведение во время выполнения: запуск, остановку, плагины.
|
||||||
4. Отдельно прогоняй callbacks, middleware и long replies.
|
4. Отдельно прогоняй callbacks, middleware и длинные ответы.
|
||||||
5. Добавь regression tests под найденные переломы.
|
5. Добавь regression tests под найденные переломы.
|
||||||
|
|
||||||
## Что читать дальше
|
## Что читать дальше
|
||||||
|
|||||||
+22
-22
@@ -6,17 +6,17 @@ English version: [[MsgContext]]
|
|||||||
|
|
||||||
## Что такое `MsgContext`
|
## Что такое `MsgContext`
|
||||||
|
|
||||||
`MsgContext` — это runtime object, который приходит в:
|
`MsgContext` — это объект времени выполнения, который приходит в:
|
||||||
- command handlers;
|
- обработчики команд;
|
||||||
- payload handlers;
|
- обработчики данных callback;
|
||||||
- middleware;
|
- middleware;
|
||||||
- update handlers.
|
- обработчики обновлений.
|
||||||
|
|
||||||
Через него ты получаешь:
|
Через него ты получаешь:
|
||||||
- incoming update;
|
- входящее обновление;
|
||||||
- текущее сообщение и отправителя;
|
- текущее сообщение и отправителя;
|
||||||
- parsed command/payload args;
|
- разобранные аргументы команд и callback;
|
||||||
- helpers для reply, edit, delete, callback, drafts и localization.
|
- вспомогательные методы для reply, edit, delete, callback, drafts и localization.
|
||||||
|
|
||||||
## Поля, которые используются чаще всего
|
## Поля, которые используются чаще всего
|
||||||
|
|
||||||
@@ -26,12 +26,12 @@ English version: [[MsgContext]]
|
|||||||
|
|
||||||
Пример:
|
Пример:
|
||||||
- вход: `/echo hello world`
|
- вход: `/echo hello world`
|
||||||
- command: `echo`
|
- команда: `echo`
|
||||||
- `ctx.Text == "hello world"`
|
- `ctx.Text == "hello world"`
|
||||||
|
|
||||||
### `Args`
|
### `Args`
|
||||||
|
|
||||||
`ctx.Args` — tokenized версия `ctx.Text`.
|
`ctx.Args` — разбитая на токены версия `ctx.Text`.
|
||||||
|
|
||||||
Пример:
|
Пример:
|
||||||
- `ctx.Text == "hello world"`
|
- `ctx.Text == "hello world"`
|
||||||
@@ -52,17 +52,17 @@ English version: [[MsgContext]]
|
|||||||
|
|
||||||
`ctx.FromID` — тот же ID, но уже вынесенный для удобства.
|
`ctx.FromID` — тот же ID, но уже вынесенный для удобства.
|
||||||
|
|
||||||
## Основные helper methods
|
## Основные вспомогательные методы
|
||||||
|
|
||||||
### `Answer(...)`
|
### `Answer(...)`
|
||||||
|
|
||||||
Базовый helper для обычного текстового ответа.
|
Базовый вспомогательный метод для обычного текстового ответа.
|
||||||
|
|
||||||
### `AnswerLong(...)`
|
### `AnswerLong(...)`
|
||||||
|
|
||||||
Используется, когда текст может превысить Telegram message limit.
|
Используется, когда текст может превысить Telegram message limit.
|
||||||
|
|
||||||
Это отдельный API специально для явной multi-message semantics.
|
Это отдельный API специально для явной семантики многочастного ответа.
|
||||||
|
|
||||||
### `Keyboard(...)`
|
### `Keyboard(...)`
|
||||||
|
|
||||||
@@ -70,9 +70,9 @@ English version: [[MsgContext]]
|
|||||||
|
|
||||||
### `KeyboardLong(...)`
|
### `KeyboardLong(...)`
|
||||||
|
|
||||||
Подходит для длинного текста, где keyboard должен остаться на последнем chunk.
|
Подходит для длинного текста, где клавиатура должна остаться на последней части.
|
||||||
|
|
||||||
## Markdown helpers
|
## Markdown-вспомогательные методы
|
||||||
|
|
||||||
Есть `...Markdown` варианты:
|
Есть `...Markdown` варианты:
|
||||||
- `AnswerMarkdown(...)`
|
- `AnswerMarkdown(...)`
|
||||||
@@ -82,18 +82,18 @@ English version: [[MsgContext]]
|
|||||||
Важно:
|
Важно:
|
||||||
- пользовательский ввод нужно экранировать через `EscapeMarkdownV2(...)`.
|
- пользовательский ввод нужно экранировать через `EscapeMarkdownV2(...)`.
|
||||||
|
|
||||||
## Edit и delete helpers
|
## Вспомогательные методы для edit и delete
|
||||||
|
|
||||||
Если у тебя уже есть `AnswerMessage`, его можно:
|
Если у тебя уже есть `AnswerMessage`, его можно:
|
||||||
- редактировать;
|
- редактировать;
|
||||||
- удалять;
|
- удалять;
|
||||||
- менять caption.
|
- менять caption.
|
||||||
|
|
||||||
Это удобно для progressive UX вроде “Working... -> Done”.
|
Это удобно для пошагового UX вроде “Working... -> Done”.
|
||||||
|
|
||||||
## Callback-specific helpers
|
## Callback-специфичные вспомогательные методы
|
||||||
|
|
||||||
В callback flow особенно полезны:
|
В потоке callback особенно полезны:
|
||||||
- `EditCallback(...)`
|
- `EditCallback(...)`
|
||||||
- `AnswerCbQuery()`
|
- `AnswerCbQuery()`
|
||||||
- `AnswerCbQueryText(...)`
|
- `AnswerCbQueryText(...)`
|
||||||
@@ -101,7 +101,7 @@ English version: [[MsgContext]]
|
|||||||
- `AnswerCbQueryUrl(...)`
|
- `AnswerCbQueryUrl(...)`
|
||||||
- `CallbackDelete()`
|
- `CallbackDelete()`
|
||||||
|
|
||||||
Они покрывают most common callback UX без ручного хождения в `tgapi`.
|
Они покрывают самые частые callback-сценарии без ручного хождения в `tgapi`.
|
||||||
|
|
||||||
## Drafts и localization
|
## Drafts и localization
|
||||||
|
|
||||||
@@ -110,17 +110,17 @@ English version: [[MsgContext]]
|
|||||||
- `NewDraftMarkdown()`
|
- `NewDraftMarkdown()`
|
||||||
- `Translate(key)`
|
- `Translate(key)`
|
||||||
|
|
||||||
Это делает `MsgContext` основным ergonomic surface почти для всего handler-time кода.
|
Это делает `MsgContext` основной удобной точкой доступа почти для всего кода обработчиков.
|
||||||
|
|
||||||
## `NewInlineKeyboard(...)`
|
## `NewInlineKeyboard(...)`
|
||||||
|
|
||||||
Внутри handler'ов keyboard обычно удобнее всего строить так:
|
Внутри обработчиков keyboard обычно удобнее всего строить так:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
kb := ctx.NewInlineKeyboard(2)
|
kb := ctx.NewInlineKeyboard(2)
|
||||||
```
|
```
|
||||||
|
|
||||||
Этот builder автоматически наследует текущую payload policy context'а.
|
Этот builder автоматически наследует текущую политику данных callback из контекста.
|
||||||
|
|
||||||
## Что читать дальше
|
## Что читать дальше
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -48,7 +48,7 @@ English version: [[Page-Priority]]
|
|||||||
|
|
||||||
При изменении кода сначала проверяй:
|
При изменении кода сначала проверяй:
|
||||||
1. страницы core API;
|
1. страницы core API;
|
||||||
2. страницы по runtime behavior;
|
2. страницы про поведение во время выполнения;
|
||||||
3. advanced и maintenance pages.
|
3. advanced и maintenance pages.
|
||||||
|
|
||||||
## Что читать дальше
|
## Что читать дальше
|
||||||
|
|||||||
+15
-15
@@ -2,26 +2,26 @@
|
|||||||
|
|
||||||
English version: [[Rate-Limiting]]
|
English version: [[Rate-Limiting]]
|
||||||
|
|
||||||
Это краткая русскоязычная версия страницы про rate limiting. Полная и наиболее актуальная страница: [[Rate-Limiting]].
|
Это краткая русскоязычная версия страницы про ограничение частоты. Полная и наиболее актуальная страница: [[Rate-Limiting]].
|
||||||
|
|
||||||
## Где действует limiter
|
## Где действует limiter
|
||||||
|
|
||||||
Rate limiting в Laniakea относится к Telegram API client layer.
|
Ограничение частоты в Laniakea относится к слою Telegram API client.
|
||||||
|
|
||||||
Он влияет на исходящие запросы бота и помогает:
|
Он влияет на исходящие запросы бота и помогает:
|
||||||
- не превысить Telegram limits;
|
- не превысить Telegram limits;
|
||||||
- переживать bursts;
|
- переживать всплески;
|
||||||
- корректно обрабатывать `retry_after`.
|
- корректно обрабатывать `retry_after`.
|
||||||
|
|
||||||
## Основные режимы
|
## Основные режимы
|
||||||
|
|
||||||
Есть два общих режима поведения:
|
Есть два общих режима поведения:
|
||||||
- waiting mode;
|
- режим ожидания;
|
||||||
- drop mode.
|
- режим сброса.
|
||||||
|
|
||||||
В waiting mode запросы ждут своей очереди.
|
В режиме ожидания запросы ждут своей очереди.
|
||||||
|
|
||||||
В drop mode overflow можно отклонять сразу, чтобы сохранить отзывчивость под нагрузкой.
|
В режиме сброса лишние запросы можно отклонять сразу, чтобы сохранить отзывчивость под нагрузкой.
|
||||||
|
|
||||||
## Ключевые настройки
|
## Ключевые настройки
|
||||||
|
|
||||||
@@ -29,12 +29,12 @@ Rate limiting в Laniakea относится к Telegram API client layer.
|
|||||||
- `RateLimit`
|
- `RateLimit`
|
||||||
- `DropRLOverflow`
|
- `DropRLOverflow`
|
||||||
|
|
||||||
Это задает общий policy для API client'а, который bot строит внутри `NewBot(...)`.
|
Это задает общую политику для API client, который бот строит внутри `NewBot(...)`.
|
||||||
|
|
||||||
## `retry_after`
|
## `retry_after`
|
||||||
|
|
||||||
Если Telegram отвечает `429 retry_after`, библиотека:
|
Если Telegram отвечает `429 retry_after`, библиотека:
|
||||||
- обновляет limiter state;
|
- обновляет состояние limiter;
|
||||||
- ждет нужное время;
|
- ждет нужное время;
|
||||||
- повторяет запрос, если context не был отменен.
|
- повторяет запрос, если context не был отменен.
|
||||||
|
|
||||||
@@ -42,25 +42,25 @@ Rate limiting в Laniakea относится к Telegram API client layer.
|
|||||||
|
|
||||||
## Global и per-chat поведение
|
## Global и per-chat поведение
|
||||||
|
|
||||||
На практике limiter связан не только с глобальным потоком запросов, но и с chat-aware поведением в API layer.
|
На практике limiter связан не только с глобальным потоком запросов, но и с поведением, зависящим от чата, в API layer.
|
||||||
|
|
||||||
Из-за этого важно думать не только о “сколько запросов в секунду вообще”, но и о burst behavior внутри одного chat flow.
|
Из-за этого важно думать не только о “сколько запросов в секунду вообще”, но и о поведении всплесков внутри одного потока чата.
|
||||||
|
|
||||||
## Когда дропать, а когда ждать
|
## Когда дропать, а когда ждать
|
||||||
|
|
||||||
Обычно waiting mode лучше, когда:
|
Обычно режим ожидания лучше, когда:
|
||||||
- важнее надежная доставка;
|
- важнее надежная доставка;
|
||||||
- бот обрабатывает пользовательские команды, которые нельзя терять.
|
- бот обрабатывает пользовательские команды, которые нельзя терять.
|
||||||
|
|
||||||
Drop mode может быть полезен, когда:
|
Режим сброса может быть полезен, когда:
|
||||||
- бот работает под очень высоким наплывом;
|
- бот работает под очень высоким наплывом;
|
||||||
- часть исходящих запросов допустимо потерять;
|
- часть исходящих запросов допустимо потерять;
|
||||||
- важнее быстрый ответ системы под перегрузкой.
|
- важнее быстрый ответ системы под перегрузкой.
|
||||||
|
|
||||||
## Практические советы
|
## Практические советы
|
||||||
|
|
||||||
- Начинай с дефолтного rate limit.
|
- Начинай с дефолтного ограничения частоты.
|
||||||
- Повышай `MaxWorkers` и rate limit только после реальных наблюдений.
|
- Повышай `MaxWorkers` и ограничение частоты только после реальных наблюдений.
|
||||||
- Ожидай, что `retry_after` иногда будет нормальной частью жизни бота.
|
- Ожидай, что `retry_after` иногда будет нормальной частью жизни бота.
|
||||||
|
|
||||||
## Что читать дальше
|
## Что читать дальше
|
||||||
|
|||||||
+11
-11
@@ -12,13 +12,13 @@ English version: [[Recipes]]
|
|||||||
|
|
||||||
### Admin-only command
|
### Admin-only command
|
||||||
|
|
||||||
Используй plugin middleware или command middleware, если команда должна быть доступна только ограниченной группе пользователей.
|
Используй middleware плагина или middleware команды, если команда должна быть доступна только ограниченной группе пользователей.
|
||||||
|
|
||||||
### Callback flow
|
### Поток callback
|
||||||
|
|
||||||
Комбинируй:
|
Комбинируй:
|
||||||
- `NewInlineKeyboard(...)`
|
- `NewInlineKeyboard(...)`
|
||||||
- payload handler;
|
- обработчик данных callback;
|
||||||
- `AnswerCbQuery...`;
|
- `AnswerCbQuery...`;
|
||||||
- `EditCallback(...)`
|
- `EditCallback(...)`
|
||||||
|
|
||||||
@@ -30,29 +30,29 @@ English version: [[Recipes]]
|
|||||||
|
|
||||||
Подключи `L10n` к bot и используй `ctx.Translate(...)` внутри handler'ов.
|
Подключи `L10n` к bot и используй `ctx.Translate(...)` внутри handler'ов.
|
||||||
|
|
||||||
### Non-command update handler
|
### Обработчик обновлений вне команд
|
||||||
|
|
||||||
Для `inline_query`, `poll`, `chat_member` и других update types используй `AddUpdateHandler(...)`.
|
Для `inline_query`, `poll`, `chat_member` и других update types используй `AddUpdateHandler(...)`.
|
||||||
|
|
||||||
### Draft-based flow
|
### Поток на основе черновиков
|
||||||
|
|
||||||
Если ответ строится постепенно, начни с `ctx.NewDraft()`.
|
Если ответ строится постепенно, начни с `ctx.NewDraft()`.
|
||||||
|
|
||||||
### Upload через `tgapi`
|
### Upload через `tgapi`
|
||||||
|
|
||||||
Когда нужны multipart uploads или lower-level file APIs, выходи в `tgapi`.
|
Когда нужны multipart uploads или более низкоуровневые file API, выходи в `tgapi`.
|
||||||
|
|
||||||
### Strict payload mode
|
### Строгий режим данных callback
|
||||||
|
|
||||||
Если payload format mismatch должен считаться ошибкой, включай `SetStrictPayloadType(true)`.
|
Если несовпадение формата данных callback должно считаться ошибкой, включай `SetStrictPayloadType(true)`.
|
||||||
|
|
||||||
## Как использовать recipes правильно
|
## Как использовать recipes правильно
|
||||||
|
|
||||||
Recipes хороши как стартовые шаблоны, но не заменяют более подробные страницы про:
|
Recipes хороши как стартовые шаблоны, но не заменяют более подробные страницы про:
|
||||||
- lifecycle;
|
- жизненный цикл;
|
||||||
- middleware;
|
- middleware;
|
||||||
- payload model;
|
- модель данных callback;
|
||||||
- testing.
|
- тестирование.
|
||||||
|
|
||||||
## Что читать дальше
|
## Что читать дальше
|
||||||
|
|
||||||
|
|||||||
+26
-26
@@ -2,17 +2,17 @@
|
|||||||
|
|
||||||
English version: [[Runners]]
|
English version: [[Runners]]
|
||||||
|
|
||||||
Это краткая русскоязычная версия страницы про runners. Полная и наиболее актуальная страница: [[Runners]].
|
Это краткая русскоязычная версия страницы про фоновые задачи. Полная и наиболее актуальная страница: [[Runners]].
|
||||||
|
|
||||||
## Что такое runner
|
## Что такое runner
|
||||||
|
|
||||||
Runner — это background или one-time task, который живет рядом с bot runtime, но не относится к конкретному update handler.
|
Runner — это фоновая или одноразовая задача, которая живет рядом с механизмом выполнения бота, но не относится к конкретному обработчику обновлений.
|
||||||
|
|
||||||
Типичные use cases:
|
Типичные сценарии:
|
||||||
- cleanup jobs;
|
- задачи очистки;
|
||||||
- maintenance tasks;
|
- служебные задачи обслуживания;
|
||||||
- health checks;
|
- health checks;
|
||||||
- startup warmups.
|
- подготовка при запуске.
|
||||||
|
|
||||||
## Как создается runner
|
## Как создается runner
|
||||||
|
|
||||||
@@ -22,26 +22,26 @@ Runner — это background или one-time task, который живет р
|
|||||||
runner := laniakea.NewRunner("cleanup", fn)
|
runner := laniakea.NewRunner("cleanup", fn)
|
||||||
```
|
```
|
||||||
|
|
||||||
Потом конфигурируются builder methods:
|
Потом конфигурируются методы builder:
|
||||||
- `Onetime(bool)`
|
- `Onetime(bool)`
|
||||||
- `Async(bool)`
|
- `Async(bool)`
|
||||||
- `Timeout(duration)`
|
- `Timeout(duration)`
|
||||||
|
|
||||||
## Основные режимы
|
## Основные режимы
|
||||||
|
|
||||||
### One-time sync
|
### Одноразовый sync
|
||||||
|
|
||||||
- выполняется один раз;
|
- выполняется один раз;
|
||||||
- блокирует startup;
|
- блокирует запуск;
|
||||||
- полезен для startup-critical работы.
|
- полезен для работы, критичной на старте.
|
||||||
|
|
||||||
### One-time async
|
### Одноразовый async
|
||||||
|
|
||||||
- выполняется один раз;
|
- выполняется один раз;
|
||||||
- стартует в goroutine;
|
- стартует в goroutine;
|
||||||
- не блокирует startup.
|
- не блокирует запуск.
|
||||||
|
|
||||||
### Repeating async
|
### Повторяющийся async
|
||||||
|
|
||||||
- работает циклически;
|
- работает циклически;
|
||||||
- использует ticker;
|
- использует ticker;
|
||||||
@@ -49,29 +49,29 @@ runner := laniakea.NewRunner("cleanup", fn)
|
|||||||
|
|
||||||
## Невалидная конфигурация
|
## Невалидная конфигурация
|
||||||
|
|
||||||
Повторяющийся synchronous runner считается невалидным и пропускается с warning.
|
Повторяющийся synchronous runner считается невалидным и пропускается с предупреждением.
|
||||||
|
|
||||||
Также repeating async runner без `Timeout(...)` пропускается.
|
Также повторяющийся async runner без `Timeout(...)` пропускается.
|
||||||
|
|
||||||
## Когда стартуют runners
|
## Когда стартуют фоновые задачи
|
||||||
|
|
||||||
Runners стартуют из `RunWithContext(...)`, а не из `NewBot(...)`.
|
Фоновые задачи стартуют из `RunWithContext(...)`, а не из `NewBot(...)`.
|
||||||
|
|
||||||
Это часть runtime phase, а не construction phase.
|
Это часть фазы выполнения, а не фазы сборки конфигурации.
|
||||||
|
|
||||||
## Shutdown semantics
|
## Семантика остановки
|
||||||
|
|
||||||
При graceful shutdown bot:
|
При корректной остановке бот:
|
||||||
- ждет one-time async runners;
|
- ждет одноразовые асинхронные задачи;
|
||||||
- ждет завершения background runners после того, как они заметят `ctx.Done()`.
|
- ждет завершения фоновых задач после того, как они заметят `ctx.Done()`.
|
||||||
|
|
||||||
Поэтому runner body должен завершаться reasonably promptly.
|
Поэтому код runner должен завершаться достаточно быстро.
|
||||||
|
|
||||||
## Рекомендации
|
## Рекомендации
|
||||||
|
|
||||||
- Для periodic jobs используй repeating async runner с timeout.
|
- Для периодических задач используй повторяющийся async runner с timeout.
|
||||||
- Для startup-critical work используй one-time sync runner.
|
- Для критичной стартовой работы используй одноразовый sync runner.
|
||||||
- Не держи сложную бизнес-логику внутри runner body; лучше делегируй ее в обычные сервисы приложения.
|
- Не держи сложную бизнес-логику внутри runner; лучше делегируй ее в обычные сервисы приложения.
|
||||||
|
|
||||||
## Что читать дальше
|
## Что читать дальше
|
||||||
|
|
||||||
|
|||||||
+90
-168
@@ -1,53 +1,17 @@
|
|||||||
# Scenes
|
# Scenes
|
||||||
|
|
||||||
Scenes — это work-in-progress слой stateful-маршрутизации для долгоживущих диалогов в Laniakea. Базовый скелет уже есть в библиотеке: сцены можно регистрировать в plugins, запускать через `MsgContext`, сохранять через `SessionStore` и маршрутизировать раньше обычных команд. Эта страница описывает текущий API, уже работающие части и зоны, которые пока намеренно остаются незавершёнными.
|
Scenes — это слой маршрутизации Laniakea с сохранением состояния для многошаговых и модальных диалогов. Сцена регистрируется внутри плагина, запускается через `MsgContext`, хранится через `SessionStore` и получает обновления раньше обычной маршрутизации команд, пока её сессия активна.
|
||||||
|
|
||||||
Сцены располагаются поверх обычной маршрутизации команд и payload: если пользователь или чат находится внутри активной сцены, сцена получает update первой и решает, обработать его, продолжить состояние, выйти или передать управление обратно обычному plugin flow.
|
## Что дают сцены
|
||||||
|
|
||||||
## Зачем нужны сцены
|
- Регистрацию через `Plugin.NewScene(...)` и `Plugin.AddScene(...)`.
|
||||||
|
- Явный вход и выход через `MsgContext.EnterScene(...)`, `EnterSceneStep(...)` и `ExitScene()`.
|
||||||
|
- Области действия сессии на пользователя, чат или пару пользователь-чат.
|
||||||
|
- Обработчики шагов, локальные команды сцены и резервный обработчик сообщений на уровне сцены.
|
||||||
|
- JSON-состояние сцены через `SceneContext.BindData(...)` и `SaveData(...)`.
|
||||||
|
- Встроенное in-memory-хранилище и интерфейс `SessionStore` для собственного постоянного хранения.
|
||||||
|
|
||||||
- Команды и payload хорошо подходят как точки входа, но их недостаточно для многошаговых и модальных сценариев.
|
## Основной API
|
||||||
- Реальным ботам часто нужен режим "оставайся в этом состоянии, пока пользователь явно не выйдет".
|
|
||||||
- У фреймворка уже есть подходящие строительные блоки: `MsgContext`, middleware, plugins, typed input binding и request-scoped context.
|
|
||||||
- Модель сцен должна переиспользовать эти части, а не создавать вторую независимую модель выполнения.
|
|
||||||
|
|
||||||
## Цели дизайна
|
|
||||||
|
|
||||||
- Сцены должны быть опциональными и additive.
|
|
||||||
- Plugins должны оставаться основной единицей регистрации.
|
|
||||||
- `MsgContext` должен оставаться базовым контекстом для обычных handlers.
|
|
||||||
- Нужно покрыть и пошаговые flow, и модальные chat loops.
|
|
||||||
- Нужно явно различать сессии в личке и в чате.
|
|
||||||
- Локальные stop/escape-команды сцены должны быть first-class механизмом.
|
|
||||||
- Нужен встроенный способ хранить состояние сцены между шагами.
|
|
||||||
|
|
||||||
## Ментальная модель
|
|
||||||
|
|
||||||
- Команды и payload маршрутизируются по trigger.
|
|
||||||
- Сцены маршрутизируются по активному состоянию.
|
|
||||||
- Сцены являются отдельным слоем маршрутизации поверх commands, payloads и обычных update handlers.
|
|
||||||
- Команда вроде `/rpstart` входит в сцену.
|
|
||||||
- Пока сцена активна, обычные сообщения сначала уходят в сцену.
|
|
||||||
- Локальные команды сцены вроде `/rpstop` парсятся внутри активной сцены.
|
|
||||||
- Если сцена не хочет обрабатывать update, она может передать управление обратно стандартной маршрутизации.
|
|
||||||
|
|
||||||
## Область действия сессии
|
|
||||||
|
|
||||||
Сессионная модель не должна предполагать, что "один пользователь" всегда является правильной единицей. Для Telegram у личных чатов и групп разные потребности.
|
|
||||||
|
|
||||||
Текущие scope:
|
|
||||||
|
|
||||||
- `SceneScopeUser`: одна сессия на пользователя во всех чатах.
|
|
||||||
- `SceneScopeChat`: одна сессия на чат.
|
|
||||||
- `SceneScopeUserChat`: одна сессия на пару `(user, chat)`.
|
|
||||||
|
|
||||||
Рекомендуемый default:
|
|
||||||
|
|
||||||
- Для большинства интерактивных сценариев использовать `SceneScopeUserChat`.
|
|
||||||
- `SceneScopeUser` оставлять для редких account-level flow, которые специально должны жить между чатами.
|
|
||||||
- `SceneScopeChat` использовать только для общих room-level сценариев.
|
|
||||||
|
|
||||||
## Текущие типы
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type SceneScope int
|
type SceneScope int
|
||||||
@@ -71,74 +35,45 @@ type SessionStore interface {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Laniakea уже поставляется с `MemorySessionStore` по умолчанию, а `Bot.SetSessionStore(...)` позволяет заменить его на кастомный store.
|
`MemorySessionStore` используется по умолчанию. Чтобы заменить его, вызовите `Bot.SetSessionStore(...)`.
|
||||||
|
|
||||||
## Текущая регистрация сцен
|
## Область действия сессии
|
||||||
|
|
||||||
Сцены регистрируются внутри plugin в том же стиле, что и команды с payload.
|
- `SceneScopeUser`: одна сессия сцены на пользователя во всех чатах.
|
||||||
|
- `SceneScopeChat`: одна сессия сцены на чат для всех пользователей.
|
||||||
|
- `SceneScopeUserChat`: отдельная сессия на пару `(user, chat)`.
|
||||||
|
|
||||||
|
Рекомендуемое поведение по умолчанию:
|
||||||
|
|
||||||
|
- Для большинства интерактивных сценариев используйте `SceneScopeUserChat`.
|
||||||
|
- `SceneScopeUser` нужен только там, где один и тот же сценарий должен продолжаться между чатами.
|
||||||
|
- `SceneScopeChat` подходит для общих сценариев на уровень чата.
|
||||||
|
|
||||||
|
## Регистрация
|
||||||
|
|
||||||
|
Сцены регистрируются внутри плагина в том же стиле, что и команды с данными callback.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
plugin.NewScene("rp").
|
plugin.NewScene("signup").
|
||||||
SetScope(laniakea.SceneScopeUserChat).
|
SetScope(laniakea.SceneScopeUserChat).
|
||||||
SetEntry("chat").
|
SetEntry("ask_name").
|
||||||
OnMessage(handleRPMessage).
|
OnStep("ask_name", askName).
|
||||||
OnCommand("rpstop", stopRP)
|
OnStep("confirm", confirmSignup).
|
||||||
|
OnCommand("cancel", cancelSignup).
|
||||||
|
OnMessage(fallbackMessage)
|
||||||
```
|
```
|
||||||
|
|
||||||
`SetEntry(...)` задаёт начальный step или state сцены. `ctx.EnterScene("rp")` теперь валидирует, что entry-step задан и зарегистрирован, до того как создать сессию.
|
`SetEntry(...)` обязателен для `ctx.EnterScene(...)`. Если нужен явный старт с другого шага, используйте `ctx.EnterSceneStep(...)`.
|
||||||
|
|
||||||
Так plugin API остаётся визуально согласованным:
|
## Модель обработчиков
|
||||||
|
|
||||||
- `NewCommand(...)`
|
Обычные команды используют `*MsgContext`. Обработчики сцен используют `*SceneContext`.
|
||||||
- `NewPayload(...)`
|
|
||||||
- `AddUpdateHandler(...)`
|
|
||||||
- `NewScene(...)`
|
|
||||||
|
|
||||||
## Формы сцен
|
|
||||||
|
|
||||||
Текущий скелет уже покрывает две самые частые формы.
|
|
||||||
|
|
||||||
- Пошаговые сцены: именованный step обрабатывает каждый update и выбирает следующий step.
|
|
||||||
- Модальные сцены: долгоживущий "режим" обрабатывает обычные сообщения, пока пользователь явно не выйдет.
|
|
||||||
- Обе формы должны уметь регистрировать локальные команды сцены.
|
|
||||||
|
|
||||||
Пример пошагового flow:
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
plugin.NewScene("profile").
|
type SceneHandler[T any] func(ctx *SceneContext, db T) (SceneResult, error)
|
||||||
SetScope(laniakea.SceneScopeUserChat).
|
|
||||||
SetEntry("name").
|
|
||||||
OnStep("name", askName).
|
|
||||||
OnStep("confirm", confirmProfile)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Пример модального flow:
|
`SceneContext` встраивает `*MsgContext` и добавляет вспомогательные методы для сцен:
|
||||||
|
|
||||||
```go
|
|
||||||
plugin.NewScene("rp").
|
|
||||||
SetScope(laniakea.SceneScopeUserChat).
|
|
||||||
SetEntry("chat").
|
|
||||||
OnMessage(handleRPMessage).
|
|
||||||
OnCommand("rpstop", stopRP)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Текущие контексты и результаты
|
|
||||||
|
|
||||||
Обычные команды по-прежнему используют `*MsgContext`. Scene handlers используют `*SceneContext`, который встраивает `*MsgContext` и добавляет helper-методы для состояния сцены.
|
|
||||||
|
|
||||||
```go
|
|
||||||
type SceneContext struct {
|
|
||||||
*MsgContext
|
|
||||||
// internal session state
|
|
||||||
}
|
|
||||||
|
|
||||||
type SceneResult struct {
|
|
||||||
Action SceneAction
|
|
||||||
Next string
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Текущие helper-методы на `SceneContext`:
|
|
||||||
|
|
||||||
- `ctx.Stay()`
|
- `ctx.Stay()`
|
||||||
- `ctx.Next(step)`
|
- `ctx.Next(step)`
|
||||||
@@ -147,50 +82,32 @@ type SceneResult struct {
|
|||||||
- `ctx.BindData(&dst)`
|
- `ctx.BindData(&dst)`
|
||||||
- `ctx.SaveData(src)`
|
- `ctx.SaveData(src)`
|
||||||
|
|
||||||
## Сигнатуры handlers
|
Обработчик сцены возвращает `SceneResult`, который управляет переходом состояния:
|
||||||
|
|
||||||
Обычная команда входа:
|
- `Stay`: оставить ту же сцену и тот же шаг.
|
||||||
|
- `Next(step)`: перейти на другой зарегистрированный шаг.
|
||||||
|
- `Exit`: удалить текущую сессию.
|
||||||
|
- `Pass`: не менять текущую сессию и продолжить обычную маршрутизацию.
|
||||||
|
|
||||||
```go
|
`SceneActionPass` намеренно ничего не делает с состоянием сцены. Если обработчик вызвал `SaveData(...)`, а потом вернул `Pass`, эти данные не сохраняются.
|
||||||
func startRP(ctx *laniakea.MsgContext, db *App) error {
|
|
||||||
return ctx.EnterScene("rp")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Scene message handler:
|
## Порядок маршрутизации
|
||||||
|
|
||||||
```go
|
Пока сессия сцены активна, маршрутизация работает так:
|
||||||
func handleRPMessage(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
|
||||||
if ctx.Text == "" {
|
|
||||||
return ctx.Pass(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Передать сообщение ИИ-агенту и остаться внутри сцены.
|
1. Сначала выполняются middleware бота.
|
||||||
return ctx.Stay(), db.ReplyFromAgent(ctx.Context(), ctx.Text)
|
2. Бот ищет активную сессию сцены по настроенному приоритету областей действия.
|
||||||
}
|
3. Затем выполняются middleware плагина-владельца сцены.
|
||||||
```
|
4. Сначала проверяются локальные команды сцены.
|
||||||
|
5. Если локальная команда не совпала, вызывается обработчик текущего шага.
|
||||||
|
6. Если обработчик шага не найден, вызывается резервный обработчик `OnMessage(...)`.
|
||||||
|
7. Если сцена вернула `Pass`, продолжается обычная маршрутизация команд и обновлений.
|
||||||
|
|
||||||
Локальная команда сцены:
|
Текст сцены берётся из текста сообщения или подписи. Поэтому текущая модель сцен ориентирована именно на сценарии, основанные на сообщениях.
|
||||||
|
|
||||||
```go
|
|
||||||
func stopRP(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
|
||||||
ctx.Answer("RP mode disabled")
|
|
||||||
return ctx.Exit(), nil
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Состояние между шагами
|
## Состояние между шагами
|
||||||
|
|
||||||
Для реальных сценариев недостаточно хранить только `Scene` и `Step`. Обычно между update нужно накапливать данные: черновик профиля, выбранные параметры, временные ID и другие промежуточные значения.
|
Используйте `SceneSession.Data` через `SceneContext.BindData(...)` и `SaveData(...)`, если только вы не пишете собственную логику хранения.
|
||||||
|
|
||||||
Это уже не стоит полностью перекладывать на разработчика приложения. Состояние живёт в `SceneSession.Data`, а `SceneContext` уже даёт JSON-backed helper-методы поверх него.
|
|
||||||
|
|
||||||
Текущий helper API:
|
|
||||||
|
|
||||||
- `ctx.BindData(&dst)`
|
|
||||||
- `ctx.SaveData(src)`
|
|
||||||
|
|
||||||
Пример:
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type ProfileDraft struct {
|
type ProfileDraft struct {
|
||||||
@@ -200,7 +117,9 @@ type ProfileDraft struct {
|
|||||||
|
|
||||||
func askName(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
func askName(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
||||||
var draft ProfileDraft
|
var draft ProfileDraft
|
||||||
_ = ctx.BindData(&draft)
|
if err := ctx.BindData(&draft); err != nil {
|
||||||
|
return laniakea.SceneResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
draft.Name = ctx.Text
|
draft.Name = ctx.Text
|
||||||
if err := ctx.SaveData(draft); err != nil {
|
if err := ctx.SaveData(draft); err != nil {
|
||||||
@@ -211,47 +130,50 @@ func askName(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error)
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Использование `Data []byte` в `SceneSession` делает контракт `SessionStore` маленьким и не завязанным на конкретный формат хранения. Текущие helper-методы используют JSON.
|
Контракт хранилища остаётся маленьким, потому что `Data []byte` не привязан к конкретному формату хранения.
|
||||||
|
|
||||||
## Алгоритм маршрутизации
|
## Пример сценария
|
||||||
|
|
||||||
Текущий роутер ведёт себя так:
|
Точка входа из команды:
|
||||||
|
|
||||||
1. Построить `MsgContext` для update.
|
```go
|
||||||
2. Вычислить session key на основе scope сцены и текущего update.
|
func startSignup(ctx *laniakea.MsgContext, db *App) error {
|
||||||
3. Спросить `SessionStore`, есть ли активная сцена.
|
return ctx.EnterScene("signup")
|
||||||
4. Если активной сцены нет, продолжить обычную маршрутизацию command, payload и update handlers.
|
}
|
||||||
5. Если сцена активна, сначала попробовать scene-local command routing.
|
```
|
||||||
6. Если локальный route не совпал, вызвать текущий step handler, затем scene message handler.
|
|
||||||
7. Если сцена вернула `Stay`, сохранить текущую сессию без изменений.
|
|
||||||
8. Если сцена вернула `Next(step)`, сохранить новый step и оставить `Data`.
|
|
||||||
9. Если сцена вернула `Exit`, удалить сессию.
|
|
||||||
10. Если сцена вернула `Pass`, продолжить обычную маршрутизацию.
|
|
||||||
|
|
||||||
## Что уже реализовано
|
Обработчик шага:
|
||||||
|
|
||||||
- `Plugin.NewScene(...)` и `Plugin.AddScene(...)`.
|
```go
|
||||||
- `Scene.SetScope(...)`, `SetEntry(...)`, `OnStep(...)`, `OnCommand(...)` и `OnMessage(...)`.
|
func askName(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
||||||
- `MsgContext.EnterScene(...)`, `EnterSceneStep(...)` и `ExitScene(...)`.
|
if ctx.Text == "" {
|
||||||
- `SceneContext.Stay()`, `Next(...)`, `Exit()`, `Pass()`, `BindData(...)` и `SaveData(...)`.
|
ctx.Answer("Как тебя зовут?")
|
||||||
- `SessionStore` плюс дефолтный `MemorySessionStore`.
|
return ctx.Stay(), nil
|
||||||
- Маршрутизация активной сцены до обычного command flow.
|
}
|
||||||
|
|
||||||
## Что ещё не реализовано или намеренно отложено
|
ctx.Answer("Спасибо.")
|
||||||
|
return ctx.Next("confirm"), nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
- Scene-local payload routing пока не реализован.
|
Локальная команда сцены:
|
||||||
- Scene runtime намеренно остаётся внутренним; публичный API не отдаёт низкоуровневые helper-методы для lookup sessions.
|
|
||||||
- Поверхность helper-методов для scene state пока намеренно небольшая.
|
|
||||||
|
|
||||||
## Оставшаяся работа
|
```go
|
||||||
|
func cancelSignup(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
||||||
|
ctx.Answer("Регистрация отменена")
|
||||||
|
return ctx.Exit(), nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
- Расширить тесты вокруг scene-state helpers и fallback-поведения.
|
## Осознанные ограничения текущей модели
|
||||||
- Решить, нужны ли scene-local payloads уже в первом стабильном scene release.
|
|
||||||
- Решить, нужен ли вообще дополнительный публичный inspection API.
|
- Локальная маршрутизация данных callback внутри сцены пока не реализована.
|
||||||
|
- Внутренний механизм выполнения сцен не экспортируется как публичный API для просмотра состояния.
|
||||||
|
- Набор вспомогательных методов для состояния сцены намеренно остаётся небольшим.
|
||||||
|
|
||||||
Связанные страницы:
|
Связанные страницы:
|
||||||
|
|
||||||
- [[Commands-and-Plugins]]
|
- [[Commands-and-Plugins-RU]]
|
||||||
- [[MsgContext]]
|
- [[MsgContext-RU]]
|
||||||
- [[Middleware]]
|
- [[Middleware-RU]]
|
||||||
- [[Bot-Lifecycle]]
|
- [[Bot-Lifecycle-RU]]
|
||||||
|
|||||||
+85
-163
@@ -1,53 +1,17 @@
|
|||||||
# Scenes
|
# Scenes
|
||||||
|
|
||||||
Scenes are Laniakea's work-in-progress stateful routing layer for long-lived interactions. The core skeleton already exists in the library: scenes can be registered in plugins, entered through `MsgContext`, persisted through `SessionStore`, and routed before normal command handling. This page describes the current API, the behaviors that already work, and the parts that are still intentionally unfinished.
|
Scenes are Laniakea's stateful routing layer for multi-step and modal bot flows. A scene is registered inside a plugin, entered through `MsgContext`, stored through `SessionStore`, and routed before normal command handling while the session is active.
|
||||||
|
|
||||||
Scenes sit above command and payload routing: if a user or chat is inside an active scene, the scene gets the update first and decides whether to handle it, continue, exit, or pass control back to the normal plugin flow.
|
## What scenes give you
|
||||||
|
|
||||||
## Why scenes exist
|
- Scene registration through `Plugin.NewScene(...)` and `Plugin.AddScene(...)`.
|
||||||
|
- Explicit entry and exit through `MsgContext.EnterScene(...)`, `EnterSceneStep(...)`, and `ExitScene()`.
|
||||||
|
- Per-user, per-chat, or per-user-chat session scopes.
|
||||||
|
- Step handlers, scene-local commands, and a scene-level message fallback.
|
||||||
|
- JSON-backed scene state through `SceneContext.BindData(...)` and `SaveData(...)`.
|
||||||
|
- A default in-memory store plus `SessionStore` for custom persistence.
|
||||||
|
|
||||||
- Commands and payloads are good entry points, but they are not enough for multi-step or modal flows.
|
## Core API
|
||||||
- Real bots often need "stay in this mode until the user exits" behavior.
|
|
||||||
- The current framework already has the right building blocks: `MsgContext`, middleware, plugins, typed input binding, and request-scoped context.
|
|
||||||
- A scene model should reuse those pieces instead of creating a second execution model.
|
|
||||||
|
|
||||||
## Design goals
|
|
||||||
|
|
||||||
- Keep scenes optional and additive.
|
|
||||||
- Preserve plugins as the primary registration unit.
|
|
||||||
- Keep `MsgContext` as the base context for normal handlers.
|
|
||||||
- Support both step-based flows and modal chat loops.
|
|
||||||
- Distinguish private-chat and group-chat sessions explicitly.
|
|
||||||
- Make scene-local stop/escape commands first-class.
|
|
||||||
- Provide a built-in way to persist scene state between steps.
|
|
||||||
|
|
||||||
## Mental model
|
|
||||||
|
|
||||||
- Commands and payloads route by trigger.
|
|
||||||
- Scenes route by active state.
|
|
||||||
- Scenes are a separate routing layer above commands, payloads, and generic update handlers.
|
|
||||||
- A command such as `/rpstart` enters a scene.
|
|
||||||
- Once the scene is active, regular messages are routed to the scene first.
|
|
||||||
- Scene-local commands such as `/rpstop` are parsed inside the active scene.
|
|
||||||
- If the scene does not want the update, it can pass control back to normal routing.
|
|
||||||
|
|
||||||
## Session scope
|
|
||||||
|
|
||||||
Scene sessions should not assume that "one user" is always the right unit. Telegram private chats and group chats need different defaults.
|
|
||||||
|
|
||||||
Current scopes:
|
|
||||||
|
|
||||||
- `SceneScopeUser`: one session per user across all chats.
|
|
||||||
- `SceneScopeChat`: one session per chat.
|
|
||||||
- `SceneScopeUserChat`: one session per `(user, chat)` pair.
|
|
||||||
|
|
||||||
Recommended default:
|
|
||||||
|
|
||||||
- Use `SceneScopeUserChat` for most interactive flows.
|
|
||||||
- Reserve `SceneScopeUser` for rare account-level flows that intentionally cross chats.
|
|
||||||
- Use `SceneScopeChat` only for room-level shared workflows.
|
|
||||||
|
|
||||||
## Current types
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type SceneScope int
|
type SceneScope int
|
||||||
@@ -71,74 +35,45 @@ type SessionStore interface {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Laniakea currently ships with `MemorySessionStore` as the default implementation, and `Bot.SetSessionStore(...)` can replace it with a custom store.
|
`MemorySessionStore` is the default implementation. Use `Bot.SetSessionStore(...)` to replace it.
|
||||||
|
|
||||||
## Current scene registration
|
## Scopes
|
||||||
|
|
||||||
|
- `SceneScopeUser` shares one scene session across all chats for a user.
|
||||||
|
- `SceneScopeChat` shares one scene session across all users in a chat.
|
||||||
|
- `SceneScopeUserChat` isolates one session per `(user, chat)` pair.
|
||||||
|
|
||||||
|
Recommended default:
|
||||||
|
|
||||||
|
- Use `SceneScopeUserChat` for most interactive flows.
|
||||||
|
- Use `SceneScopeUser` only when the same logical flow should continue across chats.
|
||||||
|
- Use `SceneScopeChat` for room-level shared workflows.
|
||||||
|
|
||||||
|
## Registration
|
||||||
|
|
||||||
Scenes are registered inside plugins in the same style as commands and payloads.
|
Scenes are registered inside plugins in the same style as commands and payloads.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
plugin.NewScene("rp").
|
plugin.NewScene("signup").
|
||||||
SetScope(laniakea.SceneScopeUserChat).
|
SetScope(laniakea.SceneScopeUserChat).
|
||||||
SetEntry("chat").
|
SetEntry("ask_name").
|
||||||
OnMessage(handleRPMessage).
|
OnStep("ask_name", askName).
|
||||||
OnCommand("rpstop", stopRP)
|
OnStep("confirm", confirmSignup).
|
||||||
|
OnCommand("cancel", cancelSignup).
|
||||||
|
OnMessage(fallbackMessage)
|
||||||
```
|
```
|
||||||
|
|
||||||
`SetEntry(...)` defines the initial step or state of the scene. `ctx.EnterScene("rp")` validates that the entry step is set and registered before creating a session.
|
`SetEntry(...)` is required for `ctx.EnterScene(...)`. `ctx.EnterSceneStep(...)` can start from a specific registered step instead.
|
||||||
|
|
||||||
This keeps the plugin API visually consistent:
|
## Handler model
|
||||||
|
|
||||||
- `NewCommand(...)`
|
Normal commands use `*MsgContext`. Scene handlers use `*SceneContext`.
|
||||||
- `NewPayload(...)`
|
|
||||||
- `AddUpdateHandler(...)`
|
|
||||||
- `NewScene(...)`
|
|
||||||
|
|
||||||
## Scene shapes
|
|
||||||
|
|
||||||
The current skeleton already supports two common shapes.
|
|
||||||
|
|
||||||
- Step-based scenes: a named step handles each update and decides the next step.
|
|
||||||
- Modal scenes: a long-lived "mode" handles ordinary messages until an explicit exit command ends it.
|
|
||||||
- Both shapes should still be able to register scene-local commands.
|
|
||||||
|
|
||||||
Step-based flow example:
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
plugin.NewScene("profile").
|
type SceneHandler[T any] func(ctx *SceneContext, db T) (SceneResult, error)
|
||||||
SetScope(laniakea.SceneScopeUserChat).
|
|
||||||
SetEntry("name").
|
|
||||||
OnStep("name", askName).
|
|
||||||
OnStep("confirm", confirmProfile)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Modal flow example:
|
`SceneContext` embeds `*MsgContext` and adds scene helpers:
|
||||||
|
|
||||||
```go
|
|
||||||
plugin.NewScene("rp").
|
|
||||||
SetScope(laniakea.SceneScopeUserChat).
|
|
||||||
SetEntry("chat").
|
|
||||||
OnMessage(handleRPMessage).
|
|
||||||
OnCommand("rpstop", stopRP)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Current contexts and results
|
|
||||||
|
|
||||||
Normal commands still use `*MsgContext`. Scene handlers use `*SceneContext`, which embeds `*MsgContext` and adds scene-state helpers.
|
|
||||||
|
|
||||||
```go
|
|
||||||
type SceneContext struct {
|
|
||||||
*MsgContext
|
|
||||||
// internal session state
|
|
||||||
}
|
|
||||||
|
|
||||||
type SceneResult struct {
|
|
||||||
Action SceneAction
|
|
||||||
Next string
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Current helper methods on `SceneContext`:
|
|
||||||
|
|
||||||
- `ctx.Stay()`
|
- `ctx.Stay()`
|
||||||
- `ctx.Next(step)`
|
- `ctx.Next(step)`
|
||||||
@@ -147,50 +82,32 @@ Current helper methods on `SceneContext`:
|
|||||||
- `ctx.BindData(&dst)`
|
- `ctx.BindData(&dst)`
|
||||||
- `ctx.SaveData(src)`
|
- `ctx.SaveData(src)`
|
||||||
|
|
||||||
## Handler shapes
|
Scene handlers return `SceneResult` to control session flow:
|
||||||
|
|
||||||
Normal command entry:
|
- `Stay`: keep the same scene and step.
|
||||||
|
- `Next(step)`: move to another registered step.
|
||||||
|
- `Exit`: delete the current session.
|
||||||
|
- `Pass`: do not change the current session and continue normal routing.
|
||||||
|
|
||||||
```go
|
`SceneActionPass` is intentionally a no-op for scene state. If a handler calls `SaveData(...)` and then returns `Pass`, that state is not persisted.
|
||||||
func startRP(ctx *laniakea.MsgContext, db *App) error {
|
|
||||||
return ctx.EnterScene("rp")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Scene message handler:
|
## Routing order
|
||||||
|
|
||||||
```go
|
While a scene session is active, routing works like this:
|
||||||
func handleRPMessage(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
|
||||||
if ctx.Text == "" {
|
|
||||||
return ctx.Pass(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the message to the AI agent and stay inside the scene.
|
1. Bot middleware runs first.
|
||||||
return ctx.Stay(), db.ReplyFromAgent(ctx.Context(), ctx.Text)
|
2. The bot resolves the active scene session from the configured scope priority.
|
||||||
}
|
3. Plugin middleware for the owning plugin runs.
|
||||||
```
|
4. Scene-local commands are checked first.
|
||||||
|
5. If no scene command matches, the current step handler runs.
|
||||||
|
6. If no step handler matches, the scene-level `OnMessage(...)` fallback runs.
|
||||||
|
7. If the scene returns `Pass`, normal plugin command and update routing continues.
|
||||||
|
|
||||||
Scene-local command:
|
The scene text comes from message text or caption. Scenes are therefore designed around message-based flows.
|
||||||
|
|
||||||
```go
|
|
||||||
func stopRP(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
|
||||||
ctx.Answer("RP mode disabled")
|
|
||||||
return ctx.Exit(), nil
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## State between steps
|
## State between steps
|
||||||
|
|
||||||
Scenes need more than just `Scene` and `Step`. Real flows also accumulate data between updates, such as a draft profile, selected options, or temporary IDs.
|
Use `SceneSession.Data` only through `SceneContext.BindData(...)` and `SaveData(...)` unless you are implementing custom store behavior.
|
||||||
|
|
||||||
That state now lives in `SceneSession.Data`, and `SceneContext` already exposes JSON-backed helpers for it.
|
|
||||||
|
|
||||||
Current helper API:
|
|
||||||
|
|
||||||
- `ctx.BindData(&dst)`
|
|
||||||
- `ctx.SaveData(src)`
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type ProfileDraft struct {
|
type ProfileDraft struct {
|
||||||
@@ -200,7 +117,9 @@ type ProfileDraft struct {
|
|||||||
|
|
||||||
func askName(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
func askName(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
||||||
var draft ProfileDraft
|
var draft ProfileDraft
|
||||||
_ = ctx.BindData(&draft)
|
if err := ctx.BindData(&draft); err != nil {
|
||||||
|
return laniakea.SceneResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
draft.Name = ctx.Text
|
draft.Name = ctx.Text
|
||||||
if err := ctx.SaveData(draft); err != nil {
|
if err := ctx.SaveData(draft); err != nil {
|
||||||
@@ -211,43 +130,46 @@ func askName(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error)
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Using `Data []byte` in `SceneSession` keeps the `SessionStore` contract small and storage-agnostic. The current helpers use JSON.
|
The store contract stays intentionally small because `Data []byte` is storage-agnostic.
|
||||||
|
|
||||||
## Routing algorithm
|
## Example flow
|
||||||
|
|
||||||
The current router behaves like this:
|
Command entry:
|
||||||
|
|
||||||
1. Build `MsgContext` for the update.
|
```go
|
||||||
2. Compute the session key from scene scope and the current update.
|
func startSignup(ctx *laniakea.MsgContext, db *App) error {
|
||||||
3. Ask `SessionStore` whether an active scene exists.
|
return ctx.EnterScene("signup")
|
||||||
4. If no scene is active, continue normal command, payload, and update routing.
|
}
|
||||||
5. If a scene is active, try scene-local command routing first.
|
```
|
||||||
6. If no scene-local route matches, try the current step handler, then the scene message handler.
|
|
||||||
7. If the scene returns `Stay`, keep the current session.
|
|
||||||
8. If the scene returns `Next(step)`, persist the new step and keep `Data`.
|
|
||||||
9. If the scene returns `Exit`, delete the session.
|
|
||||||
10. If the scene returns `Pass`, continue normal routing.
|
|
||||||
|
|
||||||
## Implemented today
|
Step handler:
|
||||||
|
|
||||||
- `Plugin.NewScene(...)` and `Plugin.AddScene(...)`.
|
```go
|
||||||
- `Scene.SetScope(...)`, `SetEntry(...)`, `OnStep(...)`, `OnCommand(...)`, and `OnMessage(...)`.
|
func askName(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
||||||
- `MsgContext.EnterScene(...)`, `EnterSceneStep(...)`, and `ExitScene(...)`.
|
if ctx.Text == "" {
|
||||||
- `SceneContext.Stay()`, `Next(...)`, `Exit()`, `Pass()`, `BindData(...)`, and `SaveData(...)`.
|
ctx.Answer("What is your name?")
|
||||||
- `SessionStore` plus the default `MemorySessionStore`.
|
return ctx.Stay(), nil
|
||||||
- Active-scene routing before normal command flow.
|
}
|
||||||
|
|
||||||
## Still missing or intentionally deferred
|
ctx.Answer("Thanks.")
|
||||||
|
return ctx.Next("confirm"), nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
- Scene-local payload routing is not implemented yet.
|
Scene-local command:
|
||||||
- The scene runtime remains intentionally internal; the public API does not expose low-level session lookup helpers.
|
|
||||||
- The scene-state helper surface is still intentionally small.
|
|
||||||
|
|
||||||
## Remaining work
|
```go
|
||||||
|
func cancelSignup(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
||||||
|
ctx.Answer("Signup cancelled")
|
||||||
|
return ctx.Exit(), nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
- Expand tests around scene-state helpers and fallback behavior.
|
## Deliberate limits of the current model
|
||||||
- Decide whether scene-local payloads belong in the first stable scene release.
|
|
||||||
- Decide whether any additional public inspection API is actually needed.
|
- Scene-local payload routing is not implemented.
|
||||||
|
- The internal scene runtime is not exposed as public inspection API.
|
||||||
|
- The helper surface for scene state is intentionally small.
|
||||||
|
|
||||||
Related pages:
|
Related pages:
|
||||||
|
|
||||||
|
|||||||
+18
-18
@@ -2,40 +2,40 @@
|
|||||||
|
|
||||||
English version: [[Semver-and-Releases]]
|
English version: [[Semver-and-Releases]]
|
||||||
|
|
||||||
Это краткая русскоязычная версия страницы про semver и release policy. Полная и наиболее актуальная страница: [[Semver-and-Releases]].
|
Это краткая русскоязычная версия страницы про semver и политику релизов. Полная и наиболее актуальная страница: [[Semver-and-Releases]].
|
||||||
|
|
||||||
## Зачем нужна эта страница
|
## Зачем нужна эта страница
|
||||||
|
|
||||||
Она объясняет, как в проекте понимать:
|
Она объясняет, как в проекте понимать:
|
||||||
- что считается public API;
|
- что считается публичным API;
|
||||||
- что считается breaking change;
|
- что считается ломающим изменением;
|
||||||
- как выбирать target version;
|
- как выбирать целевую версию;
|
||||||
- как changelog и version file должны соотноситься.
|
- как changelog и version file должны соотноситься.
|
||||||
|
|
||||||
## Что считается public API
|
## Что считается публичным API
|
||||||
|
|
||||||
Обычно сюда входят:
|
Обычно сюда входят:
|
||||||
- exported types и methods;
|
- exported types и methods;
|
||||||
- helper methods вроде `AnswerLong(...)`;
|
- вспомогательные методы вроде `AnswerLong(...)`;
|
||||||
- behavior, на который пользователи библиотеки разумно опираются;
|
- behavior, на который пользователи библиотеки разумно опираются;
|
||||||
- documented runtime semantics.
|
- документированная семантика времени выполнения.
|
||||||
|
|
||||||
## Что считается breaking change
|
## Что считается ломающим изменением
|
||||||
|
|
||||||
Breaking change — это не только удаление функции.
|
Ломающее изменение — это не только удаление функции.
|
||||||
|
|
||||||
Сюда же относятся:
|
Сюда же относятся:
|
||||||
- несовместимые signature changes;
|
- несовместимые изменения сигнатур;
|
||||||
- изменение runtime behavior, которое ломает существующий код;
|
- изменение поведения во время выполнения, которое ломает существующий код;
|
||||||
- удаление или переименование публичных helper methods;
|
- удаление или переименование публичных вспомогательных методов;
|
||||||
- изменение documented semantics без совместимого fallback.
|
- изменение документированной семантики без совместимого запасного варианта.
|
||||||
|
|
||||||
## Как выбирать версию
|
## Как выбирать версию
|
||||||
|
|
||||||
Логика обычная semver:
|
Логика обычная semver:
|
||||||
- patch для совместимых fixes;
|
- patch для совместимых исправлений;
|
||||||
- minor для совместимых additions;
|
- minor для совместимых расширений;
|
||||||
- major для breaking changes.
|
- major для ломающих изменений.
|
||||||
|
|
||||||
Release candidates дополнительно обозначают нестабильную стадию развития API.
|
Release candidates дополнительно обозначают нестабильную стадию развития API.
|
||||||
|
|
||||||
@@ -47,8 +47,8 @@ Wiki-only изменения в `.wiki` в основной `CHANGELOG.md` не
|
|||||||
|
|
||||||
## Практический вывод
|
## Практический вывод
|
||||||
|
|
||||||
- Не делай breaking changes без осознанного version decision.
|
- Не делай ломающих изменений без осознанного решения по версии.
|
||||||
- Сначала определяй target version, потом меняй API.
|
- Сначала определяй целевую версию, потом меняй API.
|
||||||
- Поддерживай changelog, tags и version file согласованными.
|
- Поддерживай changelog, tags и version file согласованными.
|
||||||
|
|
||||||
## Что читать дальше
|
## Что читать дальше
|
||||||
|
|||||||
+25
-25
@@ -10,55 +10,55 @@ Laniakea хорошо тестируется обычными Go unit tests.
|
|||||||
|
|
||||||
В репозитории уже используются паттерны вроде:
|
В репозитории уже используются паттерны вроде:
|
||||||
- fake HTTP transport для `tgapi`;
|
- fake HTTP transport для `tgapi`;
|
||||||
- direct tests для `MsgContext` helpers;
|
- прямые тесты для вспомогательных методов `MsgContext`;
|
||||||
- routing tests для commands и payloads;
|
- routing tests для commands и payloads;
|
||||||
- runner tests.
|
- runner tests.
|
||||||
|
|
||||||
## Что стоит тестировать в первую очередь
|
## Что стоит тестировать в первую очередь
|
||||||
|
|
||||||
- public handler flows;
|
- public handler flows;
|
||||||
- argument validation;
|
- валидацию аргументов;
|
||||||
- callbacks и payload decoding;
|
- callbacks и декодирование данных callback;
|
||||||
- middleware behavior;
|
- поведение middleware;
|
||||||
- long replies;
|
- длинные ответы;
|
||||||
- startup/shutdown semantics;
|
- семантику запуска и остановки;
|
||||||
- migration-sensitive regressions.
|
- регрессии, чувствительные к миграции.
|
||||||
|
|
||||||
## Тесты для `tgapi`
|
## Тесты для `tgapi`
|
||||||
|
|
||||||
Для low-level API удобно подменять `http.Client` transport и проверять:
|
Для низкоуровневого API удобно подменять `http.Client` transport и проверять:
|
||||||
- request body;
|
- тело запроса;
|
||||||
- method name;
|
- имя метода;
|
||||||
- response parsing;
|
- разбор ответа;
|
||||||
- retry/error behavior.
|
- поведение retry и ошибок.
|
||||||
|
|
||||||
## Тесты для handler logic
|
## Тесты для логики обработчиков
|
||||||
|
|
||||||
Для handler-level тестов обычно полезно:
|
Для тестов уровня обработчика обычно полезно:
|
||||||
- собрать `MsgContext`;
|
- собрать `MsgContext`;
|
||||||
- вызвать handler напрямую;
|
- вызвать обработчик напрямую;
|
||||||
- проверить side effects и ответы.
|
- проверить побочные эффекты и ответы.
|
||||||
|
|
||||||
## Тесты для routing
|
## Тесты для routing
|
||||||
|
|
||||||
Отдельно полезно тестировать:
|
Отдельно полезно тестировать:
|
||||||
- command matching;
|
- сопоставление команд;
|
||||||
- payload matching;
|
- сопоставление данных callback;
|
||||||
- middleware order;
|
- порядок middleware;
|
||||||
- изоляцию контекста между plugin chains.
|
- изоляцию контекста между цепочками плагинов.
|
||||||
|
|
||||||
## Тесты для runners
|
## Тесты для фоновых задач
|
||||||
|
|
||||||
Для runners важно проверить:
|
Для фоновых задач важно проверить:
|
||||||
- какие режимы реально запускаются;
|
- какие режимы реально запускаются;
|
||||||
- какие конфигурации скипаются;
|
- какие конфигурации скипаются;
|
||||||
- как ведет себя shutdown.
|
- как ведет себя остановка.
|
||||||
|
|
||||||
## Практические советы
|
## Практические советы
|
||||||
|
|
||||||
- Предпочитай table-driven tests там, где много сценариев.
|
- Предпочитай table-driven tests там, где много сценариев.
|
||||||
- Добавляй regression tests на найденные bugs.
|
- Добавляй regression tests на найденные ошибки.
|
||||||
- Не ограничивайся только happy path.
|
- Не ограничивайся только успешным сценарием.
|
||||||
|
|
||||||
## Что читать дальше
|
## Что читать дальше
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -6,7 +6,7 @@ English version: [[tgapi-Overview]]
|
|||||||
|
|
||||||
## Что такое `tgapi`
|
## Что такое `tgapi`
|
||||||
|
|
||||||
`tgapi` — это low-level Telegram Bot API layer под высокоуровневым runtime Laniakea.
|
`tgapi` — это низкоуровневый слой Telegram Bot API под высокоуровневым механизмом выполнения Laniakea.
|
||||||
|
|
||||||
Используй его, когда нужен:
|
Используй его, когда нужен:
|
||||||
- direct access к Telegram methods;
|
- direct access к Telegram methods;
|
||||||
@@ -25,12 +25,12 @@ English version: [[tgapi-Overview]]
|
|||||||
|
|
||||||
Используй `MsgContext`, когда:
|
Используй `MsgContext`, когда:
|
||||||
- ты уже внутри handler'а;
|
- ты уже внутри handler'а;
|
||||||
- нужен обычный reply/edit/delete/callback flow.
|
- нужен обычный поток reply/edit/delete/callback.
|
||||||
|
|
||||||
Используй `tgapi`, когда:
|
Используй `tgapi`, когда:
|
||||||
- у `MsgContext` нет нужного helper method;
|
- у `MsgContext` нет нужного вспомогательного метода;
|
||||||
- ты работаешь вне handler flow;
|
- ты работаешь вне потока обработчика;
|
||||||
- нужен lower-level control;
|
- нужен более низкоуровневый контроль;
|
||||||
- нужно работать с uploads/downloads напрямую.
|
- нужно работать с uploads/downloads напрямую.
|
||||||
|
|
||||||
## Typed methods first
|
## Typed methods first
|
||||||
@@ -51,14 +51,14 @@ defer api.Close()
|
|||||||
- test server;
|
- test server;
|
||||||
- custom API URL;
|
- custom API URL;
|
||||||
- limiter;
|
- limiter;
|
||||||
- limiter drop mode.
|
- режим сброса у limiter.
|
||||||
|
|
||||||
## `API` и `Uploader`
|
## `API` и `Uploader`
|
||||||
|
|
||||||
`API` отвечает за:
|
`API` отвечает за:
|
||||||
- JSON request encoding;
|
- JSON request encoding;
|
||||||
- HTTP execution;
|
- HTTP execution;
|
||||||
- retry и limiter behavior;
|
- поведение retry и limiter;
|
||||||
- response decoding.
|
- response decoding.
|
||||||
|
|
||||||
`Uploader` отвечает за:
|
`Uploader` отвечает за:
|
||||||
@@ -67,19 +67,19 @@ defer api.Close()
|
|||||||
|
|
||||||
## `Close()` важен
|
## `Close()` важен
|
||||||
|
|
||||||
У `API` и `Uploader` есть собственный lifecycle.
|
У `API` и `Uploader` есть собственный жизненный цикл.
|
||||||
|
|
||||||
Если ты владеешь этими объектами напрямую, их надо закрывать явно.
|
Если ты владеешь этими объектами напрямую, их надо закрывать явно.
|
||||||
|
|
||||||
Если ими владеет `Bot`, это делает `bot.Close()`.
|
Если ими владеет `Bot`, это делает `bot.Close()`.
|
||||||
|
|
||||||
## Downloads и low-level escape hatches
|
## Downloads и низкоуровневые запасные пути
|
||||||
|
|
||||||
`tgapi` покрывает и file download flow, и raw request builders.
|
`tgapi` покрывает и поток загрузки файлов, и сырые конструкторы запросов.
|
||||||
|
|
||||||
Это полезно, когда typed helper метода еще нет или нужен очень точный контроль.
|
Это полезно, когда типизированного вспомогательного метода еще нет или нужен очень точный контроль.
|
||||||
|
|
||||||
Но в day-to-day bot code лучше оставаться на более высоком уровне, если он уже покрывает нужный кейс.
|
Но в повседневном коде бота лучше оставаться на более высоком уровне, если он уже покрывает нужный кейс.
|
||||||
|
|
||||||
## Что читать дальше
|
## Что читать дальше
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user