REPOSITORY / ScuroNeko/Laniakea

Wiki

KNOWLEDGE REPOSITORY

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
2026-03-30 00:11:30 +03:00
parent 963058806e
commit 72439e8224
26 changed files with 676 additions and 823 deletions
+13 -13
@@ -6,7 +6,7 @@ English version: [[Auto-Generated-Commands]]
## Что делает эта функция
Laniakea может пройтись по уже зарегистрированным plugin commands, собрать подходящие команды и отправить их в Telegram через `setMyCommands`.
Laniakea может пройтись по уже зарегистрированным командам плагинов, собрать подходящие команды и отправить их в Telegram через `setMyCommands`.
Для этого используются:
- `AutoGenerateCommands()`
@@ -16,16 +16,16 @@ Laniakea может пройтись по уже зарегистрирован
## Откуда берутся команды
Автогенерация работает только по уже зарегистрированным commands.
Автогенерация работает только по уже зарегистрированным командам.
Учитывается:
- имя команды;
- описание команды;
- skip-флаги на command или plugin уровне.
- skip-флаги на уровне команды или плагина.
Не участвуют:
- payload handlers;
- update handlers;
- обработчики данных callback;
- обработчики обновлений;
- команды, которые ты явно исключил из автогенерации.
## Когда вызывать
@@ -33,13 +33,13 @@ Laniakea может пройтись по уже зарегистрирован
Обычный порядок такой:
1. создать bot;
2. создать plugins;
3. зарегистрировать commands;
4. добавить plugins через `AddPlugins(...)`;
2. создать плагины;
3. зарегистрировать команды;
4. добавить плагины через `AddPlugins(...)`;
5. вызвать `AutoGenerateCommands()` или `AutoGenerateCommandsForScope(...)`;
6. запустить bot.
Важно: `AddPlugins(...)` — это snapshot point. Если ты меняешь plugin после регистрации, автогенерация не обязана увидеть эти изменения.
Важно: `AddPlugins(...)` — это точка фиксации. Если ты меняешь плагин после регистрации, автогенерация не обязана увидеть эти изменения.
## Ограничения Telegram
@@ -65,14 +65,14 @@ Laniakea может пройтись по уже зарегистрирован
Это полезно для:
- внутренних технических commands;
- переходных migration commands;
- редко используемых maintenance commands.
- переходных миграционных команд;
- редко используемых служебных команд.
## Рекомендации
- Вызывай автогенерацию после полной регистрации plugins.
- Вызывай автогенерацию после полной регистрации плагинов.
- Следи, чтобы descriptions были короткими и понятными.
- Не путай payload handlers с обычными slash-командами.
- Не путай обработчики данных callback с обычными slash-командами.
## Что читать дальше
+16 -16
@@ -2,19 +2,19 @@
English version: [[Bot-Lifecycle]]
Это краткая русскоязычная версия страницы про lifecycle `Bot`. Полная и наиболее актуальная страница: [[Bot-Lifecycle]].
Это краткая русскоязычная версия страницы про жизненный цикл `Bot`. Полная и наиболее актуальная страница: [[Bot-Lifecycle]].
## Жизненный цикл в одном списке
1. Собрать `BotOpts`.
2. Создать `Bot` через `NewBot[T](opts)`.
3. Полностью настроить bot: plugins, middleware, runners, payload policy, l10n, db context.
3. Полностью настроить бот: плагины, middleware, фоновые задачи, политику данных callback, l10n, контекст базы данных.
4. Запустить через `Run()` или `RunWithContext(...)`.
5. Остановить runtime через завершение `Run()` или cancel context.
5. Остановить выполнение через завершение `Run()` или отмену context.
6. Освободить локальные ресурсы через `Close()`.
7. Для следующего запуска создать новый `Bot`.
Главное правило: `Bot` single-use.
Главное правило: `Bot` используется только один раз.
## Что делает `NewBot(...)`
@@ -42,14 +42,14 @@ English version: [[Bot-Lifecycle]]
- `SetStrictPayloadType(...)`
- `SetDraftProvider(...)`
Это важно, потому что runtime не рассчитан на модель “запустили, а потом продолжаем собирать конфигурацию на лету”.
Это важно, потому что механизм выполнения не рассчитан на модель “запустили, а потом продолжаем собирать конфигурацию на лету”.
## Почему `AddPlugins(...)` так важен
`AddPlugins(...)` копирует конфигурацию plugin внутрь bot.
`AddPlugins(...)` копирует конфигурацию плагина внутрь бота.
Практически это значит:
- сначала закончи настройку plugin;
- сначала закончи настройку плагина;
- потом регистрируй его;
- не рассчитывай, что дальнейшая мутация исходного `*Plugin` будет официально поддерживаемой частью API.
@@ -58,33 +58,33 @@ English version: [[Bot-Lifecycle]]
`Run()` — это короткая форма для простых случаев.
`RunWithContext(...)` — основной production-вариант, потому что он:
- умеет graceful shutdown через `ctx.Done()`;
- умеет корректно завершаться через `ctx.Done()`;
- ждет завершения queued updates;
- корректно дожидается runners.
- корректно дожидается фоновых задач.
Если bot уже был запущен раньше, повторный запуск вернет `ErrBotAlreadyRun`.
## Что происходит во время runtime
## Что происходит во время выполнения
Во время работы bot делает три вещи:
Во время работы бот делает три вещи:
- long-polling `getUpdates`;
- складывает updates во внутреннюю очередь;
- обрабатывает их через worker pool.
Полезно помнить:
- размер worker pool управляется через `MaxWorkers`;
- polling при ошибках использует exponential backoff;
- после cancel сначала прекращается polling, потом дренируется очередь, потом дожидаются runners.
- polling при ошибках использует экспоненциальный backoff;
- после отмены context сначала прекращается polling, потом дренируется очередь, потом дожидаются фоновые задачи.
## `Close()` и `CloseRemote()`
Это разные вещи.
`Close()`:
- закрывает plugins через `Plugin.Close()`;
- закрывает плагины через `Plugin.Close()`;
- закрывает uploader;
- закрывает локальный API client;
- закрывает request logger и main logger.
- закрывает логгер запросов и основной логгер.
`CloseRemote(ctx)`:
- отправляет Telegram Bot API метод `close`;
@@ -96,7 +96,7 @@ English version: [[Bot-Lifecycle]]
- Пытаться повторно использовать тот же `Bot`.
- Забывать `Close()` после завершения `RunWithContext(...)`.
- Менять plugins после `AddPlugins(...)` и ждать, что bot это гарантированно увидит.
- Менять плагины после `AddPlugins(...)` и ждать, что бот это гарантированно увидит.
- Регистрировать repeating runner без timeout.
## Что читать дальше
+14 -14
@@ -11,12 +11,12 @@ English version: [[Bot-Options-and-Configuration]]
Через него настраиваются:
- токен и API endpoint;
- update types и command prefixes;
- logging behavior;
- rate limiting;
- strict payload decoding;
- поведение логирования;
- ограничение частоты;
- строгое декодирование данных callback;
- размер worker pool.
Обычный flow:
Обычный поток:
1. собрать `BotOpts` вручную или через `LoadOptsFromEnv()`;
2. при необходимости донастроить setter methods;
3. передать в `NewBot(...)`.
@@ -53,8 +53,8 @@ opts := laniakea.LoadOptsFromEnv()
- `RateLimit` по умолчанию `30`
- `MaxWorkers` по умолчанию `32`
- `ErrorTemplate` по умолчанию `"%s"`
- request logging и file logging выключены
- strict payload decoding выключен
- журналирование запросов и логирование в файл выключены
- строгое декодирование данных callback выключено
## Важные поля
@@ -70,11 +70,11 @@ opts := laniakea.LoadOptsFromEnv()
### `ErrorTemplate`
Определяет, как пользователю показываются returned handler errors.
Определяет, как пользователю показываются ошибки, возвращённые обработчиком.
### `Debug`
Включает debug logging.
Включает отладочное логирование.
### `UseRequestLogger`
@@ -86,27 +86,27 @@ opts := laniakea.LoadOptsFromEnv()
### `UseTestServer` и `APIUrl`
Полезны для test environment, proxy или custom Telegram gateway.
Полезны для тестового окружения, proxy или собственного Telegram gateway.
### `RateLimit` и `DropRLOverflow`
Управляют политикой limiter:
- ждать и доставлять надежнее;
- или дропать overflow ради отзывчивости.
- или сбрасывать лишние запросы ради отзывчивости.
### `StrictPayloadType`
Включает строгую политику декодирования callback payloads без fallback между JSON и Base64.
Включает строгую политику декодирования данных callback без запасного варианта между JSON и Base64.
### `MaxWorkers`
Определяет максимальное количество concurrent update handlers.
Определяет максимальное количество параллельных обработчиков обновлений.
## Когда выбирать маленький или большой `MaxWorkers`
Меньше:
- если handlers CPU-bound;
- если downstream services не выдержат много параллелизма.
- если обработчики CPU-bound;
- если нижележащие сервисы не выдержат много параллелизма.
Больше:
- если handlers в основном I/O-bound;
+47 -47
@@ -2,31 +2,31 @@
English version: [[Commands-and-Plugins]]
Это краткая русскоязычная версия страницы про архитектуру `Bot`, `Plugin`, commands, payloads и update handlers. Полная и наиболее актуальная страница: [[Commands-and-Plugins]].
Это краткая русскоязычная версия страницы про архитектуру `Bot`, `Plugin`, команды, данные callback и обработчики обновлений. Полная и наиболее актуальная страница: [[Commands-and-Plugins]].
## Главное сначала
Обычная модель в Laniakea такая:
- `Bot` владеет runtime, polling, логированием и API-клиентами;
- `Bot` управляет выполнением, polling, логированием и API-клиентами;
- `Plugin` группирует связанную функциональность;
- commands обрабатывают текстовые команды вроде `/start`;
- payload handlers обрабатывают callback data от inline-кнопок;
- update handlers обрабатывают остальные update types вне обычного command/payload flow.
- команды обрабатывают текстовые команды вроде `/start`;
- обработчики данных callback обрабатывают callback data от inline-кнопок;
- обработчики обновлений обрабатывают остальные типы обновлений вне обычного потока команд и callback.
Для большинства ботов стартовая структура выглядит так:
- один или несколько плагинов;
- несколько команд;
- plugin middleware для общих проверок;
- payload handlers, когда появляются inline-кнопки.
- middleware плагина для общих проверок;
- обработчики данных callback, когда появляются inline-кнопки.
## Что такое `Plugin`
Плагин — это именованная группа:
- commands;
- payload handlers;
- update handlers;
- команд;
- обработчиков данных callback;
- обработчиков обновлений;
- общих middleware;
- optional logger и `OnClose` hook.
- необязательного логгера и `OnClose`-хука.
Пример:
@@ -42,9 +42,9 @@ plugin := laniakea.NewPlugin[laniakea.NoDB]("admin")
Так проще держать границы ответственности и не превращать весь бот в один большой registry-файл.
## Command handlers
## Обработчики команд
Сигнатура command handler такая:
Сигнатура обработчика команды такая:
```go
func(ctx *laniakea.MsgContext, db T) error
@@ -56,7 +56,7 @@ func(ctx *laniakea.MsgContext, db T) error
Возвращай:
- `nil`, если все прошло успешно;
- `error`, если хочешь отдать ошибку в централизованный error flow.
- `error`, если хочешь отдать ошибку в централизованный поток обработки ошибок.
Пример:
@@ -89,7 +89,7 @@ plugin.AddCommand(plugin.NewCommand(start, "start"))
/echo hello world
```
в handler'е будет:
в обработчике будет:
- `ctx.Text == "hello world"`
- `ctx.Args == []string{"hello", "world"}`
@@ -116,11 +116,11 @@ plugin.AddCommand(
- базовый тип вроде `int` или `string`;
- 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;
- аргументы decoded payload попадают в `ctx.Args`;
- payload — это не текстовая команда, а callback from button.
- обработчик данных callback использует ту же сигнатуру, что и обработчик команды;
- аргументы разобранных данных callback попадают в `ctx.Args`;
- это не текстовая команда, а callback от кнопки.
Подробности: [[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`
- `callback_query`
не должны идти через `AddUpdateHandler(...)`, потому что они уже обслуживаются обычным command/payload pipeline.
не должны идти через `AddUpdateHandler(...)`, потому что они уже обслуживаются обычным потоком команд и callback.
## Как выглядит runtime flow
## Как выглядит поток выполнения
Для текстовой команды поток примерно такой:
1. Приходит Telegram update.
2. `Bot` готовит `MsgContext`.
3. Выполняется bot middleware.
4. Находится подходящий plugin.
5. Выполняется plugin middleware.
3. Выполняется middleware бота.
4. Находится подходящий плагин.
5. Выполняется middleware плагина.
6. Выполняется валидация аргументов.
7. Выполняется command-specific middleware.
8. Запускается handler.
9. Если handler вернул ошибку, она идет в централизованный error flow.
7. Выполняется middleware команды.
8. Запускается обработчик.
9. Если обработчик вернул ошибку, она идет в централизованный поток обработки ошибок.
Для payload flow идея та же самая, только trigger приходит не из текста сообщения, а из decoded callback data.
Для потока данных callback идея та же самая, только запуск происходит не из текста сообщения, а из разобранных callback data.
## Где использовать 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)
```
Подходит, когда проверка нужна только одной команде или одному payload handler.
Подходит, когда проверка нужна только одной команде или одному обработчику данных callback.
Подробности: [[Middleware]]
@@ -227,13 +227,13 @@ plugin.NewCommand(start, "/start")
plugin.NewCommand(start, "start")
```
### Считать payload обычной командой
### Считать данные callback обычной командой
Payload handler вызывается не из текста сообщения, а из callback data кнопки.
Обработчик данных callback вызывается не из текста сообщения, а из callback data кнопки.
### Использовать `AddUpdateHandler(...)` для `message` или `callback_query`
Эти update types относятся к обычному command/payload pipeline.
Эти типы обновлений относятся к обычному потоку команд и callback.
### Возвращать `error` там, где это просто обычная ветка UX
@@ -247,11 +247,11 @@ return nil
## Когда использовать что
Используй:
- commands для slash-команд;
- payload handlers для inline button callbacks;
- update handlers для остальных Telegram updates;
- plugin middleware для общих проверок;
- command middleware для локальных, узких проверок.
- команды для slash-команд;
- обработчики данных callback для callback кнопок;
- обработчики обновлений для остальных Telegram updates;
- middleware плагина для общих проверок;
- middleware команды для локальных, узких проверок.
## Что читать дальше
+5 -5
@@ -2,9 +2,9 @@
English version: [[Drafts]]
Это краткая русскоязычная версия страницы про drafts. Полная и наиболее актуальная страница: [[Drafts]].
Это краткая русскоязычная версия страницы про черновики. Полная и наиболее актуальная страница: [[Drafts]].
## Когда нужны drafts
## Когда нужны черновики
Drafts полезны, когда ответ:
- собирается постепенно;
@@ -36,7 +36,7 @@ draft.Flush()
## Lifecycle draft'а
Типичный flow такой:
Типичный поток такой:
1. создать draft;
2. добавлять текст и настройки;
3. при необходимости делать `Push(...)` как промежуточную отправку;
@@ -56,7 +56,7 @@ draft.Flush()
У provider есть `FlushAll()`.
Это best-effort операция:
- provider пытается отправить все pending drafts;
- провайдер пытается отправить все ожидающие черновики;
- ошибки одного draft не отменяют попытки для остальных.
## IDs и стратегии генерации
@@ -71,7 +71,7 @@ draft.Flush()
`NewDraftMarkdown()` включает `MarkdownV2`.
Как и в остальных Markdown helper methods, пользовательский ввод надо экранировать отдельно.
Как и в остальных Markdown-вспомогательных методах, пользовательский ввод надо экранировать отдельно.
## Что читать дальше
+5 -5
@@ -2,16 +2,16 @@
English version: [[Error-Handling]]
Это краткая русскоязычная версия страницы про centralized error flow. Полная и наиболее актуальная страница: [[Error-Handling]].
Это краткая русскоязычная версия страницы про централизованный поток обработки ошибок. Полная и наиболее актуальная страница: [[Error-Handling]].
## Базовая идея
В Laniakea handlers возвращают `error`.
Это относится к:
- command handlers;
- payload handlers;
- update handlers.
- обработчики команд;
- обработчики данных callback;
- обработчики обновлений.
Если handler возвращает ошибку, bot:
- форматирует user-facing текст через `ErrorTemplate(...)`;
@@ -60,7 +60,7 @@ if !allowed {
## Callback-specific поведение
Для callback flow returned error превращается не в обычное сообщение в чат, а в ответ на callback query.
Для callback-потока возвращённая ошибка превращается не в обычное сообщение в чат, а в ответ на callback query.
Если нужен другой UX, лучше:
- вызвать `AnswerCbQueryText(...)` или `AnswerCbQueryAlert(...)`;
+26 -26
@@ -6,12 +6,12 @@ English version: [[FAQ]]
## Почему хендлеры возвращают `error`?
Чтобы ошибки проходили через единый, централизованный flow, а не обрабатывались вручную в каждом command или payload handler.
Чтобы ошибки проходили через единый, централизованный поток, а не обрабатывались вручную в каждом обработчике команды или callback.
Это дает несколько плюсов:
- хендлеры остаются проще;
- user-facing error format можно контролировать через `ErrorTemplate(...)`;
- command, payload и non-command update handlers используют один и тот же контракт.
- формат пользовательских ошибок можно контролировать через `ErrorTemplate(...)`;
- команды, callback и обработчики обновлений вне команд используют один и тот же контракт.
Если тебе нужен полностью ручной ответ пользователю, ты все еще можешь ответить сам и вернуть `nil`.
@@ -19,12 +19,12 @@ English version: [[FAQ]]
## Почему `Bot` single-use?
Потому что один run владеет реальным runtime state:
- polling lifecycle;
Потому что один запуск владеет реальным состоянием выполнения:
- жизненным циклом polling;
- worker pool;
- update offsets;
- runner execution;
- API и logger resources.
- выполнением фоновых задач;
- API и ресурсами логгеров.
Из-за этого модель “создал -> настроил -> запустил -> закрыл -> создал новый” безопаснее и проще для понимания, чем попытка перезапускать один и тот же `Bot`.
@@ -39,31 +39,31 @@ English version: [[FAQ]]
- возможен partial success;
- клавиатура в `KeyboardLong(...)` вешается только на последний chunk.
Такой split сделан специально, чтобы длинные ответы не меняли поведение обычных helper methods неявно.
Такое разделение сделано специально, чтобы длинные ответы не меняли поведение обычных вспомогательных методов неявно.
## Зачем есть и JSON, и Base64 payload formats?
## Зачем есть и JSON, и Base64 форматы данных callback?
Они решают разные задачи:
- `BotPayloadJson` удобен для читаемости, логов и тестов
- `BotPayloadBase64` удобен как более компактная и “непрозрачная” transport-форма того же payload
- `BotPayloadBase64` удобен как более компактная и “непрозрачная” транспортная форма тех же данных callback
Логическая структура callback payload при этом одна и та же: меняется только encoding.
Логическая структура данных callback при этом одна и та же: меняется только кодирование.
Подробности: [[Inline-Keyboards-and-Payloads]]
## Когда использовать `MsgContext`, а когда `tgapi`?
Используй `MsgContext`, когда ты уже внутри хендлера и тебе нужен удобный reply/edit/delete flow с текущим chat, message и logger.
Используй `MsgContext`, когда ты уже внутри хендлера и тебе нужен удобный поток reply/edit/delete с текущими chat, message и logger.
Используй `tgapi`, когда:
- нужного helper method нет в `MsgContext`
- ты работаешь вне handler flow
- нужен lower-level control над Telegram methods, uploads или downloads
- нужного вспомогательного метода нет в `MsgContext`
- ты работаешь вне потока обработчика
- нужен более низкоуровневый контроль над Telegram methods, uploads или downloads
Коротко:
- `MsgContext`ergonomic default
- `tgapi`lower-level escape hatch
- `MsgContext`удобный вариант по умолчанию
- `tgapi`низкоуровневый запасной путь
Подробности: [[tgapi-Overview]]
@@ -71,30 +71,30 @@ English version: [[FAQ]]
Потому что это две разные ответственности:
- `RunWithContext(...)` управляет run loop, graceful stop и ожиданием runner'ов
- `Close()` освобождает API, uploader, plugin shutdown hooks и loggers
- `RunWithContext(...)` управляет циклом выполнения, корректной остановкой и ожиданием фоновых задач
- `Close()` освобождает API, uploader, хуки остановки плагинов и логгеры
Поэтому `Close()` все равно нужен.
## Почему `Plugin` нужно полностью настроить до `AddPlugins(...)`?
Потому что `AddPlugins(...)` — это configuration snapshot point.
Потому что `AddPlugins(...)` — это точка фиксации конфигурации.
После регистрации `Bot` хранит внутреннюю копию состояния плагина, и изменения исходного `*Plugin` уже не считаются поддерживаемым API.
До `AddPlugins(...)` стоит завершить:
- commands
- payloads
- update handlers
- plugin middleware
- logger choice
- данные callback
- обработчики обновлений
- middleware плагина
- выбор логгера
- `OnClose`
## Почему async middleware игнорирует `false`?
Потому что async middleware задуман как side-effect path, а не как механизм flow control.
Потому что async middleware задуман как путь для побочных действий, а не как механизм управления потоком.
Когда middleware уходит в goroutine, он уже не может надежно остановить основной execution path. Поэтому для блокировки и отказов нужно использовать обычный synchronous middleware.
Когда middleware уходит в goroutine, он уже не может надежно остановить основной путь выполнения. Поэтому для блокировки и отказов нужно использовать обычный synchronous middleware.
Подробности: [[Middleware]]
+86 -82
@@ -1,97 +1,101 @@
# 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.
- В нём уже есть полезные низкоуровневые строительные блоки: `MsgContext`, drafts, payload routing, plugins и update handlers.
- Теперь в нём уже есть work-in-progress skeleton для долгоживущих interaction flows: сцены можно регистрировать в plugins, запускать через `MsgContext`, сохранять через `SessionStore` и маршрутизировать раньше обычной обработки команд.
- Модель выполнения webhook: у библиотеки есть хорошая polling-модель, но нет полноценной модели выполнения webhook на уровне фреймворка.
- Модель авторизации и политик: middleware могут реализовать аутентификацию и права доступа, но нет явной модели уровня фреймворка для политик доступа, ролей или проверок возможностей.
- Модель наблюдаемости: логирование уже сильное, но метрики, трассировка и структурированные хуки фреймворка пока не являются полноценной частью 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" или "нажатие кнопки переводит пользователя в следующее состояние сцены".
- Без scene model пользователи библиотеки начинают строить свой mini-framework поверх Laniakea.
- Без модели сцен пользователи библиотеки начинают строить свой мини-фреймворк поверх Laniakea.
What is already present:
Что уже есть:
- Маршрутизация активной сцены раньше обычного command flow.
- Session scopes на пользователя, чат и пару пользователь-чат.
- Маршрутизация активной сцены раньше обычной маршрутизации команд.
- Области действия сессии на пользователя, чат и пару пользователь-чат.
- Явный вход и выход через `MsgContext`.
- Step handlers, scene-local commands и `OnMessage(...)`.
- In-memory session storage по умолчанию плюс интерфейс `SessionStore` для кастомного persistence.
- Обработчики шагов, локальные команды сцены и `OnMessage(...)`.
- Встроенное in-memory-хранилище по умолчанию и интерфейс `SessionStore` для собственного постоянного хранения.
What is still missing or not yet settled:
Что ещё отсутствует или не до конца определено:
- Scene-local payload routing.
- Ясное решение о том, нужен ли вообще дополнительный публичный API для inspection сцен.
- Более широкая стабилизация и документация вокруг helper-методов для scene state.
- Локальная маршрутизация данных callback внутри сцены.
- Ясное решение о том, нужен ли вообще дополнительный публичный API для просмотра состояния сцен.
- Дальнейшие расширения поверх текущей модели сцен, ориентированной на сообщения.
Current API direction:
Текущее направление API:
- `Scene`, `SceneContext`, `SceneSession` и `SessionStore`.
- `Plugin.NewScene(...)` и `Plugin.AddScene(...)`.
- `MsgContext.EnterScene(...)`, `EnterSceneStep(...)` и `ExitScene(...)`.
- `SceneContext.Stay()`, `Next(...)`, `Exit()`, `Pass()`, `BindData(...)` и `SaveData(...)`.
- Storage-backed состояние на пользователя или чат с чистым интерфейсом для кастомного persistence.
- Состояние на пользователя или чат с хранением в `SessionStore` и чистым интерфейсом для собственного постоянного хранения.
Important design constraints:
Важные ограничения дизайна:
- Это должно быть опциональным и additive.
- Это не должно заменять plugins, commands или handlers как обычные точки входа во фреймворк.
- Это должно быть опциональным и расширяющим текущую модель.
- Это не должно заменять плагины, команды или обработчики как обычные точки входа во фреймворк.
- Это должно работать поверх существующих 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
Current state:
Текущее состояние:
- Commands и payloads сейчас отдают parsed text через `ctx.Text` и `ctx.Args`.
- Команды и данные callback сейчас отдают разобранный текст через `ctx.Text` и `ctx.Args`.
- `CommandArg` даёт базовую проверку аргументов и их формы.
- Handlers всё ещё делают большую часть нетривиального парсинга вручную.
- Обработчики всё ещё делают большую часть нетривиального разбора вручную.
Why this matters:
Почему это важно:
- По мере роста бота handlers часто начинают с повторяющегося boilerplate для разбора `ctx.Args`.
- Validation logic расползается по handlers вместо того, чтобы жить в одном предсказуемом binding layer.
- По мере роста бота обработчики часто начинают с повторяющегося шаблонного кода для разбора `ctx.Args`.
- Логика валидации расползается по обработчикам вместо того, чтобы жить в одном предсказуемом слое привязки.
- Текущая модель проста и честна, но помогает недостаточно, когда команды становятся более структурированными.
What is missing:
Чего не хватает:
- First-class способ bind'ить command или payload arguments в typed Go value.
- Framework-level паттерн для conversion errors и validation errors вместо чистой работы со строками.
- Low-friction путь перехода от positional arguments к structured input object.
- Полноценный способ привязывать аргументы команды или данные callback к типизированному Go-значению.
- Паттерн уровня фреймворка для ошибок преобразования и ошибок валидации вместо чистой работы со строками.
- Низкопороговый путь перехода от позиционных аргументов к структурированному входному объекту.
Possible API direction:
Возможное направление API:
- Lightweight binding API вроде `ctx.BindArgs(&input)`.
- Или explicit typed command registration вроде `NewCommandTyped(...)`.
- Positional mapping в structs, optional fields, basic conversion support и интеграция с текущим validation flow.
- Unified binding и validation failures, которые идут через существующий centralized error path.
- Лёгкий API привязки вроде `ctx.BindArgs(&input)`.
- Или явная регистрация типизированных команд вроде `NewCommandTyped(...)`.
- Позиционное отображение в структуры, optional-поля, базовая поддержка преобразований и интеграция с текущим потоком валидации.
- Единые ошибки привязки и валидации, которые идут через существующий централизованный путь обработки ошибок.
Example of the kind of user code this should enable:
Пример пользовательского кода, который это должно сделать удобным:
```go
type BanInput struct {
@@ -109,50 +113,50 @@ func ban(ctx *laniakea.MsgContext, db *App) error {
}
```
Important design constraints:
Важные ограничения дизайна:
- Избегать reflection-heavy и слишком "магической" подсистемы.
- Сохранять текущую модель `ctx.Args` как минимальный baseline.
- Относиться к typed binding как к ergonomic layer поверх текущей command model, а не как к её замене.
- Избегать тяжёлой зависимости от reflection и слишком "магической" подсистемы.
- Сохранять текущую модель `ctx.Args` как минимальную базовую модель.
- Относиться к типизированной привязке как к удобному слою поверх текущей модели команд, а не как к её замене.
Practical target:
Практическая цель:
- Убрать повторяющийся parsing boilerplate, сохранив явный и Go-like характер framework.
- Убрать повторяющийся код разбора, сохранив явный и Go-подобный характер фреймворка.
### [1.0.0-rc.12] Request Context / Cancellation Model
Current state:
Текущее состояние:
- `RunWithContext(...)` управляет runtime lifecycle бота и graceful shutdown.
- `tgapi` уже поддерживает context-aware методы.
- Обычные handlers не получают first-class request-scoped `context.Context`.
- `RunWithContext(...)` управляет жизненным циклом выполнения бота и корректным завершением.
- `tgapi` уже поддерживает методы, принимающие `context.Context`.
- Обычные обработчики не получают полноценный `context.Context`, привязанный к обработке конкретного запроса.
Why this matters:
Почему это важно:
- Handler business logic часто требует cancellation-aware database calls, HTTP calls или обращения к downstream services.
- У framework уже есть хорошая runtime cancellation story, но она пока не доходит естественным образом до пользовательского кода внутри handlers.
- В современном Go API `context.Context` — стандартная часть operational correctness.
- Бизнес-логика обработчиков часто требует вызовов базы данных, HTTP-вызовов или обращений к нижележащим сервисам с поддержкой отмены.
- У фреймворка уже есть хорошая история с отменой выполнения на уровне механизма выполнения, но она пока не доходит естественным образом до пользовательского кода внутри обработчиков.
- В современном Go API `context.Context` — стандартная часть корректного управления выполнением.
What is missing:
Чего не хватает:
- Чистый request-scoped context, который сопровождает каждый update через всё выполнение handler.
- Стандартный способ для application code остановить работу, когда бот shutting down или update processing context отменён.
- Прямой мост между bot lifecycle control и service-layer cancellation.
- Чистый `context.Context`, который сопровождает каждое обновление через всё выполнение обработчика.
- Стандартный способ для кода приложения остановить работу, когда бот завершает работу или когда контекст обработки обновления отменён.
- Прямой мост между управлением жизненным циклом бота и отменой в сервисном слое.
Possible API direction:
Возможное направление API:
- Предпочесть non-breaking подход и выдавать context через `MsgContext`, например `ctx.Context()`.
- Строить context из update-processing lifecycle, чтобы он был meaningful во время graceful shutdown.
- Сделать естественным передачу этого context в database methods, HTTP clients и `tgapi.WithContext(...)`.
- Строить context из жизненного цикла обработки обновления, чтобы он оставался полезным во время корректного завершения.
- Сделать естественной передачу этого context в методы базы данных, HTTP-клиенты и `tgapi.WithContext(...)`.
Why this should probably not be a signature change:
Почему это, скорее всего, не должно быть изменением сигнатуры:
- Изменение handler signatures на прямой `context.Context` было бы public breaking change.
- Accessor на `MsgContext` сохраняет совместимость и при этом даёт handlers idiomatic Go path для cancellation.
- Изменение сигнатур обработчиков на прямой `context.Context` было бы публичным ломающим изменением.
- Аксессор на `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.
## 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:
- 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 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:
@@ -30,7 +47,7 @@ What is still missing or not yet settled:
- Scene-local payload routing.
- 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:
@@ -48,21 +65,8 @@ Important design constraints:
Practical target:
- Stabilize the current scene skeleton into a first-class framework-supported pattern.
- Cover both step-based forms and mode-based chat flows 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
- Extend the current first-class scene model where it adds clear value.
- Keep both step-based forms and mode-based chat flows supported without forcing users to build custom routing layers around active sessions.
### [1.0.0-rc.12] Typed Handler Input Model
+2 -2
@@ -116,7 +116,7 @@ func(ctx *laniakea.MsgContext, db T) error
То есть:
- на успехе возвращай `nil`
- если хочешь централизованный flow обработки ошибок, возвращай `error`
- если хочешь централизованный поток обработки ошибок, возвращай `error`
Пример:
@@ -181,7 +181,7 @@ defer bot.Close()
### Неожидание, что `ctx.Text` уже очищен от команды
Для обычного command flow:
Для обычного потока команд:
- сообщение: `/echo hello world`
- имя команды: `echo`
- `ctx.Text`: `hello world`
+21 -20
@@ -2,7 +2,7 @@
English version: [[Home]]
Это краткая русскоязычная точка входа в wiki Laniakea. Подробная и наиболее полная документация остается в англоязычной части wiki, поэтому за полным API-справочником лучше переходить по ссылкам на соответствующие английские страницы.
Это краткая русскоязычная точка входа в wiki Laniakea. Подробная и наиболее полная документация остаётся в англоязычной части wiki, поэтому за полным API-справочником лучше переходить по ссылкам на соответствующие английские страницы.
## Старт здесь
@@ -12,13 +12,14 @@ English version: [[Home]]
- [[MsgContext-RU]]
- [[FAQ-RU]]
## Runtime и архитектура
## Выполнение и архитектура
- [[Bot-Lifecycle-RU]]
- [[Middleware-RU]]
- [[Runners-RU]]
- [[Error-Handling-RU]]
- [[Logging-RU]]
- [[Scenes-RU]]
## Telegram API и взаимодействие
@@ -34,47 +35,47 @@ English version: [[Home]]
- [[Recipes-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`
Почему это важно:
Сейчас 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`
Почему это важно:
Страница про `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`
Почему это важно:
Wiki уже объясняет, как plugins использовать на практике, но почти не говорит о том, как о них думать архитектурно.
Wiki уже объясняет, как использовать плагины на практике, но почти не говорит о том, как о них думать архитектурно.
Что туда войдет:
Как резать бот на plugins, что должно жить в plugin middleware, когда выделять новый plugin и как не прийти к giant-plugin design.
Как делить бот на плагины, что должно жить в middleware плагина, когда выделять новый плагин и как не прийти к дизайну одного гигантского плагина.
4. `Handler-Design-Guidelines`
Почему это важно:
Это будет страница не столько про API, сколько про стиль и idiomatic use framework'а.
Это будет страница не столько про API, сколько про стиль и идиоматичное использование фреймворка.
Что туда войдет:
Когда возвращать `error`, когда отвечать вручную, как держать handlers thin, когда выносить логику в сервисный слой и как не смешивать `tgapi` и high-level helpers без необходимости.
Когда возвращать `error`, когда отвечать вручную, как держать обработчики тонкими, когда выносить логику в сервисный слой и как не смешивать `tgapi` и высокоуровневые вспомогательные методы без необходимости.
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`
Почему это важно:
Часть этой информации уже разбросана по страницам про 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`
@@ -94,9 +95,9 @@ Routing categories, update-specific context guarantees и влияние фор
- [[Page-Priority-RU]]
- [[FAQ-RU]]
## Полный английский reference
## Полный английский справочник
Если нужен полный и наиболее свежий reference, смотри исходные англоязычные страницы:
Если нужен полный и наиболее свежий справочник, смотри исходные англоязычные страницы:
- [[Getting-Started]]
- [[Bot-Options-and-Configuration]]
@@ -112,8 +113,8 @@ Routing categories, update-specific context guarantees и влияние фор
1. Прочитать [[Getting-Started-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]].
5. Для максимальной точности переходить в соответствующую англоязычную страницу.
Такой подход позволяет пользоваться русской wiki как полноценным слоем документации, но при этом при желании всегда уходить в англоязычный source of truth.
Такой подход позволяет пользоваться русской wiki как полноценным слоем документации, но при этом при желании всегда уходить в англоязычный основной источник.
+26 -26
@@ -2,17 +2,17 @@
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;
- callback button хранит `CallbackData` с command name и args;
- payload может кодироваться как JSON или Base64;
- у bot есть default payload type;
- конкретная keyboard может переопределить его локально.
- `InlineKeyboard` строит inline-клавиатуру ряд за рядом;
- callback-кнопка хранит `CallbackData` с именем команды и аргументами;
- данные callback могут кодироваться как JSON или Base64;
- у бота есть тип данных callback по умолчанию;
- конкретная клавиатура может переопределить его локально.
## Как строить keyboard
## Как строить клавиатуру
Основные конструкторы:
- `NewInlineKeyboardJson(maxRow)`
@@ -30,46 +30,46 @@ kb := laniakea.NewInlineKeyboardJson(2).
`maxRow` определяет, сколько кнопок автоматически помещается в один ряд.
## Что такое payload
## Что такое данные callback
Callback payload логически содержит:
- command name;
- список string arguments.
Данные callback логически содержат:
- имя команды;
- список строковых аргументов.
Ты обычно не собираешь JSON вручную. Вместо этого используешь:
- `AddCallbackButton(...)`
- `AddCallbackButtonStyle(...)`
- `NewCallbackData(...)`
Все аргументы через `fmt.Sprint` превращаются в строки, поэтому в handler'е ты читаешь их через `ctx.Args`.
Все аргументы через `fmt.Sprint` превращаются в строки, поэтому в обработчике ты читаешь их через `ctx.Args`.
## `ctx.Args`, а не `ctx.Payload`
В текущем API нет отдельного `ctx.Payload`.
В payload handler'е decoded args доступны через:
В обработчике данных callback разобранные аргументы доступны через:
- `ctx.Args`
А выбор handler'а происходит по имени payload command.
А выбор обработчика происходит по имени callback-команды.
## JSON vs Base64
`BotPayloadJson`:
- удобнее читать в логах и тестах;
- проще дебажить.
- проще отлаживать.
`BotPayloadBase64`:
- более компактный transport form;
- более компактная транспортная форма;
- выглядит более “непрозрачно” в callback data.
Логическая структура payload при этом одна и та же.
Логическая структура данных callback при этом одна и та же.
## Strict vs tolerant decoding
## Строгое и терпимое декодирование
По умолчанию bot tolerant:
- если основной decoder не сработал, может попробовать второй формат.
По умолчанию бот работает в терпимом режиме:
- если основной декодер не сработал, может попробовать второй формат.
Strict mode отключает этот fallback:
Строгий режим отключает этот запасной вариант:
```go
bot.SetStrictPayloadType(true)
@@ -77,24 +77,24 @@ bot.SetStrictPayloadType(true)
или через `BotOpts`.
Это полезно, если payload-format drift нужно считать настоящей ошибкой.
Это полезно, если расхождение формата данных callback нужно считать настоящей ошибкой.
## Keyboard-local override
## Локальное переопределение для клавиатуры
Даже если у bot есть default payload type, keyboard можно переопределить локально:
Даже если у бота есть тип данных callback по умолчанию, клавиатуру можно переопределить локально:
```go
kb := ctx.NewInlineKeyboard(2).
SetPayloadType(laniakea.BotPayloadJson)
```
Это удобно для migration или debugging-сценариев.
Это удобно для сценариев миграции или отладки.
## `KeyboardLong(...)`
Если текст длинный, используй `KeyboardLong(...)`.
Keyboard прикрепляется только к последнему chunk, чтобы не дублировать кнопки на каждой части.
Клавиатура прикрепляется только к последней части, чтобы не дублировать кнопки на каждом фрагменте.
## Что читать дальше
+2 -2
@@ -56,11 +56,11 @@ bot.AddL10n(l10n)
- `bot.L10n(lang, key)`
- `ctx.Translate(key)`
Если вызвать `AddL10n(nil)`, bot залогирует warning и оставит текущий provider без изменений.
Если вызвать `AddL10n(nil)`, бот залогирует предупреждение и оставит текущий provider без изменений.
## `ctx.Translate(...)`
Это основной ergonomic helper внутри handler'ов.
Это основной удобный вспомогательный метод внутри обработчиков.
Он:
- берет language code из `ctx.From`, если он есть;
+32 -32
@@ -2,30 +2,30 @@
English version: [[Logging]]
Это краткая русскоязычная версия страницы про logging. Полная и наиболее актуальная страница: [[Logging]].
Это краткая русскоязычная версия страницы про логирование. Полная и наиболее актуальная страница: [[Logging]].
## Какие logger layers есть
## Какие уровни логирования есть
У 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(...)`.
Используется для:
- startup/shutdown сообщений;
- runner и middleware warnings;
- bot-level operational logs.
- сообщений о запуске и остановке;
- предупреждений от фоновых задач и middleware;
- операционных логов уровня бота.
Получить можно через:
@@ -33,36 +33,36 @@ English version: [[Logging]]
logger := bot.GetLogger()
```
## Request logger
## Логгер запросов
Включается через `BotOpts.UseRequestLogger`.
Он логирует raw updates после успешного `getUpdates`.
Он логирует сырые обновления после успешного `getUpdates`.
Это особенно полезно для:
- debugging routing;
- отладки маршрутизации;
- изучения реальной формы Telegram updates;
- подготовки тестовых samples.
- подготовки тестовых примеров.
## Plugin loggers и `ctx.Logger`
## Логгеры плагинов и `ctx.Logger`
У plugin может быть свой logger.
У плагина может быть свой логгер.
Если он есть, в handler'е `ctx.Logger` обычно указывает именно на него.
Если нет, `ctx.Logger` падает обратно на bot logger.
Если нет, `ctx.Logger` переключается обратно на логгер бота.
Из-за этого внутри handler'ов чаще всего правильнее использовать именно `ctx.Logger`.
Из-за этого внутри обработчиков чаще всего правильнее использовать именно `ctx.Logger`.
## Debug mode
`Bot.Debug(true)` или `BotOpts.Debug`:
- поднимает уровень логирования;
- влияет на bot logger;
- влияет на request logger;
- влияет на уже зарегистрированные plugin loggers.
- влияет на логгер запросов;
- влияет на уже зарегистрированные логгеры плагинов.
## File logging
## Логирование в файл
Если включить:
- `WriteToFile`
@@ -72,21 +72,21 @@ logger := bot.GetLogger()
- `main.log`
- `requests.log`
Если создание file logger не удалось, bot не падает, а остается на stdout logging.
Если создание файлового логгера не удалось, бот не падает, а остается на логировании в stdout.
## `AddDatabaseLoggerWriter(...)`
Этот метод позволяет прикрепить writer, полученный из DB/shared context, сразу к нескольким logger layers.
Этот метод позволяет прикрепить writer, полученный из DB/shared context, сразу к нескольким уровням логирования.
Он добавляется в:
- bot logger;
- request logger;
- API/uploader loggers;
- уже зарегистрированные plugin loggers.
- логгер бота;
- логгер запросов;
- логгеры API/uploader;
- уже зарегистрированные логгеры плагинов.
Важно:
- сначала должен быть задан `DatabaseContext(...)`;
- если хочешь, чтобы plugin loggers точно получили writer, вызывай метод после `AddPlugins(...)`.
- если хочешь, чтобы логгеры плагинов точно получили writer, вызывай метод после `AddPlugins(...)`.
## Что читать дальше
+39 -39
@@ -6,11 +6,11 @@ English version: [[Middleware]]
## Зачем нужен middleware
Middleware позволяет запускать логику до command handlers, payload handlers и update handlers.
Middleware позволяет запускать логику до обработчиков команд, данных callback и обновлений.
Это главное место для cross-cutting concerns:
- auth checks;
- request logging;
Это главное место для сквозной логики:
- проверки доступа;
- журналирование запросов;
- feature flags;
- простая подготовка контекста;
- side effects вроде analytics.
@@ -18,27 +18,27 @@ Middleware позволяет запускать логику до command handl
## Уровни middleware
Есть три уровня:
- bot-level через `Bot.AddMiddleware(...)`;
- plugin-level через `Plugin.AddMiddleware(...)`;
- command/payload-level через `Command.Use(...)`.
- на уровне бота через `Bot.AddMiddleware(...)`;
- на уровне плагина через `Plugin.AddMiddleware(...)`;
- на уровне команды или callback через `Command.Use(...)`.
Их удобно понимать так:
- bot-level действует на весь bot;
- plugin-level на один plugin;
- command-level на одну конкретную command или payload.
- уровень бота действует на весь бот;
- уровень плагина на один плагин;
- уровень команды на одну конкретную команду или данные callback.
## Порядок выполнения
Для commands и payloads порядок такой:
1. bot middleware;
2. plugin middleware;
3. command/payload middleware;
4. final handler.
Для команд и данных callback порядок такой:
1. middleware бота;
2. middleware плагина;
3. middleware команды или callback;
4. финальный обработчик.
Для update handlers:
1. bot middleware;
2. plugin middleware для каждого подходящего plugin;
3. update handler.
Для обработчиков обновлений:
1. middleware бота;
2. middleware плагина для каждого подходящего плагина;
3. обработчик обновления.
## Synchronous middleware
@@ -53,53 +53,53 @@ func(ctx *laniakea.MsgContext, db T) bool
- `false` — остановить текущую цепочку.
Это правильный вариант для:
- access control;
- required validation;
- rate limiting gates;
- любой логики, которая должна уметь реально остановить handler path.
- контроля доступа;
- обязательной валидации;
- ограничителей частоты;
- любой логики, которая должна уметь реально остановить цепочку обработки.
## Asynchronous middleware
Можно включить `SetAsync(true)`.
Но это меняет semantics:
Но это меняет семантику:
- middleware идет в goroutine;
- выполнение цепочки продолжается сразу;
- `bool` return value игнорируется;
- возвращаемое значение `bool` игнорируется;
- middleware получает копию `MsgContext`.
Поэтому async middleware подходит только для:
- telemetry;
- audit logs;
- fire-and-forget notifications;
- телеметрии;
- аудиторских логов;
- уведомлений без ожидания результата;
- best-effort side effects.
Он не подходит для:
- auth checks;
- required validation;
- логики, которая должна переписать `ctx` и повлиять на handler.
- проверок доступа;
- обязательной валидации;
- логики, которая должна переписать `ctx` и повлиять на обработчик.
## Почему async middleware получает копию `MsgContext`
Так библиотека избегает очевидных data races между goroutine middleware и основной handler chain.
Так библиотека избегает очевидных гонок данных между goroutine middleware и основной цепочкой обработки.
Практический вывод:
- изменения `ctx` внутри async middleware не становятся canonical context для handler'а;
- async middleware не может надежно остановить flow.
- изменения `ctx` внутри async middleware не становятся каноническим контекстом для обработчика;
- async middleware не может надежно остановить поток выполнения.
## Ordering
## Порядок
Только bot-level middleware имеет explicit sorting:
Только middleware уровня бота имеет явную сортировку:
- сначала по `order`;
- потом по `name`.
Plugin-level и command-level middleware сохраняют insertion order.
Middleware уровня плагина и команды сохраняют порядок добавления.
## Частые ошибки
- Использовать async middleware для блокировки доступа.
- Надеяться, что async middleware сможет изменить `ctx.Text` для handler'а.
- Настраивать plugin middleware после `AddPlugins(...)`.
- Надеяться, что async middleware сможет изменить `ctx.Text` для обработчика.
- Настраивать middleware плагина после `AddPlugins(...)`.
- Оставлять middleware без внятного имени.
## Что читать дальше
+14 -14
@@ -17,51 +17,51 @@ English version: [[Migration]]
## Что важно помнить при апгрейде
- Сначала прочитай `CHANGELOG.md`.
- Потом проверь runtime model, если затрагивались `Bot`, plugins или handlers.
- После этого отдельно пройди по payloads, drafts и command generation, если используешь эти части API.
- Потом проверь модель выполнения, если затрагивались `Bot`, плагины или обработчики.
- После этого отдельно пройди по данным callback, черновикам и генерации команд, если используешь эти части API.
## Ключевые изменения последних `rc`
### `rc.12`
Главный смысл релиза:
- handlers стали возвращать `error`;
- появились long reply helpers;
- появился strict payload mode.
- обработчики стали возвращать `error`;
- появились вспомогательные методы для длинных ответов;
- появился строгий режим обработки данных callback.
Что обычно нужно менять:
- перевести handlers на `error`-return signature;
- перевести обработчики на сигнатуру с возвратом `error`;
- если длинные ответы могли выходить за Telegram limit, перейти на `AnswerLong(...)` или `KeyboardLong(...)`;
- если payload policy важна, решить, нужен ли strict mode.
- если политика данных callback важна, решить, нужен ли строгий режим.
### `rc.10`
Главные изменения:
- `NewBot[T](opts)` теперь возвращает `(*Bot[T], error)`;
- `Run()` и `RunWithContext(...)` возвращают `error`;
- `AddPlugins(...)` стал configuration snapshot point.
- `AddPlugins(...)` стал точкой фиксации конфигурации.
Что обычно нужно менять:
- добавить error handling после `NewBot`, `Run`, `RunWithContext`;
- перестать мутировать plugins после регистрации.
- перестать менять плагины после регистрации.
### `rc.7`
На этой стадии заметно укрепился plugin lifecycle:
На этой стадии заметно укрепился жизненный цикл плагинов:
- logger APIs;
- shutdown hooks;
- хуки остановки;
- `Plugin.Close()`.
### `rc.4`
Ранние изменения в основном затрагивали общую структуру API и helper surface.
Ранние изменения в основном затрагивали общую структуру API и набор вспомогательных методов.
## Практическая стратегия миграции
1. Сначала собери проект.
2. Исправь signature-level ошибки.
3. Проверь runtime behavior: startup, shutdown, plugins.
4. Отдельно прогоняй callbacks, middleware и long replies.
3. Проверь поведение во время выполнения: запуск, остановку, плагины.
4. Отдельно прогоняй callbacks, middleware и длинные ответы.
5. Добавь regression tests под найденные переломы.
## Что читать дальше
+22 -22
@@ -6,17 +6,17 @@ English version: [[MsgContext]]
## Что такое `MsgContext`
`MsgContext` — это runtime object, который приходит в:
- command handlers;
- payload handlers;
`MsgContext` — это объект времени выполнения, который приходит в:
- обработчики команд;
- обработчики данных callback;
- middleware;
- update handlers.
- обработчики обновлений.
Через него ты получаешь:
- incoming update;
- входящее обновление;
- текущее сообщение и отправителя;
- parsed command/payload args;
- helpers для reply, edit, delete, callback, drafts и localization.
- разобранные аргументы команд и callback;
- вспомогательные методы для reply, edit, delete, callback, drafts и localization.
## Поля, которые используются чаще всего
@@ -26,12 +26,12 @@ English version: [[MsgContext]]
Пример:
- вход: `/echo hello world`
- command: `echo`
- команда: `echo`
- `ctx.Text == "hello world"`
### `Args`
`ctx.Args`tokenized версия `ctx.Text`.
`ctx.Args`разбитая на токены версия `ctx.Text`.
Пример:
- `ctx.Text == "hello world"`
@@ -52,17 +52,17 @@ English version: [[MsgContext]]
`ctx.FromID` — тот же ID, но уже вынесенный для удобства.
## Основные helper methods
## Основные вспомогательные методы
### `Answer(...)`
Базовый helper для обычного текстового ответа.
Базовый вспомогательный метод для обычного текстового ответа.
### `AnswerLong(...)`
Используется, когда текст может превысить Telegram message limit.
Это отдельный API специально для явной multi-message semantics.
Это отдельный API специально для явной семантики многочастного ответа.
### `Keyboard(...)`
@@ -70,9 +70,9 @@ English version: [[MsgContext]]
### `KeyboardLong(...)`
Подходит для длинного текста, где keyboard должен остаться на последнем chunk.
Подходит для длинного текста, где клавиатура должна остаться на последней части.
## Markdown helpers
## Markdown-вспомогательные методы
Есть `...Markdown` варианты:
- `AnswerMarkdown(...)`
@@ -82,18 +82,18 @@ English version: [[MsgContext]]
Важно:
- пользовательский ввод нужно экранировать через `EscapeMarkdownV2(...)`.
## Edit и delete helpers
## Вспомогательные методы для edit и delete
Если у тебя уже есть `AnswerMessage`, его можно:
- редактировать;
- удалять;
- менять caption.
Это удобно для progressive UX вроде “Working... -> Done”.
Это удобно для пошагового UX вроде “Working... -> Done”.
## Callback-specific helpers
## Callback-специфичные вспомогательные методы
В callback flow особенно полезны:
В потоке callback особенно полезны:
- `EditCallback(...)`
- `AnswerCbQuery()`
- `AnswerCbQueryText(...)`
@@ -101,7 +101,7 @@ English version: [[MsgContext]]
- `AnswerCbQueryUrl(...)`
- `CallbackDelete()`
Они покрывают most common callback UX без ручного хождения в `tgapi`.
Они покрывают самые частые callback-сценарии без ручного хождения в `tgapi`.
## Drafts и localization
@@ -110,17 +110,17 @@ English version: [[MsgContext]]
- `NewDraftMarkdown()`
- `Translate(key)`
Это делает `MsgContext` основным ergonomic surface почти для всего handler-time кода.
Это делает `MsgContext` основной удобной точкой доступа почти для всего кода обработчиков.
## `NewInlineKeyboard(...)`
Внутри handler'ов keyboard обычно удобнее всего строить так:
Внутри обработчиков keyboard обычно удобнее всего строить так:
```go
kb := ctx.NewInlineKeyboard(2)
```
Этот builder автоматически наследует текущую payload policy context'а.
Этот builder автоматически наследует текущую политику данных callback из контекста.
## Что читать дальше
+1 -1
@@ -48,7 +48,7 @@ English version: [[Page-Priority]]
При изменении кода сначала проверяй:
1. страницы core API;
2. страницы по runtime behavior;
2. страницы про поведение во время выполнения;
3. advanced и maintenance pages.
## Что читать дальше
+15 -15
@@ -2,26 +2,26 @@
English version: [[Rate-Limiting]]
Это краткая русскоязычная версия страницы про rate limiting. Полная и наиболее актуальная страница: [[Rate-Limiting]].
Это краткая русскоязычная версия страницы про ограничение частоты. Полная и наиболее актуальная страница: [[Rate-Limiting]].
## Где действует limiter
Rate limiting в Laniakea относится к Telegram API client layer.
Ограничение частоты в Laniakea относится к слою Telegram API client.
Он влияет на исходящие запросы бота и помогает:
- не превысить Telegram limits;
- переживать bursts;
- переживать всплески;
- корректно обрабатывать `retry_after`.
## Основные режимы
Есть два общих режима поведения:
- waiting mode;
- drop mode.
- режим ожидания;
- режим сброса.
В waiting mode запросы ждут своей очереди.
В режиме ожидания запросы ждут своей очереди.
В drop mode overflow можно отклонять сразу, чтобы сохранить отзывчивость под нагрузкой.
В режиме сброса лишние запросы можно отклонять сразу, чтобы сохранить отзывчивость под нагрузкой.
## Ключевые настройки
@@ -29,12 +29,12 @@ Rate limiting в Laniakea относится к Telegram API client layer.
- `RateLimit`
- `DropRLOverflow`
Это задает общий policy для API client'а, который bot строит внутри `NewBot(...)`.
Это задает общую политику для API client, который бот строит внутри `NewBot(...)`.
## `retry_after`
Если Telegram отвечает `429 retry_after`, библиотека:
- обновляет limiter state;
- обновляет состояние limiter;
- ждет нужное время;
- повторяет запрос, если context не был отменен.
@@ -42,25 +42,25 @@ Rate limiting в Laniakea относится к Telegram API client layer.
## 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` иногда будет нормальной частью жизни бота.
## Что читать дальше
+11 -11
@@ -12,13 +12,13 @@ English version: [[Recipes]]
### Admin-only command
Используй plugin middleware или command middleware, если команда должна быть доступна только ограниченной группе пользователей.
Используй middleware плагина или middleware команды, если команда должна быть доступна только ограниченной группе пользователей.
### Callback flow
### Поток callback
Комбинируй:
- `NewInlineKeyboard(...)`
- payload handler;
- обработчик данных callback;
- `AnswerCbQuery...`;
- `EditCallback(...)`
@@ -30,29 +30,29 @@ English version: [[Recipes]]
Подключи `L10n` к bot и используй `ctx.Translate(...)` внутри handler'ов.
### Non-command update handler
### Обработчик обновлений вне команд
Для `inline_query`, `poll`, `chat_member` и других update types используй `AddUpdateHandler(...)`.
### Draft-based flow
### Поток на основе черновиков
Если ответ строится постепенно, начни с `ctx.NewDraft()`.
### 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 хороши как стартовые шаблоны, но не заменяют более подробные страницы про:
- lifecycle;
- жизненный цикл;
- middleware;
- payload model;
- testing.
- модель данных callback;
- тестирование.
## Что читать дальше
+26 -26
@@ -2,17 +2,17 @@
English version: [[Runners]]
Это краткая русскоязычная версия страницы про runners. Полная и наиболее актуальная страница: [[Runners]].
Это краткая русскоязычная версия страницы про фоновые задачи. Полная и наиболее актуальная страница: [[Runners]].
## Что такое runner
Runner — это background или one-time task, который живет рядом с bot runtime, но не относится к конкретному update handler.
Runner — это фоновая или одноразовая задача, которая живет рядом с механизмом выполнения бота, но не относится к конкретному обработчику обновлений.
Типичные use cases:
- cleanup jobs;
- maintenance tasks;
Типичные сценарии:
- задачи очистки;
- служебные задачи обслуживания;
- health checks;
- startup warmups.
- подготовка при запуске.
## Как создается runner
@@ -22,26 +22,26 @@ Runner — это background или one-time task, который живет р
runner := laniakea.NewRunner("cleanup", fn)
```
Потом конфигурируются builder methods:
Потом конфигурируются методы builder:
- `Onetime(bool)`
- `Async(bool)`
- `Timeout(duration)`
## Основные режимы
### One-time sync
### Одноразовый sync
- выполняется один раз;
- блокирует startup;
- полезен для startup-critical работы.
- блокирует запуск;
- полезен для работы, критичной на старте.
### One-time async
### Одноразовый async
- выполняется один раз;
- стартует в goroutine;
- не блокирует startup.
- не блокирует запуск.
### Repeating async
### Повторяющийся async
- работает циклически;
- использует 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.
- Для startup-critical work используй one-time sync runner.
- Не держи сложную бизнес-логику внутри runner body; лучше делегируй ее в обычные сервисы приложения.
- Для периодических задач используй повторяющийся async runner с timeout.
- Для критичной стартовой работы используй одноразовый sync runner.
- Не держи сложную бизнес-логику внутри runner; лучше делегируй ее в обычные сервисы приложения.
## Что читать дальше
+90 -168
@@ -1,53 +1,17 @@
# 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 хорошо подходят как точки входа, но их недостаточно для многошаговых и модальных сценариев.
- Реальным ботам часто нужен режим "оставайся в этом состоянии, пока пользователь явно не выйдет".
- У фреймворка уже есть подходящие строительные блоки: `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 сценариев.
## Текущие типы
## Основной API
```go
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
plugin.NewScene("rp").
plugin.NewScene("signup").
SetScope(laniakea.SceneScopeUserChat).
SetEntry("chat").
OnMessage(handleRPMessage).
OnCommand("rpstop", stopRP)
SetEntry("ask_name").
OnStep("ask_name", askName).
OnStep("confirm", confirmSignup).
OnCommand("cancel", cancelSignup).
OnMessage(fallbackMessage)
```
`SetEntry(...)` задаёт начальный step или state сцены. `ctx.EnterScene("rp")` теперь валидирует, что entry-step задан и зарегистрирован, до того как создать сессию.
`SetEntry(...)` обязателен для `ctx.EnterScene(...)`. Если нужен явный старт с другого шага, используйте `ctx.EnterSceneStep(...)`.
Так plugin API остаётся визуально согласованным:
## Модель обработчиков
- `NewCommand(...)`
- `NewPayload(...)`
- `AddUpdateHandler(...)`
- `NewScene(...)`
## Формы сцен
Текущий скелет уже покрывает две самые частые формы.
- Пошаговые сцены: именованный step обрабатывает каждый update и выбирает следующий step.
- Модальные сцены: долгоживущий "режим" обрабатывает обычные сообщения, пока пользователь явно не выйдет.
- Обе формы должны уметь регистрировать локальные команды сцены.
Пример пошагового flow:
Обычные команды используют `*MsgContext`. Обработчики сцен используют `*SceneContext`.
```go
plugin.NewScene("profile").
SetScope(laniakea.SceneScopeUserChat).
SetEntry("name").
OnStep("name", askName).
OnStep("confirm", confirmProfile)
type SceneHandler[T any] func(ctx *SceneContext, db T) (SceneResult, error)
```
Пример модального flow:
```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`:
`SceneContext` встраивает `*MsgContext` и добавляет вспомогательные методы для сцен:
- `ctx.Stay()`
- `ctx.Next(step)`
@@ -147,50 +82,32 @@ type SceneResult struct {
- `ctx.BindData(&dst)`
- `ctx.SaveData(src)`
## Сигнатуры handlers
Обработчик сцены возвращает `SceneResult`, который управляет переходом состояния:
Обычная команда входа:
- `Stay`: оставить ту же сцену и тот же шаг.
- `Next(step)`: перейти на другой зарегистрированный шаг.
- `Exit`: удалить текущую сессию.
- `Pass`: не менять текущую сессию и продолжить обычную маршрутизацию.
```go
func startRP(ctx *laniakea.MsgContext, db *App) error {
return ctx.EnterScene("rp")
}
```
`SceneActionPass` намеренно ничего не делает с состоянием сцены. Если обработчик вызвал `SaveData(...)`, а потом вернул `Pass`, эти данные не сохраняются.
Scene message handler:
## Порядок маршрутизации
```go
func handleRPMessage(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
if ctx.Text == "" {
return ctx.Pass(), nil
}
Пока сессия сцены активна, маршрутизация работает так:
// Передать сообщение ИИ-агенту и остаться внутри сцены.
return ctx.Stay(), db.ReplyFromAgent(ctx.Context(), ctx.Text)
}
```
1. Сначала выполняются middleware бота.
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` уже даёт JSON-backed helper-методы поверх него.
Текущий helper API:
- `ctx.BindData(&dst)`
- `ctx.SaveData(src)`
Пример:
Используйте `SceneSession.Data` через `SceneContext.BindData(...)` и `SaveData(...)`, если только вы не пишете собственную логику хранения.
```go
type ProfileDraft struct {
@@ -200,7 +117,9 @@ type ProfileDraft struct {
func askName(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
var draft ProfileDraft
_ = ctx.BindData(&draft)
if err := ctx.BindData(&draft); err != nil {
return laniakea.SceneResult{}, err
}
draft.Name = ctx.Text
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.
2. Вычислить session key на основе scope сцены и текущего update.
3. Спросить `SessionStore`, есть ли активная сцена.
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`, продолжить обычную маршрутизацию.
```go
func startSignup(ctx *laniakea.MsgContext, db *App) error {
return ctx.EnterScene("signup")
}
```
## Что уже реализовано
Обработчик шага:
- `Plugin.NewScene(...)` и `Plugin.AddScene(...)`.
- `Scene.SetScope(...)`, `SetEntry(...)`, `OnStep(...)`, `OnCommand(...)` и `OnMessage(...)`.
- `MsgContext.EnterScene(...)`, `EnterSceneStep(...)` и `ExitScene(...)`.
- `SceneContext.Stay()`, `Next(...)`, `Exit()`, `Pass()`, `BindData(...)` и `SaveData(...)`.
- `SessionStore` плюс дефолтный `MemorySessionStore`.
- Маршрутизация активной сцены до обычного command flow.
```go
func askName(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
if ctx.Text == "" {
ctx.Answer("Как тебя зовут?")
return ctx.Stay(), nil
}
## Что ещё не реализовано или намеренно отложено
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]]
- [[MsgContext]]
- [[Middleware]]
- [[Bot-Lifecycle]]
- [[Commands-and-Plugins-RU]]
- [[MsgContext-RU]]
- [[Middleware-RU]]
- [[Bot-Lifecycle-RU]]
+85 -163
@@ -1,53 +1,17 @@
# 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.
- 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
## Core API
```go
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.
```go
plugin.NewScene("rp").
plugin.NewScene("signup").
SetScope(laniakea.SceneScopeUserChat).
SetEntry("chat").
OnMessage(handleRPMessage).
OnCommand("rpstop", stopRP)
SetEntry("ask_name").
OnStep("ask_name", askName).
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(...)`
- `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:
Normal commands use `*MsgContext`. Scene handlers use `*SceneContext`.
```go
plugin.NewScene("profile").
SetScope(laniakea.SceneScopeUserChat).
SetEntry("name").
OnStep("name", askName).
OnStep("confirm", confirmProfile)
type SceneHandler[T any] func(ctx *SceneContext, db T) (SceneResult, error)
```
Modal flow example:
```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`:
`SceneContext` embeds `*MsgContext` and adds scene helpers:
- `ctx.Stay()`
- `ctx.Next(step)`
@@ -147,50 +82,32 @@ Current helper methods on `SceneContext`:
- `ctx.BindData(&dst)`
- `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
func startRP(ctx *laniakea.MsgContext, db *App) error {
return ctx.EnterScene("rp")
}
```
`SceneActionPass` is intentionally a no-op for scene state. If a handler calls `SaveData(...)` and then returns `Pass`, that state is not persisted.
Scene message handler:
## Routing order
```go
func handleRPMessage(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
if ctx.Text == "" {
return ctx.Pass(), nil
}
While a scene session is active, routing works like this:
// Send the message to the AI agent and stay inside the scene.
return ctx.Stay(), db.ReplyFromAgent(ctx.Context(), ctx.Text)
}
```
1. Bot middleware runs first.
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:
```go
func stopRP(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
ctx.Answer("RP mode disabled")
return ctx.Exit(), nil
}
```
The scene text comes from message text or caption. Scenes are therefore designed around message-based flows.
## 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.
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:
Use `SceneSession.Data` only through `SceneContext.BindData(...)` and `SaveData(...)` unless you are implementing custom store behavior.
```go
type ProfileDraft struct {
@@ -200,7 +117,9 @@ type ProfileDraft struct {
func askName(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
var draft ProfileDraft
_ = ctx.BindData(&draft)
if err := ctx.BindData(&draft); err != nil {
return laniakea.SceneResult{}, err
}
draft.Name = ctx.Text
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.
2. Compute the session key from scene scope and the current update.
3. Ask `SessionStore` whether an active scene exists.
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.
```go
func startSignup(ctx *laniakea.MsgContext, db *App) error {
return ctx.EnterScene("signup")
}
```
## Implemented today
Step handler:
- `Plugin.NewScene(...)` and `Plugin.AddScene(...)`.
- `Scene.SetScope(...)`, `SetEntry(...)`, `OnStep(...)`, `OnCommand(...)`, and `OnMessage(...)`.
- `MsgContext.EnterScene(...)`, `EnterSceneStep(...)`, and `ExitScene(...)`.
- `SceneContext.Stay()`, `Next(...)`, `Exit()`, `Pass()`, `BindData(...)`, and `SaveData(...)`.
- `SessionStore` plus the default `MemorySessionStore`.
- Active-scene routing before normal command flow.
```go
func askName(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
if ctx.Text == "" {
ctx.Answer("What is your name?")
return ctx.Stay(), nil
}
## Still missing or intentionally deferred
ctx.Answer("Thanks.")
return ctx.Next("confirm"), nil
}
```
- Scene-local payload routing is not implemented yet.
- 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.
Scene-local command:
## 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.
- Decide whether scene-local payloads belong in the first stable scene release.
- Decide whether any additional public inspection API is actually needed.
## Deliberate limits of the current model
- 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:
+18 -18
@@ -2,40 +2,40 @@
English version: [[Semver-and-Releases]]
Это краткая русскоязычная версия страницы про semver и release policy. Полная и наиболее актуальная страница: [[Semver-and-Releases]].
Это краткая русскоязычная версия страницы про semver и политику релизов. Полная и наиболее актуальная страница: [[Semver-and-Releases]].
## Зачем нужна эта страница
Она объясняет, как в проекте понимать:
- что считается public API;
- что считается breaking change;
- как выбирать target version;
- что считается публичным API;
- что считается ломающим изменением;
- как выбирать целевую версию;
- как changelog и version file должны соотноситься.
## Что считается public API
## Что считается публичным API
Обычно сюда входят:
- exported types и methods;
- helper methods вроде `AnswerLong(...)`;
- вспомогательные методы вроде `AnswerLong(...)`;
- behavior, на который пользователи библиотеки разумно опираются;
- documented runtime semantics.
- документированная семантика времени выполнения.
## Что считается breaking change
## Что считается ломающим изменением
Breaking change — это не только удаление функции.
Ломающее изменение — это не только удаление функции.
Сюда же относятся:
- несовместимые signature changes;
- изменение runtime behavior, которое ломает существующий код;
- удаление или переименование публичных helper methods;
- изменение documented semantics без совместимого fallback.
- несовместимые изменения сигнатур;
- изменение поведения во время выполнения, которое ломает существующий код;
- удаление или переименование публичных вспомогательных методов;
- изменение документированной семантики без совместимого запасного варианта.
## Как выбирать версию
Логика обычная semver:
- patch для совместимых fixes;
- minor для совместимых additions;
- major для breaking changes.
- patch для совместимых исправлений;
- minor для совместимых расширений;
- major для ломающих изменений.
Release candidates дополнительно обозначают нестабильную стадию развития API.
@@ -47,8 +47,8 @@ Wiki-only изменения в `.wiki` в основной `CHANGELOG.md` не
## Практический вывод
- Не делай breaking changes без осознанного version decision.
- Сначала определяй target version, потом меняй API.
- Не делай ломающих изменений без осознанного решения по версии.
- Сначала определяй целевую версию, потом меняй API.
- Поддерживай changelog, tags и version file согласованными.
## Что читать дальше
+25 -25
@@ -10,55 +10,55 @@ Laniakea хорошо тестируется обычными Go unit tests.
В репозитории уже используются паттерны вроде:
- fake HTTP transport для `tgapi`;
- direct tests для `MsgContext` helpers;
- прямые тесты для вспомогательных методов `MsgContext`;
- routing tests для commands и payloads;
- runner tests.
## Что стоит тестировать в первую очередь
- public handler flows;
- argument validation;
- callbacks и payload decoding;
- middleware behavior;
- long replies;
- startup/shutdown semantics;
- migration-sensitive regressions.
- валидацию аргументов;
- callbacks и декодирование данных callback;
- поведение middleware;
- длинные ответы;
- семантику запуска и остановки;
- регрессии, чувствительные к миграции.
## Тесты для `tgapi`
Для low-level API удобно подменять `http.Client` transport и проверять:
- request body;
- method name;
- response parsing;
- retry/error behavior.
Для низкоуровневого API удобно подменять `http.Client` transport и проверять:
- тело запроса;
- имя метода;
- разбор ответа;
- поведение retry и ошибок.
## Тесты для handler logic
## Тесты для логики обработчиков
Для handler-level тестов обычно полезно:
Для тестов уровня обработчика обычно полезно:
- собрать `MsgContext`;
- вызвать handler напрямую;
- проверить side effects и ответы.
- вызвать обработчик напрямую;
- проверить побочные эффекты и ответы.
## Тесты для routing
Отдельно полезно тестировать:
- command matching;
- payload matching;
- middleware order;
- изоляцию контекста между plugin chains.
- сопоставление команд;
- сопоставление данных callback;
- порядок middleware;
- изоляцию контекста между цепочками плагинов.
## Тесты для runners
## Тесты для фоновых задач
Для runners важно проверить:
Для фоновых задач важно проверить:
- какие режимы реально запускаются;
- какие конфигурации скипаются;
- как ведет себя shutdown.
- как ведет себя остановка.
## Практические советы
- Предпочитай table-driven tests там, где много сценариев.
- Добавляй regression tests на найденные bugs.
- Не ограничивайся только happy path.
- Добавляй regression tests на найденные ошибки.
- Не ограничивайся только успешным сценарием.
## Что читать дальше
+12 -12
@@ -6,7 +6,7 @@ English version: [[tgapi-Overview]]
## Что такое `tgapi`
`tgapi` — это low-level Telegram Bot API layer под высокоуровневым runtime Laniakea.
`tgapi` — это низкоуровневый слой Telegram Bot API под высокоуровневым механизмом выполнения Laniakea.
Используй его, когда нужен:
- direct access к Telegram methods;
@@ -25,12 +25,12 @@ English version: [[tgapi-Overview]]
Используй `MsgContext`, когда:
- ты уже внутри handler'а;
- нужен обычный reply/edit/delete/callback flow.
- нужен обычный поток reply/edit/delete/callback.
Используй `tgapi`, когда:
- у `MsgContext` нет нужного helper method;
- ты работаешь вне handler flow;
- нужен lower-level control;
- у `MsgContext` нет нужного вспомогательного метода;
- ты работаешь вне потока обработчика;
- нужен более низкоуровневый контроль;
- нужно работать с uploads/downloads напрямую.
## Typed methods first
@@ -51,14 +51,14 @@ defer api.Close()
- test server;
- custom API URL;
- limiter;
- limiter drop mode.
- режим сброса у limiter.
## `API` и `Uploader`
`API` отвечает за:
- JSON request encoding;
- HTTP execution;
- retry и limiter behavior;
- поведение retry и limiter;
- response decoding.
`Uploader` отвечает за:
@@ -67,19 +67,19 @@ defer api.Close()
## `Close()` важен
У `API` и `Uploader` есть собственный lifecycle.
У `API` и `Uploader` есть собственный жизненный цикл.
Если ты владеешь этими объектами напрямую, их надо закрывать явно.
Если ими владеет `Bot`, это делает `bot.Close()`.
## Downloads и low-level escape hatches
## Downloads и низкоуровневые запасные пути
`tgapi` покрывает и file download flow, и raw request builders.
`tgapi` покрывает и поток загрузки файлов, и сырые конструкторы запросов.
Это полезно, когда typed helper метода еще нет или нужен очень точный контроль.
Это полезно, когда типизированного вспомогательного метода еще нет или нужен очень точный контроль.
Но в day-to-day bot code лучше оставаться на более высоком уровне, если он уже покрывает нужный кейс.
Но в повседневном коде бота лучше оставаться на более высоком уровне, если он уже покрывает нужный кейс.
## Что читать дальше