REPOSITORY / ScuroNeko/Laniakea
Wiki
Refresh wiki runtime and API docs
Add dedicated webhook runtime guides and simplify navigation Merge lifecycle and configuration-freeze material into the main runtime pages Update examples to current imports and NoData/SetAppData/SetL10n APIs
+55
-17
@@ -8,9 +8,9 @@ English version: [[Bot-Lifecycle]]
|
|||||||
|
|
||||||
1. Собрать `BotOpts`.
|
1. Собрать `BotOpts`.
|
||||||
2. Создать `Bot` через `NewBot[T](opts)`.
|
2. Создать `Bot` через `NewBot[T](opts)`.
|
||||||
3. Полностью настроить бот: плагины, middleware, фоновые задачи, политику данных callback, l10n, контекст базы данных.
|
3. Полностью настроить бот: плагины, middleware, фоновые задачи, политику данных callback, l10n и app data.
|
||||||
4. Запустить через `Run()` или `RunWithContext(...)`.
|
4. Запустить через `Run()`, `RunWithContext(...)` или `RunWebHookWithContext(...)`.
|
||||||
5. Остановить выполнение через завершение `Run()` или отмену context.
|
5. Остановить выполнение через завершение runtime или отмену context.
|
||||||
6. Освободить локальные ресурсы через `Close()`.
|
6. Освободить локальные ресурсы через `Close()`.
|
||||||
7. Для следующего запуска создать новый `Bot`.
|
7. Для следующего запуска создать новый `Bot`.
|
||||||
|
|
||||||
@@ -30,19 +30,22 @@ English version: [[Bot-Lifecycle]]
|
|||||||
- токен пустой;
|
- токен пустой;
|
||||||
- Telegram не принимает токен.
|
- Telegram не принимает токен.
|
||||||
|
|
||||||
## Что нужно закончить до `Run`
|
## Что нужно закончить до runtime
|
||||||
|
|
||||||
До запуска обычно нужно завершить:
|
До запуска обычно нужно завершить:
|
||||||
- `DatabaseContext(...)`
|
- `SetAppData(...)`
|
||||||
|
- `AddPrefixes(...)`
|
||||||
|
- `SetPayloadType(...)`
|
||||||
|
- `SetStrictPayloadType(...)`
|
||||||
|
- `SetUpdateTypes(...)` и `AddUpdateType(...)`
|
||||||
- `AddPlugins(...)`
|
- `AddPlugins(...)`
|
||||||
- `AddMiddleware(...)`
|
- `AddMiddleware(...)`
|
||||||
- `AddRunner(...)`
|
- `AddRunner(...)`
|
||||||
- `AddL10n(...)`
|
- `SetL10n(...)`
|
||||||
- `SetPayloadType(...)`
|
|
||||||
- `SetStrictPayloadType(...)`
|
|
||||||
- `SetDraftProvider(...)`
|
- `SetDraftProvider(...)`
|
||||||
|
- `SetSessionStore(...)`
|
||||||
Явную модель фаз конфигурации и фиксации смотри в [[Configuration-Freeze-Model-RU]].
|
- `SetSceneScopePriority(...)`
|
||||||
|
- `SetErrorTemplate(...)`
|
||||||
|
|
||||||
Это важно, потому что механизм выполнения не рассчитан на модель “запустили, а потом продолжаем собирать конфигурацию на лету”.
|
Это важно, потому что механизм выполнения не рассчитан на модель “запустили, а потом продолжаем собирать конфигурацию на лету”.
|
||||||
|
|
||||||
@@ -55,30 +58,61 @@ English version: [[Bot-Lifecycle]]
|
|||||||
- потом регистрируй его;
|
- потом регистрируй его;
|
||||||
- не рассчитывай, что дальнейшая мутация исходного `*Plugin` будет официально поддерживаемой частью API.
|
- не рассчитывай, что дальнейшая мутация исходного `*Plugin` будет официально поддерживаемой частью API.
|
||||||
|
|
||||||
На уровне самого `Bot` конфигурация фиксируется ещё сильнее после начала первого запуска. После этого поздние bot-level конфигурационные вызовы игнорируются. Подробности: [[Configuration-Freeze-Model-RU]].
|
Бот клонирует commands, payload handlers, update handlers, scenes, middleware, logger и `OnClose` callbacks из плагина. После регистрации исходный `*Plugin` уже не является авторитетным источником конфигурации для этого экземпляра `Bot`.
|
||||||
|
|
||||||
## `Run()` и `RunWithContext(...)`
|
## Фазы фиксации конфигурации
|
||||||
|
|
||||||
|
В Laniakea удобно думать о трёх фазах:
|
||||||
|
|
||||||
|
1. Построение и настройка `Bot` после `NewBot[T](opts)`.
|
||||||
|
2. Снимок конфигурации плагина в `AddPlugins(...)`.
|
||||||
|
3. Фиксация bot-level конфигурации после первого `Run()`, `RunWithContext(...)` или `RunWebHookWithContext(...)`.
|
||||||
|
|
||||||
|
Практически это значит:
|
||||||
|
- структуру плагина нужно закончить до `AddPlugins(...)`;
|
||||||
|
- структуру `Bot` нужно закончить до первого запуска runtime;
|
||||||
|
- поздние структурные изменения в `Bot` считаются намеренно игнорируемыми.
|
||||||
|
|
||||||
|
После старта runtime такие методы, как `AddPlugins(...)`, `AddMiddleware(...)`, `AddRunner(...)`, `AddPrefixes(...)`, `SetPayloadType(...)`, `SetStrictPayloadType(...)`, `SetDraftProvider(...)`, `SetSessionStore(...)`, `SetSceneScopePriority(...)`, `SetL10n(...)`, `SetAppData(...)`, `SetUpdateTypes(...)`, `AddUpdateType(...)` и `SetErrorTemplate(...)`, игнорируются.
|
||||||
|
|
||||||
|
## Что значит «игнорируется»
|
||||||
|
|
||||||
|
Это значит, что метод возвращается без изменения структурного runtime-состояния.
|
||||||
|
|
||||||
|
Laniakea предпочитает предсказуемый no-op вместо частичной live-мутации running bot. Это убирает:
|
||||||
|
- неясный порядок между конфигурационными изменениями и обработкой update;
|
||||||
|
- гонки вокруг общего состояния бота;
|
||||||
|
- путаницу в том, влияет ли изменение только на будущие update или ещё и на уже принятую работу;
|
||||||
|
- разные ментальные модели для snapshot-поведения плагинов и bot-level состояния.
|
||||||
|
|
||||||
|
## `Run()`, `RunWithContext(...)` и `RunWebHookWithContext(...)`
|
||||||
|
|
||||||
`Run()` — это короткая форма для простых случаев.
|
`Run()` — это короткая форма для простых случаев.
|
||||||
|
|
||||||
`RunWithContext(...)` — основной production-вариант, потому что он:
|
`RunWithContext(...)` — основной polling-вариант. Он:
|
||||||
- умеет корректно завершаться через `ctx.Done()`;
|
- корректно завершает выполнение через `ctx.Done()`;
|
||||||
- ждет завершения queued updates;
|
- ждет завершения queued updates;
|
||||||
- корректно дожидается фоновых задач.
|
- корректно дожидается фоновых задач.
|
||||||
|
|
||||||
|
`RunWebHookWithContext(...)` — webhook-вариант runtime. Он использует тот же single-use контракт, тот же запуск runners, ту же очередь обновлений и ту же worker-pool обработку.
|
||||||
|
|
||||||
Если bot уже был запущен раньше, повторный запуск вернет `ErrBotAlreadyRun`.
|
Если bot уже был запущен раньше, повторный запуск вернет `ErrBotAlreadyRun`.
|
||||||
|
|
||||||
|
Если ты переводишь уже существующий deployment с webhook-доставки на polling, сначала удали текущий webhook через `CloseWebHook()` или низкоуровневый `tgapi.DeleteWebhook(...)`. Пока webhook не удалён, Telegram продолжает доставлять update через него.
|
||||||
|
|
||||||
|
Для webhook-специфичных опций, транспортного поведения и практических советов смотри [[Webhook-Runtime-RU]].
|
||||||
|
|
||||||
## Что происходит во время выполнения
|
## Что происходит во время выполнения
|
||||||
|
|
||||||
Во время работы бот делает три вещи:
|
Во время работы бот делает три вещи:
|
||||||
- long-polling `getUpdates`;
|
- принимает updates через polling или webhook ingress;
|
||||||
- складывает updates во внутреннюю очередь;
|
- складывает updates во внутреннюю очередь;
|
||||||
- обрабатывает их через worker pool.
|
- обрабатывает их через worker pool.
|
||||||
|
|
||||||
Полезно помнить:
|
Полезно помнить:
|
||||||
- размер worker pool управляется через `MaxWorkers`;
|
- размер worker pool управляется через `MaxWorkers`;
|
||||||
- polling при ошибках использует экспоненциальный backoff;
|
- polling при ошибках использует экспоненциальный backoff;
|
||||||
- после отмены context сначала прекращается polling, потом дренируется очередь, потом дожидаются фоновые задачи.
|
- после отмены context сначала прекращается приём новых updates, потом дренируется очередь, потом дожидаются фоновые задачи.
|
||||||
|
|
||||||
## `Close()` и `CloseRemote()`
|
## `Close()` и `CloseRemote()`
|
||||||
|
|
||||||
@@ -86,6 +120,7 @@ English version: [[Bot-Lifecycle]]
|
|||||||
|
|
||||||
`Close()`:
|
`Close()`:
|
||||||
- закрывает плагины через `Plugin.Close()`;
|
- закрывает плагины через `Plugin.Close()`;
|
||||||
|
- закрывает webhook logger, если его успел инициализировать webhook runtime;
|
||||||
- закрывает uploader;
|
- закрывает uploader;
|
||||||
- закрывает локальный API client;
|
- закрывает локальный API client;
|
||||||
- закрывает логгер запросов и основной логгер.
|
- закрывает логгер запросов и основной логгер.
|
||||||
@@ -96,10 +131,13 @@ English version: [[Bot-Lifecycle]]
|
|||||||
|
|
||||||
Обычно боту нужен именно `Close()`.
|
Обычно боту нужен именно `Close()`.
|
||||||
|
|
||||||
|
`RunWithContext(...)` и `RunWebHookWithContext(...)` не заменяют `Close()`: локальные ресурсы всё равно нужно закрывать отдельно.
|
||||||
|
|
||||||
## Частые ошибки
|
## Частые ошибки
|
||||||
|
|
||||||
- Пытаться повторно использовать тот же `Bot`.
|
- Пытаться повторно использовать тот же `Bot`.
|
||||||
- Забывать `Close()` после завершения `RunWithContext(...)`.
|
- Продолжать менять bot-level конфигурацию после старта runtime.
|
||||||
|
- Забывать `Close()` после завершения runtime.
|
||||||
- Менять плагины после `AddPlugins(...)` и ждать, что бот это гарантированно увидит.
|
- Менять плагины после `AddPlugins(...)` и ждать, что бот это гарантированно увидит.
|
||||||
- Регистрировать repeating runner без timeout.
|
- Регистрировать repeating runner без timeout.
|
||||||
|
|
||||||
|
|||||||
+66
-31
@@ -6,11 +6,11 @@ This page explains how a `Bot` is created, configured, started, stopped, and ret
|
|||||||
|
|
||||||
## Lifecycle at a glance
|
## Lifecycle at a glance
|
||||||
1. Build `BotOpts` and call `NewBot`.
|
1. Build `BotOpts` and call `NewBot`.
|
||||||
2. Configure the bot instance: prefixes, plugins, middleware, runners, localization, payload defaults, and optional database context.
|
2. Configure the bot instance: prefixes, plugins, middleware, runners, localization, payload defaults, and optional app data.
|
||||||
3. Start it with `RunWithContext(ctx)` or `Run()`.
|
3. Start it with `RunWithContext(ctx)`, `Run()`, or `RunWebHookWithContext(...)`.
|
||||||
4. Cancel the context or let `Run()` return.
|
4. Stop runtime by canceling the context or letting the run method return.
|
||||||
5. Call `Close()` to release local resources.
|
5. Call `Close()` to release local resources.
|
||||||
6. Create a new `Bot` if you need to run again.
|
6. Create a new `Bot` if you need another run.
|
||||||
|
|
||||||
## Creating a bot
|
## Creating a bot
|
||||||
|
|
||||||
@@ -28,29 +28,29 @@ Useful defaults applied during construction:
|
|||||||
- the default error template is `"%s"`;
|
- the default error template is `"%s"`;
|
||||||
- a random draft provider is installed unless you replace it.
|
- a random draft provider is installed unless you replace it.
|
||||||
|
|
||||||
## Configuration before `Run`
|
## Configuration before runtime
|
||||||
|
|
||||||
The normal pattern is to finish all structural configuration before starting the bot:
|
The normal pattern is to finish all structural configuration before starting the bot:
|
||||||
|
|
||||||
- `DatabaseContext(...)` injects your shared dependency container or database handle.
|
- `SetAppData(...)` injects your shared dependency container or database handle.
|
||||||
- `AddPrefixes(...)` extends the accepted command prefixes.
|
- `AddPrefixes(...)` extends the accepted command prefixes.
|
||||||
- `SetPayloadType(...)` and `SetStrictPayloadType(...)` control callback payload decoding defaults.
|
- `SetPayloadType(...)` and `SetStrictPayloadType(...)` control callback payload decoding defaults.
|
||||||
- `AddPlugins(...)` registers command, payload, and update handlers.
|
- `SetUpdateTypes(...)` and `AddUpdateType(...)` control which Telegram update kinds are requested.
|
||||||
|
- `AddPlugins(...)` registers command, payload, update, and scene handlers.
|
||||||
- `AddMiddleware(...)` adds bot-level middleware, sorted by order and then by name.
|
- `AddMiddleware(...)` adds bot-level middleware, sorted by order and then by name.
|
||||||
- `AddRunner(...)` registers background or one-time tasks.
|
- `AddRunner(...)` registers background or one-time tasks.
|
||||||
- `AddL10n(...)` replaces the localization provider.
|
- `SetL10n(...)` replaces the localization provider.
|
||||||
- `SetDraftProvider(...)` replaces the default draft ID strategy.
|
- `SetDraftProvider(...)`, `SetSessionStore(...)`, and `SetSceneScopePriority(...)` replace runtime helpers.
|
||||||
- `ErrorTemplate(...)` and `Debug(...)` adjust runtime behavior and logging.
|
- `SetErrorTemplate(...)` adjusts centralized user-facing error text.
|
||||||
|
|
||||||
For an overview of handlers and plugins, see [[Commands-and-Plugins]]. For context helpers available inside handlers, see [[MsgContext]].
|
For an overview of handlers and plugins, see [[Commands-and-Plugins]]. For context helpers available inside handlers, see [[MsgContext]].
|
||||||
For the explicit configuration-phase and freeze-phase rules, see [[Configuration-Freeze-Model]].
|
|
||||||
|
|
||||||
## `AddPlugins` is a configuration commit point
|
## `AddPlugins(...)` is a configuration commit point
|
||||||
|
|
||||||
`Bot.AddPlugins(...)` clones the plugin configuration into the bot. That means the original `*Plugin` should be treated as finished before registration.
|
`Bot.AddPlugins(...)` clones the plugin configuration into the bot. That means the original `*Plugin` should be treated as finished before registration.
|
||||||
|
|
||||||
In practice:
|
In practice:
|
||||||
- add commands, payloads, update handlers, middleware, logger, and `OnClose` callbacks before `AddPlugins`;
|
- add commands, payloads, update handlers, scenes, middleware, logger, and `OnClose` callbacks before `AddPlugins(...)`;
|
||||||
- do not rely on mutating the original plugin after registration;
|
- do not rely on mutating the original plugin after registration;
|
||||||
- later edits to the original plugin may not appear in the bot, because the bot keeps its own internal snapshot.
|
- later edits to the original plugin may not appear in the bot, because the bot keeps its own internal snapshot.
|
||||||
|
|
||||||
@@ -60,21 +60,44 @@ This is especially important for:
|
|||||||
- custom plugin loggers;
|
- custom plugin loggers;
|
||||||
- plugin shutdown hooks.
|
- plugin shutdown hooks.
|
||||||
|
|
||||||
At the bot level, configuration freezes even further once the first run begins. After that point, late bot-level configuration calls are ignored. See [[Configuration-Freeze-Model]].
|
## Configuration freeze phases
|
||||||
|
|
||||||
|
Laniakea has three practical configuration phases:
|
||||||
|
|
||||||
|
1. Construction and bot setup after `NewBot[T](opts)`.
|
||||||
|
2. Plugin snapshotting at `AddPlugins(...)`.
|
||||||
|
3. Runtime freeze after the first `Run()`, `RunWithContext(...)`, or `RunWebHookWithContext(...)`.
|
||||||
|
|
||||||
|
That means:
|
||||||
|
- finish plugin structure before `AddPlugins(...)`;
|
||||||
|
- finish bot-level structure before the first runtime entry point;
|
||||||
|
- treat later structural bot mutations as intentionally ignored.
|
||||||
|
|
||||||
|
After runtime starts, structural calls such as `AddPlugins(...)`, `AddMiddleware(...)`, `AddRunner(...)`, `AddPrefixes(...)`, `SetPayloadType(...)`, `SetStrictPayloadType(...)`, `SetDraftProvider(...)`, `SetSessionStore(...)`, `SetSceneScopePriority(...)`, `SetL10n(...)`, `SetAppData(...)`, `SetUpdateTypes(...)`, `AddUpdateType(...)`, and `SetErrorTemplate(...)` are ignored.
|
||||||
|
|
||||||
|
## What “ignored” means
|
||||||
|
|
||||||
|
Ignored means the method returns without changing the bot's structural runtime state.
|
||||||
|
|
||||||
|
Laniakea prefers a predictable no-op over partially applying runtime configuration changes while updates may already be queued or handlers may already be running. That avoids:
|
||||||
|
- unclear ordering between configuration edits and update processing;
|
||||||
|
- races around shared bot state;
|
||||||
|
- confusion about whether a change affects only future updates or also already accepted work;
|
||||||
|
- different mental models for plugin snapshots and bot-level state.
|
||||||
|
|
||||||
## Minimal startup pattern
|
## Minimal startup pattern
|
||||||
|
|
||||||
```go
|
```go
|
||||||
opts := &laniakea.BotOpts{Token: "TOKEN"}
|
opts := &laniakea.BotOpts{Token: "TOKEN"}
|
||||||
|
|
||||||
bot, err := laniakea.NewBot[laniakea.NoDB](opts)
|
bot, err := laniakea.NewBot[laniakea.NoData](opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer bot.Close()
|
defer bot.Close()
|
||||||
|
|
||||||
plugin := laniakea.NewPlugin[laniakea.NoDB]("main")
|
plugin := laniakea.NewPlugin[laniakea.NoData]("main")
|
||||||
plugin.NewCommand(func(ctx *laniakea.MsgContext, db laniakea.NoDB) error {
|
plugin.NewCommand(func(ctx *laniakea.MsgContext, db laniakea.NoData) error {
|
||||||
ctx.Answer("pong")
|
ctx.Answer("pong")
|
||||||
return nil
|
return nil
|
||||||
}, "ping")
|
}, "ping")
|
||||||
@@ -86,9 +109,9 @@ if err := bot.Run(); err != nil {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## `RunWithContext` versus `Run`
|
## `RunWithContext(...)`, `Run()`, and `RunWebHookWithContext(...)`
|
||||||
|
|
||||||
`RunWithContext(ctx)` is the main runtime entry point.
|
`RunWithContext(ctx)` is the main polling runtime entry point.
|
||||||
|
|
||||||
It:
|
It:
|
||||||
- rejects startup when no prefixes are defined;
|
- rejects startup when no prefixes are defined;
|
||||||
@@ -101,24 +124,35 @@ It:
|
|||||||
|
|
||||||
`Run()` is only a shorthand for `RunWithContext(context.Background())`.
|
`Run()` is only a shorthand for `RunWithContext(context.Background())`.
|
||||||
|
|
||||||
Use `RunWithContext` for production services so you can stop the bot gracefully on `SIGINT` or `SIGTERM`.
|
`RunWebHookWithContext(...)` is the webhook runtime entry point. It shares the same:
|
||||||
|
- single-use rule;
|
||||||
|
- runner startup behavior;
|
||||||
|
- internal update queue;
|
||||||
|
- worker-pool dispatch model;
|
||||||
|
- graceful shutdown semantics.
|
||||||
|
|
||||||
|
Use `RunWithContext(...)` for production services that poll Telegram directly. Use `RunWebHookWithContext(...)` when Telegram should deliver updates through your HTTP endpoint.
|
||||||
|
|
||||||
|
If you switch an existing deployment from webhook delivery to polling, remove the current webhook first with `CloseWebHook()` or low-level `tgapi.DeleteWebhook(...)`. Telegram keeps webhook delivery active until the webhook is deleted.
|
||||||
|
|
||||||
|
For the webhook-specific option model, transport behavior, and operational guidance, see [[Webhook-Runtime]].
|
||||||
|
|
||||||
## What happens during runtime
|
## What happens during runtime
|
||||||
|
|
||||||
After startup, the bot runs three main pieces of work:
|
After startup, the bot runs three main pieces of work:
|
||||||
|
|
||||||
- a polling loop fetches updates from Telegram;
|
- an update ingress path receives updates from polling or webhook transport;
|
||||||
- an internal queue buffers updates before processing;
|
- an internal queue buffers updates before processing;
|
||||||
- a worker pool handles updates concurrently.
|
- a worker pool handles updates concurrently.
|
||||||
|
|
||||||
Behavior worth knowing:
|
Behavior worth knowing:
|
||||||
- polling retry uses exponential backoff, capped at 30 seconds, when `getUpdates` keeps failing;
|
- polling retry uses exponential backoff, capped at 30 seconds, when `getUpdates` keeps failing;
|
||||||
- the worker pool size is controlled by `BotOpts.MaxWorkers`;
|
- the worker pool size is controlled by `BotOpts.MaxWorkers`;
|
||||||
- when the shutdown context is canceled, polling stops first, then queued work is drained, then runners are awaited.
|
- when the shutdown context is canceled, update ingress stops first, then queued work is drained, then runners are awaited.
|
||||||
|
|
||||||
## Runners and shutdown semantics
|
## Runners and shutdown semantics
|
||||||
|
|
||||||
Runners start from `RunWithContext`, not from `NewBot`.
|
Runners start from runtime entry points, not from `NewBot`.
|
||||||
|
|
||||||
Supported runner modes:
|
Supported runner modes:
|
||||||
- one-time async runners start in their own goroutine;
|
- one-time async runners start in their own goroutine;
|
||||||
@@ -132,18 +166,19 @@ Important caveats:
|
|||||||
|
|
||||||
## Graceful shutdown
|
## Graceful shutdown
|
||||||
|
|
||||||
Canceling the `RunWithContext` context tells the bot to stop polling and finish work already accepted into the queue. `RunWithContext` then waits for:
|
Canceling the runtime context tells the bot to stop accepting new work and finish work already accepted into the queue. The runtime then waits for:
|
||||||
|
|
||||||
- all queued update handlers to finish;
|
- all queued update handlers to finish;
|
||||||
- one-time async runners to finish;
|
- one-time async runners to finish;
|
||||||
- background runners to exit after noticing `ctx.Done()`.
|
- background runners to exit after noticing `ctx.Done()`.
|
||||||
|
|
||||||
`RunWithContext` does not automatically release API, uploader, or logger resources. You still need to call `Close()`.
|
`RunWithContext(...)` and `RunWebHookWithContext(...)` do not automatically release API, uploader, or logger resources. You still need to call `Close()`.
|
||||||
|
|
||||||
## `Close()` versus `CloseRemote()`
|
## `Close()` versus `CloseRemote()`
|
||||||
|
|
||||||
`Close()` releases local process resources owned by the bot. It closes, in order:
|
`Close()` releases local process resources owned by the bot. It closes, in order:
|
||||||
- registered plugins via `Plugin.Close()`;
|
- registered plugins via `Plugin.Close()`;
|
||||||
|
- the webhook logger when webhook runtime initialized it;
|
||||||
- the uploader;
|
- the uploader;
|
||||||
- the local API client internals;
|
- the local API client internals;
|
||||||
- the optional request logger;
|
- the optional request logger;
|
||||||
@@ -157,10 +192,10 @@ Use `CloseRemote(ctx)` only when you specifically need Telegram-side session shu
|
|||||||
|
|
||||||
## Single-use rule
|
## Single-use rule
|
||||||
|
|
||||||
A `Bot` cannot be started twice. After `Run()` or `RunWithContext()` returns, later start attempts fail with `ErrBotAlreadyRun`.
|
A `Bot` cannot be started twice. After `Run()`, `RunWithContext(...)`, or `RunWebHookWithContext(...)` returns, later start attempts fail with `ErrBotAlreadyRun`.
|
||||||
|
|
||||||
That means:
|
That means:
|
||||||
- do not call `Run()` again after a graceful stop;
|
- do not call a runtime entry point again after a graceful stop;
|
||||||
- do not reuse a bot instance across tests that start it;
|
- do not reuse a bot instance across tests that start it;
|
||||||
- create a fresh `Bot` for every new run cycle.
|
- create a fresh `Bot` for every new run cycle.
|
||||||
|
|
||||||
@@ -176,7 +211,7 @@ if err != nil {
|
|||||||
}
|
}
|
||||||
defer bot.Close()
|
defer bot.Close()
|
||||||
|
|
||||||
bot.DatabaseContext(db)
|
bot.SetAppData(db)
|
||||||
bot.AddPlugins(plugin)
|
bot.AddPlugins(plugin)
|
||||||
|
|
||||||
if err := bot.RunWithContext(ctx); err != nil {
|
if err := bot.RunWithContext(ctx); err != nil {
|
||||||
@@ -187,7 +222,7 @@ if err := bot.RunWithContext(ctx); err != nil {
|
|||||||
## Common mistakes
|
## Common mistakes
|
||||||
|
|
||||||
- Mutating a plugin after `AddPlugins(...)` and expecting the bot to see the change.
|
- Mutating a plugin after `AddPlugins(...)` and expecting the bot to see the change.
|
||||||
- Calling `Run()` twice on the same bot instance.
|
- Calling a runtime entry point twice on the same bot instance.
|
||||||
- Using `Run()` in long-lived services where graceful shutdown matters.
|
- Continuing to mutate bot-level structure after runtime already started.
|
||||||
- Forgetting to call `Close()` after `RunWithContext(...)` returns.
|
- Forgetting to call `Close()` after runtime returns.
|
||||||
- Registering background runners without a timeout.
|
- Registering background runners without a timeout.
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ Env:
|
|||||||
- `ERROR_TEMPLATE`
|
- `ERROR_TEMPLATE`
|
||||||
|
|
||||||
Related page:
|
Related page:
|
||||||
- [[FAQ]]
|
- [[Error-Handling]]
|
||||||
|
|
||||||
## Logging configuration
|
## Logging configuration
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ English version: [[Commands-and-Plugins]]
|
|||||||
Пример:
|
Пример:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
plugin := laniakea.NewPlugin[laniakea.NoDB]("admin")
|
plugin := laniakea.NewPlugin[laniakea.NoData]("admin")
|
||||||
```
|
```
|
||||||
|
|
||||||
Обычно плагины удобно делить по смыслу:
|
Обычно плагины удобно делить по смыслу:
|
||||||
@@ -256,7 +256,7 @@ return nil
|
|||||||
## Что читать дальше
|
## Что читать дальше
|
||||||
|
|
||||||
- [[Getting-Started-RU]]
|
- [[Getting-Started-RU]]
|
||||||
- [[FAQ-RU]]
|
- [[Bot-Lifecycle-RU]]
|
||||||
- [[Commands-and-Plugins]]
|
- [[Commands-and-Plugins]]
|
||||||
- [[MsgContext]]
|
- [[MsgContext]]
|
||||||
- [[Inline-Keyboards-and-Payloads]]
|
- [[Inline-Keyboards-and-Payloads]]
|
||||||
|
|||||||
+2
-2
@@ -33,7 +33,7 @@ A plugin is a named group of:
|
|||||||
Example:
|
Example:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
plugin := laniakea.NewPlugin[laniakea.NoDB]("admin")
|
plugin := laniakea.NewPlugin[laniakea.NoData]("admin")
|
||||||
```
|
```
|
||||||
|
|
||||||
Use plugins to group functionality by concern:
|
Use plugins to group functionality by concern:
|
||||||
@@ -89,7 +89,7 @@ So:
|
|||||||
## The easiest command example
|
## The easiest command example
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func echo(ctx *laniakea.MsgContext, db laniakea.NoDB) error {
|
func echo(ctx *laniakea.MsgContext, db laniakea.NoData) error {
|
||||||
if ctx.Text == "" {
|
if ctx.Text == "" {
|
||||||
ctx.Answer("Usage: /echo <text>")
|
ctx.Answer("Usage: /echo <text>")
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -1,136 +0,0 @@
|
|||||||
# Configuration Freeze Model RU
|
|
||||||
|
|
||||||
English version: [[Configuration-Freeze-Model]]
|
|
||||||
|
|
||||||
Эта страница объясняет, когда конфигурацию бота в Laniakea ещё можно менять, когда она считается зафиксированной и почему поздние конфигурационные вызовы намеренно игнорируются.
|
|
||||||
|
|
||||||
Коротко:
|
|
||||||
- конфигурацию плагина нужно считать завершённой до `AddPlugins(...)`;
|
|
||||||
- конфигурацию бота нужно считать завершённой до первого `Run()` или `RunWithContext(...)`;
|
|
||||||
- после старта runtime поздние bot-level конфигурационные вызовы игнорируются, а не применяются частично.
|
|
||||||
|
|
||||||
## Зачем нужна такая модель
|
|
||||||
|
|
||||||
Laniakea разводит:
|
|
||||||
- фазу построения и настройки;
|
|
||||||
- фазу регистрации и снимка состояния плагинов;
|
|
||||||
- фазу выполнения и обработки обновлений.
|
|
||||||
|
|
||||||
Это делает поведение фреймворка предсказуемым. Без такой границы running bot мог бы увидеть наполовину применённые изменения prefixes, middleware, политики данных callback, локализации или общих зависимостей уже во время обработки update.
|
|
||||||
|
|
||||||
Цель здесь не в том, чтобы сделать из `Bot` живую control surface. Цель в том, чтобы сделать startup и runtime-семантику явной и стабильной.
|
|
||||||
|
|
||||||
## Три практические фазы
|
|
||||||
|
|
||||||
### 1. Фаза построения
|
|
||||||
|
|
||||||
Она начинается в `NewBot[T](opts)` и продолжается, пока ты настраиваешь экземпляр бота.
|
|
||||||
|
|
||||||
Типичные операции этой фазы:
|
|
||||||
- `DatabaseContext(...)`
|
|
||||||
- `AddPrefixes(...)`
|
|
||||||
- `SetPayloadType(...)`
|
|
||||||
- `SetStrictPayloadType(...)`
|
|
||||||
- `UpdateTypes(...)`
|
|
||||||
- `AddUpdateType(...)`
|
|
||||||
- `AddPlugins(...)`
|
|
||||||
- `AddMiddleware(...)`
|
|
||||||
- `AddRunner(...)`
|
|
||||||
- `AddL10n(...)`
|
|
||||||
- `SetDraftProvider(...)`
|
|
||||||
- `SetSessionStore(...)`
|
|
||||||
- `SetSceneScopePriority(...)`
|
|
||||||
- `ErrorTemplate(...)`
|
|
||||||
|
|
||||||
Именно здесь и должна происходить вся структурная настройка.
|
|
||||||
|
|
||||||
### 2. Фаза снимка конфигурации плагина
|
|
||||||
|
|
||||||
`AddPlugins(...)` — это точка фиксации конфигурации плагинов.
|
|
||||||
|
|
||||||
Когда плагин добавляется в `Bot`, бот клонирует конфигурацию плагина во внутреннее состояние. После этого исходный `*Plugin` уже не стоит считать авторитетным источником конфигурации именно для этого экземпляра бота.
|
|
||||||
|
|
||||||
До `AddPlugins(...)` нужно закончить:
|
|
||||||
- commands;
|
|
||||||
- обработчики данных callback;
|
|
||||||
- обработчики обновлений;
|
|
||||||
- middleware плагина;
|
|
||||||
- logger плагина;
|
|
||||||
- регистрацию сцен;
|
|
||||||
- `OnClose` callbacks.
|
|
||||||
|
|
||||||
Если потом изменить исходный плагин, бот не обязан увидеть эти изменения.
|
|
||||||
|
|
||||||
### 3. Фаза фиксации конфигурации бота
|
|
||||||
|
|
||||||
Первый вызов `Run()` или `RunWithContext(...)` фиксирует bot-level конфигурацию.
|
|
||||||
|
|
||||||
С этого момента бот считает структурную конфигурацию закрытой. Поздние вызовы конфигурационных методов игнорируются вместо того, чтобы применяться в тот момент, когда runtime уже активен или уже однажды был запущен.
|
|
||||||
|
|
||||||
Сейчас это относится к таким bot-level методам, как:
|
|
||||||
- `SetDraftProvider(...)`
|
|
||||||
- `SetSessionStore(...)`
|
|
||||||
- `SetSceneScopePriority(...)`
|
|
||||||
- `DatabaseContext(...)`
|
|
||||||
- `UpdateTypes(...)`
|
|
||||||
- `SetPayloadType(...)`
|
|
||||||
- `SetStrictPayloadType(...)`
|
|
||||||
- `AddUpdateType(...)`
|
|
||||||
- `AddPrefixes(...)`
|
|
||||||
- `ErrorTemplate(...)`
|
|
||||||
- `AddPlugins(...)`
|
|
||||||
- `AddMiddleware(...)`
|
|
||||||
- `AddRunner(...)`
|
|
||||||
- `AddL10n(...)`
|
|
||||||
|
|
||||||
Практически это нужно читать так:
|
|
||||||
- создать бот;
|
|
||||||
- закончить конфигурацию;
|
|
||||||
- один раз запустить runtime;
|
|
||||||
- для другой runtime-конфигурации создать новый `Bot`.
|
|
||||||
|
|
||||||
## Что значит «игнорируется»
|
|
||||||
|
|
||||||
«Игнорируется» значит, что метод возвращается без изменения структурного runtime-состояния бота.
|
|
||||||
|
|
||||||
Это намеренное решение. Фреймворк предпочитает предсказуемый no-op вместо частичной live-мутации running bot. Это помогает избежать:
|
|
||||||
- неясного порядка между конфигурационными изменениями и обработкой update;
|
|
||||||
- гонок вокруг общего состояния бота во время runtime;
|
|
||||||
- путаницы в том, влияет ли изменение только на будущие update или ещё и на уже поставленную в очередь работу;
|
|
||||||
- несовместимого поведения между snapshot-моделью плагинов и живым состоянием бота.
|
|
||||||
|
|
||||||
## Чего эта модель не означает
|
|
||||||
|
|
||||||
Она не означает, что после старта каждый метод `Bot` становится бессмысленным.
|
|
||||||
|
|
||||||
Она относится именно к структурным конфигурационным методам. Runtime-операции вроде:
|
|
||||||
- `RunWithContext(...)`;
|
|
||||||
- `Close()`;
|
|
||||||
- `CloseRemote(...)`;
|
|
||||||
- операций `MsgContext` внутри хендлеров;
|
|
||||||
|
|
||||||
сохраняют обычную семантику.
|
|
||||||
|
|
||||||
Важно и то, что не каждый нефункциональный флаг обязан быть частью freeze contract. Ключевой вопрос здесь такой: меняет ли метод структурную runtime-модель бота.
|
|
||||||
|
|
||||||
## Рекомендуемая ментальная модель
|
|
||||||
|
|
||||||
Удобно думать о `Bot` как о builder плюс одной runtime-сессии:
|
|
||||||
|
|
||||||
1. создать бот;
|
|
||||||
2. полностью его настроить;
|
|
||||||
3. зарегистрировать законченные плагины;
|
|
||||||
4. один раз запустить runtime;
|
|
||||||
5. остановить его;
|
|
||||||
6. закрыть локальные ресурсы;
|
|
||||||
7. для следующего запуска с другой конфигурацией создать новый `Bot`.
|
|
||||||
|
|
||||||
Именно эта модель сейчас лучше всего соответствует реализации и является самым безопасным контрактом для `1.0`.
|
|
||||||
|
|
||||||
## Связанные страницы
|
|
||||||
|
|
||||||
- [[Bot-Lifecycle-RU]]
|
|
||||||
- [[Commands-and-Plugins-RU]]
|
|
||||||
- [[Update-Routing-Model-RU]]
|
|
||||||
- [[Scenes-RU]]
|
|
||||||
- [[FAQ-RU]]
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
# Configuration Freeze Model
|
|
||||||
|
|
||||||
Russian version: [[Configuration-Freeze-Model-RU]]
|
|
||||||
|
|
||||||
This page explains when Laniakea bot configuration is still mutable, when it becomes fixed, and what kinds of late changes are intentionally ignored.
|
|
||||||
|
|
||||||
The short version:
|
|
||||||
- plugin configuration should be treated as finished before `AddPlugins(...)`;
|
|
||||||
- bot configuration should be treated as finished before the first `Run()` or `RunWithContext(...)`;
|
|
||||||
- after runtime starts, late bot-level configuration calls are ignored rather than partially applied.
|
|
||||||
|
|
||||||
## Why this model exists
|
|
||||||
|
|
||||||
Laniakea separates:
|
|
||||||
- construction-time configuration;
|
|
||||||
- plugin registration and snapshotting;
|
|
||||||
- runtime update processing.
|
|
||||||
|
|
||||||
That separation keeps the framework predictable. Without it, a running bot could observe half-applied changes to prefixes, middleware, payload policy, localization, or shared dependencies while updates are already being processed.
|
|
||||||
|
|
||||||
The goal is not to create a live control surface. The goal is to make startup and runtime semantics explicit and stable.
|
|
||||||
|
|
||||||
## The three practical phases
|
|
||||||
|
|
||||||
### 1. Construction phase
|
|
||||||
|
|
||||||
This begins at `NewBot[T](opts)` and continues while you configure the bot instance.
|
|
||||||
|
|
||||||
Typical operations in this phase:
|
|
||||||
- `DatabaseContext(...)`
|
|
||||||
- `AddPrefixes(...)`
|
|
||||||
- `SetPayloadType(...)`
|
|
||||||
- `SetStrictPayloadType(...)`
|
|
||||||
- `UpdateTypes(...)`
|
|
||||||
- `AddUpdateType(...)`
|
|
||||||
- `AddPlugins(...)`
|
|
||||||
- `AddMiddleware(...)`
|
|
||||||
- `AddRunner(...)`
|
|
||||||
- `AddL10n(...)`
|
|
||||||
- `SetDraftProvider(...)`
|
|
||||||
- `SetSessionStore(...)`
|
|
||||||
- `SetSceneScopePriority(...)`
|
|
||||||
- `ErrorTemplate(...)`
|
|
||||||
|
|
||||||
This is the intended place for all structural configuration.
|
|
||||||
|
|
||||||
### 2. Plugin snapshot phase
|
|
||||||
|
|
||||||
`AddPlugins(...)` is a configuration commit point for plugins.
|
|
||||||
|
|
||||||
When a plugin is added to a bot, the bot clones the plugin configuration into its own internal state. After that, the original `*Plugin` should be treated as no longer authoritative for that bot instance.
|
|
||||||
|
|
||||||
Configure these before `AddPlugins(...)`:
|
|
||||||
- commands;
|
|
||||||
- payload handlers;
|
|
||||||
- update handlers;
|
|
||||||
- plugin middleware;
|
|
||||||
- plugin logger;
|
|
||||||
- scene registrations;
|
|
||||||
- `OnClose` callbacks.
|
|
||||||
|
|
||||||
If you mutate the original plugin later, the bot is not expected to observe those edits.
|
|
||||||
|
|
||||||
## 3. Bot runtime freeze phase
|
|
||||||
|
|
||||||
The first call to `Run()` or `RunWithContext(...)` freezes bot-level configuration.
|
|
||||||
|
|
||||||
From that point on, the bot treats structural configuration as closed. Late calls to configuration mutators are ignored instead of being applied while runtime is already active or already initialized.
|
|
||||||
|
|
||||||
This currently applies to bot-level methods such as:
|
|
||||||
- `SetDraftProvider(...)`
|
|
||||||
- `SetSessionStore(...)`
|
|
||||||
- `SetSceneScopePriority(...)`
|
|
||||||
- `DatabaseContext(...)`
|
|
||||||
- `UpdateTypes(...)`
|
|
||||||
- `SetPayloadType(...)`
|
|
||||||
- `SetStrictPayloadType(...)`
|
|
||||||
- `AddUpdateType(...)`
|
|
||||||
- `AddPrefixes(...)`
|
|
||||||
- `ErrorTemplate(...)`
|
|
||||||
- `AddPlugins(...)`
|
|
||||||
- `AddMiddleware(...)`
|
|
||||||
- `AddRunner(...)`
|
|
||||||
- `AddL10n(...)`
|
|
||||||
|
|
||||||
In practice, you should read this as:
|
|
||||||
- build a bot;
|
|
||||||
- finish configuration;
|
|
||||||
- run it once;
|
|
||||||
- create a new bot if you need a different runtime configuration.
|
|
||||||
|
|
||||||
## What “ignored” means
|
|
||||||
|
|
||||||
Ignored means the method returns without changing the bot's structural runtime state.
|
|
||||||
|
|
||||||
This behavior is intentional. The framework prefers a predictable no-op over partial live mutation of a running bot. That avoids:
|
|
||||||
- unclear ordering between configuration edits and update handling;
|
|
||||||
- runtime races around shared bot state;
|
|
||||||
- confusion about whether a change affects only future updates or also already queued work;
|
|
||||||
- inconsistent behavior between plugin-level snapshots and bot-level live state.
|
|
||||||
|
|
||||||
## What this model does not mean
|
|
||||||
|
|
||||||
This model does not mean every method on `Bot` becomes unusable after startup.
|
|
||||||
|
|
||||||
It applies specifically to structural configuration methods. Runtime operations such as:
|
|
||||||
- `RunWithContext(...)`;
|
|
||||||
- `Close()`;
|
|
||||||
- `CloseRemote(...)`;
|
|
||||||
- handler-time `MsgContext` operations;
|
|
||||||
|
|
||||||
still have their normal meaning.
|
|
||||||
|
|
||||||
Also note that not every non-structural flag is part of the freeze contract. The important rule is whether the method changes the bot's runtime configuration model, not whether it is simply callable on `Bot`.
|
|
||||||
|
|
||||||
## Recommended mental model
|
|
||||||
|
|
||||||
Treat `Bot` as a builder plus one runtime session:
|
|
||||||
|
|
||||||
1. construct a bot;
|
|
||||||
2. fully configure it;
|
|
||||||
3. register finished plugins;
|
|
||||||
4. start runtime once;
|
|
||||||
5. stop it;
|
|
||||||
6. close it;
|
|
||||||
7. create a new bot for the next differently configured run.
|
|
||||||
|
|
||||||
That mental model matches the current implementation and is the safest contract to rely on for `1.0`.
|
|
||||||
|
|
||||||
## Related pages
|
|
||||||
|
|
||||||
- [[Bot-Lifecycle]]
|
|
||||||
- [[Commands-and-Plugins]]
|
|
||||||
- [[Update-Routing-Model]]
|
|
||||||
- [[Scenes]]
|
|
||||||
- [[FAQ]]
|
|
||||||
@@ -113,7 +113,6 @@ Internal-only ошибки идут по тому же logger path, но не с
|
|||||||
|
|
||||||
## Что читать дальше
|
## Что читать дальше
|
||||||
|
|
||||||
- [[FAQ-RU]]
|
|
||||||
- [[Logging-RU]]
|
- [[Logging-RU]]
|
||||||
- [[Middleware-RU]]
|
- [[Middleware-RU]]
|
||||||
- [[Error-Handling]]
|
- [[Error-Handling]]
|
||||||
|
|||||||
@@ -227,7 +227,6 @@ plugin.NewPayload(func(ctx *laniakea.MsgContext, db *App) error {
|
|||||||
|
|
||||||
## Related pages
|
## Related pages
|
||||||
|
|
||||||
- [[FAQ]]
|
|
||||||
- [[Middleware]]
|
- [[Middleware]]
|
||||||
- [[Logging]]
|
- [[Logging]]
|
||||||
- [[Commands-and-Plugins]]
|
- [[Commands-and-Plugins]]
|
||||||
|
|||||||
-124
@@ -1,124 +0,0 @@
|
|||||||
# FAQ RU
|
|
||||||
|
|
||||||
English version: [[FAQ]]
|
|
||||||
|
|
||||||
Это краткая русскоязычная версия частых вопросов о дизайне Laniakea. Полная и наиболее актуальная страница: [[FAQ]].
|
|
||||||
|
|
||||||
## Почему хендлеры возвращают `error`?
|
|
||||||
|
|
||||||
Чтобы ошибки проходили через единый, централизованный поток, а не обрабатывались вручную в каждом обработчике команды или callback.
|
|
||||||
|
|
||||||
Это дает несколько плюсов:
|
|
||||||
- хендлеры остаются проще;
|
|
||||||
- формат пользовательских ошибок можно контролировать через `ErrorTemplate(...)`;
|
|
||||||
- команды, callback и обработчики обновлений вне команд используют один и тот же контракт.
|
|
||||||
|
|
||||||
Если тебе нужен полностью ручной ответ пользователю, ты все еще можешь ответить сам и вернуть `nil`.
|
|
||||||
|
|
||||||
Подробности: [[Error-Handling]]
|
|
||||||
|
|
||||||
## Почему `Bot` single-use?
|
|
||||||
|
|
||||||
Потому что один запуск владеет реальным состоянием выполнения:
|
|
||||||
- жизненным циклом polling;
|
|
||||||
- worker pool;
|
|
||||||
- update offsets;
|
|
||||||
- выполнением фоновых задач;
|
|
||||||
- API и ресурсами логгеров.
|
|
||||||
|
|
||||||
Из-за этого модель “создал -> настроил -> запустил -> закрыл -> создал новый” безопаснее и проще для понимания, чем попытка перезапускать один и тот же `Bot`.
|
|
||||||
|
|
||||||
Подробности: [[Bot-Lifecycle]]
|
|
||||||
|
|
||||||
## Почему `AnswerLong(...)` существует отдельно от `Answer(...)`?
|
|
||||||
|
|
||||||
Потому что `Answer(...)` сохраняет простую семантику “одно сообщение”.
|
|
||||||
|
|
||||||
А `AnswerLong(...)` — это уже явный режим, где:
|
|
||||||
- текст может быть разбит на несколько сообщений;
|
|
||||||
- возможен partial success;
|
|
||||||
- клавиатура в `KeyboardLong(...)` вешается только на последний chunk.
|
|
||||||
|
|
||||||
Такое разделение сделано специально, чтобы длинные ответы не меняли поведение обычных вспомогательных методов неявно.
|
|
||||||
|
|
||||||
## Зачем есть и JSON, и Base64 форматы данных callback?
|
|
||||||
|
|
||||||
Они решают разные задачи:
|
|
||||||
|
|
||||||
- `BotPayloadJson` удобен для читаемости, логов и тестов
|
|
||||||
- `BotPayloadBase64` удобен как более компактная и “непрозрачная” транспортная форма тех же данных callback
|
|
||||||
|
|
||||||
Логическая структура данных callback при этом одна и та же: меняется только кодирование.
|
|
||||||
|
|
||||||
Подробности: [[Inline-Keyboards-and-Payloads]]
|
|
||||||
|
|
||||||
## Когда использовать `MsgContext`, а когда `tgapi`?
|
|
||||||
|
|
||||||
Используй `MsgContext`, когда ты уже внутри хендлера и тебе нужен удобный поток reply/edit/delete с текущими chat, message и logger.
|
|
||||||
|
|
||||||
Используй `tgapi`, когда:
|
|
||||||
- нужного вспомогательного метода нет в `MsgContext`
|
|
||||||
- ты работаешь вне потока обработчика
|
|
||||||
- нужен более низкоуровневый контроль над Telegram methods, uploads или downloads
|
|
||||||
|
|
||||||
Коротко:
|
|
||||||
- `MsgContext` — удобный вариант по умолчанию
|
|
||||||
- `tgapi` — низкоуровневый запасной путь
|
|
||||||
|
|
||||||
Подробности: [[tgapi-Overview]]
|
|
||||||
|
|
||||||
## Почему `RunWithContext(...)` не заменяет `Close()`?
|
|
||||||
|
|
||||||
Потому что это две разные ответственности:
|
|
||||||
|
|
||||||
- `RunWithContext(...)` управляет циклом выполнения, корректной остановкой и ожиданием фоновых задач
|
|
||||||
- `Close()` освобождает API, uploader, хуки остановки плагинов и логгеры
|
|
||||||
|
|
||||||
Поэтому `Close()` все равно нужен.
|
|
||||||
|
|
||||||
## Почему `Plugin` нужно полностью настроить до `AddPlugins(...)`?
|
|
||||||
|
|
||||||
Потому что `AddPlugins(...)` — это точка фиксации конфигурации.
|
|
||||||
|
|
||||||
После регистрации `Bot` хранит внутреннюю копию состояния плагина, и изменения исходного `*Plugin` уже не считаются поддерживаемым API.
|
|
||||||
|
|
||||||
До `AddPlugins(...)` стоит завершить:
|
|
||||||
- commands
|
|
||||||
- данные callback
|
|
||||||
- обработчики обновлений
|
|
||||||
- middleware плагина
|
|
||||||
- выбор логгера
|
|
||||||
- `OnClose`
|
|
||||||
|
|
||||||
Подробную модель snapshot/freeze границ смотри в [[Configuration-Freeze-Model-RU]].
|
|
||||||
|
|
||||||
## Почему поздние bot-level конфигурационные вызовы игнорируются после старта runtime?
|
|
||||||
|
|
||||||
Потому что Laniakea воспринимает конфигурацию бота как до-runtime фазу, а не как живую control surface во время работы.
|
|
||||||
|
|
||||||
Как только начинается первый запуск, bot-level конфигурация считается frozen. Поздние вызовы вроде добавления middleware, смены payload defaults, изменения prefixes или подмены database context игнорируются вместо частичного применения.
|
|
||||||
|
|
||||||
Это помогает избежать:
|
|
||||||
- неожиданной live-мутации running bot;
|
|
||||||
- неясного порядка между конфигурационными изменениями и обработкой update;
|
|
||||||
- runtime-изменений конфигурации с риском гонок;
|
|
||||||
- путаницы между snapshotted state и всё ещё изменяемым состоянием.
|
|
||||||
|
|
||||||
Если нужна другая конфигурация, для следующего запуска нужно создать новый экземпляр `Bot`.
|
|
||||||
|
|
||||||
Полную модель фаз и фиксации смотри в [[Configuration-Freeze-Model-RU]].
|
|
||||||
|
|
||||||
## Почему async middleware игнорирует `false`?
|
|
||||||
|
|
||||||
Потому что async middleware задуман как путь для побочных действий, а не как механизм управления потоком.
|
|
||||||
|
|
||||||
Когда middleware уходит в goroutine, он уже не может надежно остановить основной путь выполнения. Поэтому для блокировки и отказов нужно использовать обычный synchronous middleware.
|
|
||||||
|
|
||||||
Подробности: [[Middleware]]
|
|
||||||
|
|
||||||
## Что читать дальше
|
|
||||||
|
|
||||||
- [[FAQ]]
|
|
||||||
- [[Getting-Started-RU]]
|
|
||||||
- [[Commands-and-Plugins]]
|
|
||||||
- [[Bot-Lifecycle]]
|
|
||||||
-163
@@ -1,163 +0,0 @@
|
|||||||
# FAQ
|
|
||||||
|
|
||||||
Russian version: [[FAQ-RU]]
|
|
||||||
|
|
||||||
This page collects recurring questions about why Laniakea behaves the way it does. It focuses on design choices that are easy to miss when you only look at examples.
|
|
||||||
|
|
||||||
## Why do handlers return `error`?
|
|
||||||
|
|
||||||
Handlers return `error` so failure reporting can be centralized instead of being reimplemented in every command or payload callback.
|
|
||||||
|
|
||||||
That gives you a few benefits:
|
|
||||||
- command handlers can stay focused on business logic;
|
|
||||||
- bot-wide error presentation can be controlled through `ErrorTemplate(...)`;
|
|
||||||
- non-command update handlers and payload handlers follow the same contract;
|
|
||||||
- callers can distinguish normal completion from a failure without relying on side effects.
|
|
||||||
|
|
||||||
A typical handler now looks like this:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func ping(ctx *laniakea.MsgContext, db *App) error {
|
|
||||||
ctx.Answer("pong")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If you want to fully control the user-facing error reply yourself, you can still do that and return `nil`.
|
|
||||||
|
|
||||||
## Why is `Bot` single-use?
|
|
||||||
|
|
||||||
`Bot` is single-use because a run session owns real runtime state:
|
|
||||||
- polling lifecycle;
|
|
||||||
- worker pool lifecycle;
|
|
||||||
- update offsets;
|
|
||||||
- background runner execution;
|
|
||||||
- API and logger resources.
|
|
||||||
|
|
||||||
Allowing the same instance to be restarted would make shutdown and resource ownership much harder to reason about. The current model is simpler and safer:
|
|
||||||
|
|
||||||
1. construct a bot;
|
|
||||||
2. configure it;
|
|
||||||
3. run it once;
|
|
||||||
4. close it;
|
|
||||||
5. create a new bot for the next run.
|
|
||||||
|
|
||||||
See [[Bot-Lifecycle]] for the full runtime model.
|
|
||||||
|
|
||||||
## Why is `AnswerLong(...)` separate from `Answer(...)`?
|
|
||||||
|
|
||||||
`Answer(...)` intentionally keeps the simple one-message semantic. It validates that the text fits into a single Telegram message and does not silently change delivery shape.
|
|
||||||
|
|
||||||
`AnswerLong(...)` exists separately so long-message splitting is explicit.
|
|
||||||
|
|
||||||
That separation is useful because splitting changes behavior:
|
|
||||||
- the user receives multiple messages instead of one;
|
|
||||||
- partial success becomes possible if later chunks fail;
|
|
||||||
- inline keyboards can only be attached to one chunk, so `KeyboardLong(...)` attaches it only to the last part.
|
|
||||||
|
|
||||||
Keeping long-message behavior explicit makes handlers easier to reason about and avoids surprising message fan-out from otherwise simple helper calls.
|
|
||||||
|
|
||||||
## Why do both JSON and Base64 payload formats exist?
|
|
||||||
|
|
||||||
Laniakea supports both because they solve different problems.
|
|
||||||
|
|
||||||
`BotPayloadJson` is useful when:
|
|
||||||
- you want readable callback payloads during debugging;
|
|
||||||
- you value transparency over compactness;
|
|
||||||
- your payloads are already small.
|
|
||||||
|
|
||||||
`BotPayloadBase64` is useful when:
|
|
||||||
- you want a more compact transport form for JSON payload data;
|
|
||||||
- you want callback data to look opaque at a glance;
|
|
||||||
- you want the historical default used by the bot.
|
|
||||||
|
|
||||||
The important nuance is that the logical payload is the same structured callback data. Only the encoding changes.
|
|
||||||
|
|
||||||
Also note:
|
|
||||||
- the bot has a default payload type;
|
|
||||||
- an `InlineKeyboard` can override that default for one keyboard;
|
|
||||||
- strict payload mode can force decoding to accept only the configured default type.
|
|
||||||
|
|
||||||
See [[Inline-Keyboards-and-Payloads]] for the full callback model.
|
|
||||||
|
|
||||||
## Why can a keyboard override the bot payload type?
|
|
||||||
|
|
||||||
Because different UI surfaces sometimes have different needs.
|
|
||||||
|
|
||||||
Most bots want one default payload encoding across the project. But some keyboards are easier to inspect or integrate when encoded differently. Keyboard-local override keeps the global default simple while still allowing exceptions when they are actually useful.
|
|
||||||
|
|
||||||
## When should I use high-level `MsgContext` helpers instead of `tgapi` directly?
|
|
||||||
|
|
||||||
Use `MsgContext` helpers when you are working inside a handler and want the common reply/edit/delete path with the current chat, callback message, thread, and logger already wired in.
|
|
||||||
|
|
||||||
Use `tgapi` directly when:
|
|
||||||
- you need a Telegram method that has no `MsgContext` helper;
|
|
||||||
- you are working outside a handler context;
|
|
||||||
- you need lower-level request control;
|
|
||||||
- you are integrating with uploader or file APIs directly.
|
|
||||||
|
|
||||||
In practice:
|
|
||||||
- `MsgContext` is the ergonomic default for handler code;
|
|
||||||
- `tgapi` is the escape hatch and infrastructure layer.
|
|
||||||
|
|
||||||
See [[tgapi-Overview]] for the lower-level API surface.
|
|
||||||
|
|
||||||
## Why does `RunWithContext(...)` still require `Close()` afterward?
|
|
||||||
|
|
||||||
Because stopping the run loop and releasing owned resources are treated as two separate responsibilities.
|
|
||||||
|
|
||||||
`RunWithContext(...)` handles:
|
|
||||||
- polling lifecycle;
|
|
||||||
- graceful stop on `ctx.Done()`;
|
|
||||||
- draining queued work;
|
|
||||||
- waiting for runners to finish.
|
|
||||||
|
|
||||||
`Close()` handles:
|
|
||||||
- plugin shutdown hooks;
|
|
||||||
- uploader shutdown;
|
|
||||||
- API client cleanup;
|
|
||||||
- logger cleanup.
|
|
||||||
|
|
||||||
This separation keeps shutdown explicit and makes it easier to control lifetime in tests, services, and embedded applications.
|
|
||||||
|
|
||||||
## Why does plugin configuration need to be finished before `AddPlugins(...)`?
|
|
||||||
|
|
||||||
Because `AddPlugins(...)` snapshots plugin state into the bot. The bot should not depend on later mutation of the original `*Plugin`.
|
|
||||||
|
|
||||||
That avoids a whole class of surprising bugs where one part of the program mutates a plugin after another part already started relying on it.
|
|
||||||
|
|
||||||
Configure these before registration:
|
|
||||||
- commands;
|
|
||||||
- payloads;
|
|
||||||
- update handlers;
|
|
||||||
- plugin middleware;
|
|
||||||
- logger choice;
|
|
||||||
- `OnClose` callback.
|
|
||||||
|
|
||||||
See [[Configuration-Freeze-Model]] for the broader lifecycle model around plugin snapshotting and bot-level configuration freeze.
|
|
||||||
|
|
||||||
## Why are late bot configuration calls ignored after runtime starts?
|
|
||||||
|
|
||||||
Because Laniakea treats bot configuration as a pre-runtime phase, not a live control surface.
|
|
||||||
|
|
||||||
Once the first run begins, bot-level configuration is considered frozen. Late calls such as adding middleware, swapping payload defaults, changing prefixes, or injecting a new database context are ignored instead of being partially applied.
|
|
||||||
|
|
||||||
That avoids:
|
|
||||||
- surprising live mutation of a running bot;
|
|
||||||
- unclear ordering between config changes and update processing;
|
|
||||||
- race-prone runtime configuration changes;
|
|
||||||
- confusion about which state is snapshotted and which state is still mutable.
|
|
||||||
|
|
||||||
If you need a different configuration, create a new bot instance for the next run.
|
|
||||||
|
|
||||||
See [[Configuration-Freeze-Model]] for the full phase model.
|
|
||||||
|
|
||||||
## Why does async middleware ignore `false`?
|
|
||||||
|
|
||||||
Because async middleware is designed for side effects, not flow control.
|
|
||||||
|
|
||||||
Once middleware runs in its own goroutine, it cannot reliably stop the handler path that is already continuing. Laniakea makes that explicit: async middleware always lets execution continue and receives a copied `MsgContext`.
|
|
||||||
|
|
||||||
If you need to block or reject execution, use synchronous middleware.
|
|
||||||
|
|
||||||
See [[Middleware]] for details.
|
|
||||||
+61
-35
@@ -2,49 +2,36 @@
|
|||||||
|
|
||||||
Эта страница отслеживает список задач уровня фреймворка, связанных с отсутствующими концепциями в библиотеке, а не просто с нехваткой документации.
|
Эта страница отслеживает список задач уровня фреймворка, связанных с отсутствующими концепциями в библиотеке, а не просто с нехваткой документации.
|
||||||
|
|
||||||
## Приоритет 2 — Важно
|
## Done
|
||||||
|
|
||||||
- Модель сервисного слоя и графа зависимостей: `DatabaseContext(T)` намеренно минималистичен, но нет более сильной концепции уровня фреймворка для сервисов приложения или зависимостей с ограниченной областью действия.
|
### [1.0.0-rc.14] Модель выполнения webhook
|
||||||
|
|
||||||
## Partial
|
|
||||||
|
|
||||||
### Модель выполнения webhook
|
|
||||||
|
|
||||||
Текущее состояние:
|
Текущее состояние:
|
||||||
|
|
||||||
- В репозитории уже есть низкоуровневые API для настройки webhook на уровне `tgapi`: `SetWebhook(...)`, `DeleteWebhook(...)`, `GetWebhookInfo(...)`, а также поддержка загрузки сертификата через uploader.
|
- В репозитории уже есть низкоуровневые API для настройки webhook на уровне `tgapi`: `SetWebhook(...)`, `DeleteWebhook(...)`, `GetWebhookInfo(...)`, а также поддержка загрузки сертификата через uploader.
|
||||||
- Основной runtime бота по-прежнему ориентирован на polling и предоставляет только `Run()` / `RunWithContext(...)` поверх `getUpdates`.
|
- Во фреймворке теперь есть полноценные bot-level точки входа webhook runtime: `RunWebHookWithContext(...)` и `RunWebHook(...)`.
|
||||||
|
- Webhook-доставка теперь использует ту же внутреннюю очередь update-ов, тот же worker pool, тот же запуск runners и тот же single-use runtime contract, что и polling.
|
||||||
|
- Поведение webhook runtime, security-модель и правила перехода обратно на polling теперь описаны в основной документации и wiki.
|
||||||
|
|
||||||
Что ещё отсутствует:
|
Почему это важно:
|
||||||
|
|
||||||
- Полноценная bot-level модель выполнения webhook, например `RunWebhook(...)`, `http.Handler` или другой явный framework-owned путь приёма входящих webhook-обновлений.
|
- Одной только поддержки webhook-транспорта на уровне API-клиента было недостаточно; пользователю всё ещё нужен был framework-owned runtime path, сопоставимый с polling.
|
||||||
- Задокументированный lifecycle contract для webhook-режима, сопоставимый с уже существующей polling-моделью.
|
- Полноценный runtime mode выравнивает worker scheduling, lifecycle behavior, правила конфигурации и shutdown semantics между двумя ingress-моделями.
|
||||||
- Единый способ пропускать webhook-обновления через те же границы runtime, не заставляя пользователя собирать внешнюю HTTP-интеграцию самостоятельно.
|
- Фреймворку также нужна явная история перехода с webhook-доставки обратно на polling deployment.
|
||||||
|
|
||||||
Почему это только partial:
|
Что теперь есть:
|
||||||
|
|
||||||
- Поддержка webhook-транспорта для Telegram уже есть на уровне API-клиента.
|
- `BotWebHookOpts`, `NewBotWebHookOpts()` и fluent helper-методы для webhook-конфигурации.
|
||||||
- Но сам фреймворк всё ещё не даёт webhook runtime concept, сопоставимый с его polling execution model.
|
- `RunWebHookWithContext(...)` и `RunWebHook(...)` как bot-owned точки входа runtime.
|
||||||
|
- Общая queued dispatch-модель, worker-pool обработка, запуск runners и single-use semantics для polling и webhook mode.
|
||||||
|
- Fallback webhook `AllowedUpdates` к bot-level конфигурации типов update.
|
||||||
|
- Валидация webhook path и количества TLS-файлов до remote webhook setup.
|
||||||
|
- Явное удаление remote webhook через `CloseWebHook()` или низкоуровневый `tgapi.DeleteWebhook(...)` при переходе deployment с webhook-доставки обратно на polling.
|
||||||
|
- Регрессионные тесты на queue delivery, запуск runners, single-use behavior, rejection слишком большого body, path/TLS validation и auth-поведение status endpoint.
|
||||||
|
|
||||||
### Контракт композиции плагинов
|
Практическая цель:
|
||||||
|
|
||||||
Текущее состояние:
|
- Считать webhook-доставку полноценным runtime mode фреймворка, а не оставлять её только низкоуровневой transport primitive из `tgapi`.
|
||||||
|
|
||||||
- Плагины уже не являются просто "мешком команд": регистрация через `AddPlugins(...)` делает snapshot plugin state, считает регистрацию точкой фиксации и документирует post-registration mutation как неподдерживаемую.
|
|
||||||
- Это уже даёт фреймворку осмысленный базовый контракт вокруг владения конфигурацией плагина и его неизменяемости во время runtime.
|
|
||||||
|
|
||||||
Что ещё отсутствует:
|
|
||||||
|
|
||||||
- Явные зависимости между плагинами.
|
|
||||||
- Декларации общих возможностей или требований между плагинами.
|
|
||||||
- Модель композиции уровня фреймворка для валидации и координации отношений между плагинами.
|
|
||||||
|
|
||||||
Почему это только partial:
|
|
||||||
|
|
||||||
- В репозитории уже есть реальный контракт регистрации.
|
|
||||||
- Но более сильной composition model, о которой говорилось в backlog, пока всё ещё нет.
|
|
||||||
|
|
||||||
## Done
|
|
||||||
|
|
||||||
### [1.0.0-rc.13] Модель наблюдаемости
|
### [1.0.0-rc.13] Модель наблюдаемости
|
||||||
|
|
||||||
@@ -130,7 +117,7 @@
|
|||||||
|
|
||||||
Текущее состояние:
|
Текущее состояние:
|
||||||
|
|
||||||
- Фреймворк теперь считает конфигурацию бота структурно завершённой после начала первого `Run()` или `RunWithContext(...)`.
|
- Фреймворк теперь считает конфигурацию бота структурно завершённой после начала первого runtime entry point: `Run()`, `RunWithContext(...)` или `RunWebHookWithContext(...)`.
|
||||||
- Поздние bot-level попытки мутации больше не применяются частично после старта runtime.
|
- Поздние bot-level попытки мутации больше не применяются частично после старта runtime.
|
||||||
- Границы между регистрацией плагинов, стартом runtime и фиксацией конфигурации теперь оформлены как явное поведение фреймворка и закреплены тестами.
|
- Границы между регистрацией плагинов, стартом runtime и фиксацией конфигурации теперь оформлены как явное поведение фреймворка и закреплены тестами.
|
||||||
|
|
||||||
@@ -258,7 +245,6 @@ func ban(ctx *laniakea.MsgContext, db *App) error {
|
|||||||
if err := ctx.BindArgs(&input); err != nil {
|
if err := ctx.BindArgs(&input); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return db.Ban(input.UserID, input.Reason)
|
return db.Ban(input.UserID, input.Reason)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -277,7 +263,7 @@ func ban(ctx *laniakea.MsgContext, db *App) error {
|
|||||||
|
|
||||||
Текущее состояние:
|
Текущее состояние:
|
||||||
|
|
||||||
- `RunWithContext(...)` управляет жизненным циклом выполнения бота и корректным завершением.
|
- `RunWithContext(...)` и `RunWebHookWithContext(...)` управляют жизненным циклом выполнения бота и корректным завершением.
|
||||||
- `tgapi` уже поддерживает методы, принимающие `context.Context`.
|
- `tgapi` уже поддерживает методы, принимающие `context.Context`.
|
||||||
- Обычные обработчики не получают полноценный `context.Context`, привязанный к обработке конкретного запроса.
|
- Обычные обработчики не получают полноценный `context.Context`, привязанный к обработке конкретного запроса.
|
||||||
|
|
||||||
@@ -314,3 +300,43 @@ func ban(ctx *laniakea.MsgContext, db *App) error {
|
|||||||
- [[MsgContext]]
|
- [[MsgContext]]
|
||||||
- [[Bot-Lifecycle]]
|
- [[Bot-Lifecycle]]
|
||||||
- [[Migration]]
|
- [[Migration]]
|
||||||
|
|
||||||
|
## Ideas
|
||||||
|
|
||||||
|
Пункты в этом разделе намеренно остаются теоретическими. Они могут позже превратиться в реальные задачи беклога, а могут так и остаться design notes, если текущей модели фреймворка окажется достаточно.
|
||||||
|
|
||||||
|
### Модель сервисного слоя и графа зависимостей
|
||||||
|
|
||||||
|
Текущее состояние:
|
||||||
|
|
||||||
|
- `SetAppData(...)` уже даёт фреймворку простую модель общих зависимостей через generic-параметр типа бота.
|
||||||
|
- Эта модель намеренно лёгкая и хорошо подходит ботам, которым нужен только небольшой набор долгоживущих общих зависимостей.
|
||||||
|
|
||||||
|
Почему это пока только идея:
|
||||||
|
|
||||||
|
- В репозитории сейчас нет достаточно сильных признаков того, что нужен более тяжёлый service container или scoped dependency model.
|
||||||
|
- Framework-owned граф зависимостей добавит сложность в API и lifecycle, поэтому такой пункт стоит переносить в активный backlog только тогда, когда повторяющееся реальное использование покажет, что `AppData` уже недостаточно.
|
||||||
|
|
||||||
|
Что это могло бы значить позже, если станет нужно:
|
||||||
|
|
||||||
|
- Полноценный service registry поверх одного общего значения `AppData`.
|
||||||
|
- Опциональную валидацию требуемых сервисов до старта runtime.
|
||||||
|
- Более явные lifecycle- или scope-правила для сервисов, которыми управляет сам фреймворк.
|
||||||
|
|
||||||
|
### Контракт композиции плагинов
|
||||||
|
|
||||||
|
Текущее состояние:
|
||||||
|
|
||||||
|
- Плагины уже не являются просто "мешком команд": регистрация через `AddPlugins(...)` делает snapshot plugin state, считает регистрацию точкой фиксации и документирует post-registration mutation как неподдерживаемую.
|
||||||
|
- Это уже даёт фреймворку осмысленный базовый контракт вокруг владения конфигурацией плагина и его неизменяемости во время runtime.
|
||||||
|
|
||||||
|
Почему это пока только идея:
|
||||||
|
|
||||||
|
- Текущее состояние репозитория пока не показывает достаточно сильного давления в сторону более богатой framework-level модели зависимостей или capabilities между плагинами.
|
||||||
|
- Дополнительный composition contract увеличит API surface и число правил валидации, поэтому его стоит держать как опциональное направление дизайна, пока реальные сценарии повторяющихся взаимодействий между плагинами не покажут, что он действительно нужен.
|
||||||
|
|
||||||
|
Что это могло бы значить позже, если станет нужно:
|
||||||
|
|
||||||
|
- Явные зависимости между плагинами.
|
||||||
|
- Декларации общих возможностей или требований между плагинами.
|
||||||
|
- Модель композиции уровня фреймворка для валидации и координации отношений между плагинами.
|
||||||
|
|||||||
+61
-35
@@ -2,49 +2,36 @@
|
|||||||
|
|
||||||
This page tracks framework-level backlog items that are about missing concepts in the library itself, not just missing documentation.
|
This page tracks framework-level backlog items that are about missing concepts in the library itself, not just missing documentation.
|
||||||
|
|
||||||
## Priority 2 — Important
|
## Done
|
||||||
|
|
||||||
- Service layer and dependency graph model: `DatabaseContext(T)` is intentionally minimal, but there is no stronger framework concept for application services or scoped dependencies.
|
### [1.0.0-rc.14] Webhook runtime model
|
||||||
|
|
||||||
## Partial
|
|
||||||
|
|
||||||
### Webhook runtime model
|
|
||||||
|
|
||||||
Current state:
|
Current state:
|
||||||
|
|
||||||
- The repository already exposes low-level Telegram webhook setup APIs through `tgapi`, including `SetWebhook(...)`, `DeleteWebhook(...)`, `GetWebhookInfo(...)`, and uploader-based certificate upload support.
|
- The repository already exposes low-level Telegram webhook setup APIs through `tgapi`, including `SetWebhook(...)`, `DeleteWebhook(...)`, `GetWebhookInfo(...)`, and uploader-based certificate upload support.
|
||||||
- The main bot runtime is still polling-first and only exposes `Run()` / `RunWithContext(...)` around `getUpdates`.
|
- The framework now exposes first-class bot-level webhook runtime entry points through `RunWebHookWithContext(...)` and `RunWebHook(...)`.
|
||||||
|
- Webhook delivery now uses the same internal update queue, worker pool, runner startup model, and single-use runtime contract as polling.
|
||||||
|
- The webhook runtime behavior, security model, and polling-transition requirements are now documented in the main docs and wiki.
|
||||||
|
|
||||||
What is still missing:
|
Why this matters:
|
||||||
|
|
||||||
- A first-class bot-level webhook execution model such as `RunWebhook(...)`, an `http.Handler`, or another explicit framework-owned ingestion path for incoming webhook updates.
|
- Telegram webhook transport support at the API-client layer was not enough on its own; users still needed a framework-owned runtime path to make webhook delivery feel equivalent to polling.
|
||||||
- A documented lifecycle contract for webhook mode that matches the existing polling runtime model.
|
- A first-class runtime mode keeps worker scheduling, lifecycle behavior, configuration rules, and shutdown semantics aligned across both ingress models.
|
||||||
- A unified way to feed webhook-delivered updates through the same framework runtime boundaries without requiring users to assemble their own outer HTTP integration layer.
|
- The framework also needs an explicit transition story when users move a deployment back from webhook delivery to polling.
|
||||||
|
|
||||||
Why this is only partial:
|
What is now present:
|
||||||
|
|
||||||
- Telegram webhook transport support exists at the API client level.
|
- `BotWebHookOpts`, `NewBotWebHookOpts()`, and fluent helpers for webhook-specific configuration.
|
||||||
- The framework itself still does not provide a webhook runtime concept comparable to its polling execution model.
|
- `RunWebHookWithContext(...)` and `RunWebHook(...)` as bot-owned runtime entry points.
|
||||||
|
- Shared queued update dispatch, worker-pool delivery, runner startup, and single-use run semantics between polling and webhook modes.
|
||||||
|
- Default fallback from webhook `AllowedUpdates` to the bot-level update type configuration.
|
||||||
|
- Request validation for webhook path shape and TLS file count before remote webhook setup.
|
||||||
|
- Explicit remote webhook teardown through `CloseWebHook()` or low-level `tgapi.DeleteWebhook(...)` when switching a deployment from webhook delivery back to polling.
|
||||||
|
- Regression coverage for queue delivery, runner startup, single-use behavior, body-size rejection, path and TLS validation, and status-endpoint auth behavior.
|
||||||
|
|
||||||
### Plugin composition contract
|
Practical target:
|
||||||
|
|
||||||
Current state:
|
- Treat webhook delivery as a first-class framework runtime mode rather than leaving it as only a low-level `tgapi` transport primitive.
|
||||||
|
|
||||||
- Plugins are already more than a loose command bag: registration via `AddPlugins(...)` snapshots plugin state, treats registration as a commit point, and documents post-registration mutation as unsupported.
|
|
||||||
- This gives the framework a meaningful baseline contract around plugin ownership and immutability at runtime.
|
|
||||||
|
|
||||||
What is still missing:
|
|
||||||
|
|
||||||
- Explicit plugin dependencies.
|
|
||||||
- Shared capability declarations or requirements between plugins.
|
|
||||||
- A framework-level composition model for validating or coordinating plugin relationships.
|
|
||||||
|
|
||||||
Why this is only partial:
|
|
||||||
|
|
||||||
- The repository already has a real registration contract.
|
|
||||||
- It still does not have the stronger composition model originally described in the backlog item.
|
|
||||||
|
|
||||||
## Done
|
|
||||||
|
|
||||||
### [1.0.0-rc.13] Observability Model
|
### [1.0.0-rc.13] Observability Model
|
||||||
|
|
||||||
@@ -130,7 +117,7 @@ Practical target:
|
|||||||
|
|
||||||
Current state:
|
Current state:
|
||||||
|
|
||||||
- The framework now treats bot configuration as structurally complete once the first `Run()` or `RunWithContext(...)` begins.
|
- The framework now treats bot configuration as structurally complete once the first runtime entry point begins: `Run()`, `RunWithContext(...)`, or `RunWebHookWithContext(...)`.
|
||||||
- Late bot-level mutation attempts no longer partially apply after runtime startup.
|
- Late bot-level mutation attempts no longer partially apply after runtime startup.
|
||||||
- Plugin registration and runtime configuration boundaries are now documented and tested as explicit framework behavior.
|
- Plugin registration and runtime configuration boundaries are now documented and tested as explicit framework behavior.
|
||||||
|
|
||||||
@@ -258,7 +245,6 @@ func ban(ctx *laniakea.MsgContext, db *App) error {
|
|||||||
if err := ctx.BindArgs(&input); err != nil {
|
if err := ctx.BindArgs(&input); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return db.Ban(input.UserID, input.Reason)
|
return db.Ban(input.UserID, input.Reason)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -277,7 +263,7 @@ Practical target:
|
|||||||
|
|
||||||
Current state:
|
Current state:
|
||||||
|
|
||||||
- `RunWithContext(...)` controls bot runtime lifecycle and graceful shutdown.
|
- `RunWithContext(...)` and `RunWebHookWithContext(...)` control bot runtime lifecycle and graceful shutdown.
|
||||||
- `tgapi` already supports context-aware methods.
|
- `tgapi` already supports context-aware methods.
|
||||||
- Regular handlers do not receive a first-class request-scoped `context.Context`.
|
- Regular handlers do not receive a first-class request-scoped `context.Context`.
|
||||||
|
|
||||||
@@ -314,3 +300,43 @@ Related pages:
|
|||||||
- [[MsgContext]]
|
- [[MsgContext]]
|
||||||
- [[Bot-Lifecycle]]
|
- [[Bot-Lifecycle]]
|
||||||
- [[Migration]]
|
- [[Migration]]
|
||||||
|
|
||||||
|
## Ideas
|
||||||
|
|
||||||
|
Items in this section are intentionally speculative. They may become real backlog work later, or they may remain design notes if the current framework model continues to be sufficient.
|
||||||
|
|
||||||
|
### Service layer and dependency graph model
|
||||||
|
|
||||||
|
Current state:
|
||||||
|
|
||||||
|
- `SetAppData(...)` already gives the framework a simple shared dependency model through the generic bot type parameter.
|
||||||
|
- That model is intentionally lightweight and works well for bots that only need a small set of long-lived shared dependencies.
|
||||||
|
|
||||||
|
Why this is only an idea for now:
|
||||||
|
|
||||||
|
- The repository does not currently show strong evidence that a heavier service container or scoped dependency model is necessary.
|
||||||
|
- A framework-owned dependency graph would add API and lifecycle complexity, so it should only move into the active backlog if repeated real-world usage shows that `AppData` is not enough.
|
||||||
|
|
||||||
|
What this could mean later if it becomes necessary:
|
||||||
|
|
||||||
|
- A first-class service registry beyond a single shared `AppData` value.
|
||||||
|
- Optional validation of required services before runtime startup.
|
||||||
|
- Clearer lifecycle or scoping rules for shared framework-managed services.
|
||||||
|
|
||||||
|
### Plugin composition contract
|
||||||
|
|
||||||
|
Current state:
|
||||||
|
|
||||||
|
- Plugins are already more than a loose command bag: registration via `AddPlugins(...)` snapshots plugin state, treats registration as a commit point, and documents post-registration mutation as unsupported.
|
||||||
|
- This gives the framework a meaningful baseline contract around plugin ownership and immutability at runtime.
|
||||||
|
|
||||||
|
Why this is only an idea for now:
|
||||||
|
|
||||||
|
- The current repository state does not yet show strong pressure for a richer framework-level plugin dependency or capability model.
|
||||||
|
- Additional composition contracts would add API surface and validation rules, so they should remain optional design work until repeated real-world plugin interactions justify them.
|
||||||
|
|
||||||
|
What this could mean later if it becomes necessary:
|
||||||
|
|
||||||
|
- Explicit plugin dependencies.
|
||||||
|
- Shared capability declarations or requirements between plugins.
|
||||||
|
- A framework-level composition model for validating or coordinating plugin relationships.
|
||||||
|
|||||||
+15
-13
@@ -8,12 +8,12 @@ English version: [[Getting-Started]]
|
|||||||
|
|
||||||
- Go 1.24 или новее
|
- Go 1.24 или новее
|
||||||
- токен Telegram-бота от `@BotFather`
|
- токен Telegram-бота от `@BotFather`
|
||||||
- Go-модуль, который может импортировать `git.nix13.pw/scuroneko/laniakea`
|
- Go-модуль, который может импортировать `git.scuroneko.dev/scuroneko/laniakea`
|
||||||
|
|
||||||
Установка:
|
Установка:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go get git.nix13.pw/scuroneko/laniakea
|
go get git.scuroneko.dev/scuroneko/laniakea
|
||||||
```
|
```
|
||||||
|
|
||||||
или:
|
или:
|
||||||
@@ -30,16 +30,16 @@ package main
|
|||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"git.nix13.pw/scuroneko/laniakea"
|
"git.scuroneko.dev/scuroneko/laniakea"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ping(ctx *laniakea.MsgContext, db laniakea.NoDB) error {
|
func ping(ctx *laniakea.MsgContext, db laniakea.NoData) error {
|
||||||
ctx.Answer("Pong")
|
ctx.Answer("Pong")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
bot, err := laniakea.NewBot[laniakea.NoDB](&laniakea.BotOpts{
|
bot, err := laniakea.NewBot[laniakea.NoData](&laniakea.BotOpts{
|
||||||
Token: "TOKEN",
|
Token: "TOKEN",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -47,7 +47,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
defer bot.Close()
|
defer bot.Close()
|
||||||
|
|
||||||
plugin := laniakea.NewPlugin[laniakea.NoDB]("main")
|
plugin := laniakea.NewPlugin[laniakea.NoData]("main")
|
||||||
plugin.AddCommand(plugin.NewCommand(ping, "ping"))
|
plugin.AddCommand(plugin.NewCommand(ping, "ping"))
|
||||||
|
|
||||||
bot.AddPlugins(plugin)
|
bot.AddPlugins(plugin)
|
||||||
@@ -67,7 +67,7 @@ func main() {
|
|||||||
Параметр `T` — это общий контекст зависимостей, который попадает в хендлеры, middleware и runners.
|
Параметр `T` — это общий контекст зависимостей, который попадает в хендлеры, middleware и runners.
|
||||||
|
|
||||||
Используй:
|
Используй:
|
||||||
- `laniakea.NoDB`, если dependency injection не нужен
|
- `laniakea.NoData`, если dependency injection не нужен
|
||||||
- pointer type, например `*sql.DB`, `*Store` или `*App`, если нужен общий state
|
- pointer type, например `*sql.DB`, `*Store` или `*App`, если нужен общий state
|
||||||
|
|
||||||
Пример:
|
Пример:
|
||||||
@@ -84,7 +84,7 @@ if err != nil {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
bot.DatabaseContext(app)
|
bot.SetAppData(app)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Команды живут внутри плагинов
|
### 2. Команды живут внутри плагинов
|
||||||
@@ -99,7 +99,7 @@ bot.DatabaseContext(app)
|
|||||||
Пример:
|
Пример:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
plugin := laniakea.NewPlugin[laniakea.NoDB]("admin")
|
plugin := laniakea.NewPlugin[laniakea.NoData]("admin")
|
||||||
plugin.AddCommand(plugin.NewCommand(ping, "ping"))
|
plugin.AddCommand(plugin.NewCommand(ping, "ping"))
|
||||||
bot.AddPlugins(plugin)
|
bot.AddPlugins(plugin)
|
||||||
```
|
```
|
||||||
@@ -136,7 +136,7 @@ func profile(ctx *laniakea.MsgContext, db *App) error {
|
|||||||
|
|
||||||
### 4. `Bot` single-use
|
### 4. `Bot` single-use
|
||||||
|
|
||||||
После `Run()` или `RunWithContext(...)` нельзя снова запускать тот же экземпляр `Bot`.
|
После `Run()`, `RunWithContext(...)` или `RunWebHookWithContext(...)` нельзя снова запускать тот же экземпляр `Bot`.
|
||||||
|
|
||||||
Правильная модель:
|
Правильная модель:
|
||||||
- создать bot
|
- создать bot
|
||||||
@@ -145,11 +145,13 @@ func profile(ctx *laniakea.MsgContext, db *App) error {
|
|||||||
- закрыть
|
- закрыть
|
||||||
- создать новый bot для следующего запуска
|
- создать новый bot для следующего запуска
|
||||||
|
|
||||||
|
После завершения не надо повторно вызывать никакой runtime entry point на том же экземпляре.
|
||||||
|
|
||||||
Подробности: [[Bot-Lifecycle]]
|
Подробности: [[Bot-Lifecycle]]
|
||||||
|
|
||||||
### 5. `Close()` все равно нужен
|
### 5. `Close()` все равно нужен
|
||||||
|
|
||||||
Даже если ты используешь `Run()` или `RunWithContext(...)`, ресурсы нужно закрывать явно:
|
Даже если ты используешь `Run()`, `RunWithContext(...)` или `RunWebHookWithContext(...)`, ресурсы нужно закрывать явно:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
defer bot.Close()
|
defer bot.Close()
|
||||||
@@ -166,7 +168,7 @@ defer bot.Close()
|
|||||||
5. Добавить команды, payloads и middleware в плагины
|
5. Добавить команды, payloads и middleware в плагины
|
||||||
6. Зарегистрировать плагины через `AddPlugins(...)`
|
6. Зарегистрировать плагины через `AddPlugins(...)`
|
||||||
7. При необходимости вызвать `AutoGenerateCommands()`
|
7. При необходимости вызвать `AutoGenerateCommands()`
|
||||||
8. Вызвать `Run()` или `RunWithContext(...)`
|
8. Вызвать `Run()`, `RunWithContext(...)` или `RunWebHookWithContext(...)`
|
||||||
9. Закрыть bot через `Close()`
|
9. Закрыть bot через `Close()`
|
||||||
|
|
||||||
## Частые ошибки на старте
|
## Частые ошибки на старте
|
||||||
@@ -194,7 +196,7 @@ defer bot.Close()
|
|||||||
## Куда идти дальше
|
## Куда идти дальше
|
||||||
|
|
||||||
- [[Commands-and-Plugins-RU]]
|
- [[Commands-and-Plugins-RU]]
|
||||||
- [[FAQ-RU]]
|
- [[Bot-Lifecycle-RU]]
|
||||||
- [[Getting-Started]]
|
- [[Getting-Started]]
|
||||||
- [[Commands-and-Plugins]]
|
- [[Commands-and-Plugins]]
|
||||||
- [[MsgContext]]
|
- [[MsgContext]]
|
||||||
|
|||||||
+15
-15
@@ -9,12 +9,12 @@ This page covers the shortest path to a working bot, the minimum concepts you ne
|
|||||||
## What you need first
|
## What you need first
|
||||||
- Go 1.24 or newer
|
- Go 1.24 or newer
|
||||||
- a Telegram bot token from `@BotFather`
|
- a Telegram bot token from `@BotFather`
|
||||||
- a module that can import `git.nix13.pw/scuroneko/laniakea`
|
- a module that can import `git.scuroneko.dev/scuroneko/laniakea`
|
||||||
|
|
||||||
Install the module with one of:
|
Install the module with one of:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go get git.nix13.pw/scuroneko/laniakea
|
go get git.scuroneko.dev/scuroneko/laniakea
|
||||||
```
|
```
|
||||||
|
|
||||||
or
|
or
|
||||||
@@ -31,16 +31,16 @@ package main
|
|||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"git.nix13.pw/scuroneko/laniakea"
|
"git.scuroneko.dev/scuroneko/laniakea"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ping(ctx *laniakea.MsgContext, db laniakea.NoDB) error {
|
func ping(ctx *laniakea.MsgContext, db laniakea.NoData) error {
|
||||||
ctx.Answer("Pong")
|
ctx.Answer("Pong")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
bot, err := laniakea.NewBot[laniakea.NoDB](&laniakea.BotOpts{
|
bot, err := laniakea.NewBot[laniakea.NoData](&laniakea.BotOpts{
|
||||||
Token: "TOKEN",
|
Token: "TOKEN",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -48,7 +48,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
defer bot.Close()
|
defer bot.Close()
|
||||||
|
|
||||||
plugin := laniakea.NewPlugin[laniakea.NoDB]("main")
|
plugin := laniakea.NewPlugin[laniakea.NoData]("main")
|
||||||
plugin.AddCommand(plugin.NewCommand(ping, "ping"))
|
plugin.AddCommand(plugin.NewCommand(ping, "ping"))
|
||||||
|
|
||||||
bot.AddPlugins(plugin)
|
bot.AddPlugins(plugin)
|
||||||
@@ -68,7 +68,7 @@ If the user sends `/ping`, the bot replies with `Pong`.
|
|||||||
The type parameter `T` is the shared dependency context passed into handlers, middleware, and runners.
|
The type parameter `T` is the shared dependency context passed into handlers, middleware, and runners.
|
||||||
|
|
||||||
Use:
|
Use:
|
||||||
- `laniakea.NoDB` when you do not need dependency injection
|
- `laniakea.NoData` when you do not need dependency injection
|
||||||
- a pointer type like `*sql.DB`, `*Store`, or `*App` when you do
|
- a pointer type like `*sql.DB`, `*Store`, or `*App` when you do
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
@@ -85,7 +85,7 @@ if err != nil {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
bot.DatabaseContext(app)
|
bot.SetAppData(app)
|
||||||
```
|
```
|
||||||
|
|
||||||
Pointer types are usually the right default for shared application state.
|
Pointer types are usually the right default for shared application state.
|
||||||
@@ -101,7 +101,7 @@ Laniakea does not register commands directly on the bot. The normal flow is:
|
|||||||
Example:
|
Example:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
plugin := laniakea.NewPlugin[laniakea.NoDB]("admin")
|
plugin := laniakea.NewPlugin[laniakea.NoData]("admin")
|
||||||
plugin.AddCommand(plugin.NewCommand(ping, "ping"))
|
plugin.AddCommand(plugin.NewCommand(ping, "ping"))
|
||||||
bot.AddPlugins(plugin)
|
bot.AddPlugins(plugin)
|
||||||
```
|
```
|
||||||
@@ -139,8 +139,8 @@ func profile(ctx *laniakea.MsgContext, db *App) error {
|
|||||||
|
|
||||||
A `Bot` instance is single-use.
|
A `Bot` instance is single-use.
|
||||||
|
|
||||||
After `Run()` or `RunWithContext(...)` returns:
|
After `Run()`, `RunWithContext(...)`, or `RunWebHookWithContext(...)` returns:
|
||||||
- do not call `Run()` again on the same bot
|
- do not call a runtime entry point again on the same bot
|
||||||
- create a new bot instance for the next run
|
- create a new bot instance for the next run
|
||||||
|
|
||||||
This is an intentional lifecycle rule, not a temporary limitation.
|
This is an intentional lifecycle rule, not a temporary limitation.
|
||||||
@@ -149,7 +149,7 @@ See [[Bot-Lifecycle]] for details.
|
|||||||
|
|
||||||
### 5. Always close the bot
|
### 5. Always close the bot
|
||||||
|
|
||||||
`RunWithContext(...)` and `Run()` do not replace `Close()`.
|
`Run()`, `RunWithContext(...)`, and `RunWebHookWithContext(...)` do not replace `Close()`.
|
||||||
|
|
||||||
You should still release bot-owned resources explicitly:
|
You should still release bot-owned resources explicitly:
|
||||||
|
|
||||||
@@ -168,7 +168,7 @@ For most bots, this order is the least surprising:
|
|||||||
5. Add commands, payloads, and middleware to plugins
|
5. Add commands, payloads, and middleware to plugins
|
||||||
6. Register plugins with `AddPlugins(...)`
|
6. Register plugins with `AddPlugins(...)`
|
||||||
7. Optionally call `AutoGenerateCommands()`
|
7. Optionally call `AutoGenerateCommands()`
|
||||||
8. Call `Run()` or `RunWithContext(...)`
|
8. Call `Run()`, `RunWithContext(...)`, or `RunWebHookWithContext(...)`
|
||||||
9. Call `Close()` when done
|
9. Call `Close()` when done
|
||||||
|
|
||||||
## A slightly more realistic example
|
## A slightly more realistic example
|
||||||
@@ -179,7 +179,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"git.nix13.pw/scuroneko/laniakea"
|
"git.scuroneko.dev/scuroneko/laniakea"
|
||||||
)
|
)
|
||||||
|
|
||||||
type App struct{}
|
type App struct{}
|
||||||
@@ -204,7 +204,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
defer bot.Close()
|
defer bot.Close()
|
||||||
|
|
||||||
bot.DatabaseContext(&App{})
|
bot.SetAppData(&App{})
|
||||||
|
|
||||||
plugin := laniakea.NewPlugin[*App]("main")
|
plugin := laniakea.NewPlugin[*App]("main")
|
||||||
plugin.AddCommand(plugin.NewCommand(echo, "echo"))
|
plugin.AddCommand(plugin.NewCommand(echo, "echo"))
|
||||||
|
|||||||
+3
-45
@@ -10,17 +10,17 @@ English version: [[Home]]
|
|||||||
- [[Bot-Options-and-Configuration-RU]]
|
- [[Bot-Options-and-Configuration-RU]]
|
||||||
- [[Commands-and-Plugins-RU]]
|
- [[Commands-and-Plugins-RU]]
|
||||||
- [[MsgContext-RU]]
|
- [[MsgContext-RU]]
|
||||||
- [[FAQ-RU]]
|
|
||||||
|
|
||||||
## Выполнение и архитектура
|
## Выполнение и архитектура
|
||||||
|
|
||||||
- [[Bot-Lifecycle-RU]]
|
- [[Bot-Lifecycle-RU]]
|
||||||
|
- [[Webhook-Runtime-RU]]
|
||||||
- [[Middleware-RU]]
|
- [[Middleware-RU]]
|
||||||
- [[Runners-RU]]
|
- [[Runners-RU]]
|
||||||
- [[Error-Handling-RU]]
|
- [[Error-Handling-RU]]
|
||||||
- [[Logging-RU]]
|
- [[Logging-RU]]
|
||||||
- [[Update-Routing-Model-RU]]
|
- [[Update-Routing-Model-RU]]
|
||||||
- [[Configuration-Freeze-Model-RU]]
|
- [[Policies-RU]]
|
||||||
- [[Scenes-RU]]
|
- [[Scenes-RU]]
|
||||||
|
|
||||||
## Telegram API и взаимодействие
|
## Telegram API и взаимодействие
|
||||||
@@ -37,53 +37,11 @@ English version: [[Home]]
|
|||||||
- [[Recipes-RU]]
|
- [[Recipes-RU]]
|
||||||
- [[Testing-Bots-with-Laniakea-RU]]
|
- [[Testing-Bots-with-Laniakea-RU]]
|
||||||
|
|
||||||
## Чего ещё не хватает в основных концепциях
|
|
||||||
|
|
||||||
Сейчас wiki уже покрывает почти всю основную поверхность фреймворка: `Bot`, `BotOpts`, жизненный цикл, плагины и команды, `MsgContext`, middleware, данные callback, `tgapi`, черновики, локализацию, фоновые задачи, ошибки, логирование, ограничение частоты, тестирование и миграцию.
|
|
||||||
|
|
||||||
Но если смотреть именно на концептуальные дыры, а не просто на наличие страниц, то все еще выделяются такие темы:
|
|
||||||
|
|
||||||
1. `Context-and-State-Model`
|
|
||||||
Почему это важно:
|
|
||||||
Страница про `MsgContext` уже есть, но нет отдельной концептуальной страницы про разделяемое состояние, копируемое состояние, поведение `DatabaseContext(T)` и про то, почему pointer types чаще всего являются правильным выбором по умолчанию.
|
|
||||||
Что туда войдет:
|
|
||||||
Разделяемые зависимости, копируемые значения context, ожидания во время выполнения и места, где легко ошибиться с предположениями о гонках.
|
|
||||||
|
|
||||||
2. `Plugin-Boundaries-and-Composition`
|
|
||||||
Почему это важно:
|
|
||||||
Wiki уже объясняет, как использовать плагины на практике, но почти не говорит о том, как о них думать архитектурно.
|
|
||||||
Что туда войдет:
|
|
||||||
Как делить бот на плагины, что должно жить в middleware плагина, когда выделять новый плагин и как не прийти к дизайну одного гигантского плагина.
|
|
||||||
|
|
||||||
3. `Handler-Design-Guidelines`
|
|
||||||
Почему это важно:
|
|
||||||
Это будет страница не столько про API, сколько про стиль и идиоматичное использование фреймворка.
|
|
||||||
Что туда войдет:
|
|
||||||
Когда возвращать `error`, когда отвечать вручную, как держать обработчики тонкими, когда выносить логику в сервисный слой и как не смешивать `tgapi` и высокоуровневые вспомогательные методы без необходимости.
|
|
||||||
|
|
||||||
4. `Telegram-Limits-and-Validation`
|
|
||||||
Почему это важно:
|
|
||||||
Часть этой информации уже разбросана по страницам про ограничение частоты, данные callback и ошибки, но нет одной общей страницы с целостной моделью.
|
|
||||||
Что туда войдет:
|
|
||||||
Лимиты на текст сообщения, подписи и callback data, валидация до отправки, длинные ответы, особенности Markdown и ограничения, связанные с загрузкой файлов.
|
|
||||||
|
|
||||||
Менее срочные, но тоже полезные темы:
|
|
||||||
- `Concurrency-Model`
|
|
||||||
- `Bot-vs-tgapi-Decision-Guide`
|
|
||||||
- `Public-API-Stability`
|
|
||||||
|
|
||||||
Если приоритизировать, то самый полезный порядок сейчас такой:
|
|
||||||
1. `Context-and-State-Model`
|
|
||||||
2. `Plugin-Boundaries-and-Composition`
|
|
||||||
3. `Handler-Design-Guidelines`
|
|
||||||
4. `Telegram-Limits-and-Validation`
|
|
||||||
|
|
||||||
## Миграция и сопровождение
|
## Миграция и сопровождение
|
||||||
|
|
||||||
- [[Migration-RU]]
|
- [[Migration-RU]]
|
||||||
- [[Semver-and-Releases-RU]]
|
- [[Semver-and-Releases-RU]]
|
||||||
- [[Page-Priority-RU]]
|
- [[Framework-Backlog-RU]]
|
||||||
- [[FAQ-RU]]
|
|
||||||
|
|
||||||
## Полный английский справочник
|
## Полный английский справочник
|
||||||
|
|
||||||
|
|||||||
+3
-51
@@ -9,7 +9,7 @@ Use this wiki as the structured companion to the README: start with setup, then
|
|||||||
## Russian Entry Pages
|
## Russian Entry Pages
|
||||||
- [[Home-RU]]
|
- [[Home-RU]]
|
||||||
- [[Getting-Started-RU]]
|
- [[Getting-Started-RU]]
|
||||||
- [[FAQ-RU]]
|
- [[Commands-and-Plugins-RU]]
|
||||||
|
|
||||||
## Start here
|
## Start here
|
||||||
- [[Getting-Started]]
|
- [[Getting-Started]]
|
||||||
@@ -19,12 +19,13 @@ Use this wiki as the structured companion to the README: start with setup, then
|
|||||||
|
|
||||||
## Bot Runtime
|
## Bot Runtime
|
||||||
- [[Bot-Lifecycle]]
|
- [[Bot-Lifecycle]]
|
||||||
|
- [[Webhook-Runtime]]
|
||||||
- [[Middleware]]
|
- [[Middleware]]
|
||||||
- [[Runners]]
|
- [[Runners]]
|
||||||
- [[Error-Handling]]
|
- [[Error-Handling]]
|
||||||
- [[Logging]]
|
- [[Logging]]
|
||||||
- [[Update-Routing-Model]]
|
- [[Update-Routing-Model]]
|
||||||
- [[Configuration-Freeze-Model]]
|
- [[Policies]]
|
||||||
- [[Scenes]]
|
- [[Scenes]]
|
||||||
|
|
||||||
## Telegram API and Interaction
|
## Telegram API and Interaction
|
||||||
@@ -39,56 +40,7 @@ Use this wiki as the structured companion to the README: start with setup, then
|
|||||||
- [[Localization]]
|
- [[Localization]]
|
||||||
- [[Testing-Bots-with-Laniakea]]
|
- [[Testing-Bots-with-Laniakea]]
|
||||||
|
|
||||||
## Missing Core Concepts
|
|
||||||
|
|
||||||
The current wiki already covers most of the main framework surface: `Bot`, `BotOpts`, lifecycle, plugins and commands, `MsgContext`, middleware, payloads, `tgapi`, drafts, localization, runners, errors, logging, rate limiting, testing, and migration.
|
|
||||||
|
|
||||||
If we focus specifically on conceptual gaps rather than simple page presence, these topics still stand out:
|
|
||||||
|
|
||||||
1. `Context-and-State-Model`
|
|
||||||
Why it matters:
|
|
||||||
`MsgContext` exists, but there is no dedicated conceptual page about what is shared state, what is copied state, how `DatabaseContext(T)` behaves, and why pointer types are usually the right default.
|
|
||||||
What it would cover:
|
|
||||||
Shared dependencies, copied context values, runtime expectations, and where race assumptions can go wrong.
|
|
||||||
|
|
||||||
2. `Plugin-Boundaries-and-Composition`
|
|
||||||
Why it matters:
|
|
||||||
The wiki explains how plugins work in practice, but not yet how to think architecturally about plugin boundaries.
|
|
||||||
What it would cover:
|
|
||||||
How to split a bot into plugins, what belongs in plugin middleware, when to extract a new plugin, and how to avoid the giant-plugin anti-pattern.
|
|
||||||
|
|
||||||
3. `Handler-Design-Guidelines`
|
|
||||||
Why it matters:
|
|
||||||
This would explain framework style rather than raw API surface.
|
|
||||||
What it would cover:
|
|
||||||
When to return `error`, when to answer manually, how to keep handlers thin, when to push logic into services, and how to avoid mixing `tgapi` with high-level helpers unnecessarily.
|
|
||||||
|
|
||||||
4. `Telegram-Limits-and-Validation`
|
|
||||||
Why it matters:
|
|
||||||
Some of this exists in rate limiting, payload, and error docs, but not as one mental-model page.
|
|
||||||
What it would cover:
|
|
||||||
Message length, caption length, callback data size, validation-before-send behavior, long replies, Markdown caveats, and upload-related limits.
|
|
||||||
|
|
||||||
Less urgent, but still useful later:
|
|
||||||
- `Concurrency-Model`
|
|
||||||
- `Bot-vs-tgapi-Decision-Guide`
|
|
||||||
- `Public-API-Stability`
|
|
||||||
|
|
||||||
If these are prioritized, the most useful order is:
|
|
||||||
1. `Context-and-State-Model`
|
|
||||||
2. `Plugin-Boundaries-and-Composition`
|
|
||||||
3. `Handler-Design-Guidelines`
|
|
||||||
4. `Telegram-Limits-and-Validation`
|
|
||||||
|
|
||||||
## Migration and Maintenance
|
## Migration and Maintenance
|
||||||
- [[Migration]]
|
- [[Migration]]
|
||||||
- [[FAQ]]
|
|
||||||
- [[Semver-and-Releases]]
|
- [[Semver-and-Releases]]
|
||||||
- [[Page-Priority]]
|
|
||||||
|
|
||||||
## Additional topics
|
|
||||||
- [[Drafts]]
|
|
||||||
- [[Framework-Backlog]]
|
- [[Framework-Backlog]]
|
||||||
- [[Localization]]
|
|
||||||
- [[Rate-Limiting]]
|
|
||||||
- [[Recipes]]
|
|
||||||
|
|||||||
+3
-3
@@ -9,7 +9,7 @@ English version: [[Localization]]
|
|||||||
Localization в Laniakea строится вокруг `L10n`:
|
Localization в Laniakea строится вокруг `L10n`:
|
||||||
- создаешь store с fallback language;
|
- создаешь store с fallback language;
|
||||||
- добавляешь переводы по ключам;
|
- добавляешь переводы по ключам;
|
||||||
- подключаешь store к bot через `AddL10n(...)`;
|
- подключаешь store к bot через `SetL10n(...)`;
|
||||||
- внутри handler'ов используешь `ctx.Translate(key)`.
|
- внутри handler'ов используешь `ctx.Translate(key)`.
|
||||||
|
|
||||||
## Как создать словарь
|
## Как создать словарь
|
||||||
@@ -49,14 +49,14 @@ l10n.AddDictEntry("greeting", laniakea.DictEntry{
|
|||||||
## Подключение к bot
|
## Подключение к bot
|
||||||
|
|
||||||
```go
|
```go
|
||||||
bot.AddL10n(l10n)
|
bot.SetL10n(l10n)
|
||||||
```
|
```
|
||||||
|
|
||||||
После этого доступны:
|
После этого доступны:
|
||||||
- `bot.L10n(lang, key)`
|
- `bot.L10n(lang, key)`
|
||||||
- `ctx.Translate(key)`
|
- `ctx.Translate(key)`
|
||||||
|
|
||||||
Если вызвать `AddL10n(nil)`, бот залогирует предупреждение и оставит текущий provider без изменений.
|
Если вызвать `SetL10n(nil)`, бот залогирует предупреждение и оставит текущий provider без изменений.
|
||||||
|
|
||||||
## `ctx.Translate(...)`
|
## `ctx.Translate(...)`
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -9,7 +9,7 @@ Localization in Laniakea is centered around `L10n`, a small key-based translatio
|
|||||||
The localization flow has three main parts:
|
The localization flow has three main parts:
|
||||||
- create an `L10n` store with a fallback language;
|
- create an `L10n` store with a fallback language;
|
||||||
- add translation entries keyed by stable identifiers;
|
- add translation entries keyed by stable identifiers;
|
||||||
- attach the store to the bot with `AddL10n(...)`.
|
- attach the store to the bot with `SetL10n(...)`.
|
||||||
|
|
||||||
Inside handlers, the most convenient lookup is `ctx.Translate(key)`.
|
Inside handlers, the most convenient lookup is `ctx.Translate(key)`.
|
||||||
|
|
||||||
@@ -69,17 +69,17 @@ l10n.Translate("es", "unknown") // "unknown"
|
|||||||
|
|
||||||
## Attaching localization to the bot
|
## Attaching localization to the bot
|
||||||
|
|
||||||
After building the store, attach it with `Bot.AddL10n(...)`.
|
After building the store, attach it with `Bot.SetL10n(...)`.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
bot.AddL10n(l10n)
|
bot.SetL10n(l10n)
|
||||||
```
|
```
|
||||||
|
|
||||||
From then on:
|
From then on:
|
||||||
- `bot.L10n(lang, key)` is available for manual lookups;
|
- `bot.L10n(lang, key)` is available for manual lookups;
|
||||||
- `MsgContext.Translate(key)` becomes the ergonomic handler-level helper.
|
- `MsgContext.Translate(key)` becomes the ergonomic handler-level helper.
|
||||||
|
|
||||||
If `AddL10n(nil)` is called, the bot logs a warning and keeps the existing localization provider unchanged.
|
If `SetL10n(nil)` is called, the bot logs a warning and keeps the existing localization provider unchanged.
|
||||||
|
|
||||||
## `MsgContext.Translate`
|
## `MsgContext.Translate`
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -74,9 +74,9 @@ logger := bot.GetLogger()
|
|||||||
|
|
||||||
Если создание файлового логгера не удалось, бот не падает, а остается на логировании в stdout.
|
Если создание файлового логгера не удалось, бот не падает, а остается на логировании в stdout.
|
||||||
|
|
||||||
## `AddDatabaseLoggerWriter(...)`
|
## `AddAppDataLoggerWriter(...)`
|
||||||
|
|
||||||
Этот метод позволяет прикрепить writer, полученный из DB/shared context, сразу к нескольким уровням логирования.
|
Этот метод позволяет прикрепить writer, полученный из app data/shared context, сразу к нескольким уровням логирования.
|
||||||
|
|
||||||
Он добавляется в:
|
Он добавляется в:
|
||||||
- логгер бота;
|
- логгер бота;
|
||||||
@@ -85,7 +85,7 @@ logger := bot.GetLogger()
|
|||||||
- уже зарегистрированные логгеры плагинов.
|
- уже зарегистрированные логгеры плагинов.
|
||||||
|
|
||||||
Важно:
|
Важно:
|
||||||
- сначала должен быть задан `DatabaseContext(...)`;
|
- сначала должен быть задан `SetAppData(...)`;
|
||||||
- если хочешь, чтобы логгеры плагинов точно получили writer, вызывай метод после `AddPlugins(...)`.
|
- если хочешь, чтобы логгеры плагинов точно получили writer, вызывай метод после `AddPlugins(...)`.
|
||||||
|
|
||||||
## Что читать дальше
|
## Что читать дальше
|
||||||
|
|||||||
+8
-8
@@ -103,9 +103,9 @@ The helper constructors in `utils` apply a shared logging policy:
|
|||||||
|
|
||||||
This gives the library a consistent baseline logging style across components.
|
This gives the library a consistent baseline logging style across components.
|
||||||
|
|
||||||
## Database logger writers
|
## App-data logger writers
|
||||||
|
|
||||||
`AddDatabaseLoggerWriter(...)` lets you attach a writer derived from your database or shared dependency context to multiple loggers at once.
|
`AddAppDataLoggerWriter(...)` lets you attach a writer derived from your app data or shared dependency context to multiple loggers at once.
|
||||||
|
|
||||||
When it succeeds, the writer is attached to:
|
When it succeeds, the writer is attached to:
|
||||||
- the main bot logger;
|
- the main bot logger;
|
||||||
@@ -115,9 +115,9 @@ When it succeeds, the writer is attached to:
|
|||||||
|
|
||||||
Important nuance:
|
Important nuance:
|
||||||
- call it after `AddPlugins(...)` if plugin loggers should receive the writer;
|
- call it after `AddPlugins(...)` if plugin loggers should receive the writer;
|
||||||
- plugins registered later do not automatically inherit previously attached database writers.
|
- plugins registered later do not automatically inherit previously attached app-data writers.
|
||||||
|
|
||||||
If the database context is unset or nil, the method logs a warning and skips the writer.
|
If app data is unset or nil, the method logs a warning and skips the writer.
|
||||||
|
|
||||||
## Plugin logger customization
|
## Plugin logger customization
|
||||||
|
|
||||||
@@ -165,12 +165,12 @@ plugin := laniakea.NewPlugin[*App]("admin")
|
|||||||
plugin.SetLogger(customLogger)
|
plugin.SetLogger(customLogger)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Attach database-backed logging
|
### Attach app-data-backed logging
|
||||||
|
|
||||||
```go
|
```go
|
||||||
bot.DatabaseContext(db)
|
bot.SetAppData(db)
|
||||||
bot.AddPlugins(plugin)
|
bot.AddPlugins(plugin)
|
||||||
bot.AddDatabaseLoggerWriter(func(db *sql.DB) slog.LoggerWriter {
|
bot.AddAppDataLoggerWriter(func(db *sql.DB) slog.LoggerWriter {
|
||||||
return newDBWriter(db)
|
return newDBWriter(db)
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
@@ -180,7 +180,7 @@ bot.AddDatabaseLoggerWriter(func(db *sql.DB) slog.LoggerWriter {
|
|||||||
- Use `ctx.Logger` inside handlers instead of reaching for global loggers directly.
|
- Use `ctx.Logger` inside handlers instead of reaching for global loggers directly.
|
||||||
- Enable request logging when debugging update routing, then turn it back down.
|
- Enable request logging when debugging update routing, then turn it back down.
|
||||||
- Treat plugin loggers as a useful boundary for large bots with multiple modules.
|
- Treat plugin loggers as a useful boundary for large bots with multiple modules.
|
||||||
- Call `AddDatabaseLoggerWriter(...)` only after the bot has a valid database context.
|
- Call `AddAppDataLoggerWriter(...)` only after the bot has valid app data.
|
||||||
|
|
||||||
## Related pages
|
## Related pages
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -67,5 +67,5 @@ English version: [[Migration]]
|
|||||||
## Что читать дальше
|
## Что читать дальше
|
||||||
|
|
||||||
- [[Semver-and-Releases-RU]]
|
- [[Semver-and-Releases-RU]]
|
||||||
- [[FAQ-RU]]
|
- [[Bot-Lifecycle-RU]]
|
||||||
- [[Migration]]
|
- [[Migration]]
|
||||||
|
|||||||
+2
-2
@@ -68,7 +68,7 @@ If your callback payloads relied on tolerant decoding, be aware that enabling st
|
|||||||
- `Run()` and `RunWithContext(ctx)` now return `error`.
|
- `Run()` and `RunWithContext(ctx)` now return `error`.
|
||||||
- `Bot` instances became explicitly single-use.
|
- `Bot` instances became explicitly single-use.
|
||||||
- Handler dependency typing changed from forced `*T` usage to consistent `T`.
|
- Handler dependency typing changed from forced `*T` usage to consistent `T`.
|
||||||
- `DatabaseContext(...)`, `GetDBContext()`, and `DbLogger[T]` were updated to that `T`-based model.
|
- The old `DatabaseContext(...)`, `GetDBContext()`, and `DbLogger[T]` names were replaced by the app-data-based `SetAppData(...)`, `GetAppData()`, and `AppDataLogger[T]` model.
|
||||||
- `Plugin.AddUpdateHandler(...)` was introduced for non-command update routing.
|
- `Plugin.AddUpdateHandler(...)` was introduced for non-command update routing.
|
||||||
- Builder helpers such as `NewMiddleware(...)` now return values instead of pointers.
|
- Builder helpers such as `NewMiddleware(...)` now return values instead of pointers.
|
||||||
- Plugin registration now snapshots plugin state at `AddPlugins(...)`.
|
- Plugin registration now snapshots plugin state at `AddPlugins(...)`.
|
||||||
@@ -98,7 +98,7 @@ If your bot type used `Bot[MyDB]` while handlers expected `*MyDB`, update the ge
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
bot, err := laniakea.NewBot[*sql.DB](opts)
|
bot, err := laniakea.NewBot[*sql.DB](opts)
|
||||||
bot.DatabaseContext(db)
|
bot.SetAppData(db)
|
||||||
```
|
```
|
||||||
|
|
||||||
If you mutated plugins after `AddPlugins(...)`, stop doing that. Register commands, payloads, middleware, logger configuration, and `OnClose` hooks before handing the plugin to the bot.
|
If you mutated plugins after `AddPlugins(...)`, stop doing that. Register commands, payloads, middleware, logger configuration, and `OnClose` hooks before handing the plugin to the bot.
|
||||||
|
|||||||
+8
-8
@@ -63,7 +63,7 @@ Use `FromID` when you only need the identifier and do not want to keep checking
|
|||||||
Use `Answer(...)` for the normal “reply with text” case.
|
Use `Answer(...)` for the normal “reply with text” case.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func start(ctx *laniakea.MsgContext, db laniakea.NoDB) error {
|
func start(ctx *laniakea.MsgContext, db laniakea.NoData) error {
|
||||||
ctx.Answer("Welcome")
|
ctx.Answer("Welcome")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,7 @@ This is the default high-level reply helper for plain text.
|
|||||||
Use `AnswerLong(...)` when plain text may exceed Telegram’s message limit.
|
Use `AnswerLong(...)` when plain text may exceed Telegram’s message limit.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func help(ctx *laniakea.MsgContext, db laniakea.NoDB) error {
|
func help(ctx *laniakea.MsgContext, db laniakea.NoData) error {
|
||||||
ctx.AnswerLong(buildLargeHelpText())
|
ctx.AnswerLong(buildLargeHelpText())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -92,7 +92,7 @@ Important:
|
|||||||
Use `Keyboard(...)` when you want to send a message with an inline keyboard.
|
Use `Keyboard(...)` when you want to send a message with an inline keyboard.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func menu(ctx *laniakea.MsgContext, db laniakea.NoDB) error {
|
func menu(ctx *laniakea.MsgContext, db laniakea.NoData) error {
|
||||||
kb := ctx.NewInlineKeyboard(2).
|
kb := ctx.NewInlineKeyboard(2).
|
||||||
AddCallbackButton("Profile", "profile.open").
|
AddCallbackButton("Profile", "profile.open").
|
||||||
AddCallbackButton("Settings", "settings.open")
|
AddCallbackButton("Settings", "settings.open")
|
||||||
@@ -127,7 +127,7 @@ Use `laniakea.EscapeMarkdownV2(...)` for this.
|
|||||||
Example:
|
Example:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func whoami(ctx *laniakea.MsgContext, db laniakea.NoDB) error {
|
func whoami(ctx *laniakea.MsgContext, db laniakea.NoData) error {
|
||||||
name := laniakea.EscapeMarkdownV2(ctx.From.FirstName)
|
name := laniakea.EscapeMarkdownV2(ctx.From.FirstName)
|
||||||
ctx.AnswerMarkdown("*User:* " + name)
|
ctx.AnswerMarkdown("*User:* " + name)
|
||||||
return nil
|
return nil
|
||||||
@@ -141,7 +141,7 @@ Once you already have an `AnswerMessage`, you can edit or delete it.
|
|||||||
Example:
|
Example:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func slowTask(ctx *laniakea.MsgContext, db laniakea.NoDB) error {
|
func slowTask(ctx *laniakea.MsgContext, db laniakea.NoData) error {
|
||||||
msg := ctx.Answer("Working...")
|
msg := ctx.Answer("Working...")
|
||||||
if msg == nil {
|
if msg == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -173,7 +173,7 @@ When handling inline button callbacks, these helpers are especially useful.
|
|||||||
Edits the callback-linked message.
|
Edits the callback-linked message.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func approve(ctx *laniakea.MsgContext, db laniakea.NoDB) error {
|
func approve(ctx *laniakea.MsgContext, db laniakea.NoData) error {
|
||||||
ctx.EditCallback("Approved", nil)
|
ctx.EditCallback("Approved", nil)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -192,7 +192,7 @@ Use:
|
|||||||
Example:
|
Example:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func approve(ctx *laniakea.MsgContext, db laniakea.NoDB) error {
|
func approve(ctx *laniakea.MsgContext, db laniakea.NoData) error {
|
||||||
ctx.AnswerCbQueryText("Saved")
|
ctx.AnswerCbQueryText("Saved")
|
||||||
ctx.EditCallback("Saved", nil)
|
ctx.EditCallback("Saved", nil)
|
||||||
return nil
|
return nil
|
||||||
@@ -246,7 +246,7 @@ This looks up text using the current user’s language when available and falls
|
|||||||
Example:
|
Example:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func ping(ctx *laniakea.MsgContext, db laniakea.NoDB) error {
|
func ping(ctx *laniakea.MsgContext, db laniakea.NoData) error {
|
||||||
ctx.Answer(ctx.Translate("ping.answer"))
|
ctx.Answer(ctx.Translate("ping.answer"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
# Page Priority RU
|
|
||||||
|
|
||||||
English version: [[Page-Priority]]
|
|
||||||
|
|
||||||
Это краткая русскоязычная версия maintenance-страницы про приоритеты wiki. Полная и наиболее актуальная страница: [[Page-Priority]].
|
|
||||||
|
|
||||||
## Зачем нужна эта страница
|
|
||||||
|
|
||||||
Когда wiki уже в целом заполнена, вопрос обычно не в том, “чего совсем нет”, а в том:
|
|
||||||
- какие страницы важнее поддерживать свежими;
|
|
||||||
- где чаще всего появляются расхождения с кодом;
|
|
||||||
- что надо обновлять первым при API-изменениях.
|
|
||||||
|
|
||||||
## Самые приоритетные страницы
|
|
||||||
|
|
||||||
В первую очередь стоит поддерживать в актуальном состоянии:
|
|
||||||
- `Getting-Started`
|
|
||||||
- `Commands-and-Plugins`
|
|
||||||
- `MsgContext`
|
|
||||||
- `Bot-Lifecycle`
|
|
||||||
- `Inline-Keyboards-and-Payloads`
|
|
||||||
- `Bot-Options-and-Configuration`
|
|
||||||
|
|
||||||
Именно эти страницы чаще всего читают новички и именно они сильнее всего влияют на first-run experience.
|
|
||||||
|
|
||||||
## Средний приоритет
|
|
||||||
|
|
||||||
Далее идут страницы, которые важны для уже работающих пользователей:
|
|
||||||
- `Middleware`
|
|
||||||
- `Error-Handling`
|
|
||||||
- `Runners`
|
|
||||||
- `Localization`
|
|
||||||
- `Rate-Limiting`
|
|
||||||
- `Drafts`
|
|
||||||
- `Logging`
|
|
||||||
|
|
||||||
## Более редкие, но все еще важные
|
|
||||||
|
|
||||||
- `Migration`
|
|
||||||
- `Semver-and-Releases`
|
|
||||||
- `Testing-Bots-with-Laniakea`
|
|
||||||
- `tgapi-Overview`
|
|
||||||
- `Auto-Generated-Commands`
|
|
||||||
|
|
||||||
Эти страницы не всегда нужны с первого дня, но становятся критичными при развитии проекта и релизном процессе.
|
|
||||||
|
|
||||||
## Практический maintenance-подход
|
|
||||||
|
|
||||||
При изменении кода сначала проверяй:
|
|
||||||
1. страницы core API;
|
|
||||||
2. страницы про поведение во время выполнения;
|
|
||||||
3. advanced и maintenance pages.
|
|
||||||
|
|
||||||
## Что читать дальше
|
|
||||||
|
|
||||||
- [[Home-RU]]
|
|
||||||
- [[Page-Priority]]
|
|
||||||
-45
@@ -1,45 +0,0 @@
|
|||||||
# Page Priority
|
|
||||||
|
|
||||||
Russian version: [[Page-Priority-RU]]
|
|
||||||
|
|
||||||
This page tracks maintenance priority for the wiki now that the core page set is in place. Use it to decide where future edits, expansions, and API-alignment work should land first.
|
|
||||||
|
|
||||||
## Priority 1
|
|
||||||
- [[Getting-Started]]
|
|
||||||
- [[Bot-Options-and-Configuration]]
|
|
||||||
- [[Commands-and-Plugins]]
|
|
||||||
- [[MsgContext]]
|
|
||||||
- [[Inline-Keyboards-and-Payloads]]
|
|
||||||
- [[tgapi-Overview]]
|
|
||||||
|
|
||||||
## Priority 2
|
|
||||||
- [[Bot-Lifecycle]]
|
|
||||||
- [[Middleware]]
|
|
||||||
- [[Auto-Generated-Commands]]
|
|
||||||
- [[Rate-Limiting]]
|
|
||||||
- [[Drafts]]
|
|
||||||
- [[Localization]]
|
|
||||||
- [[Recipes]]
|
|
||||||
- [[Runners]]
|
|
||||||
- [[Error-Handling]]
|
|
||||||
- [[Logging]]
|
|
||||||
- [[Testing-Bots-with-Laniakea]]
|
|
||||||
- [[Scenes]]
|
|
||||||
- [[Migration]]
|
|
||||||
- [[FAQ]]
|
|
||||||
|
|
||||||
## Priority 3
|
|
||||||
- [[Drafts]]
|
|
||||||
- [[Framework-Backlog]]
|
|
||||||
- [[Localization]]
|
|
||||||
- [[Rate-Limiting]]
|
|
||||||
- [[Recipes]]
|
|
||||||
|
|
||||||
## Priority 4
|
|
||||||
- [[Semver-and-Releases]]
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
- Keep `Home.md` aligned with the general information architecture, not necessarily the exact priority order.
|
|
||||||
- Within each page, explain the most necessary and most frequently used APIs first.
|
|
||||||
- Add new pages here before writing large amounts of content for them.
|
|
||||||
- Revisit Priority 1 pages first when public APIs or README examples change.
|
|
||||||
+5
-5
@@ -29,16 +29,16 @@ admin.NewCommand(func(ctx *laniakea.MsgContext, app *App) error {
|
|||||||
Use a payload handler for inline keyboard callbacks.
|
Use a payload handler for inline keyboard callbacks.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
menu := laniakea.NewPlugin[laniakea.NoDB]("menu")
|
menu := laniakea.NewPlugin[laniakea.NoData]("menu")
|
||||||
|
|
||||||
menu.NewCommand(func(ctx *laniakea.MsgContext, db laniakea.NoDB) error {
|
menu.NewCommand(func(ctx *laniakea.MsgContext, db laniakea.NoData) error {
|
||||||
kb := ctx.NewInlineKeyboard(1)
|
kb := ctx.NewInlineKeyboard(1)
|
||||||
kb.NewDataButton("Open settings", laniakea.NewCallbackData("settings"))
|
kb.AddCallbackButton("Open settings", "settings")
|
||||||
ctx.Keyboard("Choose an action", kb)
|
ctx.Keyboard("Choose an action", kb)
|
||||||
return nil
|
return nil
|
||||||
}, "menu")
|
}, "menu")
|
||||||
|
|
||||||
menu.NewPayload(func(ctx *laniakea.MsgContext, db laniakea.NoDB) error {
|
menu.NewPayload(func(ctx *laniakea.MsgContext, db laniakea.NoData) error {
|
||||||
ctx.EditCallback("Settings screen", nil)
|
ctx.EditCallback("Settings screen", nil)
|
||||||
return nil
|
return nil
|
||||||
}, "settings")
|
}, "settings")
|
||||||
@@ -69,7 +69,7 @@ l10n := laniakea.NewL10n("en").
|
|||||||
"ru": "Privet",
|
"ru": "Privet",
|
||||||
})
|
})
|
||||||
|
|
||||||
bot.AddL10n(l10n)
|
bot.SetL10n(l10n)
|
||||||
|
|
||||||
plugin.NewCommand(func(ctx *laniakea.MsgContext, db *App) error {
|
plugin.NewCommand(func(ctx *laniakea.MsgContext, db *App) error {
|
||||||
ctx.Answer(ctx.Translate("greeting"))
|
ctx.Answer(ctx.Translate("greeting"))
|
||||||
|
|||||||
+1
-1
@@ -55,7 +55,7 @@ runner := laniakea.NewRunner("cleanup", fn)
|
|||||||
|
|
||||||
## Когда стартуют фоновые задачи
|
## Когда стартуют фоновые задачи
|
||||||
|
|
||||||
Фоновые задачи стартуют из `RunWithContext(...)`, а не из `NewBot(...)`.
|
Фоновые задачи стартуют из `RunWithContext(...)` или `RunWebHookWithContext(...)`, а не из `NewBot(...)`.
|
||||||
|
|
||||||
Это часть фазы выполнения, а не фазы сборки конфигурации.
|
Это часть фазы выполнения, а не фазы сборки конфигурации.
|
||||||
|
|
||||||
|
|||||||
+10
-10
@@ -6,7 +6,7 @@ Runners are background or one-time tasks that start with the bot and live alongs
|
|||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
Runners are registered with `Bot.AddRunner(...)` and executed from `RunWithContext(...)`.
|
Runners are registered with `Bot.AddRunner(...)` and executed from the bot runtime entry points.
|
||||||
|
|
||||||
Each runner is built from:
|
Each runner is built from:
|
||||||
- a name;
|
- a name;
|
||||||
@@ -24,7 +24,7 @@ Use `NewRunner(name, fn)` to create a runner.
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
cleanup := laniakea.NewRunner("cleanup", func(bot *laniakea.Bot[*App]) error {
|
cleanup := laniakea.NewRunner("cleanup", func(bot *laniakea.Bot[*App]) error {
|
||||||
return cleanupExpiredState(bot.GetDBContext())
|
return cleanupExpiredState(bot.GetAppData())
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ runner := laniakea.NewRunner("cleanup", fn).
|
|||||||
|
|
||||||
Behavior:
|
Behavior:
|
||||||
- runs on a ticker;
|
- runs on a ticker;
|
||||||
- keeps running until `ctx.Done()` from `RunWithContext(...)`;
|
- keeps running until `ctx.Done()` from the bot runtime context;
|
||||||
- is awaited during graceful shutdown.
|
- is awaited during graceful shutdown.
|
||||||
|
|
||||||
Use this for recurring background jobs.
|
Use this for recurring background jobs.
|
||||||
@@ -106,16 +106,16 @@ Runners with an empty name are skipped with a warning, so always give them a sta
|
|||||||
|
|
||||||
## Lifecycle
|
## Lifecycle
|
||||||
|
|
||||||
Runners are not started by `NewBot(...)`. They start from `RunWithContext(...)`, right before the bot begins its polling loop.
|
Runners are not started by `NewBot(...)`. They start from `RunWithContext(...)` or `RunWebHookWithContext(...)`, right before the bot begins polling or webhook ingestion.
|
||||||
|
|
||||||
That means runner execution belongs to the bot's runtime lifecycle, not to its configuration phase.
|
That means runner execution belongs to the bot's runtime lifecycle, not to its configuration phase.
|
||||||
|
|
||||||
High-level order:
|
High-level order:
|
||||||
1. validate bot startup;
|
1. validate bot startup;
|
||||||
2. start runners;
|
2. start runners;
|
||||||
3. begin update polling;
|
3. begin polling or webhook ingress;
|
||||||
4. process updates concurrently;
|
4. process updates concurrently;
|
||||||
5. cancel context to stop polling and let runners exit.
|
5. cancel context to stop ingress and let runners exit.
|
||||||
|
|
||||||
For the broader bot runtime model, see [[Bot-Lifecycle]].
|
For the broader bot runtime model, see [[Bot-Lifecycle]].
|
||||||
|
|
||||||
@@ -134,7 +134,7 @@ If a runner must be fatal for startup, make it one-time synchronous and return a
|
|||||||
|
|
||||||
## Shutdown behavior
|
## Shutdown behavior
|
||||||
|
|
||||||
`RunWithContext(...)` waits for runner completion in two groups:
|
`RunWithContext(...)` and `RunWebHookWithContext(...)` wait for runner completion in two groups:
|
||||||
- one-time async runners;
|
- one-time async runners;
|
||||||
- background repeating runners.
|
- background repeating runners.
|
||||||
|
|
||||||
@@ -148,7 +148,7 @@ This means graceful shutdown includes runner shutdown, but only if your runner f
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
cleanup := laniakea.NewRunner("cleanup", func(bot *laniakea.Bot[*App]) error {
|
cleanup := laniakea.NewRunner("cleanup", func(bot *laniakea.Bot[*App]) error {
|
||||||
return bot.GetDBContext().CleanupExpired()
|
return bot.GetAppData().CleanupExpired()
|
||||||
}).Timeout(5 * time.Minute)
|
}).Timeout(5 * time.Minute)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -156,7 +156,7 @@ cleanup := laniakea.NewRunner("cleanup", func(bot *laniakea.Bot[*App]) error {
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
warmup := laniakea.NewRunner("warmup", func(bot *laniakea.Bot[*App]) error {
|
warmup := laniakea.NewRunner("warmup", func(bot *laniakea.Bot[*App]) error {
|
||||||
return bot.GetDBContext().WarmCaches()
|
return bot.GetAppData().WarmCaches()
|
||||||
}).Onetime(true).Async(false)
|
}).Onetime(true).Async(false)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -164,7 +164,7 @@ warmup := laniakea.NewRunner("warmup", func(bot *laniakea.Bot[*App]) error {
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
metrics := laniakea.NewRunner("metrics", func(bot *laniakea.Bot[*App]) error {
|
metrics := laniakea.NewRunner("metrics", func(bot *laniakea.Bot[*App]) error {
|
||||||
return pushMetrics(bot.GetDBContext())
|
return pushMetrics(bot.GetAppData())
|
||||||
}).Timeout(30 * time.Second)
|
}).Timeout(30 * time.Second)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -138,4 +138,4 @@ Before editing version or changelog data:
|
|||||||
## Related pages
|
## Related pages
|
||||||
|
|
||||||
- [[Migration]] for upgrade guidance across release candidates
|
- [[Migration]] for upgrade guidance across release candidates
|
||||||
- [[FAQ]] for rationale behind some API decisions
|
- [[Bot-Lifecycle]] for runtime and single-use guarantees
|
||||||
|
|||||||
@@ -0,0 +1,243 @@
|
|||||||
|
# Webhook Runtime RU
|
||||||
|
|
||||||
|
English version: [[Webhook-Runtime]]
|
||||||
|
|
||||||
|
Эта страница объясняет bot-level webhook runtime в Laniakea: как работает `RunWebHookWithContext(...)`, чем он отличается от низкоуровневых webhook-вызовов в `tgapi` и какие runtime-гарантии он делит с polling-режимом.
|
||||||
|
|
||||||
|
Важно про naming:
|
||||||
|
- текущий публичный API использует историческое написание `WebHook` в идентификаторах вроде `RunWebHookWithContext(...)`, `RunWebHook(...)` и `BotWebHookOpts`;
|
||||||
|
- в тексте страницы используется обычное слово "webhook", но в примерах остаются реальные имена Go API.
|
||||||
|
|
||||||
|
## Когда использовать webhook runtime
|
||||||
|
|
||||||
|
Используй webhook runtime, когда Telegram должен сам доставлять update в твой бот по HTTP вместо long polling.
|
||||||
|
|
||||||
|
Обычно это хороший вариант, если:
|
||||||
|
- бот работает за стабильным публичным HTTPS endpoint;
|
||||||
|
- у тебя уже есть reverse proxy или ingress;
|
||||||
|
- ты хочешь, чтобы Telegram сам отправлял update, а бот не держал polling loop.
|
||||||
|
|
||||||
|
Используй polling, если:
|
||||||
|
- тебе нужен самый простой локальный или маленький серверный setup;
|
||||||
|
- ты не хочешь открывать входящий HTTP endpoint;
|
||||||
|
- webhook-модель тебе не нужна.
|
||||||
|
|
||||||
|
## Точки входа
|
||||||
|
|
||||||
|
Главные bot-level точки входа такие:
|
||||||
|
- `RunWebHookWithContext(ctx, opts, tlsFiles...)`
|
||||||
|
- `RunWebHook(opts, tlsFiles...)`
|
||||||
|
- `NewBotWebHookOpts()`
|
||||||
|
|
||||||
|
`RunWebHook(...)` — это просто короткая форма для `RunWebHookWithContext(context.Background(), ...)`.
|
||||||
|
|
||||||
|
Обычный шаблон выглядит так:
|
||||||
|
|
||||||
|
```go
|
||||||
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
bot, err := laniakea.NewBot[*App](opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer bot.Close()
|
||||||
|
|
||||||
|
bot.SetAppData(app)
|
||||||
|
bot.AddPlugins(plugin)
|
||||||
|
|
||||||
|
webhookOpts := laniakea.NewBotWebHookOpts().
|
||||||
|
SetURL("https://bot.example.com").
|
||||||
|
SetPath("/telegram").
|
||||||
|
SetLocalPort(8080).
|
||||||
|
SetSecretToken("shared-secret")
|
||||||
|
|
||||||
|
if err := bot.RunWebHookWithContext(ctx, webhookOpts); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Что именно берет на себя bot-level runtime
|
||||||
|
|
||||||
|
`RunWebHookWithContext(...)` — это не просто обертка над Telegram `setWebhook`.
|
||||||
|
|
||||||
|
Он:
|
||||||
|
- валидирует bot-level условия старта, например prefixes и наличие зарегистрированных plugins;
|
||||||
|
- использует тот же single-use runtime contract, что и polling;
|
||||||
|
- запускает runners;
|
||||||
|
- настраивает webhook-доставку у Telegram;
|
||||||
|
- поднимает локальный HTTP server, которым владеет сам бот;
|
||||||
|
- принимает входящие update и отправляет их в ту же внутреннюю очередь и тот же worker pool, что используются в polling;
|
||||||
|
- корректно завершает работу при `ctx.Done()`.
|
||||||
|
|
||||||
|
То есть webhook mode — это часть основной runtime-модели, а не просто низкоуровневая transport setting.
|
||||||
|
|
||||||
|
## Какие гарантии он делит с polling
|
||||||
|
|
||||||
|
Webhook runtime использует те же основные гарантии, что и `RunWithContext(...)`:
|
||||||
|
- бот остаётся single-use;
|
||||||
|
- update проходят через внутреннюю очередь;
|
||||||
|
- `BotOpts.MaxWorkers` по-прежнему управляет параллельным выполнением handlers;
|
||||||
|
- runners стартуют на входе в runtime, а не в `NewBot(...)`;
|
||||||
|
- shutdown дренирует уже принятые update перед возвратом;
|
||||||
|
- после завершения всё равно нужен `Close()` для освобождения локальных ресурсов.
|
||||||
|
|
||||||
|
Если ты уже понимаешь [[Bot-Lifecycle]], то webhook mode стоит воспринимать как другой ingress path для update, а не как второй отдельный фреймворк.
|
||||||
|
|
||||||
|
## Основные webhook options
|
||||||
|
|
||||||
|
`BotWebHookOpts` управляет и регистрацией webhook у Telegram, и локальным HTTP server.
|
||||||
|
|
||||||
|
Поля, которые важны в первую очередь:
|
||||||
|
|
||||||
|
### `URL`
|
||||||
|
|
||||||
|
Это публичный base URL, который Telegram будет вызывать.
|
||||||
|
|
||||||
|
Если:
|
||||||
|
- `URL == "https://bot.example.com"`
|
||||||
|
- `Path == "/telegram"`
|
||||||
|
|
||||||
|
то в Telegram регистрируется:
|
||||||
|
- `https://bot.example.com/telegram`
|
||||||
|
|
||||||
|
`URL` обязателен.
|
||||||
|
|
||||||
|
### `Path`
|
||||||
|
|
||||||
|
Это локальный HTTP path, который обслуживает бот.
|
||||||
|
|
||||||
|
Используй его, чтобы не держать webhook на `/` и явно отделить этот маршрут в reverse proxy.
|
||||||
|
|
||||||
|
### `LocalPort`
|
||||||
|
|
||||||
|
Это локальный порт, который слушает бот.
|
||||||
|
|
||||||
|
Типичный production-паттерн:
|
||||||
|
- публичный TLS завершается на reverse proxy;
|
||||||
|
- сам бот слушает внутренний HTTP-порт, например `8080`.
|
||||||
|
|
||||||
|
### `SecretToken`
|
||||||
|
|
||||||
|
Это общий секрет, который ожидается в заголовке Telegram `X-Telegram-Bot-Api-Secret-Token`.
|
||||||
|
|
||||||
|
Его почти всегда стоит задавать.
|
||||||
|
|
||||||
|
Без него endpoint всё равно работает, но боту приходится доверять тому, что к этому маршруту приходит только Telegram.
|
||||||
|
|
||||||
|
### `AllowedUpdates`
|
||||||
|
|
||||||
|
Если явно вызвать `SetAllowedUpdates(...)`, именно эти виды update будут зарегистрированы для webhook delivery.
|
||||||
|
|
||||||
|
Если оставить поле пустым, Laniakea по умолчанию возьмёт update types из bot-level конфигурации:
|
||||||
|
- `SetUpdateTypes(...)`
|
||||||
|
- `AddUpdateType(...)`
|
||||||
|
|
||||||
|
Это помогает не расходиться webhook-режиму с остальной конфигурацией бота.
|
||||||
|
|
||||||
|
### `MaxConnections`
|
||||||
|
|
||||||
|
Это Telegram webhook-параметр `max_connections`.
|
||||||
|
|
||||||
|
Сейчас Laniakea заранее валидирует диапазон Telegram `1..100`.
|
||||||
|
|
||||||
|
### `DropPendingUpdates`
|
||||||
|
|
||||||
|
Используй это, если хочешь, чтобы Telegram отбросил уже накопившиеся update во время замены webhook.
|
||||||
|
|
||||||
|
Это deployment-решение, а не обычная runtime-настройка.
|
||||||
|
|
||||||
|
### `Certificate`
|
||||||
|
|
||||||
|
Задай это поле, если нужно загрузить self-signed certificate в Telegram.
|
||||||
|
|
||||||
|
В этом случае бот внутри использует uploader-based регистрацию webhook.
|
||||||
|
|
||||||
|
### `UseStatusPath`
|
||||||
|
|
||||||
|
Если включить эту опцию, бот дополнительно отдаёт `/status`, который возвращает текущий Telegram webhook info в виде JSON.
|
||||||
|
|
||||||
|
Для этого endpoint нужен непустой `SecretToken`. Если включить `/status` без секрета, Laniakea завершит startup ошибкой.
|
||||||
|
|
||||||
|
Это operational endpoint, а не публичный пользовательский route.
|
||||||
|
|
||||||
|
### `IPAddress`
|
||||||
|
|
||||||
|
Используй это только тогда, когда тебе действительно нужен Telegram webhook option `ip_address`.
|
||||||
|
|
||||||
|
## Поведение HTTP и TLS
|
||||||
|
|
||||||
|
По умолчанию `RunWebHookWithContext(...)` поднимает обычный HTTP server на `LocalPort`.
|
||||||
|
|
||||||
|
Если передать два TLS-файла, локально стартует HTTPS.
|
||||||
|
|
||||||
|
Важно:
|
||||||
|
- текущий публичный API ожидает существующий порядок аргументов `key, cert` при вызове `RunWebHookWithContext(...)`;
|
||||||
|
- это отличается от более привычной ментальной модели `cert, key`, которую многие Go-разработчики ожидают от `ListenAndServeTLS`.
|
||||||
|
|
||||||
|
Поэтому в реальном setup лучше писать этот вызов максимально явно.
|
||||||
|
|
||||||
|
## Как обрабатываются запросы
|
||||||
|
|
||||||
|
Встроенный webhook server сейчас:
|
||||||
|
- принимает только `POST`;
|
||||||
|
- при необходимости валидирует `X-Telegram-Bot-Api-Secret-Token`;
|
||||||
|
- декодирует входящий Telegram `Update`;
|
||||||
|
- кладёт update в обычный runtime queue бота;
|
||||||
|
- возвращает `200 OK`, когда enqueue прошёл успешно.
|
||||||
|
|
||||||
|
Если enqueue не удался из-за остановки runtime, сервер возвращает `503`.
|
||||||
|
|
||||||
|
Ключевая идея в том, что webhook mode не гоняет handlers прямо внутри HTTP request. Он передаёт принятые update в ту же очередь и тот же worker pool, что используются в других runtime path.
|
||||||
|
|
||||||
|
## Практика по безопасности
|
||||||
|
|
||||||
|
Минимум стоит сделать следующее:
|
||||||
|
- задать `SecretToken`;
|
||||||
|
- использовать явный `Path`, а не случайный `/`;
|
||||||
|
- включать `/status` только если он реально нужен;
|
||||||
|
- ставить бот за нормальный reverse proxy или ingress.
|
||||||
|
|
||||||
|
Также важно помнить:
|
||||||
|
- `URL` — это то, что видит Telegram;
|
||||||
|
- `Path` и `LocalPort` — это то, что реально обслуживает твой бот;
|
||||||
|
- в production эти значения часто относятся к разным слоям инфраструктуры.
|
||||||
|
- если ты переводишь работающий deployment с webhook-режима на polling, сначала удали webhook через `CloseWebHook()` или `tgapi.DeleteWebhook(...)`; Telegram не прекращает webhook-доставку автоматически.
|
||||||
|
|
||||||
|
## Связь с webhook methods в `tgapi`
|
||||||
|
|
||||||
|
Используй bot-level webhook runtime, когда хочешь, чтобы Laniakea сама владела:
|
||||||
|
- регистрацией webhook;
|
||||||
|
- локальным HTTP server;
|
||||||
|
- приёмом update в обычную runtime queue;
|
||||||
|
- worker-pool dispatch и shutdown semantics.
|
||||||
|
|
||||||
|
Используй более низкоуровневые вызовы `tgapi`, например:
|
||||||
|
- `SetWebhook(...)`
|
||||||
|
- `DeleteWebhook(...)`
|
||||||
|
- `GetWebhookInfo(...)`
|
||||||
|
- `Uploader.SetWebhook(...)`
|
||||||
|
|
||||||
|
когда тебе нужна собственная инфраструктура вокруг webhook path и ты не хочешь, чтобы сам бот владел HTTP server.
|
||||||
|
|
||||||
|
То есть:
|
||||||
|
- `RunWebHookWithContext(...)` — это framework runtime API;
|
||||||
|
- webhook methods из `tgapi` — это низкоуровневые transport primitives.
|
||||||
|
|
||||||
|
## Частые ошибки
|
||||||
|
|
||||||
|
- Забывать, что webhook mode тоже single-use для одного экземпляра `Bot`.
|
||||||
|
- Забывать вызвать `Close()` после завершения runtime.
|
||||||
|
- Переключаться с webhook mode на polling, не удалив webhook заранее.
|
||||||
|
- Путать публичный `URL` с локальными `Path` и `LocalPort`.
|
||||||
|
- Не задавать `SecretToken` в production-подобном deployment.
|
||||||
|
- Предполагать привычный порядок TLS-файлов `cert, key`.
|
||||||
|
- Считать `/status` безобидным публичным endpoint.
|
||||||
|
|
||||||
|
## Связанные страницы
|
||||||
|
|
||||||
|
- [[Bot-Lifecycle-RU]]
|
||||||
|
- [[Bot-Options-and-Configuration-RU]]
|
||||||
|
- [[Update-Routing-Model-RU]]
|
||||||
|
- [[Runners-RU]]
|
||||||
|
- [[tgapi-Overview-RU]]
|
||||||
+243
@@ -0,0 +1,243 @@
|
|||||||
|
# Webhook Runtime
|
||||||
|
|
||||||
|
Russian version: [[Webhook-Runtime-RU]]
|
||||||
|
|
||||||
|
This page explains the bot-level webhook runtime in Laniakea: how `RunWebHookWithContext(...)` works, what it owns, how it differs from low-level `tgapi` webhook calls, and what runtime guarantees it shares with polling mode.
|
||||||
|
|
||||||
|
Important naming note:
|
||||||
|
- the current public API uses the historical `WebHook` spelling in identifiers such as `RunWebHookWithContext(...)`, `RunWebHook(...)`, and `BotWebHookOpts`;
|
||||||
|
- this page uses the more common English term "webhook" for readability, but examples keep the actual Go API names.
|
||||||
|
|
||||||
|
## When to use webhook runtime
|
||||||
|
|
||||||
|
Use webhook runtime when Telegram should push updates to your bot over HTTP instead of your bot pulling updates through long polling.
|
||||||
|
|
||||||
|
This is usually a good fit when:
|
||||||
|
- your bot runs behind a stable public HTTPS endpoint;
|
||||||
|
- you already have reverse-proxy or ingress infrastructure;
|
||||||
|
- you want Telegram to deliver updates directly instead of keeping a polling loop open.
|
||||||
|
|
||||||
|
Use polling when:
|
||||||
|
- you want the simplest local or small-server setup;
|
||||||
|
- you do not want to expose an incoming HTTP endpoint;
|
||||||
|
- you do not need webhook-style deployment.
|
||||||
|
|
||||||
|
## Entry points
|
||||||
|
|
||||||
|
The main bot-level entry points are:
|
||||||
|
- `RunWebHookWithContext(ctx, opts, tlsFiles...)`
|
||||||
|
- `RunWebHook(opts, tlsFiles...)`
|
||||||
|
- `NewBotWebHookOpts()`
|
||||||
|
|
||||||
|
`RunWebHook(...)` is only a shorthand for `RunWebHookWithContext(context.Background(), ...)`.
|
||||||
|
|
||||||
|
The usual pattern looks like:
|
||||||
|
|
||||||
|
```go
|
||||||
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
bot, err := laniakea.NewBot[*App](opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer bot.Close()
|
||||||
|
|
||||||
|
bot.SetAppData(app)
|
||||||
|
bot.AddPlugins(plugin)
|
||||||
|
|
||||||
|
webhookOpts := laniakea.NewBotWebHookOpts().
|
||||||
|
SetURL("https://bot.example.com").
|
||||||
|
SetPath("/telegram").
|
||||||
|
SetLocalPort(8080).
|
||||||
|
SetSecretToken("shared-secret")
|
||||||
|
|
||||||
|
if err := bot.RunWebHookWithContext(ctx, webhookOpts); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## What the bot-level runtime owns
|
||||||
|
|
||||||
|
`RunWebHookWithContext(...)` is more than a wrapper around Telegram's `setWebhook`.
|
||||||
|
|
||||||
|
It:
|
||||||
|
- validates bot startup preconditions such as prefixes and registered plugins;
|
||||||
|
- keeps the same single-use runtime model as polling;
|
||||||
|
- starts registered runners;
|
||||||
|
- configures Telegram webhook delivery;
|
||||||
|
- starts a local HTTP server owned by the bot;
|
||||||
|
- accepts incoming updates and feeds them into the same internal queue and worker pool used by polling;
|
||||||
|
- shuts down gracefully when `ctx.Done()` fires.
|
||||||
|
|
||||||
|
This means webhook mode is part of the main runtime model, not just a low-level transport setting.
|
||||||
|
|
||||||
|
## Runtime guarantees shared with polling
|
||||||
|
|
||||||
|
Webhook runtime shares the same main guarantees as `RunWithContext(...)`:
|
||||||
|
- the bot is single-use;
|
||||||
|
- updates go through the internal queue;
|
||||||
|
- `BotOpts.MaxWorkers` still controls concurrent handler execution;
|
||||||
|
- runners start at runtime entry, not at `NewBot(...)`;
|
||||||
|
- shutdown drains already accepted work before returning;
|
||||||
|
- `Close()` is still required afterward to release local resources.
|
||||||
|
|
||||||
|
If you already understand [[Bot-Lifecycle]], the webhook mode should feel like a different update ingress path, not a second framework.
|
||||||
|
|
||||||
|
## Main webhook options
|
||||||
|
|
||||||
|
`BotWebHookOpts` controls both Telegram webhook registration and the local server behavior.
|
||||||
|
|
||||||
|
Fields you will care about first:
|
||||||
|
|
||||||
|
### `URL`
|
||||||
|
|
||||||
|
This is the public base URL Telegram will call.
|
||||||
|
|
||||||
|
If:
|
||||||
|
- `URL == "https://bot.example.com"`
|
||||||
|
- `Path == "/telegram"`
|
||||||
|
|
||||||
|
then Telegram is registered with:
|
||||||
|
- `https://bot.example.com/telegram`
|
||||||
|
|
||||||
|
`URL` is required.
|
||||||
|
|
||||||
|
### `Path`
|
||||||
|
|
||||||
|
This is the local HTTP path the bot serves.
|
||||||
|
|
||||||
|
Use it to keep webhook traffic off `/` and make reverse-proxy routing explicit.
|
||||||
|
|
||||||
|
### `LocalPort`
|
||||||
|
|
||||||
|
This is the local server port the bot binds to.
|
||||||
|
|
||||||
|
Common deployment pattern:
|
||||||
|
- public TLS terminates at a reverse proxy;
|
||||||
|
- the bot itself listens on an internal HTTP port such as `8080`.
|
||||||
|
|
||||||
|
### `SecretToken`
|
||||||
|
|
||||||
|
This is the shared secret expected in Telegram's `X-Telegram-Bot-Api-Secret-Token` header.
|
||||||
|
|
||||||
|
You should almost always set it.
|
||||||
|
|
||||||
|
Without it, the endpoint still works, but the bot has to trust that only Telegram reaches that path.
|
||||||
|
|
||||||
|
### `AllowedUpdates`
|
||||||
|
|
||||||
|
If you call `SetAllowedUpdates(...)` explicitly, those update kinds are registered for webhook delivery.
|
||||||
|
|
||||||
|
If you leave it empty, Laniakea falls back to the bot's configured update types from:
|
||||||
|
- `SetUpdateTypes(...)`
|
||||||
|
- `AddUpdateType(...)`
|
||||||
|
|
||||||
|
That keeps webhook mode aligned with the rest of your bot configuration by default.
|
||||||
|
|
||||||
|
### `MaxConnections`
|
||||||
|
|
||||||
|
This maps to Telegram's webhook `max_connections` setting.
|
||||||
|
|
||||||
|
Laniakea currently enforces the Telegram `1..100` range before startup.
|
||||||
|
|
||||||
|
### `DropPendingUpdates`
|
||||||
|
|
||||||
|
Use this when you want Telegram to discard already queued updates while replacing the webhook.
|
||||||
|
|
||||||
|
This is a deployment decision, not a normal runtime requirement.
|
||||||
|
|
||||||
|
### `Certificate`
|
||||||
|
|
||||||
|
Set this when you need to upload a self-signed certificate to Telegram.
|
||||||
|
|
||||||
|
The bot then uses uploader-based webhook registration internally.
|
||||||
|
|
||||||
|
### `UseStatusPath`
|
||||||
|
|
||||||
|
If enabled, the bot also serves `/status`, which returns the current Telegram webhook info as JSON.
|
||||||
|
|
||||||
|
This endpoint requires a non-empty `SecretToken`. Laniakea rejects startup if you enable `/status` without setting one.
|
||||||
|
|
||||||
|
Treat this as an operational endpoint, not a public user-facing route.
|
||||||
|
|
||||||
|
### `IPAddress`
|
||||||
|
|
||||||
|
Use this only when you specifically need Telegram's `ip_address` webhook option.
|
||||||
|
|
||||||
|
## HTTP and TLS behavior
|
||||||
|
|
||||||
|
By default, `RunWebHookWithContext(...)` starts a plain HTTP server on `LocalPort`.
|
||||||
|
|
||||||
|
If you pass two TLS files, it starts HTTPS locally instead.
|
||||||
|
|
||||||
|
Important:
|
||||||
|
- the current public API expects the existing key-then-cert argument order when calling `RunWebHookWithContext(...)`;
|
||||||
|
- that differs from the more common `cert, key` mental model many Go developers expect from `ListenAndServeTLS`.
|
||||||
|
|
||||||
|
Be explicit in your own setup code so this does not become a deployment footgun.
|
||||||
|
|
||||||
|
## Request handling behavior
|
||||||
|
|
||||||
|
The built-in webhook server currently:
|
||||||
|
- accepts only `POST`;
|
||||||
|
- optionally validates `X-Telegram-Bot-Api-Secret-Token`;
|
||||||
|
- decodes the incoming Telegram `Update`;
|
||||||
|
- enqueues the update into the normal bot runtime;
|
||||||
|
- returns `200 OK` after successful enqueue.
|
||||||
|
|
||||||
|
If enqueue fails because runtime is shutting down, the server returns `503`.
|
||||||
|
|
||||||
|
The important design point is that webhook mode does not run handlers inline inside the HTTP request. It hands accepted updates to the same queue and worker pool model used elsewhere.
|
||||||
|
|
||||||
|
## Security and exposure guidance
|
||||||
|
|
||||||
|
At minimum:
|
||||||
|
- set `SecretToken`;
|
||||||
|
- keep the webhook path specific, not guessable by accident;
|
||||||
|
- expose `/status` only when you actually need it;
|
||||||
|
- prefer putting the bot behind a real reverse proxy or ingress layer.
|
||||||
|
|
||||||
|
Also keep in mind:
|
||||||
|
- `URL` is what Telegram sees;
|
||||||
|
- `Path` and `LocalPort` are what your bot actually serves;
|
||||||
|
- these are often not the same thing in production.
|
||||||
|
- if you switch a running deployment from webhook mode to polling, delete the webhook first with `CloseWebHook()` or `tgapi.DeleteWebhook(...)`; Telegram does not stop webhook delivery automatically.
|
||||||
|
|
||||||
|
## Relation to `tgapi` webhook methods
|
||||||
|
|
||||||
|
Use bot-level webhook runtime when you want Laniakea to own:
|
||||||
|
- webhook registration;
|
||||||
|
- the local HTTP server;
|
||||||
|
- update ingestion into the normal runtime queue;
|
||||||
|
- worker-pool dispatch and shutdown behavior.
|
||||||
|
|
||||||
|
Use lower-level `tgapi` calls such as:
|
||||||
|
- `SetWebhook(...)`
|
||||||
|
- `DeleteWebhook(...)`
|
||||||
|
- `GetWebhookInfo(...)`
|
||||||
|
- `Uploader.SetWebhook(...)`
|
||||||
|
|
||||||
|
when you need custom infrastructure around the webhook path and do not want the bot to own the HTTP server itself.
|
||||||
|
|
||||||
|
In other words:
|
||||||
|
- `RunWebHookWithContext(...)` is the framework runtime API;
|
||||||
|
- `tgapi` webhook methods are the lower-level transport primitives.
|
||||||
|
|
||||||
|
## Common mistakes
|
||||||
|
|
||||||
|
- Forgetting that webhook mode is still single-use per bot instance.
|
||||||
|
- Forgetting to call `Close()` after runtime returns.
|
||||||
|
- Switching from webhook mode to polling without deleting the webhook first.
|
||||||
|
- Mixing up public `URL` with local `Path` and `LocalPort`.
|
||||||
|
- Omitting `SecretToken` in production-style deployments.
|
||||||
|
- Assuming TLS file order follows the usual `cert, key` convention.
|
||||||
|
- Treating `/status` like a harmless public endpoint.
|
||||||
|
|
||||||
|
## Related pages
|
||||||
|
|
||||||
|
- [[Bot-Lifecycle]]
|
||||||
|
- [[Bot-Options-and-Configuration]]
|
||||||
|
- [[Update-Routing-Model]]
|
||||||
|
- [[Runners]]
|
||||||
|
- [[tgapi-Overview]]
|
||||||
+19
-18
@@ -2,35 +2,36 @@
|
|||||||
|
|
||||||
## Start here
|
## Start here
|
||||||
- [[Home]]
|
- [[Home]]
|
||||||
|
- [[Home-RU|Home (RU)]]
|
||||||
- [[Getting-Started]]
|
- [[Getting-Started]]
|
||||||
|
- [[Bot-Options-and-Configuration]]
|
||||||
- [[Commands-and-Plugins]]
|
- [[Commands-and-Plugins]]
|
||||||
- [[MsgContext]]
|
- [[MsgContext]]
|
||||||
|
|
||||||
## Core API
|
## Runtime and Architecture
|
||||||
- [[Inline-Keyboards-and-Payloads]]
|
|
||||||
- [[tgapi-Overview]]
|
|
||||||
- [[Bot-Lifecycle]]
|
- [[Bot-Lifecycle]]
|
||||||
|
- [[Webhook-Runtime]]
|
||||||
- [[Middleware]]
|
- [[Middleware]]
|
||||||
|
- [[Runners]]
|
||||||
|
- [[Error-Handling]]
|
||||||
|
- [[Logging]]
|
||||||
|
- [[Update-Routing-Model]]
|
||||||
- [[Policies]]
|
- [[Policies]]
|
||||||
- [[Scenes]]
|
- [[Scenes]]
|
||||||
- [[Scenes-RU|Scenes (RU)]]
|
|
||||||
- [[Policies-RU|Policies (RU)]]
|
|
||||||
|
|
||||||
## Changes and troubleshooting
|
## Interaction and Telegram API
|
||||||
- [[Migration]]
|
- [[Inline-Keyboards-and-Payloads]]
|
||||||
- [[FAQ]]
|
- [[Auto-Generated-Commands]]
|
||||||
|
|
||||||
## Additional topics
|
|
||||||
- [[Drafts]]
|
- [[Drafts]]
|
||||||
- [[Framework-Backlog]]
|
|
||||||
- [[Localization]]
|
- [[Localization]]
|
||||||
- [[Rate-Limiting]]
|
- [[Rate-Limiting]]
|
||||||
- [[Recipes]]
|
- [[tgapi-Overview]]
|
||||||
- [[Semver-and-Releases]]
|
|
||||||
- [[Page-Priority]]
|
|
||||||
|
|
||||||
## Drafts
|
## Practical Guides
|
||||||
|
- [[Recipes]]
|
||||||
|
- [[Testing-Bots-with-Laniakea]]
|
||||||
|
|
||||||
|
## Maintenance
|
||||||
|
- [[Migration]]
|
||||||
|
- [[Semver-and-Releases]]
|
||||||
- [[Framework-Backlog]]
|
- [[Framework-Backlog]]
|
||||||
- [[Scenes]]
|
|
||||||
- [[Framework-Backlog-RU|Framework Backlog (RU)]]
|
|
||||||
- [[Scenes-RU|Scenes (RU)]]
|
|
||||||
|
|||||||
Reference in New Issue
Block a user