REPOSITORY / ScuroNeko/Laniakea

Wiki

KNOWLEDGE REPOSITORY

(doc): MessageContext rename, webhook casing, Runner API, v1.0.0 migration

- MsgContext → MessageContext across all pages
- RunWebHookWithContext → RunWebhookWithContext (and CloseWebhook, BotWebhookOpts) across all pages
- Runners.md + Runners-RU.md: replace Onetime/Timeout with Every/Async, rewrite examples
- Migration.md: add v1.0.0 migration section covering all breaking changes
2026-05-20 13:19:27 +03:00
parent c8d9eae25c
commit beee616d9c
39 changed files with 403 additions and 316 deletions
+6 -6
@@ -9,7 +9,7 @@ English version: [[Bot-Lifecycle]]
1. Собрать `BotOpts`. 1. Собрать `BotOpts`.
2. Создать `Bot` через `NewBot[T](opts)`. 2. Создать `Bot` через `NewBot[T](opts)`.
3. Полностью настроить бот: плагины, middleware, фоновые задачи, политику данных callback, l10n и app data. 3. Полностью настроить бот: плагины, middleware, фоновые задачи, политику данных callback, l10n и app data.
4. Запустить через `Run()`, `RunWithContext(...)` или `RunWebHookWithContext(...)`. 4. Запустить через `Run()`, `RunWithContext(...)` или `RunWebhookWithContext(...)`.
5. Остановить выполнение через завершение runtime или отмену context. 5. Остановить выполнение через завершение runtime или отмену context.
6. Освободить локальные ресурсы через `Close()`. 6. Освободить локальные ресурсы через `Close()`.
7. Для следующего запуска создать новый `Bot`. 7. Для следующего запуска создать новый `Bot`.
@@ -66,7 +66,7 @@ English version: [[Bot-Lifecycle]]
1. Построение и настройка `Bot` после `NewBot[T](opts)`. 1. Построение и настройка `Bot` после `NewBot[T](opts)`.
2. Снимок конфигурации плагина в `AddPlugins(...)`. 2. Снимок конфигурации плагина в `AddPlugins(...)`.
3. Фиксация bot-level конфигурации после первого `Run()`, `RunWithContext(...)` или `RunWebHookWithContext(...)`. 3. Фиксация bot-level конфигурации после первого `Run()`, `RunWithContext(...)` или `RunWebhookWithContext(...)`.
Практически это значит: Практически это значит:
- структуру плагина нужно закончить до `AddPlugins(...)`; - структуру плагина нужно закончить до `AddPlugins(...)`;
@@ -85,7 +85,7 @@ Laniakea предпочитает предсказуемый no-op вместо
- путаницу в том, влияет ли изменение только на будущие update или ещё и на уже принятую работу; - путаницу в том, влияет ли изменение только на будущие update или ещё и на уже принятую работу;
- разные ментальные модели для snapshot-поведения плагинов и bot-level состояния. - разные ментальные модели для snapshot-поведения плагинов и bot-level состояния.
## `Run()`, `RunWithContext(...)` и `RunWebHookWithContext(...)` ## `Run()`, `RunWithContext(...)` и `RunWebhookWithContext(...)`
`Run()` — это короткая форма для простых случаев. `Run()` — это короткая форма для простых случаев.
@@ -94,11 +94,11 @@ Laniakea предпочитает предсказуемый no-op вместо
- ждет завершения queued updates; - ждет завершения queued updates;
- корректно дожидается фоновых задач. - корректно дожидается фоновых задач.
`RunWebHookWithContext(...)` — webhook-вариант runtime. Он использует тот же single-use контракт, тот же запуск runners, ту же очередь обновлений и ту же worker-pool обработку. `RunWebhookWithContext(...)` — webhook-вариант runtime. Он использует тот же single-use контракт, тот же запуск runners, ту же очередь обновлений и ту же worker-pool обработку.
Если bot уже был запущен раньше, повторный запуск вернет `ErrBotAlreadyRun`. Если bot уже был запущен раньше, повторный запуск вернет `ErrBotAlreadyRun`.
Если ты переводишь уже существующий deployment с webhook-доставки на polling, сначала удали текущий webhook через `CloseWebHook()` или низкоуровневый `tgapi.DeleteWebhook(...)`. Пока webhook не удалён, Telegram продолжает доставлять update через него. Если ты переводишь уже существующий deployment с webhook-доставки на polling, сначала удали текущий webhook через `CloseWebhook()` или низкоуровневый `tgapi.DeleteWebhook(...)`. Пока webhook не удалён, Telegram продолжает доставлять update через него.
Для webhook-специфичных опций, транспортного поведения и практических советов смотри [[Webhook-Runtime-RU]]. Для webhook-специфичных опций, транспортного поведения и практических советов смотри [[Webhook-Runtime-RU]].
@@ -131,7 +131,7 @@ Laniakea предпочитает предсказуемый no-op вместо
Обычно боту нужен именно `Close()`. Обычно боту нужен именно `Close()`.
`RunWithContext(...)` и `RunWebHookWithContext(...)` не заменяют `Close()`: локальные ресурсы всё равно нужно закрывать отдельно. `RunWithContext(...)` и `RunWebhookWithContext(...)` не заменяют `Close()`: локальные ресурсы всё равно нужно закрывать отдельно.
## Частые ошибки ## Частые ошибки
+10 -10
@@ -7,7 +7,7 @@ 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 app data. 2. Configure the bot instance: prefixes, plugins, middleware, runners, localization, payload defaults, and optional app data.
3. Start it with `RunWithContext(ctx)`, `Run()`, or `RunWebHookWithContext(...)`. 3. Start it with `RunWithContext(ctx)`, `Run()`, or `RunWebhookWithContext(...)`.
4. Stop runtime by canceling the context or letting the run method 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 another run. 6. Create a new `Bot` if you need another run.
@@ -43,7 +43,7 @@ The normal pattern is to finish all structural configuration before starting the
- `SetDraftProvider(...)`, `SetSessionStore(...)`, and `SetSceneScopePriority(...)` replace runtime helpers. - `SetDraftProvider(...)`, `SetSessionStore(...)`, and `SetSceneScopePriority(...)` replace runtime helpers.
- `SetErrorTemplate(...)` adjusts centralized user-facing error text. - `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 [[MessageContext]].
## `AddPlugins(...)` is a configuration commit point ## `AddPlugins(...)` is a configuration commit point
@@ -66,7 +66,7 @@ Laniakea has three practical configuration phases:
1. Construction and bot setup after `NewBot[T](opts)`. 1. Construction and bot setup after `NewBot[T](opts)`.
2. Plugin snapshotting at `AddPlugins(...)`. 2. Plugin snapshotting at `AddPlugins(...)`.
3. Runtime freeze after the first `Run()`, `RunWithContext(...)`, or `RunWebHookWithContext(...)`. 3. Runtime freeze after the first `Run()`, `RunWithContext(...)`, or `RunWebhookWithContext(...)`.
That means: That means:
- finish plugin structure before `AddPlugins(...)`; - finish plugin structure before `AddPlugins(...)`;
@@ -97,7 +97,7 @@ if err != nil {
defer bot.Close() defer bot.Close()
plugin := laniakea.NewPlugin[laniakea.NoData]("main") plugin := laniakea.NewPlugin[laniakea.NoData]("main")
plugin.NewCommand(func(ctx *laniakea.MsgContext, db laniakea.NoData) error { plugin.NewCommand(func(ctx *laniakea.MessageContext, db laniakea.NoData) error {
ctx.Answer("pong") ctx.Answer("pong")
return nil return nil
}, "ping") }, "ping")
@@ -109,7 +109,7 @@ if err := bot.Run(); err != nil {
} }
``` ```
## `RunWithContext(...)`, `Run()`, and `RunWebHookWithContext(...)` ## `RunWithContext(...)`, `Run()`, and `RunWebhookWithContext(...)`
`RunWithContext(ctx)` is the main polling runtime entry point. `RunWithContext(ctx)` is the main polling runtime entry point.
@@ -124,16 +124,16 @@ It:
`Run()` is only a shorthand for `RunWithContext(context.Background())`. `Run()` is only a shorthand for `RunWithContext(context.Background())`.
`RunWebHookWithContext(...)` is the webhook runtime entry point. It shares the same: `RunWebhookWithContext(...)` is the webhook runtime entry point. It shares the same:
- single-use rule; - single-use rule;
- runner startup behavior; - runner startup behavior;
- internal update queue; - internal update queue;
- worker-pool dispatch model; - worker-pool dispatch model;
- graceful shutdown semantics. - graceful shutdown semantics.
Use `RunWithContext(...)` for production services that poll Telegram directly. Use `RunWebHookWithContext(...)` when Telegram should deliver updates through your HTTP endpoint. 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. 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]]. For the webhook-specific option model, transport behavior, and operational guidance, see [[Webhook-Runtime]].
@@ -172,7 +172,7 @@ Canceling the runtime context tells the bot to stop accepting new work and finis
- 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(...)` and `RunWebHookWithContext(...)` do 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()`
@@ -192,7 +192,7 @@ 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()`, `RunWithContext(...)`, or `RunWebHookWithContext(...)` 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 a runtime entry point again after a graceful stop; - do not call a runtime entry point again after a graceful stop;
+7 -7
@@ -47,11 +47,11 @@ plugin := laniakea.NewPlugin[laniakea.NoData]("admin")
Сигнатура обработчика команды такая: Сигнатура обработчика команды такая:
```go ```go
func(ctx *laniakea.MsgContext, db T) error func(ctx *laniakea.MessageContext, db T) error
``` ```
Где: Где:
- `ctx` — текущий `MsgContext`; - `ctx` — текущий `MessageContext`;
- `db` — значение generic-параметра `T`, которое ты передал в `Bot`. - `db` — значение generic-параметра `T`, которое ты передал в `Bot`.
Возвращай: Возвращай:
@@ -61,7 +61,7 @@ func(ctx *laniakea.MsgContext, db T) error
Пример: Пример:
```go ```go
func start(ctx *laniakea.MsgContext, db *App) error { func start(ctx *laniakea.MessageContext, db *App) error {
ctx.Answer("Welcome") ctx.Answer("Welcome")
return nil return nil
} }
@@ -125,7 +125,7 @@ plugin.AddCommand(
Пример: Пример:
```go ```go
func confirmDelete(ctx *laniakea.MsgContext, db *App) error { func confirmDelete(ctx *laniakea.MessageContext, db *App) error {
ctx.EditCallback("Deleted", nil) ctx.EditCallback("Deleted", nil)
return nil return nil
} }
@@ -147,7 +147,7 @@ plugin.AddPayload(plugin.NewPayload(confirmDelete, "delete.confirm"))
Пример: Пример:
```go ```go
plugin.AddUpdateHandler(tgapi.UpdateTypeInlineQuery, func(ctx *laniakea.MsgContext, db *App) error { plugin.AddUpdateHandler(tgapi.UpdateTypeInlineQuery, func(ctx *laniakea.MessageContext, db *App) error {
return nil return nil
}) })
``` ```
@@ -170,7 +170,7 @@ plugin.AddUpdateHandler(tgapi.UpdateTypeInlineQuery, func(ctx *laniakea.MsgConte
Для текстовой команды поток примерно такой: Для текстовой команды поток примерно такой:
1. Приходит Telegram update. 1. Приходит Telegram update.
2. `Bot` готовит `MsgContext`. 2. `Bot` готовит `MessageContext`.
3. Выполняется middleware бота. 3. Выполняется middleware бота.
4. Находится подходящий плагин. 4. Находится подходящий плагин.
5. Выполняется middleware плагина. 5. Выполняется middleware плагина.
@@ -258,5 +258,5 @@ return nil
- [[Getting-Started-RU]] - [[Getting-Started-RU]]
- [[Bot-Lifecycle-RU]] - [[Bot-Lifecycle-RU]]
- [[Commands-and-Plugins]] - [[Commands-and-Plugins]]
- [[MsgContext]] - [[MessageContext]]
- [[Inline-Keyboards-and-Payloads]] - [[Inline-Keyboards-and-Payloads]]
+10 -10
@@ -49,7 +49,7 @@ That keeps command registration and middleware ownership clear.
The command handler signature is: The command handler signature is:
```go ```go
func(ctx *laniakea.MsgContext, db T) error func(ctx *laniakea.MessageContext, db T) error
``` ```
Where: Where:
@@ -63,7 +63,7 @@ Return:
Example: Example:
```go ```go
func start(ctx *laniakea.MsgContext, db *App) error { func start(ctx *laniakea.MessageContext, db *App) error {
ctx.Answer("Welcome") ctx.Answer("Welcome")
return nil return nil
} }
@@ -89,7 +89,7 @@ So:
## The easiest command example ## The easiest command example
```go ```go
func echo(ctx *laniakea.MsgContext, db laniakea.NoData) error { func echo(ctx *laniakea.MessageContext, db laniakea.NoData) error {
if ctx.Text == "" { if ctx.Text == "" {
ctx.Answer("Usage: /echo <text>") ctx.Answer("Usage: /echo <text>")
return nil return nil
@@ -136,7 +136,7 @@ Payload handlers are for callback data coming from inline keyboard buttons.
Register them with `NewPayload(...)` or `AddPayload(...)`: Register them with `NewPayload(...)` or `AddPayload(...)`:
```go ```go
func confirmDelete(ctx *laniakea.MsgContext, db *App) error { func confirmDelete(ctx *laniakea.MessageContext, db *App) error {
ctx.EditCallback("Deleted", nil) ctx.EditCallback("Deleted", nil)
return nil return nil
} }
@@ -158,7 +158,7 @@ Update handlers are for Telegram updates outside the normal command/payload flow
Register them with `AddUpdateHandler(...)`: Register them with `AddUpdateHandler(...)`:
```go ```go
plugin.AddUpdateHandler(tgapi.UpdateTypeInlineQuery, func(ctx *laniakea.MsgContext, db *App) error { plugin.AddUpdateHandler(tgapi.UpdateTypeInlineQuery, func(ctx *laniakea.MessageContext, db *App) error {
// handle inline query here // handle inline query here
return nil return nil
}) })
@@ -183,7 +183,7 @@ stay on the command/payload flow and are not meant to be registered through `Add
For text commands: For text commands:
1. Telegram update arrives 1. Telegram update arrives
2. bot prepares `MsgContext` 2. bot prepares `MessageContext`
3. bot middleware runs 3. bot middleware runs
4. matching plugin is found 4. matching plugin is found
5. plugin middleware runs 5. plugin middleware runs
@@ -235,7 +235,7 @@ Avoid one giant plugin for the entire bot unless the bot is very small.
```go ```go
admin := laniakea.NewPlugin[*App]("admin") admin := laniakea.NewPlugin[*App]("admin")
admin.AddMiddleware(laniakea.NewMiddleware("admin-only", func(ctx *laniakea.MsgContext, app *App) bool { admin.AddMiddleware(laniakea.NewMiddleware("admin-only", func(ctx *laniakea.MessageContext, app *App) bool {
if !app.IsAdmin(ctx.FromID) { if !app.IsAdmin(ctx.FromID) {
ctx.Answer("Access denied") ctx.Answer("Access denied")
return false return false
@@ -243,7 +243,7 @@ admin.AddMiddleware(laniakea.NewMiddleware("admin-only", func(ctx *laniakea.MsgC
return true return true
})) }))
admin.AddCommand(admin.NewCommand(func(ctx *laniakea.MsgContext, app *App) error { admin.AddCommand(admin.NewCommand(func(ctx *laniakea.MessageContext, app *App) error {
ctx.Answer("Banned") ctx.Answer("Banned")
return nil return nil
}, "ban")) }, "ban"))
@@ -252,7 +252,7 @@ admin.AddCommand(admin.NewCommand(func(ctx *laniakea.MsgContext, app *App) error
### Example: payload handler for inline keyboard callback ### Example: payload handler for inline keyboard callback
```go ```go
plugin.AddPayload(plugin.NewPayload(func(ctx *laniakea.MsgContext, app *App) error { plugin.AddPayload(plugin.NewPayload(func(ctx *laniakea.MessageContext, app *App) error {
ctx.AnswerCbQueryText("Accepted") ctx.AnswerCbQueryText("Accepted")
ctx.EditCallback("Done", nil) ctx.EditCallback("Done", nil)
return nil return nil
@@ -304,6 +304,6 @@ Use:
- command middleware for narrow, local checks - command middleware for narrow, local checks
## Where to go next ## Where to go next
- Read [[MsgContext]] next to understand what handlers can do once they are triggered. - Read [[MessageContext]] next to understand what handlers can do once they are triggered.
- Read [[Inline-Keyboards-and-Payloads]] if you are starting to use buttons and callback data. - Read [[Inline-Keyboards-and-Payloads]] if you are starting to use buttons and callback data.
- Read [[Middleware]] for execution-order and async details. - Read [[Middleware]] for execution-order and async details.
+1 -1
@@ -75,6 +75,6 @@ draft.Flush()
## Что читать дальше ## Что читать дальше
- [[MsgContext-RU]] - [[MessageContext-RU]]
- [[Bot-Lifecycle-RU]] - [[Bot-Lifecycle-RU]]
- [[Drafts]] - [[Drafts]]
+4 -4
@@ -23,9 +23,9 @@ The draft system has two layers:
The provider is safe for concurrent use. Individual drafts are intended for single-goroutine use unless you add your own synchronization. The provider is safe for concurrent use. Individual drafts are intended for single-goroutine use unless you add your own synchronization.
## The easiest entry point: `MsgContext.NewDraft()` ## The easiest entry point: `MessageContext.NewDraft()`
Inside a handler, the usual entry point is `MsgContext.NewDraft()` or `MsgContext.NewDraftMarkdown()`. Inside a handler, the usual entry point is `MessageContext.NewDraft()` or `MessageContext.NewDraftMarkdown()`.
Those helpers: Those helpers:
- create a draft from the bot's configured `DraftProvider`; - create a draft from the bot's configured `DraftProvider`;
@@ -35,7 +35,7 @@ Those helpers:
Typical usage: Typical usage:
```go ```go
func report(ctx *laniakea.MsgContext, db *App) error { func report(ctx *laniakea.MessageContext, db *App) error {
draft := ctx.NewDraft() draft := ctx.NewDraft()
if draft == nil { if draft == nil {
return nil return nil
@@ -174,5 +174,5 @@ Prefer direct reply helpers such as `Answer(...)` or `AnswerLong(...)` when:
## Related pages ## Related pages
- [[MsgContext]] for handler-scoped reply and draft helpers - [[MessageContext]] for handler-scoped reply and draft helpers
- [[Bot-Lifecycle]] for draft-provider attachment through the bot - [[Bot-Lifecycle]] for draft-provider attachment through the bot
+1 -1
@@ -25,7 +25,7 @@ English version: [[Error-Handling]]
## Как это выглядит ## Как это выглядит
```go ```go
func ping(ctx *laniakea.MsgContext, db *App) error { func ping(ctx *laniakea.MessageContext, db *App) error {
ctx.Answer("pong") ctx.Answer("pong")
return nil return nil
} }
+4 -4
@@ -16,7 +16,7 @@ That applies to:
The basic pattern is: The basic pattern is:
```go ```go
func ping(ctx *laniakea.MsgContext, db *App) error { func ping(ctx *laniakea.MessageContext, db *App) error {
ctx.Answer("pong") ctx.Answer("pong")
return nil return nil
} }
@@ -183,7 +183,7 @@ Related page:
### Centralized command failure ### Centralized command failure
```go ```go
plugin.NewCommand(func(ctx *laniakea.MsgContext, db *App) error { plugin.NewCommand(func(ctx *laniakea.MessageContext, db *App) error {
result, err := db.DoWork() result, err := db.DoWork()
if err != nil { if err != nil {
return fmt.Errorf("failed to build report: %w", err) return fmt.Errorf("failed to build report: %w", err)
@@ -196,7 +196,7 @@ plugin.NewCommand(func(ctx *laniakea.MsgContext, db *App) error {
### Manual denial response ### Manual denial response
```go ```go
plugin.NewCommand(func(ctx *laniakea.MsgContext, db *App) error { plugin.NewCommand(func(ctx *laniakea.MessageContext, db *App) error {
if ctx.From == nil || !db.Allowed(ctx.From.ID) { if ctx.From == nil || !db.Allowed(ctx.From.ID) {
ctx.Answer("Access denied") ctx.Answer("Access denied")
return nil return nil
@@ -208,7 +208,7 @@ plugin.NewCommand(func(ctx *laniakea.MsgContext, db *App) error {
### Callback-specific manual alert ### Callback-specific manual alert
```go ```go
plugin.NewPayload(func(ctx *laniakea.MsgContext, db *App) error { plugin.NewPayload(func(ctx *laniakea.MessageContext, db *App) error {
if !ready { if !ready {
ctx.AnswerCbQueryAlert("This action is not available yet") ctx.AnswerCbQueryAlert("This action is not available yet")
return nil return nil
+22 -22
@@ -9,7 +9,7 @@
Текущее состояние: Текущее состояние:
- В репозитории уже есть низкоуровневые API для настройки webhook на уровне `tgapi`: `SetWebhook(...)`, `DeleteWebhook(...)`, `GetWebhookInfo(...)`, а также поддержка загрузки сертификата через uploader. - В репозитории уже есть низкоуровневые API для настройки webhook на уровне `tgapi`: `SetWebhook(...)`, `DeleteWebhook(...)`, `GetWebhookInfo(...)`, а также поддержка загрузки сертификата через uploader.
- Во фреймворке теперь есть полноценные bot-level точки входа webhook runtime: `RunWebHookWithContext(...)` и `RunWebHook(...)`. - Во фреймворке теперь есть полноценные bot-level точки входа webhook runtime: `RunWebhookWithContext(...)` и `RunWebhook(...)`.
- Webhook-доставка теперь использует ту же внутреннюю очередь update-ов, тот же worker pool, тот же запуск runners и тот же single-use runtime contract, что и polling. - Webhook-доставка теперь использует ту же внутреннюю очередь update-ов, тот же worker pool, тот же запуск runners и тот же single-use runtime contract, что и polling.
- Поведение webhook runtime, security-модель и правила перехода обратно на polling теперь описаны в основной документации и wiki. - Поведение webhook runtime, security-модель и правила перехода обратно на polling теперь описаны в основной документации и wiki.
@@ -21,12 +21,12 @@
Что теперь есть: Что теперь есть:
- `BotWebHookOpts`, `NewBotWebHookOpts()` и fluent helper-методы для webhook-конфигурации. - `BotWebhookOpts`, `NewBotWebhookOpts()` и fluent helper-методы для webhook-конфигурации.
- `RunWebHookWithContext(...)` и `RunWebHook(...)` как bot-owned точки входа runtime. - `RunWebhookWithContext(...)` и `RunWebhook(...)` как bot-owned точки входа runtime.
- Общая queued dispatch-модель, worker-pool обработка, запуск runners и single-use semantics для polling и webhook mode. - Общая queued dispatch-модель, worker-pool обработка, запуск runners и single-use semantics для polling и webhook mode.
- Fallback webhook `AllowedUpdates` к bot-level конфигурации типов update. - Fallback webhook `AllowedUpdates` к bot-level конфигурации типов update.
- Валидация webhook path и количества TLS-файлов до remote webhook setup. - Валидация webhook path и количества TLS-файлов до remote webhook setup.
- Явное удаление remote webhook через `CloseWebHook()` или низкоуровневый `tgapi.DeleteWebhook(...)` при переходе deployment с webhook-доставки обратно на polling. - Явное удаление remote webhook через `CloseWebhook()` или низкоуровневый `tgapi.DeleteWebhook(...)` при переходе deployment с webhook-доставки обратно на polling.
- Регрессионные тесты на queue delivery, запуск runners, single-use behavior, rejection слишком большого body, path/TLS validation и auth-поведение status endpoint. - Регрессионные тесты на queue delivery, запуск runners, single-use behavior, rejection слишком большого body, path/TLS validation и auth-поведение status endpoint.
Практическая цель: Практическая цель:
@@ -65,7 +65,7 @@
Текущее состояние: Текущее состояние:
- Во фреймворке теперь есть `Policy[T]` как явная переиспользуемая модель правила доступа, работающая поверх нормализованного `MsgContext` и общих данных приложения. - Во фреймворке теперь есть `Policy[T]` как явная переиспользуемая модель правила доступа, работающая поверх нормализованного `MessageContext` и общих данных приложения.
- Политики интегрируются в уже существующую модель выполнения через `RequirePolicy(...)`, поэтому авторизация остаётся на middleware-пути и не создаёт второй pipeline маршрутизации. - Политики интегрируются в уже существующую модель выполнения через `RequirePolicy(...)`, поэтому авторизация остаётся на middleware-пути и не создаёт второй pipeline маршрутизации.
- У бота и плагинов появились явные helpers для регистрации политик на уровне конфигурации. - У бота и плагинов появились явные helpers для регистрации политик на уровне конфигурации.
@@ -82,7 +82,7 @@
- `Bot.UsePolicy(...)` и `Plugin.UsePolicy(...)` для удобной регистрации. - `Bot.UsePolicy(...)` и `Plugin.UsePolicy(...)` для удобной регистрации.
- Встроенные Telegram-aware helpers: `RequirePrivateChat(...)`, `RequireGroupChat(...)`, `RequireSupergroupChat(...)`, `RequireChatAdmin(...)`, `RequireChatCreator(...)`, `RequireBotAdmin(...)` и `RequireCallbackFromUser(...)`. - Встроенные Telegram-aware helpers: `RequirePrivateChat(...)`, `RequireGroupChat(...)`, `RequireSupergroupChat(...)`, `RequireChatAdmin(...)`, `RequireChatCreator(...)`, `RequireBotAdmin(...)` и `RequireCallbackFromUser(...)`.
- Комбинаторы `AllPolicies(...)`, `AnyPolicy(...)` и `NotPolicy(...)`. - Комбинаторы `AllPolicies(...)`, `AnyPolicy(...)` и `NotPolicy(...)`.
- Расширенная нормализация `MsgContext` для `Chat` и `ChatID`, а также регрессионные тесты на поведение политик и нормализованного контекста. - Расширенная нормализация `MessageContext` для `Chat` и `ChatID`, а также регрессионные тесты на поведение политик и нормализованного контекста.
Практическая цель: Практическая цель:
@@ -106,7 +106,7 @@
- `AsUserError(...)` и `AsInternalError(...)` для явной классификации возвращаемых ошибок. - `AsUserError(...)` и `AsInternalError(...)` для явной классификации возвращаемых ошибок.
- `IsUserError(...)` и `IsInternalError(...)` для проверки этой классификации на стороне фреймворка. - `IsUserError(...)` и `IsInternalError(...)` для проверки этой классификации на стороне фреймворка.
- Обновлённое поведение `MsgContext.Error(...)`: все ошибки по-прежнему логируются, но для внутренних ошибок автоматический ответ пользователю подавляется. - Обновлённое поведение `MessageContext.Error(...)`: все ошибки по-прежнему логируются, но для внутренних ошибок автоматический ответ пользователю подавляется.
- Регрессионные тесты для message и callback потоков. - Регрессионные тесты для message и callback потоков.
Практическая цель: Практическая цель:
@@ -117,7 +117,7 @@
Текущее состояние: Текущее состояние:
- Фреймворк теперь считает конфигурацию бота структурно завершённой после начала первого runtime entry point: `Run()`, `RunWithContext(...)` или `RunWebHookWithContext(...)`. - Фреймворк теперь считает конфигурацию бота структурно завершённой после начала первого runtime entry point: `Run()`, `RunWithContext(...)` или `RunWebhookWithContext(...)`.
- Поздние bot-level попытки мутации больше не применяются частично после старта runtime. - Поздние bot-level попытки мутации больше не применяются частично после старта runtime.
- Границы между регистрацией плагинов, стартом runtime и фиксацией конфигурации теперь оформлены как явное поведение фреймворка и закреплены тестами. - Границы между регистрацией плагинов, стартом runtime и фиксацией конфигурации теперь оформлены как явное поведение фреймворка и закреплены тестами.
@@ -141,30 +141,30 @@
Текущее состояние: Текущее состояние:
- Нормализация обновлений уже существовала, но теперь она описана и протестирована как явный контракт уровня фреймворка. - Нормализация обновлений уже существовала, но теперь она описана и протестирована как явный контракт уровня фреймворка.
- Категории маршрутизации и гарантии заполнения `MsgContext` теперь рассматриваются как полноценная часть публичной модели. - Категории маршрутизации и гарантии заполнения `MessageContext` теперь рассматриваются как полноценная часть публичной модели.
Почему это важно: Почему это важно:
- Код обработчиков должен понимать, на какие поля `MsgContext` можно безопасно опираться в каждом потоке обновлений. - Код обработчиков должен понимать, на какие поля `MessageContext` можно безопасно опираться в каждом потоке обновлений.
- Без явного контракта обработка обновлений остаётся понятной только через чтение реализации. - Без явного контракта обработка обновлений остаётся понятной только через чтение реализации.
Что теперь есть: Что теперь есть:
- Задокументированная модель маршрутизации для command flow, payload flow и generic update handlers. - Задокументированная модель маршрутизации для command flow, payload flow и generic update handlers.
- Явные комментарии на полях `MsgContext`, описывающие гарантии для update-backed, callback-backed и message-backed контекстов. - Явные комментарии на полях `MessageContext`, описывающие гарантии для update-backed, callback-backed и message-backed контекстов.
- Table-driven регрессионные тесты для нормализованного update contract, включая callback target semantics и non-command update flows. - Table-driven регрессионные тесты для нормализованного update contract, включая callback target semantics и non-command update flows.
Практическая цель: Практическая цель:
- Сделать маршрутизацию обновлений и гарантии `MsgContext` достаточно явными, чтобы на них можно было опираться как на стабильный контракт `1.0`. - Сделать маршрутизацию обновлений и гарантии `MessageContext` достаточно явными, чтобы на них можно было опираться как на стабильный контракт `1.0`.
### [1.0.0-rc.12] Conversation / Scene Model ### [1.0.0-rc.12] Conversation / Scene Model
Текущее состояние: Текущее состояние:
- Фреймворк хорошо обрабатывает одно обновление через команды, данные callback, middleware и обработчики обновлений. - Фреймворк хорошо обрабатывает одно обновление через команды, данные callback, middleware и обработчики обновлений.
- В нём уже есть полезные низкоуровневые строительные блоки: `MsgContext`, черновики, маршрутизация данных callback, плагины и обработчики обновлений. - В нём уже есть полезные низкоуровневые строительные блоки: `MessageContext`, черновики, маршрутизация данных callback, плагины и обработчики обновлений.
- Теперь в нём уже есть реализованная начальная модель сцен для долгоживущих интерактивных сценариев: сцены можно регистрировать в плагинах, запускать через `MsgContext`, сохранять через `SessionStore` и маршрутизировать раньше обычной обработки команд. - Теперь в нём уже есть реализованная начальная модель сцен для долгоживущих интерактивных сценариев: сцены можно регистрировать в плагинах, запускать через `MessageContext`, сохранять через `SessionStore` и маршрутизировать раньше обычной обработки команд.
Почему это важно: Почему это важно:
@@ -176,7 +176,7 @@
- Маршрутизация активной сцены раньше обычной маршрутизации команд. - Маршрутизация активной сцены раньше обычной маршрутизации команд.
- Области действия сессии на пользователя, чат и пару пользователь-чат. - Области действия сессии на пользователя, чат и пару пользователь-чат.
- Явный вход и выход через `MsgContext`. - Явный вход и выход через `MessageContext`.
- Обработчики шагов, локальные команды сцены и `OnMessage(...)`. - Обработчики шагов, локальные команды сцены и `OnMessage(...)`.
- Встроенное in-memory-хранилище по умолчанию и интерфейс `SessionStore` для собственного постоянного хранения. - Встроенное in-memory-хранилище по умолчанию и интерфейс `SessionStore` для собственного постоянного хранения.
@@ -190,7 +190,7 @@
- `Scene`, `SceneContext`, `SceneSession` и `SessionStore`. - `Scene`, `SceneContext`, `SceneSession` и `SessionStore`.
- `Plugin.NewScene(...)` и `Plugin.AddScene(...)`. - `Plugin.NewScene(...)` и `Plugin.AddScene(...)`.
- `MsgContext.EnterScene(...)`, `EnterSceneStep(...)` и `ExitScene(...)`. - `MessageContext.EnterScene(...)`, `EnterSceneStep(...)` и `ExitScene(...)`.
- `SceneContext.Stay()`, `Next(...)`, `Exit()`, `Pass()`, `BindData(...)` и `SaveData(...)`. - `SceneContext.Stay()`, `Next(...)`, `Exit()`, `Pass()`, `BindData(...)` и `SaveData(...)`.
- Состояние на пользователя или чат с хранением в `SessionStore` и чистым интерфейсом для собственного постоянного хранения. - Состояние на пользователя или чат с хранением в `SessionStore` и чистым интерфейсом для собственного постоянного хранения.
@@ -198,7 +198,7 @@
- Это должно быть опциональным и расширяющим текущую модель. - Это должно быть опциональным и расширяющим текущую модель.
- Это не должно заменять плагины, команды или обработчики как обычные точки входа во фреймворк. - Это не должно заменять плагины, команды или обработчики как обычные точки входа во фреймворк.
- Это должно работать поверх существующих middleware и `MsgContext`, а не вводить вторую несовместимую модель выполнения. - Это должно работать поверх существующих middleware и `MessageContext`, а не вводить вторую несовместимую модель выполнения.
Практическая цель: Практическая цель:
@@ -240,7 +240,7 @@ type BanInput struct {
Reason string Reason string
} }
func ban(ctx *laniakea.MsgContext, db *App) error { func ban(ctx *laniakea.MessageContext, db *App) error {
var input BanInput var input BanInput
if err := ctx.BindArgs(&input); err != nil { if err := ctx.BindArgs(&input); err != nil {
return err return err
@@ -263,7 +263,7 @@ func ban(ctx *laniakea.MsgContext, db *App) error {
Текущее состояние: Текущее состояние:
- `RunWithContext(...)` и `RunWebHookWithContext(...)` управляют жизненным циклом выполнения бота и корректным завершением. - `RunWithContext(...)` и `RunWebhookWithContext(...)` управляют жизненным циклом выполнения бота и корректным завершением.
- `tgapi` уже поддерживает методы, принимающие `context.Context`. - `tgapi` уже поддерживает методы, принимающие `context.Context`.
- Обычные обработчики не получают полноценный `context.Context`, привязанный к обработке конкретного запроса. - Обычные обработчики не получают полноценный `context.Context`, привязанный к обработке конкретного запроса.
@@ -281,14 +281,14 @@ func ban(ctx *laniakea.MsgContext, db *App) error {
Возможное направление API: Возможное направление API:
- Предпочесть non-breaking подход и выдавать context через `MsgContext`, например `ctx.Context()`. - Предпочесть non-breaking подход и выдавать context через `MessageContext`, например `ctx.Context()`.
- Строить context из жизненного цикла обработки обновления, чтобы он оставался полезным во время корректного завершения. - Строить context из жизненного цикла обработки обновления, чтобы он оставался полезным во время корректного завершения.
- Сделать естественной передачу этого context в методы базы данных, HTTP-клиенты и `tgapi.WithContext(...)`. - Сделать естественной передачу этого context в методы базы данных, HTTP-клиенты и `tgapi.WithContext(...)`.
Почему это, скорее всего, не должно быть изменением сигнатуры: Почему это, скорее всего, не должно быть изменением сигнатуры:
- Изменение сигнатур обработчиков на прямой `context.Context` было бы публичным ломающим изменением. - Изменение сигнатур обработчиков на прямой `context.Context` было бы публичным ломающим изменением.
- Аксессор на `MsgContext` сохраняет совместимость и при этом даёт обработчикам идиоматичный Go-путь для отмены выполнения. - Аксессор на `MessageContext` сохраняет совместимость и при этом даёт обработчикам идиоматичный Go-путь для отмены выполнения.
Практическая цель: Практическая цель:
@@ -297,7 +297,7 @@ func ban(ctx *laniakea.MsgContext, db *App) error {
Связанные страницы: Связанные страницы:
- [[Scenes]] - [[Scenes]]
- [[MsgContext]] - [[MessageContext]]
- [[Bot-Lifecycle]] - [[Bot-Lifecycle]]
- [[Migration]] - [[Migration]]
+22 -22
@@ -9,7 +9,7 @@ This page tracks framework-level backlog items that are about missing concepts i
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 framework now exposes first-class bot-level webhook runtime entry points through `RunWebHookWithContext(...)` and `RunWebHook(...)`. - 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. - 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. - The webhook runtime behavior, security model, and polling-transition requirements are now documented in the main docs and wiki.
@@ -21,12 +21,12 @@ Why this matters:
What is now present: What is now present:
- `BotWebHookOpts`, `NewBotWebHookOpts()`, and fluent helpers for webhook-specific configuration. - `BotWebhookOpts`, `NewBotWebhookOpts()`, and fluent helpers for webhook-specific configuration.
- `RunWebHookWithContext(...)` and `RunWebHook(...)` as bot-owned runtime entry points. - `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. - 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. - 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. - 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. - 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. - Regression coverage for queue delivery, runner startup, single-use behavior, body-size rejection, path and TLS validation, and status-endpoint auth behavior.
Practical target: Practical target:
@@ -65,7 +65,7 @@ Practical target:
Current state: Current state:
- The framework now exposes `Policy[T]` as a first-class reusable authorization rule that runs against the normalized `MsgContext` and injected app data. - The framework now exposes `Policy[T]` as a first-class reusable authorization rule that runs against the normalized `MessageContext` and injected app data.
- Policies integrate with the existing execution model through `RequirePolicy(...)`, so authorization stays on the middleware path instead of introducing a second routing pipeline. - Policies integrate with the existing execution model through `RequirePolicy(...)`, so authorization stays on the middleware path instead of introducing a second routing pipeline.
- Bot-level and plugin-level registration helpers now make policy usage explicit in configuration. - Bot-level and plugin-level registration helpers now make policy usage explicit in configuration.
@@ -82,7 +82,7 @@ What is now present:
- `Bot.UsePolicy(...)` and `Plugin.UsePolicy(...)` for registration ergonomics. - `Bot.UsePolicy(...)` and `Plugin.UsePolicy(...)` for registration ergonomics.
- Built-in Telegram-aware helpers such as `RequirePrivateChat(...)`, `RequireGroupChat(...)`, `RequireSupergroupChat(...)`, `RequireChatAdmin(...)`, `RequireChatCreator(...)`, `RequireBotAdmin(...)`, and `RequireCallbackFromUser(...)`. - Built-in Telegram-aware helpers such as `RequirePrivateChat(...)`, `RequireGroupChat(...)`, `RequireSupergroupChat(...)`, `RequireChatAdmin(...)`, `RequireChatCreator(...)`, `RequireBotAdmin(...)`, and `RequireCallbackFromUser(...)`.
- Composition helpers `AllPolicies(...)`, `AnyPolicy(...)`, and `NotPolicy(...)`. - Composition helpers `AllPolicies(...)`, `AnyPolicy(...)`, and `NotPolicy(...)`.
- Extended `MsgContext` normalization for `Chat` and `ChatID`, plus regression coverage for policy and normalization behavior. - Extended `MessageContext` normalization for `Chat` and `ChatID`, plus regression coverage for policy and normalization behavior.
Practical target: Practical target:
@@ -106,7 +106,7 @@ What is now present:
- `AsUserError(...)` and `AsInternalError(...)` to classify returned handler errors explicitly. - `AsUserError(...)` and `AsInternalError(...)` to classify returned handler errors explicitly.
- `IsUserError(...)` and `IsInternalError(...)` for framework-side inspection. - `IsUserError(...)` and `IsInternalError(...)` for framework-side inspection.
- Updated centralized `MsgContext.Error(...)` behavior that still logs all errors but suppresses the automatic user reply for internal-only failures. - Updated centralized `MessageContext.Error(...)` behavior that still logs all errors but suppresses the automatic user reply for internal-only failures.
- Regression coverage for both message and callback flows. - Regression coverage for both message and callback flows.
Practical target: Practical target:
@@ -117,7 +117,7 @@ Practical target:
Current state: Current state:
- The framework now treats bot configuration as structurally complete once the first runtime entry point begins: `Run()`, `RunWithContext(...)`, or `RunWebHookWithContext(...)`. - 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.
@@ -141,30 +141,30 @@ Practical target:
Current state: Current state:
- Update normalization already existed, but it is now described and tested as an explicit framework-level contract. - Update normalization already existed, but it is now described and tested as an explicit framework-level contract.
- Routing categories and `MsgContext` population guarantees are now treated as a first-class part of the public model. - Routing categories and `MessageContext` population guarantees are now treated as a first-class part of the public model.
Why this matters: Why this matters:
- Handler code needs to know which `MsgContext` fields are safe to rely on for each update path. - Handler code needs to know which `MessageContext` fields are safe to rely on for each update path.
- Without a formal contract, update handling remains understandable only by reading implementation details. - Without a formal contract, update handling remains understandable only by reading implementation details.
What is now present: What is now present:
- A documented routing model for command flow, payload flow, and generic update handlers. - A documented routing model for command flow, payload flow, and generic update handlers.
- Explicit `MsgContext` field comments for update-backed, callback-backed, and message-backed contexts. - Explicit `MessageContext` field comments for update-backed, callback-backed, and message-backed contexts.
- Table-driven regression coverage for the normalized update contract, including callback target semantics and non-command update flows. - Table-driven regression coverage for the normalized update contract, including callback target semantics and non-command update flows.
Practical target: Practical target:
- Make update routing and `MsgContext` guarantees explicit enough to serve as a stable `1.0` public contract. - Make update routing and `MessageContext` guarantees explicit enough to serve as a stable `1.0` public contract.
### [1.0.0-rc.12] Conversation / Scene Model ### [1.0.0-rc.12] Conversation / Scene Model
Current state: Current state:
- The framework is strong at handling a single update through commands, payloads, middleware, and update handlers. - The framework is strong at handling a single update through commands, payloads, middleware, and update handlers.
- It already has useful lower-level building blocks such as `MsgContext`, drafts, payload routing, plugins, and update handlers. - It already has useful lower-level building blocks such as `MessageContext`, drafts, payload routing, plugins, and update handlers.
- It now provides an implemented initial scene model for long-lived user interaction flows: scenes can be registered in plugins, entered through `MsgContext`, persisted through `SessionStore`, and routed before normal command handling. - It now provides an implemented initial scene model for long-lived user interaction flows: scenes can be registered in plugins, entered through `MessageContext`, persisted through `SessionStore`, and routed before normal command handling.
Why this matters: Why this matters:
@@ -176,7 +176,7 @@ What is already present:
- Active-scene routing before normal command flow. - Active-scene routing before normal command flow.
- Per-user, per-chat, and per-user-chat session scopes. - Per-user, per-chat, and per-user-chat session scopes.
- Explicit scene entry and exit through `MsgContext`. - Explicit scene entry and exit through `MessageContext`.
- Step handlers, scene-local commands, and `OnMessage(...)`. - Step handlers, scene-local commands, and `OnMessage(...)`.
- In-memory session storage by default, plus the `SessionStore` interface for custom persistence. - In-memory session storage by default, plus the `SessionStore` interface for custom persistence.
@@ -190,7 +190,7 @@ Current API direction:
- `Scene`, `SceneContext`, `SceneSession`, and `SessionStore`. - `Scene`, `SceneContext`, `SceneSession`, and `SessionStore`.
- `Plugin.NewScene(...)` and `Plugin.AddScene(...)`. - `Plugin.NewScene(...)` and `Plugin.AddScene(...)`.
- `MsgContext.EnterScene(...)`, `EnterSceneStep(...)`, and `ExitScene(...)`. - `MessageContext.EnterScene(...)`, `EnterSceneStep(...)`, and `ExitScene(...)`.
- `SceneContext.Stay()`, `Next(...)`, `Exit()`, `Pass()`, `BindData(...)`, and `SaveData(...)`. - `SceneContext.Stay()`, `Next(...)`, `Exit()`, `Pass()`, `BindData(...)`, and `SaveData(...)`.
- Storage-backed per-user or per-chat state with a clean interface for custom persistence. - Storage-backed per-user or per-chat state with a clean interface for custom persistence.
@@ -198,7 +198,7 @@ Important design constraints:
- This should be additive and optional. - This should be additive and optional.
- It should not replace plugins, commands, or handlers as the normal framework entry points. - It should not replace plugins, commands, or handlers as the normal framework entry points.
- It should work with existing middleware and `MsgContext` instead of introducing a second incompatible execution model. - It should work with existing middleware and `MessageContext` instead of introducing a second incompatible execution model.
Practical target: Practical target:
@@ -240,7 +240,7 @@ type BanInput struct {
Reason string Reason string
} }
func ban(ctx *laniakea.MsgContext, db *App) error { func ban(ctx *laniakea.MessageContext, db *App) error {
var input BanInput var input BanInput
if err := ctx.BindArgs(&input); err != nil { if err := ctx.BindArgs(&input); err != nil {
return err return err
@@ -263,7 +263,7 @@ Practical target:
Current state: Current state:
- `RunWithContext(...)` and `RunWebHookWithContext(...)` control 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`.
@@ -281,14 +281,14 @@ What is missing:
Possible API direction: Possible API direction:
- Prefer a non-breaking approach by exposing context through `MsgContext`, for example `ctx.Context()`. - Prefer a non-breaking approach by exposing context through `MessageContext`, for example `ctx.Context()`.
- Build the context from the update-processing lifecycle so it is meaningful during graceful shutdown. - Build the context from the update-processing lifecycle so it is meaningful during graceful shutdown.
- Make it natural to pass that context into database methods, HTTP clients, and `tgapi.WithContext(...)` calls. - Make it natural to pass that context into database methods, HTTP clients, and `tgapi.WithContext(...)` calls.
Why this should probably not be a signature change: Why this should probably not be a signature change:
- Changing handler signatures to accept `context.Context` directly would be a public breaking change. - Changing handler signatures to accept `context.Context` directly would be a public breaking change.
- A `MsgContext` accessor would preserve compatibility while still giving handlers an idiomatic Go cancellation path. - A `MessageContext` accessor would preserve compatibility while still giving handlers an idiomatic Go cancellation path.
Practical target: Practical target:
@@ -297,7 +297,7 @@ Practical target:
Related pages: Related pages:
- [[Scenes]] - [[Scenes]]
- [[MsgContext]] - [[MessageContext]]
- [[Bot-Lifecycle]] - [[Bot-Lifecycle]]
- [[Migration]] - [[Migration]]
+7 -7
@@ -33,7 +33,7 @@ import (
"git.scuroneko.dev/scuroneko/laniakea" "git.scuroneko.dev/scuroneko/laniakea"
) )
func ping(ctx *laniakea.MsgContext, db laniakea.NoData) error { func ping(ctx *laniakea.MessageContext, db laniakea.NoData) error {
ctx.Answer("Pong") ctx.Answer("Pong")
return nil return nil
} }
@@ -111,7 +111,7 @@ bot.AddPlugins(plugin)
Сигнатура хендлера: Сигнатура хендлера:
```go ```go
func(ctx *laniakea.MsgContext, db T) error func(ctx *laniakea.MessageContext, db T) error
``` ```
То есть: То есть:
@@ -121,7 +121,7 @@ func(ctx *laniakea.MsgContext, db T) error
Пример: Пример:
```go ```go
func profile(ctx *laniakea.MsgContext, db *App) error { func profile(ctx *laniakea.MessageContext, db *App) error {
user, err := db.LoadUser(ctx.FromID) user, err := db.LoadUser(ctx.FromID)
if err != nil { if err != nil {
return err return err
@@ -136,7 +136,7 @@ func profile(ctx *laniakea.MsgContext, db *App) error {
### 4. `Bot` single-use ### 4. `Bot` single-use
После `Run()`, `RunWithContext(...)` или `RunWebHookWithContext(...)` нельзя снова запускать тот же экземпляр `Bot`. После `Run()`, `RunWithContext(...)` или `RunWebhookWithContext(...)` нельзя снова запускать тот же экземпляр `Bot`.
Правильная модель: Правильная модель:
- создать bot - создать bot
@@ -151,7 +151,7 @@ func profile(ctx *laniakea.MsgContext, db *App) error {
### 5. `Close()` все равно нужен ### 5. `Close()` все равно нужен
Даже если ты используешь `Run()`, `RunWithContext(...)` или `RunWebHookWithContext(...)`, ресурсы нужно закрывать явно: Даже если ты используешь `Run()`, `RunWithContext(...)` или `RunWebhookWithContext(...)`, ресурсы нужно закрывать явно:
```go ```go
defer bot.Close() defer bot.Close()
@@ -168,7 +168,7 @@ defer bot.Close()
5. Добавить команды, payloads и middleware в плагины 5. Добавить команды, payloads и middleware в плагины
6. Зарегистрировать плагины через `AddPlugins(...)` 6. Зарегистрировать плагины через `AddPlugins(...)`
7. При необходимости вызвать `AutoGenerateCommands()` 7. При необходимости вызвать `AutoGenerateCommands()`
8. Вызвать `Run()`, `RunWithContext(...)` или `RunWebHookWithContext(...)` 8. Вызвать `Run()`, `RunWithContext(...)` или `RunWebhookWithContext(...)`
9. Закрыть bot через `Close()` 9. Закрыть bot через `Close()`
## Частые ошибки на старте ## Частые ошибки на старте
@@ -199,4 +199,4 @@ defer bot.Close()
- [[Bot-Lifecycle-RU]] - [[Bot-Lifecycle-RU]]
- [[Getting-Started]] - [[Getting-Started]]
- [[Commands-and-Plugins]] - [[Commands-and-Plugins]]
- [[MsgContext]] - [[MessageContext]]
+8 -8
@@ -34,7 +34,7 @@ import (
"git.scuroneko.dev/scuroneko/laniakea" "git.scuroneko.dev/scuroneko/laniakea"
) )
func ping(ctx *laniakea.MsgContext, db laniakea.NoData) error { func ping(ctx *laniakea.MessageContext, db laniakea.NoData) error {
ctx.Answer("Pong") ctx.Answer("Pong")
return nil return nil
} }
@@ -113,7 +113,7 @@ See [[Commands-and-Plugins]] for the full model.
The command handler signature is: The command handler signature is:
```go ```go
func(ctx *laniakea.MsgContext, db T) error func(ctx *laniakea.MessageContext, db T) error
``` ```
This means: This means:
@@ -124,7 +124,7 @@ This means:
Example: Example:
```go ```go
func profile(ctx *laniakea.MsgContext, db *App) error { func profile(ctx *laniakea.MessageContext, db *App) error {
user, err := db.LoadUser(ctx.FromID) user, err := db.LoadUser(ctx.FromID)
if err != nil { if err != nil {
return err return err
@@ -139,7 +139,7 @@ func profile(ctx *laniakea.MsgContext, db *App) error {
A `Bot` instance is single-use. A `Bot` instance is single-use.
After `Run()`, `RunWithContext(...)`, or `RunWebHookWithContext(...)` returns: After `Run()`, `RunWithContext(...)`, or `RunWebhookWithContext(...)` returns:
- do not call a runtime entry point 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
@@ -149,7 +149,7 @@ See [[Bot-Lifecycle]] for details.
### 5. Always close the bot ### 5. Always close the bot
`Run()`, `RunWithContext(...)`, and `RunWebHookWithContext(...)` 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()`, `RunWithContext(...)`, or `RunWebHookWithContext(...)` 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
@@ -184,7 +184,7 @@ import (
type App struct{} type App struct{}
func echo(ctx *laniakea.MsgContext, app *App) error { func echo(ctx *laniakea.MessageContext, app *App) error {
if ctx.Text == "" { if ctx.Text == "" {
ctx.Answer("Send some text after the command.") ctx.Answer("Send some text after the command.")
return nil return nil
@@ -259,5 +259,5 @@ Prefer pointer types unless you have a strong reason not to.
## Where to go next ## Where to go next
- Read [[Commands-and-Plugins]] next if you want to build the handler layer correctly. - Read [[Commands-and-Plugins]] next if you want to build the handler layer correctly.
- Read [[MsgContext]] next if you want to understand reply, edit, callback, and draft helpers. - Read [[MessageContext]] next if you want to understand reply, edit, callback, and draft helpers.
- Read [[Bot-Lifecycle]] if you need shutdown, worker, or startup details. - Read [[Bot-Lifecycle]] if you need shutdown, worker, or startup details.
+3 -3
@@ -9,7 +9,7 @@ English version: [[Home]]
- [[Getting-Started-RU]] - [[Getting-Started-RU]]
- [[Bot-Options-and-Configuration-RU]] - [[Bot-Options-and-Configuration-RU]]
- [[Commands-and-Plugins-RU]] - [[Commands-and-Plugins-RU]]
- [[MsgContext-RU]] - [[MessageContext-RU]]
## Выполнение и архитектура ## Выполнение и архитектура
@@ -50,7 +50,7 @@ English version: [[Home]]
- [[Getting-Started]] - [[Getting-Started]]
- [[Bot-Options-and-Configuration]] - [[Bot-Options-and-Configuration]]
- [[Commands-and-Plugins]] - [[Commands-and-Plugins]]
- [[MsgContext]] - [[MessageContext]]
- [[Bot-Lifecycle]] - [[Bot-Lifecycle]]
- [[Inline-Keyboards-and-Payloads]] - [[Inline-Keyboards-and-Payloads]]
- [[tgapi-Overview]] - [[tgapi-Overview]]
@@ -60,7 +60,7 @@ English version: [[Home]]
Рекомендуемый маршрут для русскоязычного пользователя: Рекомендуемый маршрут для русскоязычного пользователя:
1. Прочитать [[Getting-Started-RU]]. 1. Прочитать [[Getting-Started-RU]].
2. Прочитать [[Commands-and-Plugins-RU]] и [[MsgContext-RU]]. 2. Прочитать [[Commands-and-Plugins-RU]] и [[MessageContext-RU]].
3. Перейти в страницы про выполнение и поведение во время работы, например [[Bot-Lifecycle-RU]] и [[Middleware-RU]]. 3. Перейти в страницы про выполнение и поведение во время работы, например [[Bot-Lifecycle-RU]] и [[Middleware-RU]].
4. При необходимости открыть специализированные страницы вроде [[Drafts-RU]], [[Rate-Limiting-RU]] или [[tgapi-Overview-RU]]. 4. При необходимости открыть специализированные страницы вроде [[Drafts-RU]], [[Rate-Limiting-RU]] или [[tgapi-Overview-RU]].
5. Для максимальной точности переходить в соответствующую англоязычную страницу. 5. Для максимальной точности переходить в соответствующую англоязычную страницу.
+1 -1
@@ -15,7 +15,7 @@ Use this wiki as the structured companion to the README: start with setup, then
- [[Getting-Started]] - [[Getting-Started]]
- [[Bot-Options-and-Configuration]] - [[Bot-Options-and-Configuration]]
- [[Commands-and-Plugins]] - [[Commands-and-Plugins]]
- [[MsgContext]] - [[MessageContext]]
## Bot Runtime ## Bot Runtime
- [[Bot-Lifecycle]] - [[Bot-Lifecycle]]
+1 -1
@@ -99,5 +99,5 @@ kb := ctx.NewInlineKeyboard(2).
## Что читать дальше ## Что читать дальше
- [[Commands-and-Plugins-RU]] - [[Commands-and-Plugins-RU]]
- [[MsgContext-RU]] - [[MessageContext-RU]]
- [[Inline-Keyboards-and-Payloads]] - [[Inline-Keyboards-and-Payloads]]
+4 -4
@@ -91,7 +91,7 @@ Inside the payload handler, those values are available through:
- `ctx.Args` for the decoded argument list; - `ctx.Args` for the decoded argument list;
- the payload command name used to choose the matched handler. - the payload command name used to choose the matched handler.
There is no separate `ctx.Payload` object in the current API. Payload handlers receive the same `MsgContext` structure used elsewhere, with `ctx.Args` populated from callback data. There is no separate `ctx.Payload` object in the current API. Payload handlers receive the same `MessageContext` structure used elsewhere, with `ctx.Args` populated from callback data.
## JSON vs Base64 payloads ## JSON vs Base64 payloads
@@ -126,7 +126,7 @@ The bot has a default callback encoding:
bot.SetPayloadType(laniakea.BotPayloadBase64) bot.SetPayloadType(laniakea.BotPayloadBase64)
``` ```
That default is copied into `MsgContext`, so `ctx.NewInlineKeyboard(...)` starts with the bots current payload type. That default is copied into `MessageContext`, so `ctx.NewInlineKeyboard(...)` starts with the bots current payload type.
For a single keyboard, you can override it locally: For a single keyboard, you can override it locally:
@@ -247,7 +247,7 @@ Payloads are registered on plugins with:
Example: Example:
```go ```go
plugin.NewPayload(func(ctx *laniakea.MsgContext, db *App) error { plugin.NewPayload(func(ctx *laniakea.MessageContext, db *App) error {
id := ctx.Args[0] id := ctx.Args[0]
ctx.AnswerCbQueryText("Handled " + id) ctx.AnswerCbQueryText("Handled " + id)
return nil return nil
@@ -275,5 +275,5 @@ Related page:
## Related pages ## Related pages
- [[Commands-and-Plugins]] for payload handler registration and routing. - [[Commands-and-Plugins]] for payload handler registration and routing.
- [[MsgContext]] for reply, edit, and callback helpers. - [[MessageContext]] for reply, edit, and callback helpers.
- [[tgapi-Overview]] for lower-level Telegram method access when keyboard helpers are not enough. - [[tgapi-Overview]] for lower-level Telegram method access when keyboard helpers are not enough.
+5 -5
@@ -77,11 +77,11 @@ 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. - `MessageContext.Translate(key)` becomes the ergonomic handler-level helper.
If `SetL10n(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` ## `MessageContext.Translate`
`ctx.Translate(key)` is the usual choice inside handlers. `ctx.Translate(key)` is the usual choice inside handlers.
@@ -93,7 +93,7 @@ It:
Example: Example:
```go ```go
func start(ctx *laniakea.MsgContext, db *App) error { func start(ctx *laniakea.MessageContext, db *App) error {
ctx.Answer(ctx.Translate("greeting")) ctx.Answer(ctx.Translate("greeting"))
return nil return nil
} }
@@ -111,7 +111,7 @@ Use `bot.L10n(lang, key)` when you need a translation outside handler flow or wh
Examples: Examples:
- preparing background-runner messages; - preparing background-runner messages;
- rendering text for stored user preferences; - rendering text for stored user preferences;
- translating outside `MsgContext`. - translating outside `MessageContext`.
## Concurrency and mutation safety ## Concurrency and mutation safety
@@ -150,5 +150,5 @@ l10n.
## Related pages ## Related pages
- [[Getting-Started]] for basic bot setup - [[Getting-Started]] for basic bot setup
- [[MsgContext]] for handler helpers - [[MessageContext]] for handler helpers
- [[Recipes]] for localized handler examples - [[Recipes]] for localized handler examples
+2 -2
@@ -55,13 +55,13 @@ Ways plugin loggers are set:
- explicitly with `Plugin.SetLogger(...)` before `AddPlugins(...)`; - explicitly with `Plugin.SetLogger(...)` before `AddPlugins(...)`;
- implicitly by the bot, which creates a default logger if the plugin has none at registration time. - implicitly by the bot, which creates a default logger if the plugin has none at registration time.
During handler execution, `MsgContext.Logger` is set to: During handler execution, `MessageContext.Logger` is set to:
- the matched plugin logger when one exists; - the matched plugin logger when one exists;
- otherwise the bot logger. - otherwise the bot logger.
That means handler-local logs naturally follow plugin boundaries when possible. That means handler-local logs naturally follow plugin boundaries when possible.
## `MsgContext.Logger` ## `MessageContext.Logger`
Inside handlers and middleware, the easiest logger to use is `ctx.Logger`. Inside handlers and middleware, the easiest logger to use is `ctx.Logger`.
+4 -4
@@ -45,7 +45,7 @@ Middleware позволяет запускать логику до обрабо
Сигнатура: Сигнатура:
```go ```go
func(ctx *laniakea.MsgContext, db T) bool func(ctx *laniakea.MessageContext, db T) bool
``` ```
Возвращает: Возвращает:
@@ -66,7 +66,7 @@ func(ctx *laniakea.MsgContext, db T) bool
- middleware идет в goroutine; - middleware идет в goroutine;
- выполнение цепочки продолжается сразу; - выполнение цепочки продолжается сразу;
- возвращаемое значение `bool` игнорируется; - возвращаемое значение `bool` игнорируется;
- middleware получает копию `MsgContext`. - middleware получает копию `MessageContext`.
Поэтому async middleware подходит только для: Поэтому async middleware подходит только для:
- телеметрии; - телеметрии;
@@ -79,7 +79,7 @@ func(ctx *laniakea.MsgContext, db T) bool
- обязательной валидации; - обязательной валидации;
- логики, которая должна переписать `ctx` и повлиять на обработчик. - логики, которая должна переписать `ctx` и повлиять на обработчик.
## Почему async middleware получает копию `MsgContext` ## Почему async middleware получает копию `MessageContext`
Так библиотека избегает очевидных гонок данных между goroutine middleware и основной цепочкой обработки. Так библиотека избегает очевидных гонок данных между goroutine middleware и основной цепочкой обработки.
@@ -105,5 +105,5 @@ Middleware уровня плагина и команды сохраняют по
## Что читать дальше ## Что читать дальше
- [[Commands-and-Plugins-RU]] - [[Commands-and-Plugins-RU]]
- [[MsgContext-RU]] - [[MessageContext-RU]]
- [[Middleware]] - [[Middleware]]
+12 -12
@@ -11,11 +11,11 @@ Middleware is useful when the same check or side effect should apply in more tha
Typical uses: Typical uses:
- reject updates from unauthorized users; - reject updates from unauthorized users;
- log incoming commands and callback payloads; - log incoming commands and callback payloads;
- attach derived values to `MsgContext`; - attach derived values to `MessageContext`;
- stop processing early when a precondition is not met; - stop processing early when a precondition is not met;
- run non-blocking side effects such as analytics or audit logging. - run non-blocking side effects such as analytics or audit logging.
For the handler and plugin model around middleware, see [[Commands-and-Plugins]]. For the fields you can read or update on the context, see [[MsgContext]]. For the handler and plugin model around middleware, see [[Commands-and-Plugins]]. For the fields you can read or update on the context, see [[MessageContext]].
## Middleware levels ## Middleware levels
@@ -47,7 +47,7 @@ For non-command update handlers registered through `AddUpdateHandler(...)`, the
Important detail: Important detail:
- bot-level middleware runs once per update before routing; - bot-level middleware runs once per update before routing;
- for non-command update handlers, each matching plugin receives its own cloned `MsgContext`, so one plugin's mutations do not leak into the next plugin's handler chain. - for non-command update handlers, each matching plugin receives its own cloned `MessageContext`, so one plugin's mutations do not leak into the next plugin's handler chain.
## Synchronous middleware ## Synchronous middleware
@@ -56,7 +56,7 @@ By default, middleware is synchronous.
The executor signature is: The executor signature is:
```go ```go
func(ctx *laniakea.MsgContext, db T) bool func(ctx *laniakea.MessageContext, db T) bool
``` ```
Return values mean: Return values mean:
@@ -72,7 +72,7 @@ This makes synchronous middleware the right choice for:
Example: Example:
```go ```go
auth := laniakea.NewMiddleware("auth", func(ctx *laniakea.MsgContext, db *App) bool { auth := laniakea.NewMiddleware("auth", func(ctx *laniakea.MessageContext, db *App) bool {
if ctx.From == nil || !db.Allowed(ctx.From.ID) { if ctx.From == nil || !db.Allowed(ctx.From.ID) {
ctx.Answer("Access denied") ctx.Answer("Access denied")
return false return false
@@ -86,7 +86,7 @@ auth := laniakea.NewMiddleware("auth", func(ctx *laniakea.MsgContext, db *App) b
Middleware can also run asynchronously with `SetAsync(true)`. Middleware can also run asynchronously with `SetAsync(true)`.
```go ```go
audit := laniakea.NewMiddleware("audit", func(ctx *laniakea.MsgContext, db *App) bool { audit := laniakea.NewMiddleware("audit", func(ctx *laniakea.MessageContext, db *App) bool {
db.Audit(ctx.Update.UpdateID, ctx.Text) db.Audit(ctx.Update.UpdateID, ctx.Text)
return true return true
}).SetAsync(true) }).SetAsync(true)
@@ -96,7 +96,7 @@ Async middleware behaves differently:
- it runs in a goroutine; - it runs in a goroutine;
- execution always continues immediately; - execution always continues immediately;
- its boolean return value is ignored; - its boolean return value is ignored;
- it receives a copied `MsgContext`, not the original pointer. - it receives a copied `MessageContext`, not the original pointer.
That means async middleware is appropriate for: That means async middleware is appropriate for:
- fire-and-forget logging; - fire-and-forget logging;
@@ -112,7 +112,7 @@ It is not appropriate for:
## Why async middleware gets a copied context ## Why async middleware gets a copied context
When middleware is async, the library copies `MsgContext` before starting the goroutine. This prevents obvious data races against the handler path. When middleware is async, the library copies `MessageContext` before starting the goroutine. This prevents obvious data races against the handler path.
Practical consequence: Practical consequence:
- changes you make to the copied `ctx` inside async middleware are local to that goroutine; - changes you make to the copied `ctx` inside async middleware are local to that goroutine;
@@ -121,7 +121,7 @@ Practical consequence:
So this pattern does not work: So this pattern does not work:
```go ```go
bad := laniakea.NewMiddleware("bad", func(ctx *laniakea.MsgContext, db *App) bool { bad := laniakea.NewMiddleware("bad", func(ctx *laniakea.MessageContext, db *App) bool {
ctx.Text = "rewritten" ctx.Text = "rewritten"
return false return false
}).SetAsync(true) }).SetAsync(true)
@@ -177,7 +177,7 @@ This usually means:
```go ```go
bot.AddMiddleware( bot.AddMiddleware(
laniakea.NewMiddleware("private-only", func(ctx *laniakea.MsgContext, db *App) bool { laniakea.NewMiddleware("private-only", func(ctx *laniakea.MessageContext, db *App) bool {
if ctx.Chat == nil || ctx.Chat.Type != "private" { if ctx.Chat == nil || ctx.Chat.Type != "private" {
return false return false
} }
@@ -191,7 +191,7 @@ bot.AddMiddleware(
```go ```go
admin := laniakea.NewPlugin[*App]("admin") admin := laniakea.NewPlugin[*App]("admin")
admin.AddMiddleware( admin.AddMiddleware(
laniakea.NewMiddleware("admin-only", func(ctx *laniakea.MsgContext, db *App) bool { laniakea.NewMiddleware("admin-only", func(ctx *laniakea.MessageContext, db *App) bool {
return ctx.From != nil && db.IsAdmin(ctx.From.ID) return ctx.From != nil && db.IsAdmin(ctx.From.ID)
}), }),
) )
@@ -201,7 +201,7 @@ admin.AddMiddleware(
```go ```go
ban := admin.NewCommand(banUser, "ban") ban := admin.NewCommand(banUser, "ban")
ban.Use(laniakea.NewMiddleware("require-reply", func(ctx *laniakea.MsgContext, db *App) bool { ban.Use(laniakea.NewMiddleware("require-reply", func(ctx *laniakea.MessageContext, db *App) bool {
if ctx.Msg == nil || ctx.Msg.ReplyToMessage == nil { if ctx.Msg == nil || ctx.Msg.ReplyToMessage == nil {
ctx.Answer("Reply to a user message first") ctx.Answer("Reply to a user message first")
return false return false
+64 -1
@@ -25,7 +25,70 @@ When jumping across multiple RC versions:
5. Revisit any direct `tgapi` calls and renamed types. 5. Revisit any direct `tgapi` calls and renamed types.
6. Run tests against realistic update payloads and callback data. 6. Run tests against realistic update payloads and callback data.
The largest migration points in the current history are `rc.4`, `rc.7`, `rc.10`, and `rc.12`. The largest migration points in the current history are `rc.4`, `rc.7`, `rc.10`, `rc.12`, and the `v1.0.0` stable release.
## `v1.0.0`
`v1.0.0` is a public-API hygiene release. Most changes are renames and type-system tightenings that cause compile errors and are easy to fix mechanically.
### Handler type rename: `MsgContext``MessageContext`
Every handler signature and every explicit type reference to `MsgContext` must be renamed to `MessageContext`.
```go
// before
func ping(ctx *laniakea.MsgContext, db *App) error { ... }
// after
func ping(ctx *laniakea.MessageContext, db *App) error { ... }
```
This includes `CommandExecutor`, `MiddlewareExecutor`, `SceneContext.MessageContext`, and any local variable type annotations.
### Runner builder change: `Onetime` and `Timeout` removed
The `Onetime(bool)` and `Timeout(duration)` builder methods have been replaced by `Every(duration)` and `Async(bool)`.
```go
// before — one-time sync
runner.Onetime(true).Async(false)
// after — one-time sync
runner.Async(false)
// before — periodic
runner.Timeout(5 * time.Minute)
// after — periodic
runner.Every(5 * time.Minute)
```
The default remains async one-shot (`Every(0).Async(true)`), so runners without a builder call are unaffected.
### Observer method renames
If you implement the `Observer` interface directly, rename the two affected methods:
| Before | After |
|---|---|
| `OnReceiveUpdate(UpdateReceivedEvent)` | `OnUpdateReceived(UpdateReceivedEvent)` |
| `OnHandledUpdate(UpdateHandledEvent)` | `OnUpdateHandled(UpdateHandledEvent)` |
### `Scene.PluginName` unexported
`Scene.PluginName` was a mutable public field. It is now unexported. Remove any reads or writes to this field; the framework assigns it during `AddPlugins(...)` registration.
### `SceneSession.Data` unexported
`SceneSession.Data []byte` was a public field. It is now unexported. Use the accessor helpers: `HasData()`, `BindData(...)`, `SaveData(...)`, `ClearData()`.
### `BotPayloadType*` constants are now `const`
`BotPayloadTypeJSON`, `BotPayloadTypeBase64`, `BotPayloadTypeCompact`, and `BotPayloadTypeCompactBase64` were `var`. They are now `const`. Any code assigning to them will fail to compile.
### Webhook error sentinels
Inline `errors.New(...)` error values returned from webhook startup have been replaced by exported sentinels. If you were comparing webhook startup errors with `==`, switch to `errors.Is(...)`.
## `v1.0.0-rc.12` ## `v1.0.0-rc.12`
+9 -9
@@ -1,12 +1,12 @@
# MsgContext RU # MessageContext RU
English version: [[MsgContext]] English version: [[MessageContext]]
Это краткая русскоязычная версия страницы про `MsgContext`. Полная и наиболее актуальная страница: [[MsgContext]]. Это краткая русскоязычная версия страницы про `MessageContext`. Полная и наиболее актуальная страница: [[MessageContext]].
## Что такое `MsgContext` ## Что такое `MessageContext`
`MsgContext` — это объект времени выполнения, который приходит в: `MessageContext` — это объект времени выполнения, который приходит в:
- обработчики команд; - обработчики команд;
- обработчики данных callback; - обработчики данных callback;
- middleware; - middleware;
@@ -18,7 +18,7 @@ English version: [[MsgContext]]
- разобранные аргументы команд и callback; - разобранные аргументы команд и callback;
- вспомогательные методы для reply, edit, delete, callback, drafts и localization. - вспомогательные методы для reply, edit, delete, callback, drafts и localization.
Полную матрицу маршрутизации и гарантий по полям `MsgContext` для разных update types смотри в [[Update-Routing-Model-RU]]. Полную матрицу маршрутизации и гарантий по полям `MessageContext` для разных update types смотри в [[Update-Routing-Model-RU]].
## Поля, которые используются чаще всего ## Поля, которые используются чаще всего
@@ -107,12 +107,12 @@ English version: [[MsgContext]]
## Drafts и localization ## Drafts и localization
У `MsgContext` есть: У `MessageContext` есть:
- `NewDraft()` - `NewDraft()`
- `NewDraftMarkdown()` - `NewDraftMarkdown()`
- `Translate(key)` - `Translate(key)`
Это делает `MsgContext` основной удобной точкой доступа почти для всего кода обработчиков. Это делает `MessageContext` основной удобной точкой доступа почти для всего кода обработчиков.
## `NewInlineKeyboard(...)` ## `NewInlineKeyboard(...)`
@@ -130,4 +130,4 @@ kb := ctx.NewInlineKeyboard(2)
- [[Inline-Keyboards-and-Payloads-RU]] - [[Inline-Keyboards-and-Payloads-RU]]
- [[Drafts-RU]] - [[Drafts-RU]]
- [[Localization-RU]] - [[Localization-RU]]
- [[MsgContext]] - [[MessageContext]]
+16 -16
@@ -1,8 +1,8 @@
# MsgContext # MessageContext
Russian version: [[MsgContext-RU]] Russian version: [[MessageContext-RU]]
`MsgContext` is the runtime object passed into command handlers, payload handlers, middleware, and update handlers. `MessageContext` is the runtime object passed into command handlers, payload handlers, middleware, and update handlers.
It gives you access to: It gives you access to:
- the incoming update - the incoming update
@@ -10,7 +10,7 @@ It gives you access to:
- parsed command or payload arguments - parsed command or payload arguments
- reply, edit, delete, callback, draft, and localization helpers - reply, edit, delete, callback, draft, and localization helpers
If you write handlers, `MsgContext` is the API surface you will use most often. If you write handlers, `MessageContext` is the API surface you will use most often.
For the full routing and field-guarantee matrix by update kind, see [[Update-Routing-Model]]. For the full routing and field-guarantee matrix by update kind, see [[Update-Routing-Model]].
@@ -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.NoData) error { func start(ctx *laniakea.MessageContext, 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 Telegrams message limit. Use `AnswerLong(...)` when plain text may exceed Telegrams message limit.
```go ```go
func help(ctx *laniakea.MsgContext, db laniakea.NoData) error { func help(ctx *laniakea.MessageContext, 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.NoData) error { func menu(ctx *laniakea.MessageContext, 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.NoData) error { func whoami(ctx *laniakea.MessageContext, 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.NoData) error { func slowTask(ctx *laniakea.MessageContext, 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.NoData) error { func approve(ctx *laniakea.MessageContext, 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.NoData) error { func approve(ctx *laniakea.MessageContext, db laniakea.NoData) error {
ctx.AnswerCbQueryText("Saved") ctx.AnswerCbQueryText("Saved")
ctx.EditCallback("Saved", nil) ctx.EditCallback("Saved", nil)
return nil return nil
@@ -220,7 +220,7 @@ Caption rules differ from normal message text:
## Drafts ## Drafts
`MsgContext` also exposes draft creation helpers: `MessageContext` also exposes draft creation helpers:
- `NewDraft()` - `NewDraft()`
- `NewDraftMarkdown()` - `NewDraftMarkdown()`
@@ -246,7 +246,7 @@ This looks up text using the current users language when available and falls
Example: Example:
```go ```go
func ping(ctx *laniakea.MsgContext, db laniakea.NoData) error { func ping(ctx *laniakea.MessageContext, db laniakea.NoData) error {
ctx.Answer(ctx.Translate("ping.answer")) ctx.Answer(ctx.Translate("ping.answer"))
return nil return nil
} }
@@ -275,7 +275,7 @@ Use `SendAction(...)` to show activity like typing or uploading.
Example: Example:
```go ```go
func report(ctx *laniakea.MsgContext, db *App) error { func report(ctx *laniakea.MessageContext, db *App) error {
ctx.SendAction(tgapi.ChatActionTyping) ctx.SendAction(tgapi.ChatActionTyping)
text, err := db.BuildReport(ctx.FromID) text, err := db.BuildReport(ctx.FromID)
if err != nil { if err != nil {
@@ -293,7 +293,7 @@ This is especially useful for slower handlers.
A common pattern is: A common pattern is:
```go ```go
func profile(ctx *laniakea.MsgContext, db *App) error { func profile(ctx *laniakea.MessageContext, db *App) error {
user, err := db.LoadUser(ctx.FromID) user, err := db.LoadUser(ctx.FromID)
if err != nil { if err != nil {
return err return err
@@ -338,7 +338,7 @@ Methods like `EditCallback(...)` and `AnswerCbQueryText(...)` depend on callback
## A practical example ## A practical example
```go ```go
func settings(ctx *laniakea.MsgContext, db *App) error { func settings(ctx *laniakea.MessageContext, db *App) error {
kb := ctx.NewInlineKeyboard(1). kb := ctx.NewInlineKeyboard(1).
AddCallbackButton("Enable notifications", "settings.notifications.enable"). AddCallbackButton("Enable notifications", "settings.notifications.enable").
AddCallbackButton("Disable notifications", "settings.notifications.disable") AddCallbackButton("Disable notifications", "settings.notifications.disable")
+4 -4
@@ -1,6 +1,6 @@
# Policies # Policies
Policies — это полноценная модель правил авторизации в Laniakea. Политика работает поверх нормализованного `MsgContext` и переданных `AppData`, возвращает `nil`, если доступ разрешён, и возвращает ошибку, если действие нужно запретить или сама проверка не смогла корректно выполниться. Policies — это полноценная модель правил авторизации в Laniakea. Политика работает поверх нормализованного `MessageContext` и переданных `AppData`, возвращает `nil`, если доступ разрешён, и возвращает ошибку, если действие нужно запретить или сама проверка не смогла корректно выполниться.
Policies не вводят вторую модель выполнения. Они встраиваются в уже существующий middleware pipeline через `RequirePolicy(...)`, поэтому авторизация остаётся на том же пути маршрутизации, что и остальной фреймворк. Policies не вводят вторую модель выполнения. Они встраиваются в уже существующий middleware pipeline через `RequirePolicy(...)`, поэтому авторизация остаётся на том же пути маршрутизации, что и остальной фреймворк.
@@ -15,7 +15,7 @@ Policies не вводят вторую модель выполнения. Он
## Основной API ## Основной API
```go ```go
type Policy[T laniakea.AppData] func(ctx *laniakea.MsgContext, data T) error type Policy[T laniakea.AppData] func(ctx *laniakea.MessageContext, data T) error
func RequirePolicy[T laniakea.AppData](name string, p Policy[T]) Middleware[T] func RequirePolicy[T laniakea.AppData](name string, p Policy[T]) Middleware[T]
@@ -62,7 +62,7 @@ plugin.UsePolicy(
- `RequireBotAdmin(...)` - `RequireBotAdmin(...)`
- `RequireCallbackFromUser(...)` - `RequireCallbackFromUser(...)`
Эти helpers используют нормализованные данные `MsgContext`. В частности, chat-aware политики опираются на `Chat` и `ChatID`, которые теперь заполняются не только для message-backed обновлений. Эти helpers используют нормализованные данные `MessageContext`. В частности, chat-aware политики опираются на `Chat` и `ChatID`, которые теперь заполняются не только для message-backed обновлений.
## Композиция ## Композиция
@@ -133,6 +133,6 @@ policy := laniakea.NotPolicy(laniakea.RequirePrivateChat())
Связанные страницы: Связанные страницы:
- [[Middleware-RU]] - [[Middleware-RU]]
- [[MsgContext-RU]] - [[MessageContext-RU]]
- [[Commands-and-Plugins-RU]] - [[Commands-and-Plugins-RU]]
- [[Framework-Backlog-RU]] - [[Framework-Backlog-RU]]
+4 -4
@@ -1,6 +1,6 @@
# Policies # Policies
Policies are Laniakea's first-class authorization rules. A policy runs against the normalized `MsgContext` and injected `AppData`, returns `nil` when access is allowed, and returns an error when access should be denied or when the check itself fails. Policies are Laniakea's first-class authorization rules. A policy runs against the normalized `MessageContext` and injected `AppData`, returns `nil` when access is allowed, and returns an error when access should be denied or when the check itself fails.
Policies do not introduce a second execution model. They plug into the existing middleware pipeline through `RequirePolicy(...)`, so authorization stays on the same routing path as the rest of the framework. Policies do not introduce a second execution model. They plug into the existing middleware pipeline through `RequirePolicy(...)`, so authorization stays on the same routing path as the rest of the framework.
@@ -15,7 +15,7 @@ Policies do not introduce a second execution model. They plug into the existing
## Core API ## Core API
```go ```go
type Policy[T laniakea.AppData] func(ctx *laniakea.MsgContext, data T) error type Policy[T laniakea.AppData] func(ctx *laniakea.MessageContext, data T) error
func RequirePolicy[T laniakea.AppData](name string, p Policy[T]) Middleware[T] func RequirePolicy[T laniakea.AppData](name string, p Policy[T]) Middleware[T]
@@ -62,7 +62,7 @@ The current built-in helpers focus on common Telegram-specific access checks:
- `RequireBotAdmin(...)` - `RequireBotAdmin(...)`
- `RequireCallbackFromUser(...)` - `RequireCallbackFromUser(...)`
These helpers use normalized `MsgContext` data. In particular, chat-aware policies rely on `Chat` and `ChatID`, which are now populated for more update kinds than only message-backed ones. These helpers use normalized `MessageContext` data. In particular, chat-aware policies rely on `Chat` and `ChatID`, which are now populated for more update kinds than only message-backed ones.
## Composition ## Composition
@@ -133,6 +133,6 @@ Those features can be added later if the existing `Policy[T]` model proves too s
Related pages: Related pages:
- [[Middleware]] - [[Middleware]]
- [[MsgContext]] - [[MessageContext]]
- [[Commands-and-Plugins]] - [[Commands-and-Plugins]]
- [[Framework-Backlog]] - [[Framework-Backlog]]
+9 -9
@@ -14,11 +14,11 @@ type App struct{}
func (a *App) IsAdmin(userID int64) bool { return userID == 42 } func (a *App) IsAdmin(userID int64) bool { return userID == 42 }
admin := laniakea.NewPlugin[*App]("admin") admin := laniakea.NewPlugin[*App]("admin")
admin.AddMiddleware(laniakea.NewMiddleware("admin-only", func(ctx *laniakea.MsgContext, app *App) bool { admin.AddMiddleware(laniakea.NewMiddleware("admin-only", func(ctx *laniakea.MessageContext, app *App) bool {
return ctx.From != nil && app.IsAdmin(ctx.From.ID) return ctx.From != nil && app.IsAdmin(ctx.From.ID)
})) }))
admin.NewCommand(func(ctx *laniakea.MsgContext, app *App) error { admin.NewCommand(func(ctx *laniakea.MessageContext, app *App) error {
ctx.Answer("Admin command executed") ctx.Answer("Admin command executed")
return nil return nil
}, "reload") }, "reload")
@@ -31,14 +31,14 @@ Use a payload handler for inline keyboard callbacks.
```go ```go
menu := laniakea.NewPlugin[laniakea.NoData]("menu") menu := laniakea.NewPlugin[laniakea.NoData]("menu")
menu.NewCommand(func(ctx *laniakea.MsgContext, db laniakea.NoData) error { menu.NewCommand(func(ctx *laniakea.MessageContext, db laniakea.NoData) error {
kb := ctx.NewInlineKeyboard(1) kb := ctx.NewInlineKeyboard(1)
kb.AddCallbackButton("Open settings", "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.NoData) error { menu.NewPayload(func(ctx *laniakea.MessageContext, db laniakea.NoData) error {
ctx.EditCallback("Settings screen", nil) ctx.EditCallback("Settings screen", nil)
return nil return nil
}, "settings") }, "settings")
@@ -49,7 +49,7 @@ menu.NewPayload(func(ctx *laniakea.MsgContext, db laniakea.NoData) error {
Use `AnswerLong(...)` when you want explicit splitting into multiple safe Telegram messages. Use `AnswerLong(...)` when you want explicit splitting into multiple safe Telegram messages.
```go ```go
plugin.NewCommand(func(ctx *laniakea.MsgContext, db *App) error { plugin.NewCommand(func(ctx *laniakea.MessageContext, db *App) error {
report := buildLargePlainTextReport() report := buildLargePlainTextReport()
ctx.AnswerLong(report) ctx.AnswerLong(report)
return nil return nil
@@ -71,7 +71,7 @@ l10n := laniakea.NewL10n("en").
bot.SetL10n(l10n) bot.SetL10n(l10n)
plugin.NewCommand(func(ctx *laniakea.MsgContext, db *App) error { plugin.NewCommand(func(ctx *laniakea.MessageContext, db *App) error {
ctx.Answer(ctx.Translate("greeting")) ctx.Answer(ctx.Translate("greeting"))
return nil return nil
}, "start") }, "start")
@@ -82,7 +82,7 @@ plugin.NewCommand(func(ctx *laniakea.MsgContext, db *App) error {
Use `AddUpdateHandler(...)` for Telegram update types that are outside the command and payload flow. Use `AddUpdateHandler(...)` for Telegram update types that are outside the command and payload flow.
```go ```go
plugin.AddUpdateHandler(tgapi.UpdateTypeInlineQuery, func(ctx *laniakea.MsgContext, db *App) error { plugin.AddUpdateHandler(tgapi.UpdateTypeInlineQuery, func(ctx *laniakea.MessageContext, db *App) error {
if ctx.From != nil { if ctx.From != nil {
ctx.Logger.Infoln("inline query from", ctx.From.ID) ctx.Logger.Infoln("inline query from", ctx.From.ID)
} }
@@ -97,7 +97,7 @@ This is usually cleaner than forcing non-command traffic through a command parse
Use drafts when you want to build a reply progressively and publish it once at the end. Use drafts when you want to build a reply progressively and publish it once at the end.
```go ```go
plugin.NewCommand(func(ctx *laniakea.MsgContext, db *App) error { plugin.NewCommand(func(ctx *laniakea.MessageContext, db *App) error {
draft := ctx.NewDraft() draft := ctx.NewDraft()
if draft == nil { if draft == nil {
return nil return nil
@@ -119,7 +119,7 @@ plugin.NewCommand(func(ctx *laniakea.MsgContext, db *App) error {
Use the higher-level handler flow for routing, but drop down to `tgapi` uploader methods when you need multipart upload behavior. Use the higher-level handler flow for routing, but drop down to `tgapi` uploader methods when you need multipart upload behavior.
```go ```go
plugin.NewCommand(func(ctx *laniakea.MsgContext, db *App) error { plugin.NewCommand(func(ctx *laniakea.MessageContext, db *App) error {
uploader := tgapi.NewUploader(ctx.Api) uploader := tgapi.NewUploader(ctx.Api)
defer uploader.Close() defer uploader.Close()
+50 -16
@@ -22,43 +22,59 @@ Runner — это фоновая или одноразовая задача, к
runner := laniakea.NewRunner("cleanup", fn) runner := laniakea.NewRunner("cleanup", fn)
``` ```
Потом конфигурируются методы builder: Методы builder:
- `Onetime(bool)` - `Every(time.Duration)` — интервал повторного запуска. Ноль (по умолчанию) означает одноразовый запуск; положительное значение — периодический.
- `Async(bool)` - `Async(bool)` — если `true` (по умолчанию), запускается в goroutine; если `false`, блокирует запуск runtime.
- `Timeout(duration)`
## Основные режимы ## Основные режимы
### Одноразовый sync ### Одноразовый async (по умолчанию)
- выполняется один раз; ```go
- блокирует запуск; runner := laniakea.NewRunner("prefetch", fn)
- полезен для работы, критичной на старте. ```
### Одноразовый async
- выполняется один раз; - выполняется один раз;
- стартует в goroutine; - стартует в goroutine;
- не блокирует запуск. - не блокирует запуск.
### Одноразовый sync
```go
runner := laniakea.NewRunner("warmup", fn).Async(false)
```
- выполняется один раз;
- блокирует запуск до завершения;
- полезен для работы, критичной на старте.
### Повторяющийся async ### Повторяющийся async
- работает циклически; ```go
runner := laniakea.NewRunner("cleanup", fn).Every(time.Minute)
```
- работает циклически с заданным интервалом;
- использует ticker; - использует ticker;
- живет до `ctx.Done()`. - живет до `ctx.Done()`.
## Невалидная конфигурация ## Невалидная конфигурация
Повторяющийся synchronous runner считается невалидным и пропускается с предупреждением. Повторяющийся synchronous runner (`Every(d > 0).Async(false)`) считается невалидным и пропускается с предупреждением — блокировать запуск бесконечно никогда не имеет смысла.
Также повторяющийся async runner без `Timeout(...)` пропускается.
## Когда стартуют фоновые задачи ## Когда стартуют фоновые задачи
Фоновые задачи стартуют из `RunWithContext(...)` или `RunWebHookWithContext(...)`, а не из `NewBot(...)`. Фоновые задачи стартуют из `RunWithContext(...)` или `RunWebhookWithContext(...)`, а не из `NewBot(...)`.
Это часть фазы выполнения, а не фазы сборки конфигурации. Это часть фазы выполнения, а не фазы сборки конфигурации.
## Обработка ошибок
Если runner возвращает ошибку:
- бот логирует предупреждение;
- через observer отправляется `ErrorEvent`;
- бот продолжает работу.
## Семантика остановки ## Семантика остановки
При корректной остановке бот: При корректной остановке бот:
@@ -67,9 +83,27 @@ runner := laniakea.NewRunner("cleanup", fn)
Поэтому код runner должен завершаться достаточно быстро. Поэтому код runner должен завершаться достаточно быстро.
## Практические примеры
### Периодическая очистка
```go
cleanup := laniakea.NewRunner("cleanup", func(bot *laniakea.Bot[*App]) error {
return bot.GetAppData().CleanupExpired()
}).Every(5 * time.Minute)
```
### Блокирующий запуск при старте
```go
warmup := laniakea.NewRunner("warmup", func(bot *laniakea.Bot[*App]) error {
return bot.GetAppData().WarmCaches()
}).Async(false)
```
## Рекомендации ## Рекомендации
- Для периодических задач используй повторяющийся async runner с timeout. - Для периодических задач используй повторяющийся async runner с `Every(...)`.
- Для критичной стартовой работы используй одноразовый sync runner. - Для критичной стартовой работы используй одноразовый sync runner.
- Не держи сложную бизнес-логику внутри runner; лучше делегируй ее в обычные сервисы приложения. - Не держи сложную бизнес-логику внутри runner; лучше делегируй ее в обычные сервисы приложения.
+39 -49
@@ -13,10 +13,9 @@ Each runner is built from:
- a function `func(*Bot[T]) error`; - a function `func(*Bot[T]) error`;
- execution flags configured through builder methods. - execution flags configured through builder methods.
Main builder methods: Builder methods:
- `Onetime(bool)` - `Every(time.Duration)` — sets the repeat interval. Zero (default) means run once; positive means repeat.
- `Async(bool)` - `Async(bool)` — if `true` (default), the runner runs in a goroutine; if `false`, it blocks runtime startup.
- `Timeout(duration)`
## Creating a runner ## Creating a runner
@@ -29,36 +28,21 @@ cleanup := laniakea.NewRunner("cleanup", func(bot *laniakea.Bot[*App]) error {
``` ```
By default, a new runner is: By default, a new runner is:
- asynchronous; - asynchronous (`Async(true)`);
- not one-time; - one-shot (`Every(0)`).
- configured with zero timeout.
That default means you almost always want to finish configuration before adding it to the bot. That default fires the runner once in a goroutine when the bot starts.
## Runner execution modes ## Runner execution modes
There are three meaningful configurations. There are three meaningful configurations.
### One-time synchronous ### One-time asynchronous (default)
```go ```go
runner := laniakea.NewRunner("warmup", fn). runner := laniakea.NewRunner("prefetch", fn)
Onetime(true). // or explicitly:
Async(false) runner := laniakea.NewRunner("prefetch", fn).Every(0).Async(true)
```
Behavior:
- runs once;
- blocks startup until it finishes;
- logs a warning if it takes longer than two seconds.
Use this for startup work that must complete before the bot is considered ready.
### One-time asynchronous
```go
runner := laniakea.NewRunner("prefetch", fn).
Onetime(true)
``` ```
Behavior: Behavior:
@@ -68,31 +52,37 @@ Behavior:
Use this for fire-and-forget startup work that is useful but not required before handling updates. Use this for fire-and-forget startup work that is useful but not required before handling updates.
### Repeating asynchronous ### One-time synchronous
```go ```go
runner := laniakea.NewRunner("cleanup", fn). runner := laniakea.NewRunner("warmup", fn).Async(false)
Timeout(time.Minute)
``` ```
Behavior: Behavior:
- runs on a ticker; - runs once;
- blocks startup until it finishes;
- logs a warning if it takes longer than two seconds.
Use this for startup work that must complete before the bot is considered ready.
### Repeating asynchronous
```go
runner := laniakea.NewRunner("cleanup", fn).Every(time.Minute)
```
Behavior:
- runs on a ticker with the configured interval;
- keeps running until `ctx.Done()` from the bot runtime context; - 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.
## Invalid configuration ## Invalid configurations
One configuration is intentionally treated as invalid: One configuration is intentionally treated as invalid and skipped with a warning:
- `Onetime(false).Async(false)` - `Every(d > 0).Async(false)` — a periodic sync runner blocks startup indefinitely, which is never correct.
That means:
- synchronous repeating runners are skipped;
- the bot logs a warning instead of trying to run them inline forever.
Also, repeating async runners with `Timeout(0)` are skipped with a warning.
## Registration ## Registration
@@ -106,7 +96,7 @@ 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(...)` or `RunWebHookWithContext(...)`, right before the bot begins polling or webhook ingestion. 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.
@@ -125,6 +115,7 @@ Runner functions return `error`.
When a runner returns a non-nil error: When a runner returns a non-nil error:
- the bot logs a warning; - the bot logs a warning;
- an `ErrorEvent` is emitted through the observer;
- the process continues; - the process continues;
- the bot does not crash automatically. - the bot does not crash automatically.
@@ -134,7 +125,7 @@ If a runner must be fatal for startup, make it one-time synchronous and return a
## Shutdown behavior ## Shutdown behavior
`RunWithContext(...)` and `RunWebHookWithContext(...)` wait 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.
@@ -149,15 +140,15 @@ 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.GetAppData().CleanupExpired() return bot.GetAppData().CleanupExpired()
}).Timeout(5 * time.Minute) }).Every(5 * time.Minute)
``` ```
### Startup warmup ### Startup warmup (blocking)
```go ```go
warmup := laniakea.NewRunner("warmup", func(bot *laniakea.Bot[*App]) error { warmup := laniakea.NewRunner("warmup", func(bot *laniakea.Bot[*App]) error {
return bot.GetAppData().WarmCaches() return bot.GetAppData().WarmCaches()
}).Onetime(true).Async(false) }).Async(false)
``` ```
### Background metrics push ### Background metrics push
@@ -165,25 +156,24 @@ 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.GetAppData()) return pushMetrics(bot.GetAppData())
}).Timeout(30 * time.Second) }).Every(30 * time.Second)
``` ```
## Recommendations ## Recommendations
- Use one-time sync runners only for short startup-critical work. - Use one-time sync runners only for short startup-critical work.
- Use repeating async runners for periodic jobs. - Use repeating async runners for periodic jobs.
- Always set `Timeout(...)` on repeating runners. - Always set `Every(...)` on repeating runners.
- Keep runner bodies small and delegate complex work to regular application services. - Keep runner bodies small and delegate complex work to regular application services.
- Treat runner names as operational identifiers that should make sense in logs. - Treat runner names as operational identifiers that should make sense in logs.
## Caveats ## Caveats
- Runners do not receive `context.Context` directly; they receive `*Bot[T]`. - Runners do not receive `context.Context` directly; they receive `*Bot[T]`.
- Repeating sync runners are skipped. - Periodic sync runners are skipped with a warning.
- Repeating async runners without timeout are skipped.
- Slow one-time sync runners delay bot startup. - Slow one-time sync runners delay bot startup.
## Related pages ## Related pages
- [[Bot-Lifecycle]] - [[Bot-Lifecycle]]
- [[Testing-Bots-with-Laniakea]] - [[Testing-Bots-with-Laniakea]]
+6 -6
@@ -1,11 +1,11 @@
# Scenes # Scenes
Scenes — это слой маршрутизации Laniakea с сохранением состояния для многошаговых и модальных диалогов. Сцена регистрируется внутри плагина, запускается через `MsgContext`, хранится через `SessionStore` и получает обновления раньше обычной маршрутизации команд, пока её сессия активна. Scenes — это слой маршрутизации Laniakea с сохранением состояния для многошаговых и модальных диалогов. Сцена регистрируется внутри плагина, запускается через `MessageContext`, хранится через `SessionStore` и получает обновления раньше обычной маршрутизации команд, пока её сессия активна.
## Что дают сцены ## Что дают сцены
- Регистрацию через `Plugin.NewScene(...)` и `Plugin.AddScene(...)`. - Регистрацию через `Plugin.NewScene(...)` и `Plugin.AddScene(...)`.
- Явный вход и выход через `MsgContext.EnterScene(...)`, `EnterSceneStep(...)` и `ExitScene()`. - Явный вход и выход через `MessageContext.EnterScene(...)`, `EnterSceneStep(...)` и `ExitScene()`.
- Области действия сессии на пользователя, чат или пару пользователь-чат. - Области действия сессии на пользователя, чат или пару пользователь-чат.
- Обработчики шагов, локальные команды сцены и резервный обработчик сообщений на уровне сцены. - Обработчики шагов, локальные команды сцены и резервный обработчик сообщений на уровне сцены.
- JSON-состояние сцены через `SceneContext.BindData(...)` и `SaveData(...)`. - JSON-состояние сцены через `SceneContext.BindData(...)` и `SaveData(...)`.
@@ -67,13 +67,13 @@ plugin.NewScene("signup").
## Модель обработчиков ## Модель обработчиков
Обычные команды используют `*MsgContext`. Обработчики сцен используют `*SceneContext`. Обычные команды используют `*MessageContext`. Обработчики сцен используют `*SceneContext`.
```go ```go
type SceneHandler[T any] func(ctx *SceneContext, db T) (SceneResult, error) type SceneHandler[T any] func(ctx *SceneContext, db T) (SceneResult, error)
``` ```
`SceneContext` встраивает `*MsgContext` и добавляет вспомогательные методы для сцен: `SceneContext` встраивает `*MessageContext` и добавляет вспомогательные методы для сцен:
- `ctx.Stay()` - `ctx.Stay()`
- `ctx.Next(step)` - `ctx.Next(step)`
@@ -137,7 +137,7 @@ func askName(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error)
Точка входа из команды: Точка входа из команды:
```go ```go
func startSignup(ctx *laniakea.MsgContext, db *App) error { func startSignup(ctx *laniakea.MessageContext, db *App) error {
return ctx.EnterScene("signup") return ctx.EnterScene("signup")
} }
``` ```
@@ -174,6 +174,6 @@ func cancelSignup(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, er
Связанные страницы: Связанные страницы:
- [[Commands-and-Plugins-RU]] - [[Commands-and-Plugins-RU]]
- [[MsgContext-RU]] - [[MessageContext-RU]]
- [[Middleware-RU]] - [[Middleware-RU]]
- [[Bot-Lifecycle-RU]] - [[Bot-Lifecycle-RU]]
+6 -6
@@ -1,11 +1,11 @@
# Scenes # Scenes
Scenes are Laniakea's stateful routing layer for multi-step and modal bot flows. A scene is registered inside a plugin, entered through `MsgContext`, stored through `SessionStore`, and routed before normal command handling while the session is active. Scenes are Laniakea's stateful routing layer for multi-step and modal bot flows. A scene is registered inside a plugin, entered through `MessageContext`, stored through `SessionStore`, and routed before normal command handling while the session is active.
## What scenes give you ## What scenes give you
- Scene registration through `Plugin.NewScene(...)` and `Plugin.AddScene(...)`. - Scene registration through `Plugin.NewScene(...)` and `Plugin.AddScene(...)`.
- Explicit entry and exit through `MsgContext.EnterScene(...)`, `EnterSceneStep(...)`, and `ExitScene()`. - Explicit entry and exit through `MessageContext.EnterScene(...)`, `EnterSceneStep(...)`, and `ExitScene()`.
- Per-user, per-chat, or per-user-chat session scopes. - Per-user, per-chat, or per-user-chat session scopes.
- Step handlers, scene-local commands, and a scene-level message fallback. - Step handlers, scene-local commands, and a scene-level message fallback.
- JSON-backed scene state through `SceneContext.BindData(...)` and `SaveData(...)`. - JSON-backed scene state through `SceneContext.BindData(...)` and `SaveData(...)`.
@@ -67,13 +67,13 @@ plugin.NewScene("signup").
## Handler model ## Handler model
Normal commands use `*MsgContext`. Scene handlers use `*SceneContext`. Normal commands use `*MessageContext`. Scene handlers use `*SceneContext`.
```go ```go
type SceneHandler[T any] func(ctx *SceneContext, db T) (SceneResult, error) type SceneHandler[T any] func(ctx *SceneContext, db T) (SceneResult, error)
``` ```
`SceneContext` embeds `*MsgContext` and adds scene helpers: `SceneContext` embeds `*MessageContext` and adds scene helpers:
- `ctx.Stay()` - `ctx.Stay()`
- `ctx.Next(step)` - `ctx.Next(step)`
@@ -137,7 +137,7 @@ The store contract stays intentionally small because `Data []byte` is storage-ag
Command entry: Command entry:
```go ```go
func startSignup(ctx *laniakea.MsgContext, db *App) error { func startSignup(ctx *laniakea.MessageContext, db *App) error {
return ctx.EnterScene("signup") return ctx.EnterScene("signup")
} }
``` ```
@@ -174,6 +174,6 @@ func cancelSignup(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, er
Related pages: Related pages:
- [[Commands-and-Plugins]] - [[Commands-and-Plugins]]
- [[MsgContext]] - [[MessageContext]]
- [[Middleware]] - [[Middleware]]
- [[Bot-Lifecycle]] - [[Bot-Lifecycle]]
+3 -3
@@ -10,7 +10,7 @@ Laniakea хорошо тестируется обычными Go unit tests.
В репозитории уже используются паттерны вроде: В репозитории уже используются паттерны вроде:
- fake HTTP transport для `tgapi`; - fake HTTP transport для `tgapi`;
- прямые тесты для вспомогательных методов `MsgContext`; - прямые тесты для вспомогательных методов `MessageContext`;
- routing tests для commands и payloads; - routing tests для commands и payloads;
- runner tests. - runner tests.
@@ -35,7 +35,7 @@ Laniakea хорошо тестируется обычными Go unit tests.
## Тесты для логики обработчиков ## Тесты для логики обработчиков
Для тестов уровня обработчика обычно полезно: Для тестов уровня обработчика обычно полезно:
- собрать `MsgContext`; - собрать `MessageContext`;
- вызвать обработчик напрямую; - вызвать обработчик напрямую;
- проверить побочные эффекты и ответы. - проверить побочные эффекты и ответы.
@@ -63,5 +63,5 @@ Laniakea хорошо тестируется обычными Go unit tests.
## Что читать дальше ## Что читать дальше
- [[Runners-RU]] - [[Runners-RU]]
- [[MsgContext-RU]] - [[MessageContext-RU]]
- [[Testing-Bots-with-Laniakea]] - [[Testing-Bots-with-Laniakea]]
+7 -7
@@ -21,15 +21,15 @@ Good test targets include:
The most practical approach is: The most practical approach is:
1. isolate one behavior; 1. isolate one behavior;
2. create a small bot, plugin, or `MsgContext`; 2. create a small bot, plugin, or `MessageContext`;
3. use a fake HTTP client when you need to inspect Telegram requests; 3. use a fake HTTP client when you need to inspect Telegram requests;
4. assert the outgoing request shape or returned behavior directly. 4. assert the outgoing request shape or returned behavior directly.
This keeps tests fast and independent from real Telegram infrastructure. This keeps tests fast and independent from real Telegram infrastructure.
## Testing `MsgContext` helpers ## Testing `MessageContext` helpers
Many helper methods can be tested by constructing a `MsgContext` directly. Many helper methods can be tested by constructing a `MessageContext` directly.
Common ingredients: Common ingredients:
- a fake `tgapi.API` with a custom `http.Client`; - a fake `tgapi.API` with a custom `http.Client`;
@@ -39,7 +39,7 @@ Common ingredients:
Example pattern: Example pattern:
```go ```go
ctx := &laniakea.MsgContext{ ctx := &laniakea.MessageContext{
Api: api, Api: api,
Msg: &tgapi.Message{Chat: &tgapi.Chat{ID: 42, Type: string(tgapi.ChatTypePrivate)}}, Msg: &tgapi.Message{Chat: &tgapi.Chat{ID: 42, Type: string(tgapi.ChatTypePrivate)}},
Logger: slog.CreateLogger(), Logger: slog.CreateLogger(),
@@ -64,7 +64,7 @@ This is especially useful for:
- split long-message requests; - split long-message requests;
- keyboard attachment behavior. - keyboard attachment behavior.
The repository already uses this pattern extensively for `MsgContext` helper tests. The repository already uses this pattern extensively for `MessageContext` helper tests.
## Testing long replies ## Testing long replies
@@ -96,7 +96,7 @@ For routing tests, create a small bot with:
Then call the bot's handling path in a focused test and assert: Then call the bot's handling path in a focused test and assert:
- which handler was called; - which handler was called;
- what `MsgContext` fields were populated; - what `MessageContext` fields were populated;
- whether context mutations leaked across plugins. - whether context mutations leaked across plugins.
This is particularly useful for: This is particularly useful for:
@@ -184,4 +184,4 @@ When you fix a bug, consider adding a test for:
- [[Commands-and-Plugins]] - [[Commands-and-Plugins]]
- [[Error-Handling]] - [[Error-Handling]]
- [[Runners]] - [[Runners]]
- [[MsgContext]] - [[MessageContext]]
+7 -7
@@ -2,9 +2,9 @@
English version: [[Update-Routing-Model]] English version: [[Update-Routing-Model]]
Эта страница фиксирует текущий контракт маршрутизации обновлений в Laniakea: какие Telegram update types идут через команды, какие через данные callback, какие через generic update handlers, и какие гарантии по `MsgContext` существуют в каждом потоке. Эта страница фиксирует текущий контракт маршрутизации обновлений в Laniakea: какие Telegram update types идут через команды, какие через данные callback, какие через generic update handlers, и какие гарантии по `MessageContext` существуют в каждом потоке.
Это описание текущего поведения фреймворка, а не отдельная новая API-поверхность. Цель страницы — сделать уже существующую маршрутизацию и нормализацию `MsgContext` явной и тестируемой. Это описание текущего поведения фреймворка, а не отдельная новая API-поверхность. Цель страницы — сделать уже существующую маршрутизацию и нормализацию `MessageContext` явной и тестируемой.
## Верхнеуровневая маршрутизация ## Верхнеуровневая маршрутизация
@@ -23,7 +23,7 @@ English version: [[Update-Routing-Model]]
## Что делает `prepareUpdateCtx(...)` ## Что делает `prepareUpdateCtx(...)`
До маршрутизации обработчиков Laniakea нормализует `MsgContext` из входящего Telegram update. До маршрутизации обработчиков Laniakea нормализует `MessageContext` из входящего Telegram update.
Эта нормализация намеренно шире, чем command и payload routing: Эта нормализация намеренно шире, чем command и payload routing:
- некоторые update types заполняют `ctx.Msg` - некоторые update types заполняют `ctx.Msg`
@@ -101,12 +101,12 @@ Generic update handlers регистрируются через `Plugin.AddUpdat
- `ctx.Text` не нормализуется - `ctx.Text` не нормализуется
- `ctx.Args` не нормализуется - `ctx.Args` не нормализуется
- `ctx.Prefix` не нормализуется - `ctx.Prefix` не нормализуется
- каждый plugin update handler получает свою копию `MsgContext` struct - каждый plugin update handler получает свою копию `MessageContext` struct
Важное ограничение: Важное ограничение:
- копируется сам `MsgContext`, но не обещается глубокая копия всех вложенных Telegram-структур - копируется сам `MessageContext`, но не обещается глубокая копия всех вложенных Telegram-структур
## Нормализованные поля `MsgContext` по видам update ## Нормализованные поля `MessageContext` по видам update
### Update types, которые несут message ### Update types, которые несут message
@@ -176,6 +176,6 @@ Generic update handlers регистрируются через `Plugin.AddUpdat
## Связанные страницы ## Связанные страницы
- [[Commands-and-Plugins-RU]] - [[Commands-and-Plugins-RU]]
- [[MsgContext-RU]] - [[MessageContext-RU]]
- [[Scenes-RU]] - [[Scenes-RU]]
- [[Bot-Lifecycle-RU]] - [[Bot-Lifecycle-RU]]
+6 -6
@@ -2,9 +2,9 @@
Russian version: [[Update-Routing-Model-RU]] Russian version: [[Update-Routing-Model-RU]]
This page defines the current update-routing contract in Laniakea: which Telegram update kinds go through command routing, which go through payload routing, which go through generic update handlers, and what `MsgContext` guarantees exist in each path. This page defines the current update-routing contract in Laniakea: which Telegram update kinds go through command routing, which go through payload routing, which go through generic update handlers, and what `MessageContext` guarantees exist in each path.
This is a model of current framework behavior, not a second API surface. The goal is to make the existing routing and `MsgContext` normalization explicit and testable. This is a model of current framework behavior, not a second API surface. The goal is to make the existing routing and `MessageContext` normalization explicit and testable.
## Top-level routing ## Top-level routing
@@ -23,7 +23,7 @@ Important:
## What `prepareUpdateCtx(...)` does ## What `prepareUpdateCtx(...)` does
Before handler routing, Laniakea normalizes a `MsgContext` from the incoming Telegram update. Before handler routing, Laniakea normalizes a `MessageContext` from the incoming Telegram update.
That normalization is intentionally broader than command and payload routing: That normalization is intentionally broader than command and payload routing:
- some update kinds populate `ctx.Msg` - some update kinds populate `ctx.Msg`
@@ -101,12 +101,12 @@ General guarantees:
- `ctx.Text` is not normalized - `ctx.Text` is not normalized
- `ctx.Args` is not normalized - `ctx.Args` is not normalized
- `ctx.Prefix` is not normalized - `ctx.Prefix` is not normalized
- each plugin update handler receives its own copied `MsgContext` struct - each plugin update handler receives its own copied `MessageContext` struct
Important limitation: Important limitation:
- the copied context is an isolated struct copy, not a deep copy of all nested Telegram payload objects - the copied context is an isolated struct copy, not a deep copy of all nested Telegram payload objects
## Normalized `MsgContext` fields by update kind ## Normalized `MessageContext` fields by update kind
### Message-backed update kinds ### Message-backed update kinds
@@ -176,6 +176,6 @@ Important:
## Related pages ## Related pages
- [[Commands-and-Plugins]] - [[Commands-and-Plugins]]
- [[MsgContext]] - [[MessageContext]]
- [[Scenes]] - [[Scenes]]
- [[Bot-Lifecycle]] - [[Bot-Lifecycle]]
+14 -14
@@ -2,10 +2,10 @@
English version: [[Webhook-Runtime]] English version: [[Webhook-Runtime]]
Эта страница объясняет bot-level webhook runtime в Laniakea: как работает `RunWebHookWithContext(...)`, чем он отличается от низкоуровневых webhook-вызовов в `tgapi` и какие runtime-гарантии он делит с polling-режимом. Эта страница объясняет bot-level webhook runtime в Laniakea: как работает `RunWebhookWithContext(...)`, чем он отличается от низкоуровневых webhook-вызовов в `tgapi` и какие runtime-гарантии он делит с polling-режимом.
Важно про naming: Важно про naming:
- текущий публичный API использует историческое написание `WebHook` в идентификаторах вроде `RunWebHookWithContext(...)`, `RunWebHook(...)` и `BotWebHookOpts`; - текущий публичный API использует историческое написание `WebHook` в идентификаторах вроде `RunWebhookWithContext(...)`, `RunWebhook(...)` и `BotWebhookOpts`;
- в тексте страницы используется обычное слово "webhook", но в примерах остаются реальные имена Go API. - в тексте страницы используется обычное слово "webhook", но в примерах остаются реальные имена Go API.
## Когда использовать webhook runtime ## Когда использовать webhook runtime
@@ -25,11 +25,11 @@ English version: [[Webhook-Runtime]]
## Точки входа ## Точки входа
Главные bot-level точки входа такие: Главные bot-level точки входа такие:
- `RunWebHookWithContext(ctx, opts, tlsFiles...)` - `RunWebhookWithContext(ctx, opts, tlsFiles...)`
- `RunWebHook(opts, tlsFiles...)` - `RunWebhook(opts, tlsFiles...)`
- `NewBotWebHookOpts()` - `NewBotWebhookOpts()`
`RunWebHook(...)` — это просто короткая форма для `RunWebHookWithContext(context.Background(), ...)`. `RunWebhook(...)` — это просто короткая форма для `RunWebhookWithContext(context.Background(), ...)`.
Обычный шаблон выглядит так: Обычный шаблон выглядит так:
@@ -46,20 +46,20 @@ defer bot.Close()
bot.SetAppData(app) bot.SetAppData(app)
bot.AddPlugins(plugin) bot.AddPlugins(plugin)
webhookOpts := laniakea.NewBotWebHookOpts(). webhookOpts := laniakea.NewBotWebhookOpts().
SetURL("https://bot.example.com"). SetURL("https://bot.example.com").
SetPath("/telegram"). SetPath("/telegram").
SetLocalPort(8080). SetLocalPort(8080).
SetSecretToken("shared-secret") SetSecretToken("shared-secret")
if err := bot.RunWebHookWithContext(ctx, webhookOpts); err != nil { if err := bot.RunWebhookWithContext(ctx, webhookOpts); err != nil {
return err return err
} }
``` ```
## Что именно берет на себя bot-level runtime ## Что именно берет на себя bot-level runtime
`RunWebHookWithContext(...)` — это не просто обертка над Telegram `setWebhook`. `RunWebhookWithContext(...)` — это не просто обертка над Telegram `setWebhook`.
Он: Он:
- валидирует bot-level условия старта, например prefixes и наличие зарегистрированных plugins; - валидирует bot-level условия старта, например prefixes и наличие зарегистрированных plugins;
@@ -86,7 +86,7 @@ Webhook runtime использует те же основные гарантии
## Основные webhook options ## Основные webhook options
`BotWebHookOpts` управляет и регистрацией webhook у Telegram, и локальным HTTP server. `BotWebhookOpts` управляет и регистрацией webhook у Telegram, и локальным HTTP server.
Поля, которые важны в первую очередь: Поля, которые важны в первую очередь:
@@ -167,12 +167,12 @@ Webhook runtime использует те же основные гарантии
## Поведение HTTP и TLS ## Поведение HTTP и TLS
По умолчанию `RunWebHookWithContext(...)` поднимает обычный HTTP server на `LocalPort`. По умолчанию `RunWebhookWithContext(...)` поднимает обычный HTTP server на `LocalPort`.
Если передать два TLS-файла, локально стартует HTTPS. Если передать два TLS-файла, локально стартует HTTPS.
Важно: Важно:
- текущий публичный API ожидает существующий порядок аргументов `key, cert` при вызове `RunWebHookWithContext(...)`; - текущий публичный API ожидает существующий порядок аргументов `key, cert` при вызове `RunWebhookWithContext(...)`;
- это отличается от более привычной ментальной модели `cert, key`, которую многие Go-разработчики ожидают от `ListenAndServeTLS`. - это отличается от более привычной ментальной модели `cert, key`, которую многие Go-разработчики ожидают от `ListenAndServeTLS`.
Поэтому в реальном setup лучше писать этот вызов максимально явно. Поэтому в реальном setup лучше писать этот вызов максимально явно.
@@ -202,7 +202,7 @@ Webhook runtime использует те же основные гарантии
- `URL` — это то, что видит Telegram; - `URL` — это то, что видит Telegram;
- `Path` и `LocalPort` — это то, что реально обслуживает твой бот; - `Path` и `LocalPort` — это то, что реально обслуживает твой бот;
- в production эти значения часто относятся к разным слоям инфраструктуры. - в production эти значения часто относятся к разным слоям инфраструктуры.
- если ты переводишь работающий deployment с webhook-режима на polling, сначала удали webhook через `CloseWebHook()` или `tgapi.DeleteWebhook(...)`; Telegram не прекращает webhook-доставку автоматически. - если ты переводишь работающий deployment с webhook-режима на polling, сначала удали webhook через `CloseWebhook()` или `tgapi.DeleteWebhook(...)`; Telegram не прекращает webhook-доставку автоматически.
## Связь с webhook methods в `tgapi` ## Связь с webhook methods в `tgapi`
@@ -221,7 +221,7 @@ Webhook runtime использует те же основные гарантии
когда тебе нужна собственная инфраструктура вокруг webhook path и ты не хочешь, чтобы сам бот владел HTTP server. когда тебе нужна собственная инфраструктура вокруг webhook path и ты не хочешь, чтобы сам бот владел HTTP server.
То есть: То есть:
- `RunWebHookWithContext(...)` — это framework runtime API; - `RunWebhookWithContext(...)` — это framework runtime API;
- webhook methods из `tgapi` — это низкоуровневые transport primitives. - webhook methods из `tgapi` — это низкоуровневые transport primitives.
## Частые ошибки ## Частые ошибки
+14 -14
@@ -2,10 +2,10 @@
Russian version: [[Webhook-Runtime-RU]] 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. 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: Important naming note:
- the current public API uses the historical `WebHook` spelling in identifiers such as `RunWebHookWithContext(...)`, `RunWebHook(...)`, and `BotWebHookOpts`; - 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. - this page uses the more common English term "webhook" for readability, but examples keep the actual Go API names.
## When to use webhook runtime ## When to use webhook runtime
@@ -25,11 +25,11 @@ Use polling when:
## Entry points ## Entry points
The main bot-level entry points are: The main bot-level entry points are:
- `RunWebHookWithContext(ctx, opts, tlsFiles...)` - `RunWebhookWithContext(ctx, opts, tlsFiles...)`
- `RunWebHook(opts, tlsFiles...)` - `RunWebhook(opts, tlsFiles...)`
- `NewBotWebHookOpts()` - `NewBotWebhookOpts()`
`RunWebHook(...)` is only a shorthand for `RunWebHookWithContext(context.Background(), ...)`. `RunWebhook(...)` is only a shorthand for `RunWebhookWithContext(context.Background(), ...)`.
The usual pattern looks like: The usual pattern looks like:
@@ -46,20 +46,20 @@ defer bot.Close()
bot.SetAppData(app) bot.SetAppData(app)
bot.AddPlugins(plugin) bot.AddPlugins(plugin)
webhookOpts := laniakea.NewBotWebHookOpts(). webhookOpts := laniakea.NewBotWebhookOpts().
SetURL("https://bot.example.com"). SetURL("https://bot.example.com").
SetPath("/telegram"). SetPath("/telegram").
SetLocalPort(8080). SetLocalPort(8080).
SetSecretToken("shared-secret") SetSecretToken("shared-secret")
if err := bot.RunWebHookWithContext(ctx, webhookOpts); err != nil { if err := bot.RunWebhookWithContext(ctx, webhookOpts); err != nil {
return err return err
} }
``` ```
## What the bot-level runtime owns ## What the bot-level runtime owns
`RunWebHookWithContext(...)` is more than a wrapper around Telegram's `setWebhook`. `RunWebhookWithContext(...)` is more than a wrapper around Telegram's `setWebhook`.
It: It:
- validates bot startup preconditions such as prefixes and registered plugins; - validates bot startup preconditions such as prefixes and registered plugins;
@@ -86,7 +86,7 @@ If you already understand [[Bot-Lifecycle]], the webhook mode should feel like a
## Main webhook options ## Main webhook options
`BotWebHookOpts` controls both Telegram webhook registration and the local server behavior. `BotWebhookOpts` controls both Telegram webhook registration and the local server behavior.
Fields you will care about first: Fields you will care about first:
@@ -167,12 +167,12 @@ Use this only when you specifically need Telegram's `ip_address` webhook option.
## HTTP and TLS behavior ## HTTP and TLS behavior
By default, `RunWebHookWithContext(...)` starts a plain HTTP server on `LocalPort`. By default, `RunWebhookWithContext(...)` starts a plain HTTP server on `LocalPort`.
If you pass two TLS files, it starts HTTPS locally instead. If you pass two TLS files, it starts HTTPS locally instead.
Important: Important:
- the current public API expects the existing key-then-cert argument order when calling `RunWebHookWithContext(...)`; - 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`. - 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. Be explicit in your own setup code so this does not become a deployment footgun.
@@ -202,7 +202,7 @@ Also keep in mind:
- `URL` is what Telegram sees; - `URL` is what Telegram sees;
- `Path` and `LocalPort` are what your bot actually serves; - `Path` and `LocalPort` are what your bot actually serves;
- these are often not the same thing in production. - 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. - 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 ## Relation to `tgapi` webhook methods
@@ -221,7 +221,7 @@ Use lower-level `tgapi` calls such as:
when you need custom infrastructure around the webhook path and do not want the bot to own the HTTP server itself. when you need custom infrastructure around the webhook path and do not want the bot to own the HTTP server itself.
In other words: In other words:
- `RunWebHookWithContext(...)` is the framework runtime API; - `RunWebhookWithContext(...)` is the framework runtime API;
- `tgapi` webhook methods are the lower-level transport primitives. - `tgapi` webhook methods are the lower-level transport primitives.
## Common mistakes ## Common mistakes
+1 -1
@@ -6,7 +6,7 @@
- [[Getting-Started]] - [[Getting-Started]]
- [[Bot-Options-and-Configuration]] - [[Bot-Options-and-Configuration]]
- [[Commands-and-Plugins]] - [[Commands-and-Plugins]]
- [[MsgContext]] - [[MessageContext]]
## Runtime and Architecture ## Runtime and Architecture
- [[Bot-Lifecycle]] - [[Bot-Lifecycle]]
+4 -4
@@ -21,14 +21,14 @@ English version: [[tgapi-Overview]]
Это разделение специально сделано, чтобы JSON methods и file upload methods не смешивались в одну слишком размытую abstraction. Это разделение специально сделано, чтобы JSON methods и file upload methods не смешивались в одну слишком размытую abstraction.
## Когда использовать `MsgContext`, а когда `tgapi` ## Когда использовать `MessageContext`, а когда `tgapi`
Используй `MsgContext`, когда: Используй `MessageContext`, когда:
- ты уже внутри handler'а; - ты уже внутри handler'а;
- нужен обычный поток reply/edit/delete/callback. - нужен обычный поток reply/edit/delete/callback.
Используй `tgapi`, когда: Используй `tgapi`, когда:
- у `MsgContext` нет нужного вспомогательного метода; - у `MessageContext` нет нужного вспомогательного метода;
- ты работаешь вне потока обработчика; - ты работаешь вне потока обработчика;
- нужен более низкоуровневый контроль; - нужен более низкоуровневый контроль;
- нужно работать с uploads/downloads напрямую. - нужно работать с uploads/downloads напрямую.
@@ -100,7 +100,7 @@ _, err := uploader.SendPhoto(tgapi.UploadPhoto{
## Что читать дальше ## Что читать дальше
- [[MsgContext-RU]] - [[MessageContext-RU]]
- [[Inline-Keyboards-and-Payloads-RU]] - [[Inline-Keyboards-and-Payloads-RU]]
- [[Rate-Limiting-RU]] - [[Rate-Limiting-RU]]
- [[tgapi-Overview]] - [[tgapi-Overview]]
+6 -6
@@ -2,7 +2,7 @@
Russian version: [[tgapi-Overview-RU]] Russian version: [[tgapi-Overview-RU]]
`tgapi` is the low-level Telegram Bot API layer used under Laniakeas higher-level bot runtime. Use it when you need direct access to Telegram methods, explicit parameter structs, upload control, or raw request building that sits below plugins and `MsgContext` helpers. `tgapi` is the low-level Telegram Bot API layer used under Laniakeas higher-level bot runtime. Use it when you need direct access to Telegram methods, explicit parameter structs, upload control, or raw request building that sits below plugins and `MessageContext` helpers.
## The important split first ## The important split first
@@ -20,7 +20,7 @@ If you stay on the typed method surface, you usually do not need to think about
## The normal layering ## The normal layering
In practice, Laniakea has three levels: In practice, Laniakea has three levels:
- high-level handler helpers on `MsgContext`; - high-level handler helpers on `MessageContext`;
- runtime structure on `Bot`, plugins, and middleware; - runtime structure on `Bot`, plugins, and middleware;
- low-level Telegram access in `tgapi`. - low-level Telegram access in `tgapi`.
@@ -29,7 +29,7 @@ In practice, Laniakea has three levels:
## When to use `tgapi` directly ## When to use `tgapi` directly
Use `tgapi` directly when: Use `tgapi` directly when:
- a `MsgContext` helper does not expose the Telegram feature you need; - a `MessageContext` helper does not expose the Telegram feature you need;
- you need a Telegram method outside the high-level command/payload flow; - you need a Telegram method outside the high-level command/payload flow;
- you want explicit control over params, parse modes, message edits, or uploads; - you want explicit control over params, parse modes, message edits, or uploads;
- you are writing infrastructure code rather than command logic. - you are writing infrastructure code rather than command logic.
@@ -43,7 +43,7 @@ Typical examples where `tgapi` is the better tool:
- setting bot metadata or command scopes directly; - setting bot metadata or command scopes directly;
- file downloads and streaming; - file downloads and streaming;
- multipart uploads; - multipart uploads;
- one-off Telegram methods that do not have a `MsgContext` wrapper. - one-off Telegram methods that do not have a `MessageContext` wrapper.
## Typed methods first ## Typed methods first
@@ -244,7 +244,7 @@ Related page:
As a rule of thumb: As a rule of thumb:
- use `MsgContext` when responding to the current update; - use `MessageContext` when responding to the current update;
- use `Bot` and plugins when structuring runtime behavior; - use `Bot` and plugins when structuring runtime behavior;
- use `tgapi` when you need direct Telegram method control; - use `tgapi` when you need direct Telegram method control;
- use raw `NewRequest` or `NewUploaderRequest` only as the final fallback. - use raw `NewRequest` or `NewUploaderRequest` only as the final fallback.
@@ -254,7 +254,7 @@ That boundary keeps normal bot code ergonomic without hiding Telegram-specific c
## Related pages ## Related pages
- [[Getting-Started]] for the normal high-level bot setup path. - [[Getting-Started]] for the normal high-level bot setup path.
- [[MsgContext]] for handler-time reply helpers. - [[MessageContext]] for handler-time reply helpers.
- [[Inline-Keyboards-and-Payloads]] for callback button construction. - [[Inline-Keyboards-and-Payloads]] for callback button construction.
- [[Bot-Lifecycle]] for runtime startup and shutdown responsibilities around `Bot`, `API`, and `Uploader`. - [[Bot-Lifecycle]] for runtime startup and shutdown responsibilities around `Bot`, `API`, and `Uploader`.
- [[Rate-Limiting]] for limiter and `retry_after` behavior. - [[Rate-Limiting]] for limiter and `retry_after` behavior.