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`.
2. Создать `Bot` через `NewBot[T](opts)`.
3. Полностью настроить бот: плагины, middleware, фоновые задачи, политику данных callback, l10n и app data.
4. Запустить через `Run()`, `RunWithContext(...)` или `RunWebHookWithContext(...)`.
4. Запустить через `Run()`, `RunWithContext(...)` или `RunWebhookWithContext(...)`.
5. Остановить выполнение через завершение runtime или отмену context.
6. Освободить локальные ресурсы через `Close()`.
7. Для следующего запуска создать новый `Bot`.
@@ -66,7 +66,7 @@ English version: [[Bot-Lifecycle]]
1. Построение и настройка `Bot` после `NewBot[T](opts)`.
2. Снимок конфигурации плагина в `AddPlugins(...)`.
3. Фиксация bot-level конфигурации после первого `Run()`, `RunWithContext(...)` или `RunWebHookWithContext(...)`.
3. Фиксация bot-level конфигурации после первого `Run()`, `RunWithContext(...)` или `RunWebhookWithContext(...)`.
Практически это значит:
- структуру плагина нужно закончить до `AddPlugins(...)`;
@@ -85,7 +85,7 @@ Laniakea предпочитает предсказуемый no-op вместо
- путаницу в том, влияет ли изменение только на будущие update или ещё и на уже принятую работу;
- разные ментальные модели для snapshot-поведения плагинов и bot-level состояния.
## `Run()`, `RunWithContext(...)` и `RunWebHookWithContext(...)`
## `Run()`, `RunWithContext(...)` и `RunWebhookWithContext(...)`
`Run()` — это короткая форма для простых случаев.
@@ -94,11 +94,11 @@ Laniakea предпочитает предсказуемый no-op вместо
- ждет завершения queued updates;
- корректно дожидается фоновых задач.
`RunWebHookWithContext(...)` — webhook-вариант runtime. Он использует тот же single-use контракт, тот же запуск runners, ту же очередь обновлений и ту же worker-pool обработку.
`RunWebhookWithContext(...)` — webhook-вариант runtime. Он использует тот же single-use контракт, тот же запуск runners, ту же очередь обновлений и ту же worker-pool обработку.
Если 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]].
@@ -131,7 +131,7 @@ Laniakea предпочитает предсказуемый no-op вместо
Обычно боту нужен именно `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
1. Build `BotOpts` and call `NewBot`.
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.
5. Call `Close()` to release local resources.
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.
- `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
@@ -66,7 +66,7 @@ Laniakea has three practical configuration phases:
1. Construction and bot setup after `NewBot[T](opts)`.
2. Plugin snapshotting at `AddPlugins(...)`.
3. Runtime freeze after the first `Run()`, `RunWithContext(...)`, or `RunWebHookWithContext(...)`.
3. Runtime freeze after the first `Run()`, `RunWithContext(...)`, or `RunWebhookWithContext(...)`.
That means:
- finish plugin structure before `AddPlugins(...)`;
@@ -97,7 +97,7 @@ if err != nil {
defer bot.Close()
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")
return nil
}, "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.
@@ -124,16 +124,16 @@ It:
`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;
- runner startup behavior;
- internal update queue;
- worker-pool dispatch model;
- graceful shutdown semantics.
Use `RunWithContext(...)` for production services that poll Telegram directly. Use `RunWebHookWithContext(...)` when Telegram should deliver updates through your HTTP endpoint.
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]].
@@ -172,7 +172,7 @@ Canceling the runtime context tells the bot to stop accepting new work and finis
- one-time async runners to finish;
- 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()`
@@ -192,7 +192,7 @@ Use `CloseRemote(ctx)` only when you specifically need Telegram-side session shu
## 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:
- 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
func(ctx *laniakea.MsgContext, db T) error
func(ctx *laniakea.MessageContext, db T) error
```
Где:
- `ctx` — текущий `MsgContext`;
- `ctx` — текущий `MessageContext`;
- `db` — значение generic-параметра `T`, которое ты передал в `Bot`.
Возвращай:
@@ -61,7 +61,7 @@ func(ctx *laniakea.MsgContext, db T) error
Пример:
```go
func start(ctx *laniakea.MsgContext, db *App) error {
func start(ctx *laniakea.MessageContext, db *App) error {
ctx.Answer("Welcome")
return nil
}
@@ -125,7 +125,7 @@ plugin.AddCommand(
Пример:
```go
func confirmDelete(ctx *laniakea.MsgContext, db *App) error {
func confirmDelete(ctx *laniakea.MessageContext, db *App) error {
ctx.EditCallback("Deleted", nil)
return nil
}
@@ -147,7 +147,7 @@ plugin.AddPayload(plugin.NewPayload(confirmDelete, "delete.confirm"))
Пример:
```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
})
```
@@ -170,7 +170,7 @@ plugin.AddUpdateHandler(tgapi.UpdateTypeInlineQuery, func(ctx *laniakea.MsgConte
Для текстовой команды поток примерно такой:
1. Приходит Telegram update.
2. `Bot` готовит `MsgContext`.
2. `Bot` готовит `MessageContext`.
3. Выполняется middleware бота.
4. Находится подходящий плагин.
5. Выполняется middleware плагина.
@@ -258,5 +258,5 @@ return nil
- [[Getting-Started-RU]]
- [[Bot-Lifecycle-RU]]
- [[Commands-and-Plugins]]
- [[MsgContext]]
- [[MessageContext]]
- [[Inline-Keyboards-and-Payloads]]
+10 -10
@@ -49,7 +49,7 @@ That keeps command registration and middleware ownership clear.
The command handler signature is:
```go
func(ctx *laniakea.MsgContext, db T) error
func(ctx *laniakea.MessageContext, db T) error
```
Where:
@@ -63,7 +63,7 @@ Return:
Example:
```go
func start(ctx *laniakea.MsgContext, db *App) error {
func start(ctx *laniakea.MessageContext, db *App) error {
ctx.Answer("Welcome")
return nil
}
@@ -89,7 +89,7 @@ So:
## The easiest command example
```go
func echo(ctx *laniakea.MsgContext, db laniakea.NoData) error {
func echo(ctx *laniakea.MessageContext, db laniakea.NoData) error {
if ctx.Text == "" {
ctx.Answer("Usage: /echo <text>")
return nil
@@ -136,7 +136,7 @@ Payload handlers are for callback data coming from inline keyboard buttons.
Register them with `NewPayload(...)` or `AddPayload(...)`:
```go
func confirmDelete(ctx *laniakea.MsgContext, db *App) error {
func confirmDelete(ctx *laniakea.MessageContext, db *App) error {
ctx.EditCallback("Deleted", nil)
return nil
}
@@ -158,7 +158,7 @@ Update handlers are for Telegram updates outside the normal command/payload flow
Register them with `AddUpdateHandler(...)`:
```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
return nil
})
@@ -183,7 +183,7 @@ stay on the command/payload flow and are not meant to be registered through `Add
For text commands:
1. Telegram update arrives
2. bot prepares `MsgContext`
2. bot prepares `MessageContext`
3. bot middleware runs
4. matching plugin is found
5. plugin middleware runs
@@ -235,7 +235,7 @@ Avoid one giant plugin for the entire bot unless the bot is very small.
```go
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) {
ctx.Answer("Access denied")
return false
@@ -243,7 +243,7 @@ admin.AddMiddleware(laniakea.NewMiddleware("admin-only", func(ctx *laniakea.MsgC
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")
return nil
}, "ban"))
@@ -252,7 +252,7 @@ admin.AddCommand(admin.NewCommand(func(ctx *laniakea.MsgContext, app *App) error
### Example: payload handler for inline keyboard callback
```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.EditCallback("Done", nil)
return nil
@@ -304,6 +304,6 @@ Use:
- command middleware for narrow, local checks
## 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 [[Middleware]] for execution-order and async details.
+1 -1
@@ -75,6 +75,6 @@ draft.Flush()
## Что читать дальше
- [[MsgContext-RU]]
- [[MessageContext-RU]]
- [[Bot-Lifecycle-RU]]
- [[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 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:
- create a draft from the bot's configured `DraftProvider`;
@@ -35,7 +35,7 @@ Those helpers:
Typical usage:
```go
func report(ctx *laniakea.MsgContext, db *App) error {
func report(ctx *laniakea.MessageContext, db *App) error {
draft := ctx.NewDraft()
if draft == nil {
return nil
@@ -174,5 +174,5 @@ Prefer direct reply helpers such as `Answer(...)` or `AnswerLong(...)` when:
## 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
+1 -1
@@ -25,7 +25,7 @@ English version: [[Error-Handling]]
## Как это выглядит
```go
func ping(ctx *laniakea.MsgContext, db *App) error {
func ping(ctx *laniakea.MessageContext, db *App) error {
ctx.Answer("pong")
return nil
}
+4 -4
@@ -16,7 +16,7 @@ That applies to:
The basic pattern is:
```go
func ping(ctx *laniakea.MsgContext, db *App) error {
func ping(ctx *laniakea.MessageContext, db *App) error {
ctx.Answer("pong")
return nil
}
@@ -183,7 +183,7 @@ Related page:
### Centralized command failure
```go
plugin.NewCommand(func(ctx *laniakea.MsgContext, db *App) error {
plugin.NewCommand(func(ctx *laniakea.MessageContext, db *App) error {
result, err := db.DoWork()
if err != nil {
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
```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) {
ctx.Answer("Access denied")
return nil
@@ -208,7 +208,7 @@ plugin.NewCommand(func(ctx *laniakea.MsgContext, db *App) error {
### Callback-specific manual alert
```go
plugin.NewPayload(func(ctx *laniakea.MsgContext, db *App) error {
plugin.NewPayload(func(ctx *laniakea.MessageContext, db *App) error {
if !ready {
ctx.AnswerCbQueryAlert("This action is not available yet")
return nil
+22 -22
@@ -9,7 +9,7 @@
Текущее состояние:
- В репозитории уже есть низкоуровневые 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 runtime, security-модель и правила перехода обратно на polling теперь описаны в основной документации и wiki.
@@ -21,12 +21,12 @@
Что теперь есть:
- `BotWebHookOpts`, `NewBotWebHookOpts()` и fluent helper-методы для webhook-конфигурации.
- `RunWebHookWithContext(...)` и `RunWebHook(...)` как bot-owned точки входа runtime.
- `BotWebhookOpts`, `NewBotWebhookOpts()` и fluent helper-методы для webhook-конфигурации.
- `RunWebhookWithContext(...)` и `RunWebhook(...)` как bot-owned точки входа runtime.
- Общая queued dispatch-модель, worker-pool обработка, запуск runners и single-use semantics для polling и webhook mode.
- Fallback webhook `AllowedUpdates` к bot-level конфигурации типов update.
- Валидация webhook path и количества TLS-файлов до remote webhook setup.
- Явное удаление remote webhook через `CloseWebHook()` или низкоуровневый `tgapi.DeleteWebhook(...)` при переходе deployment с webhook-доставки обратно на polling.
- Явное удаление remote webhook через `CloseWebhook()` или низкоуровневый `tgapi.DeleteWebhook(...)` при переходе deployment с webhook-доставки обратно на polling.
- Регрессионные тесты на 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 маршрутизации.
- У бота и плагинов появились явные helpers для регистрации политик на уровне конфигурации.
@@ -82,7 +82,7 @@
- `Bot.UsePolicy(...)` и `Plugin.UsePolicy(...)` для удобной регистрации.
- Встроенные Telegram-aware helpers: `RequirePrivateChat(...)`, `RequireGroupChat(...)`, `RequireSupergroupChat(...)`, `RequireChatAdmin(...)`, `RequireChatCreator(...)`, `RequireBotAdmin(...)` и `RequireCallbackFromUser(...)`.
- Комбинаторы `AllPolicies(...)`, `AnyPolicy(...)` и `NotPolicy(...)`.
- Расширенная нормализация `MsgContext` для `Chat` и `ChatID`, а также регрессионные тесты на поведение политик и нормализованного контекста.
- Расширенная нормализация `MessageContext` для `Chat` и `ChatID`, а также регрессионные тесты на поведение политик и нормализованного контекста.
Практическая цель:
@@ -106,7 +106,7 @@
- `AsUserError(...)` и `AsInternalError(...)` для явной классификации возвращаемых ошибок.
- `IsUserError(...)` и `IsInternalError(...)` для проверки этой классификации на стороне фреймворка.
- Обновлённое поведение `MsgContext.Error(...)`: все ошибки по-прежнему логируются, но для внутренних ошибок автоматический ответ пользователю подавляется.
- Обновлённое поведение `MessageContext.Error(...)`: все ошибки по-прежнему логируются, но для внутренних ошибок автоматический ответ пользователю подавляется.
- Регрессионные тесты для message и callback потоков.
Практическая цель:
@@ -117,7 +117,7 @@
Текущее состояние:
- Фреймворк теперь считает конфигурацию бота структурно завершённой после начала первого runtime entry point: `Run()`, `RunWithContext(...)` или `RunWebHookWithContext(...)`.
- Фреймворк теперь считает конфигурацию бота структурно завершённой после начала первого runtime entry point: `Run()`, `RunWithContext(...)` или `RunWebhookWithContext(...)`.
- Поздние bot-level попытки мутации больше не применяются частично после старта runtime.
- Границы между регистрацией плагинов, стартом runtime и фиксацией конфигурации теперь оформлены как явное поведение фреймворка и закреплены тестами.
@@ -141,30 +141,30 @@
Текущее состояние:
- Нормализация обновлений уже существовала, но теперь она описана и протестирована как явный контракт уровня фреймворка.
- Категории маршрутизации и гарантии заполнения `MsgContext` теперь рассматриваются как полноценная часть публичной модели.
- Категории маршрутизации и гарантии заполнения `MessageContext` теперь рассматриваются как полноценная часть публичной модели.
Почему это важно:
- Код обработчиков должен понимать, на какие поля `MsgContext` можно безопасно опираться в каждом потоке обновлений.
- Код обработчиков должен понимать, на какие поля `MessageContext` можно безопасно опираться в каждом потоке обновлений.
- Без явного контракта обработка обновлений остаётся понятной только через чтение реализации.
Что теперь есть:
- Задокументированная модель маршрутизации для 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.
Практическая цель:
- Сделать маршрутизацию обновлений и гарантии `MsgContext` достаточно явными, чтобы на них можно было опираться как на стабильный контракт `1.0`.
- Сделать маршрутизацию обновлений и гарантии `MessageContext` достаточно явными, чтобы на них можно было опираться как на стабильный контракт `1.0`.
### [1.0.0-rc.12] Conversation / Scene Model
Текущее состояние:
- Фреймворк хорошо обрабатывает одно обновление через команды, данные callback, middleware и обработчики обновлений.
- В нём уже есть полезные низкоуровневые строительные блоки: `MsgContext`, черновики, маршрутизация данных callback, плагины и обработчики обновлений.
- Теперь в нём уже есть реализованная начальная модель сцен для долгоживущих интерактивных сценариев: сцены можно регистрировать в плагинах, запускать через `MsgContext`, сохранять через `SessionStore` и маршрутизировать раньше обычной обработки команд.
- В нём уже есть полезные низкоуровневые строительные блоки: `MessageContext`, черновики, маршрутизация данных callback, плагины и обработчики обновлений.
- Теперь в нём уже есть реализованная начальная модель сцен для долгоживущих интерактивных сценариев: сцены можно регистрировать в плагинах, запускать через `MessageContext`, сохранять через `SessionStore` и маршрутизировать раньше обычной обработки команд.
Почему это важно:
@@ -176,7 +176,7 @@
- Маршрутизация активной сцены раньше обычной маршрутизации команд.
- Области действия сессии на пользователя, чат и пару пользователь-чат.
- Явный вход и выход через `MsgContext`.
- Явный вход и выход через `MessageContext`.
- Обработчики шагов, локальные команды сцены и `OnMessage(...)`.
- Встроенное in-memory-хранилище по умолчанию и интерфейс `SessionStore` для собственного постоянного хранения.
@@ -190,7 +190,7 @@
- `Scene`, `SceneContext`, `SceneSession` и `SessionStore`.
- `Plugin.NewScene(...)` и `Plugin.AddScene(...)`.
- `MsgContext.EnterScene(...)`, `EnterSceneStep(...)` и `ExitScene(...)`.
- `MessageContext.EnterScene(...)`, `EnterSceneStep(...)` и `ExitScene(...)`.
- `SceneContext.Stay()`, `Next(...)`, `Exit()`, `Pass()`, `BindData(...)` и `SaveData(...)`.
- Состояние на пользователя или чат с хранением в `SessionStore` и чистым интерфейсом для собственного постоянного хранения.
@@ -198,7 +198,7 @@
- Это должно быть опциональным и расширяющим текущую модель.
- Это не должно заменять плагины, команды или обработчики как обычные точки входа во фреймворк.
- Это должно работать поверх существующих middleware и `MsgContext`, а не вводить вторую несовместимую модель выполнения.
- Это должно работать поверх существующих middleware и `MessageContext`, а не вводить вторую несовместимую модель выполнения.
Практическая цель:
@@ -240,7 +240,7 @@ type BanInput struct {
Reason string
}
func ban(ctx *laniakea.MsgContext, db *App) error {
func ban(ctx *laniakea.MessageContext, db *App) error {
var input BanInput
if err := ctx.BindArgs(&input); err != nil {
return err
@@ -263,7 +263,7 @@ func ban(ctx *laniakea.MsgContext, db *App) error {
Текущее состояние:
- `RunWithContext(...)` и `RunWebHookWithContext(...)` управляют жизненным циклом выполнения бота и корректным завершением.
- `RunWithContext(...)` и `RunWebhookWithContext(...)` управляют жизненным циклом выполнения бота и корректным завершением.
- `tgapi` уже поддерживает методы, принимающие `context.Context`.
- Обычные обработчики не получают полноценный `context.Context`, привязанный к обработке конкретного запроса.
@@ -281,14 +281,14 @@ func ban(ctx *laniakea.MsgContext, db *App) error {
Возможное направление API:
- Предпочесть non-breaking подход и выдавать context через `MsgContext`, например `ctx.Context()`.
- Предпочесть non-breaking подход и выдавать context через `MessageContext`, например `ctx.Context()`.
- Строить context из жизненного цикла обработки обновления, чтобы он оставался полезным во время корректного завершения.
- Сделать естественной передачу этого context в методы базы данных, HTTP-клиенты и `tgapi.WithContext(...)`.
Почему это, скорее всего, не должно быть изменением сигнатуры:
- Изменение сигнатур обработчиков на прямой `context.Context` было бы публичным ломающим изменением.
- Аксессор на `MsgContext` сохраняет совместимость и при этом даёт обработчикам идиоматичный Go-путь для отмены выполнения.
- Аксессор на `MessageContext` сохраняет совместимость и при этом даёт обработчикам идиоматичный Go-путь для отмены выполнения.
Практическая цель:
@@ -297,7 +297,7 @@ func ban(ctx *laniakea.MsgContext, db *App) error {
Связанные страницы:
- [[Scenes]]
- [[MsgContext]]
- [[MessageContext]]
- [[Bot-Lifecycle]]
- [[Migration]]
+22 -22
@@ -9,7 +9,7 @@ This page tracks framework-level backlog items that are about missing concepts i
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 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.
- 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:
- `BotWebHookOpts`, `NewBotWebHookOpts()`, and fluent helpers for webhook-specific configuration.
- `RunWebHookWithContext(...)` and `RunWebHook(...)` as bot-owned runtime entry points.
- `BotWebhookOpts`, `NewBotWebhookOpts()`, and fluent helpers for webhook-specific configuration.
- `RunWebhookWithContext(...)` and `RunWebhook(...)` as bot-owned runtime entry points.
- Shared queued update dispatch, worker-pool delivery, runner startup, and single-use run semantics between polling and webhook modes.
- Default fallback from webhook `AllowedUpdates` to the bot-level update type configuration.
- Request validation for webhook path shape and TLS file count before remote webhook setup.
- Explicit remote webhook teardown through `CloseWebHook()` or low-level `tgapi.DeleteWebhook(...)` when switching a deployment from webhook delivery back to polling.
- 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.
Practical target:
@@ -65,7 +65,7 @@ Practical target:
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.
- 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.
- Built-in Telegram-aware helpers such as `RequirePrivateChat(...)`, `RequireGroupChat(...)`, `RequireSupergroupChat(...)`, `RequireChatAdmin(...)`, `RequireChatCreator(...)`, `RequireBotAdmin(...)`, and `RequireCallbackFromUser(...)`.
- 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:
@@ -106,7 +106,7 @@ What is now present:
- `AsUserError(...)` and `AsInternalError(...)` to classify returned handler errors explicitly.
- `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.
Practical target:
@@ -117,7 +117,7 @@ Practical target:
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.
- Plugin registration and runtime configuration boundaries are now documented and tested as explicit framework behavior.
@@ -141,30 +141,30 @@ Practical target:
Current state:
- 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:
- 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.
What is now present:
- 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.
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
Current state:
- The framework is strong at handling a single update through commands, payloads, middleware, and update handlers.
- It already has useful lower-level building blocks such as `MsgContext`, drafts, payload routing, plugins, and update handlers.
- It now provides 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 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 `MessageContext`, persisted through `SessionStore`, and routed before normal command handling.
Why this matters:
@@ -176,7 +176,7 @@ What is already present:
- Active-scene routing before normal command flow.
- 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(...)`.
- 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`.
- `Plugin.NewScene(...)` and `Plugin.AddScene(...)`.
- `MsgContext.EnterScene(...)`, `EnterSceneStep(...)`, and `ExitScene(...)`.
- `MessageContext.EnterScene(...)`, `EnterSceneStep(...)`, and `ExitScene(...)`.
- `SceneContext.Stay()`, `Next(...)`, `Exit()`, `Pass()`, `BindData(...)`, and `SaveData(...)`.
- 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.
- 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:
@@ -240,7 +240,7 @@ type BanInput struct {
Reason string
}
func ban(ctx *laniakea.MsgContext, db *App) error {
func ban(ctx *laniakea.MessageContext, db *App) error {
var input BanInput
if err := ctx.BindArgs(&input); err != nil {
return err
@@ -263,7 +263,7 @@ Practical target:
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.
- Regular handlers do not receive a first-class request-scoped `context.Context`.
@@ -281,14 +281,14 @@ What is missing:
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.
- 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:
- 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:
@@ -297,7 +297,7 @@ Practical target:
Related pages:
- [[Scenes]]
- [[MsgContext]]
- [[MessageContext]]
- [[Bot-Lifecycle]]
- [[Migration]]
+7 -7
@@ -33,7 +33,7 @@ import (
"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")
return nil
}
@@ -111,7 +111,7 @@ bot.AddPlugins(plugin)
Сигнатура хендлера:
```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
func profile(ctx *laniakea.MsgContext, db *App) error {
func profile(ctx *laniakea.MessageContext, db *App) error {
user, err := db.LoadUser(ctx.FromID)
if err != nil {
return err
@@ -136,7 +136,7 @@ func profile(ctx *laniakea.MsgContext, db *App) error {
### 4. `Bot` single-use
После `Run()`, `RunWithContext(...)` или `RunWebHookWithContext(...)` нельзя снова запускать тот же экземпляр `Bot`.
После `Run()`, `RunWithContext(...)` или `RunWebhookWithContext(...)` нельзя снова запускать тот же экземпляр `Bot`.
Правильная модель:
- создать bot
@@ -151,7 +151,7 @@ func profile(ctx *laniakea.MsgContext, db *App) error {
### 5. `Close()` все равно нужен
Даже если ты используешь `Run()`, `RunWithContext(...)` или `RunWebHookWithContext(...)`, ресурсы нужно закрывать явно:
Даже если ты используешь `Run()`, `RunWithContext(...)` или `RunWebhookWithContext(...)`, ресурсы нужно закрывать явно:
```go
defer bot.Close()
@@ -168,7 +168,7 @@ defer bot.Close()
5. Добавить команды, payloads и middleware в плагины
6. Зарегистрировать плагины через `AddPlugins(...)`
7. При необходимости вызвать `AutoGenerateCommands()`
8. Вызвать `Run()`, `RunWithContext(...)` или `RunWebHookWithContext(...)`
8. Вызвать `Run()`, `RunWithContext(...)` или `RunWebhookWithContext(...)`
9. Закрыть bot через `Close()`
## Частые ошибки на старте
@@ -199,4 +199,4 @@ defer bot.Close()
- [[Bot-Lifecycle-RU]]
- [[Getting-Started]]
- [[Commands-and-Plugins]]
- [[MsgContext]]
- [[MessageContext]]
+8 -8
@@ -34,7 +34,7 @@ import (
"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")
return nil
}
@@ -113,7 +113,7 @@ See [[Commands-and-Plugins]] for the full model.
The command handler signature is:
```go
func(ctx *laniakea.MsgContext, db T) error
func(ctx *laniakea.MessageContext, db T) error
```
This means:
@@ -124,7 +124,7 @@ This means:
Example:
```go
func profile(ctx *laniakea.MsgContext, db *App) error {
func profile(ctx *laniakea.MessageContext, db *App) error {
user, err := db.LoadUser(ctx.FromID)
if err != nil {
return err
@@ -139,7 +139,7 @@ func profile(ctx *laniakea.MsgContext, db *App) error {
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
- create a new bot instance for the next run
@@ -149,7 +149,7 @@ See [[Bot-Lifecycle]] for details.
### 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:
@@ -168,7 +168,7 @@ For most bots, this order is the least surprising:
5. Add commands, payloads, and middleware to plugins
6. Register plugins with `AddPlugins(...)`
7. Optionally call `AutoGenerateCommands()`
8. Call `Run()`, `RunWithContext(...)`, or `RunWebHookWithContext(...)`
8. Call `Run()`, `RunWithContext(...)`, or `RunWebhookWithContext(...)`
9. Call `Close()` when done
## A slightly more realistic example
@@ -184,7 +184,7 @@ import (
type App struct{}
func echo(ctx *laniakea.MsgContext, app *App) error {
func echo(ctx *laniakea.MessageContext, app *App) error {
if ctx.Text == "" {
ctx.Answer("Send some text after the command.")
return nil
@@ -259,5 +259,5 @@ Prefer pointer types unless you have a strong reason not to.
## Where to go next
- 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.
+3 -3
@@ -9,7 +9,7 @@ English version: [[Home]]
- [[Getting-Started-RU]]
- [[Bot-Options-and-Configuration-RU]]
- [[Commands-and-Plugins-RU]]
- [[MsgContext-RU]]
- [[MessageContext-RU]]
## Выполнение и архитектура
@@ -50,7 +50,7 @@ English version: [[Home]]
- [[Getting-Started]]
- [[Bot-Options-and-Configuration]]
- [[Commands-and-Plugins]]
- [[MsgContext]]
- [[MessageContext]]
- [[Bot-Lifecycle]]
- [[Inline-Keyboards-and-Payloads]]
- [[tgapi-Overview]]
@@ -60,7 +60,7 @@ English version: [[Home]]
Рекомендуемый маршрут для русскоязычного пользователя:
1. Прочитать [[Getting-Started-RU]].
2. Прочитать [[Commands-and-Plugins-RU]] и [[MsgContext-RU]].
2. Прочитать [[Commands-and-Plugins-RU]] и [[MessageContext-RU]].
3. Перейти в страницы про выполнение и поведение во время работы, например [[Bot-Lifecycle-RU]] и [[Middleware-RU]].
4. При необходимости открыть специализированные страницы вроде [[Drafts-RU]], [[Rate-Limiting-RU]] или [[tgapi-Overview-RU]].
5. Для максимальной точности переходить в соответствующую англоязычную страницу.
+1 -1
@@ -15,7 +15,7 @@ Use this wiki as the structured companion to the README: start with setup, then
- [[Getting-Started]]
- [[Bot-Options-and-Configuration]]
- [[Commands-and-Plugins]]
- [[MsgContext]]
- [[MessageContext]]
## Bot Runtime
- [[Bot-Lifecycle]]
+1 -1
@@ -99,5 +99,5 @@ kb := ctx.NewInlineKeyboard(2).
## Что читать дальше
- [[Commands-and-Plugins-RU]]
- [[MsgContext-RU]]
- [[MessageContext-RU]]
- [[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;
- 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
@@ -126,7 +126,7 @@ The bot has a default callback encoding:
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:
@@ -247,7 +247,7 @@ Payloads are registered on plugins with:
Example:
```go
plugin.NewPayload(func(ctx *laniakea.MsgContext, db *App) error {
plugin.NewPayload(func(ctx *laniakea.MessageContext, db *App) error {
id := ctx.Args[0]
ctx.AnswerCbQueryText("Handled " + id)
return nil
@@ -275,5 +275,5 @@ Related page:
## Related pages
- [[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.
+5 -5
@@ -77,11 +77,11 @@ bot.SetL10n(l10n)
From then on:
- `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.
## `MsgContext.Translate`
## `MessageContext.Translate`
`ctx.Translate(key)` is the usual choice inside handlers.
@@ -93,7 +93,7 @@ It:
Example:
```go
func start(ctx *laniakea.MsgContext, db *App) error {
func start(ctx *laniakea.MessageContext, db *App) error {
ctx.Answer(ctx.Translate("greeting"))
return nil
}
@@ -111,7 +111,7 @@ Use `bot.L10n(lang, key)` when you need a translation outside handler flow or wh
Examples:
- preparing background-runner messages;
- rendering text for stored user preferences;
- translating outside `MsgContext`.
- translating outside `MessageContext`.
## Concurrency and mutation safety
@@ -150,5 +150,5 @@ l10n.
## Related pages
- [[Getting-Started]] for basic bot setup
- [[MsgContext]] for handler helpers
- [[MessageContext]] for handler helpers
- [[Recipes]] for localized handler examples
+2 -2
@@ -55,13 +55,13 @@ Ways plugin loggers are set:
- explicitly with `Plugin.SetLogger(...)` before `AddPlugins(...)`;
- 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;
- otherwise the bot logger.
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`.
+4 -4
@@ -45,7 +45,7 @@ Middleware позволяет запускать логику до обрабо
Сигнатура:
```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;
- выполнение цепочки продолжается сразу;
- возвращаемое значение `bool` игнорируется;
- middleware получает копию `MsgContext`.
- middleware получает копию `MessageContext`.
Поэтому async middleware подходит только для:
- телеметрии;
@@ -79,7 +79,7 @@ func(ctx *laniakea.MsgContext, db T) bool
- обязательной валидации;
- логики, которая должна переписать `ctx` и повлиять на обработчик.
## Почему async middleware получает копию `MsgContext`
## Почему async middleware получает копию `MessageContext`
Так библиотека избегает очевидных гонок данных между goroutine middleware и основной цепочкой обработки.
@@ -105,5 +105,5 @@ Middleware уровня плагина и команды сохраняют по
## Что читать дальше
- [[Commands-and-Plugins-RU]]
- [[MsgContext-RU]]
- [[MessageContext-RU]]
- [[Middleware]]
+12 -12
@@ -11,11 +11,11 @@ Middleware is useful when the same check or side effect should apply in more tha
Typical uses:
- reject updates from unauthorized users;
- 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;
- 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
@@ -47,7 +47,7 @@ For non-command update handlers registered through `AddUpdateHandler(...)`, the
Important detail:
- 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
@@ -56,7 +56,7 @@ By default, middleware is synchronous.
The executor signature is:
```go
func(ctx *laniakea.MsgContext, db T) bool
func(ctx *laniakea.MessageContext, db T) bool
```
Return values mean:
@@ -72,7 +72,7 @@ This makes synchronous middleware the right choice for:
Example:
```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) {
ctx.Answer("Access denied")
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)`.
```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)
return true
}).SetAsync(true)
@@ -96,7 +96,7 @@ Async middleware behaves differently:
- it runs in a goroutine;
- execution always continues immediately;
- 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:
- fire-and-forget logging;
@@ -112,7 +112,7 @@ It is not appropriate for:
## 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:
- 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:
```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"
return false
}).SetAsync(true)
@@ -177,7 +177,7 @@ This usually means:
```go
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" {
return false
}
@@ -191,7 +191,7 @@ bot.AddMiddleware(
```go
admin := laniakea.NewPlugin[*App]("admin")
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)
}),
)
@@ -201,7 +201,7 @@ admin.AddMiddleware(
```go
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 {
ctx.Answer("Reply to a user message first")
return false
+64 -1
@@ -25,7 +25,70 @@ When jumping across multiple RC versions:
5. Revisit any direct `tgapi` calls and renamed types.
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`
+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;
- middleware;
@@ -18,7 +18,7 @@ English version: [[MsgContext]]
- разобранные аргументы команд и callback;
- вспомогательные методы для 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
У `MsgContext` есть:
У `MessageContext` есть:
- `NewDraft()`
- `NewDraftMarkdown()`
- `Translate(key)`
Это делает `MsgContext` основной удобной точкой доступа почти для всего кода обработчиков.
Это делает `MessageContext` основной удобной точкой доступа почти для всего кода обработчиков.
## `NewInlineKeyboard(...)`
@@ -130,4 +130,4 @@ kb := ctx.NewInlineKeyboard(2)
- [[Inline-Keyboards-and-Payloads-RU]]
- [[Drafts-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:
- the incoming update
@@ -10,7 +10,7 @@ It gives you access to:
- parsed command or payload arguments
- 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]].
@@ -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.
```go
func start(ctx *laniakea.MsgContext, db laniakea.NoData) error {
func start(ctx *laniakea.MessageContext, db laniakea.NoData) error {
ctx.Answer("Welcome")
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.
```go
func help(ctx *laniakea.MsgContext, db laniakea.NoData) error {
func help(ctx *laniakea.MessageContext, db laniakea.NoData) error {
ctx.AnswerLong(buildLargeHelpText())
return nil
}
@@ -92,7 +92,7 @@ Important:
Use `Keyboard(...)` when you want to send a message with an inline keyboard.
```go
func menu(ctx *laniakea.MsgContext, db laniakea.NoData) error {
func menu(ctx *laniakea.MessageContext, db laniakea.NoData) error {
kb := ctx.NewInlineKeyboard(2).
AddCallbackButton("Profile", "profile.open").
AddCallbackButton("Settings", "settings.open")
@@ -127,7 +127,7 @@ Use `laniakea.EscapeMarkdownV2(...)` for this.
Example:
```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)
ctx.AnswerMarkdown("*User:* " + name)
return nil
@@ -141,7 +141,7 @@ Once you already have an `AnswerMessage`, you can edit or delete it.
Example:
```go
func slowTask(ctx *laniakea.MsgContext, db laniakea.NoData) error {
func slowTask(ctx *laniakea.MessageContext, db laniakea.NoData) error {
msg := ctx.Answer("Working...")
if msg == nil {
return nil
@@ -173,7 +173,7 @@ When handling inline button callbacks, these helpers are especially useful.
Edits the callback-linked message.
```go
func approve(ctx *laniakea.MsgContext, db laniakea.NoData) error {
func approve(ctx *laniakea.MessageContext, db laniakea.NoData) error {
ctx.EditCallback("Approved", nil)
return nil
}
@@ -192,7 +192,7 @@ Use:
Example:
```go
func approve(ctx *laniakea.MsgContext, db laniakea.NoData) error {
func approve(ctx *laniakea.MessageContext, db laniakea.NoData) error {
ctx.AnswerCbQueryText("Saved")
ctx.EditCallback("Saved", nil)
return nil
@@ -220,7 +220,7 @@ Caption rules differ from normal message text:
## Drafts
`MsgContext` also exposes draft creation helpers:
`MessageContext` also exposes draft creation helpers:
- `NewDraft()`
- `NewDraftMarkdown()`
@@ -246,7 +246,7 @@ This looks up text using the current users language when available and falls
Example:
```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"))
return nil
}
@@ -275,7 +275,7 @@ Use `SendAction(...)` to show activity like typing or uploading.
Example:
```go
func report(ctx *laniakea.MsgContext, db *App) error {
func report(ctx *laniakea.MessageContext, db *App) error {
ctx.SendAction(tgapi.ChatActionTyping)
text, err := db.BuildReport(ctx.FromID)
if err != nil {
@@ -293,7 +293,7 @@ This is especially useful for slower handlers.
A common pattern is:
```go
func profile(ctx *laniakea.MsgContext, db *App) error {
func profile(ctx *laniakea.MessageContext, db *App) error {
user, err := db.LoadUser(ctx.FromID)
if err != nil {
return err
@@ -338,7 +338,7 @@ Methods like `EditCallback(...)` and `AnswerCbQueryText(...)` depend on callback
## A practical example
```go
func settings(ctx *laniakea.MsgContext, db *App) error {
func settings(ctx *laniakea.MessageContext, db *App) error {
kb := ctx.NewInlineKeyboard(1).
AddCallbackButton("Enable notifications", "settings.notifications.enable").
AddCallbackButton("Disable notifications", "settings.notifications.disable")
+4 -4
@@ -1,6 +1,6 @@
# Policies
Policies — это полноценная модель правил авторизации в Laniakea. Политика работает поверх нормализованного `MsgContext` и переданных `AppData`, возвращает `nil`, если доступ разрешён, и возвращает ошибку, если действие нужно запретить или сама проверка не смогла корректно выполниться.
Policies — это полноценная модель правил авторизации в Laniakea. Политика работает поверх нормализованного `MessageContext` и переданных `AppData`, возвращает `nil`, если доступ разрешён, и возвращает ошибку, если действие нужно запретить или сама проверка не смогла корректно выполниться.
Policies не вводят вторую модель выполнения. Они встраиваются в уже существующий middleware pipeline через `RequirePolicy(...)`, поэтому авторизация остаётся на том же пути маршрутизации, что и остальной фреймворк.
@@ -15,7 +15,7 @@ Policies не вводят вторую модель выполнения. Он
## Основной API
```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]
@@ -62,7 +62,7 @@ plugin.UsePolicy(
- `RequireBotAdmin(...)`
- `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]]
- [[MsgContext-RU]]
- [[MessageContext-RU]]
- [[Commands-and-Plugins-RU]]
- [[Framework-Backlog-RU]]
+4 -4
@@ -1,6 +1,6 @@
# 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.
@@ -15,7 +15,7 @@ Policies do not introduce a second execution model. They plug into the existing
## Core API
```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]
@@ -62,7 +62,7 @@ The current built-in helpers focus on common Telegram-specific access checks:
- `RequireBotAdmin(...)`
- `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
@@ -133,6 +133,6 @@ Those features can be added later if the existing `Policy[T]` model proves too s
Related pages:
- [[Middleware]]
- [[MsgContext]]
- [[MessageContext]]
- [[Commands-and-Plugins]]
- [[Framework-Backlog]]
+9 -9
@@ -14,11 +14,11 @@ type App struct{}
func (a *App) IsAdmin(userID int64) bool { return userID == 42 }
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)
}))
admin.NewCommand(func(ctx *laniakea.MsgContext, app *App) error {
admin.NewCommand(func(ctx *laniakea.MessageContext, app *App) error {
ctx.Answer("Admin command executed")
return nil
}, "reload")
@@ -31,14 +31,14 @@ Use a payload handler for inline keyboard callbacks.
```go
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.AddCallbackButton("Open settings", "settings")
ctx.Keyboard("Choose an action", kb)
return nil
}, "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)
return nil
}, "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.
```go
plugin.NewCommand(func(ctx *laniakea.MsgContext, db *App) error {
plugin.NewCommand(func(ctx *laniakea.MessageContext, db *App) error {
report := buildLargePlainTextReport()
ctx.AnswerLong(report)
return nil
@@ -71,7 +71,7 @@ l10n := laniakea.NewL10n("en").
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"))
return nil
}, "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.
```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 {
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.
```go
plugin.NewCommand(func(ctx *laniakea.MsgContext, db *App) error {
plugin.NewCommand(func(ctx *laniakea.MessageContext, db *App) error {
draft := ctx.NewDraft()
if draft == 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.
```go
plugin.NewCommand(func(ctx *laniakea.MsgContext, db *App) error {
plugin.NewCommand(func(ctx *laniakea.MessageContext, db *App) error {
uploader := tgapi.NewUploader(ctx.Api)
defer uploader.Close()
+50 -16
@@ -22,43 +22,59 @@ Runner — это фоновая или одноразовая задача, к
runner := laniakea.NewRunner("cleanup", fn)
```
Потом конфигурируются методы builder:
- `Onetime(bool)`
- `Async(bool)`
- `Timeout(duration)`
Методы builder:
- `Every(time.Duration)` — интервал повторного запуска. Ноль (по умолчанию) означает одноразовый запуск; положительное значение — периодический.
- `Async(bool)` — если `true` (по умолчанию), запускается в goroutine; если `false`, блокирует запуск runtime.
## Основные режимы
### Одноразовый sync
### Одноразовый async (по умолчанию)
- выполняется один раз;
- блокирует запуск;
- полезен для работы, критичной на старте.
### Одноразовый async
```go
runner := laniakea.NewRunner("prefetch", fn)
```
- выполняется один раз;
- стартует в goroutine;
- не блокирует запуск.
### Одноразовый sync
```go
runner := laniakea.NewRunner("warmup", fn).Async(false)
```
- выполняется один раз;
- блокирует запуск до завершения;
- полезен для работы, критичной на старте.
### Повторяющийся async
- работает циклически;
```go
runner := laniakea.NewRunner("cleanup", fn).Every(time.Minute)
```
- работает циклически с заданным интервалом;
- использует ticker;
- живет до `ctx.Done()`.
## Невалидная конфигурация
Повторяющийся synchronous runner считается невалидным и пропускается с предупреждением.
Также повторяющийся async runner без `Timeout(...)` пропускается.
Повторяющийся synchronous runner (`Every(d > 0).Async(false)`) считается невалидным и пропускается с предупреждением — блокировать запуск бесконечно никогда не имеет смысла.
## Когда стартуют фоновые задачи
Фоновые задачи стартуют из `RunWithContext(...)` или `RunWebHookWithContext(...)`, а не из `NewBot(...)`.
Фоновые задачи стартуют из `RunWithContext(...)` или `RunWebhookWithContext(...)`, а не из `NewBot(...)`.
Это часть фазы выполнения, а не фазы сборки конфигурации.
## Обработка ошибок
Если runner возвращает ошибку:
- бот логирует предупреждение;
- через observer отправляется `ErrorEvent`;
- бот продолжает работу.
## Семантика остановки
При корректной остановке бот:
@@ -67,9 +83,27 @@ runner := laniakea.NewRunner("cleanup", fn)
Поэтому код 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.
- Не держи сложную бизнес-логику внутри runner; лучше делегируй ее в обычные сервисы приложения.
+39 -49
@@ -13,10 +13,9 @@ Each runner is built from:
- a function `func(*Bot[T]) error`;
- execution flags configured through builder methods.
Main builder methods:
- `Onetime(bool)`
- `Async(bool)`
- `Timeout(duration)`
Builder methods:
- `Every(time.Duration)` — sets the repeat interval. Zero (default) means run once; positive means repeat.
- `Async(bool)` — if `true` (default), the runner runs in a goroutine; if `false`, it blocks runtime startup.
## Creating a runner
@@ -29,36 +28,21 @@ cleanup := laniakea.NewRunner("cleanup", func(bot *laniakea.Bot[*App]) error {
```
By default, a new runner is:
- asynchronous;
- not one-time;
- configured with zero timeout.
- asynchronous (`Async(true)`);
- one-shot (`Every(0)`).
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
There are three meaningful configurations.
### One-time synchronous
### One-time asynchronous (default)
```go
runner := laniakea.NewRunner("warmup", fn).
Onetime(true).
Async(false)
```
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)
runner := laniakea.NewRunner("prefetch", fn)
// or explicitly:
runner := laniakea.NewRunner("prefetch", fn).Every(0).Async(true)
```
Behavior:
@@ -68,31 +52,37 @@ Behavior:
Use this for fire-and-forget startup work that is useful but not required before handling updates.
### Repeating asynchronous
### One-time synchronous
```go
runner := laniakea.NewRunner("cleanup", fn).
Timeout(time.Minute)
runner := laniakea.NewRunner("warmup", fn).Async(false)
```
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;
- is awaited during graceful shutdown.
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)`
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.
- `Every(d > 0).Async(false)` — a periodic sync runner blocks startup indefinitely, which is never correct.
## Registration
@@ -106,7 +96,7 @@ Runners with an empty name are skipped with a warning, so always give them a sta
## 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.
@@ -125,6 +115,7 @@ Runner functions return `error`.
When a runner returns a non-nil error:
- the bot logs a warning;
- an `ErrorEvent` is emitted through the observer;
- the process continues;
- 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
`RunWithContext(...)` and `RunWebHookWithContext(...)` wait for runner completion in two groups:
`RunWithContext(...)` and `RunWebhookWithContext(...)` wait for runner completion in two groups:
- one-time async runners;
- background repeating runners.
@@ -149,15 +140,15 @@ This means graceful shutdown includes runner shutdown, but only if your runner f
```go
cleanup := laniakea.NewRunner("cleanup", func(bot *laniakea.Bot[*App]) error {
return bot.GetAppData().CleanupExpired()
}).Timeout(5 * time.Minute)
}).Every(5 * time.Minute)
```
### Startup warmup
### Startup warmup (blocking)
```go
warmup := laniakea.NewRunner("warmup", func(bot *laniakea.Bot[*App]) error {
return bot.GetAppData().WarmCaches()
}).Onetime(true).Async(false)
}).Async(false)
```
### Background metrics push
@@ -165,25 +156,24 @@ warmup := laniakea.NewRunner("warmup", func(bot *laniakea.Bot[*App]) error {
```go
metrics := laniakea.NewRunner("metrics", func(bot *laniakea.Bot[*App]) error {
return pushMetrics(bot.GetAppData())
}).Timeout(30 * time.Second)
}).Every(30 * time.Second)
```
## Recommendations
- Use one-time sync runners only for short startup-critical work.
- 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.
- Treat runner names as operational identifiers that should make sense in logs.
## Caveats
- Runners do not receive `context.Context` directly; they receive `*Bot[T]`.
- Repeating sync runners are skipped.
- Repeating async runners without timeout are skipped.
- Periodic sync runners are skipped with a warning.
- Slow one-time sync runners delay bot startup.
## Related pages
- [[Bot-Lifecycle]]
- [[Testing-Bots-with-Laniakea]]
- [[Testing-Bots-with-Laniakea]]
+6 -6
@@ -1,11 +1,11 @@
# Scenes
Scenes — это слой маршрутизации Laniakea с сохранением состояния для многошаговых и модальных диалогов. Сцена регистрируется внутри плагина, запускается через `MsgContext`, хранится через `SessionStore` и получает обновления раньше обычной маршрутизации команд, пока её сессия активна.
Scenes — это слой маршрутизации Laniakea с сохранением состояния для многошаговых и модальных диалогов. Сцена регистрируется внутри плагина, запускается через `MessageContext`, хранится через `SessionStore` и получает обновления раньше обычной маршрутизации команд, пока её сессия активна.
## Что дают сцены
- Регистрацию через `Plugin.NewScene(...)` и `Plugin.AddScene(...)`.
- Явный вход и выход через `MsgContext.EnterScene(...)`, `EnterSceneStep(...)` и `ExitScene()`.
- Явный вход и выход через `MessageContext.EnterScene(...)`, `EnterSceneStep(...)` и `ExitScene()`.
- Области действия сессии на пользователя, чат или пару пользователь-чат.
- Обработчики шагов, локальные команды сцены и резервный обработчик сообщений на уровне сцены.
- JSON-состояние сцены через `SceneContext.BindData(...)` и `SaveData(...)`.
@@ -67,13 +67,13 @@ plugin.NewScene("signup").
## Модель обработчиков
Обычные команды используют `*MsgContext`. Обработчики сцен используют `*SceneContext`.
Обычные команды используют `*MessageContext`. Обработчики сцен используют `*SceneContext`.
```go
type SceneHandler[T any] func(ctx *SceneContext, db T) (SceneResult, error)
```
`SceneContext` встраивает `*MsgContext` и добавляет вспомогательные методы для сцен:
`SceneContext` встраивает `*MessageContext` и добавляет вспомогательные методы для сцен:
- `ctx.Stay()`
- `ctx.Next(step)`
@@ -137,7 +137,7 @@ func askName(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error)
Точка входа из команды:
```go
func startSignup(ctx *laniakea.MsgContext, db *App) error {
func startSignup(ctx *laniakea.MessageContext, db *App) error {
return ctx.EnterScene("signup")
}
```
@@ -174,6 +174,6 @@ func cancelSignup(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, er
Связанные страницы:
- [[Commands-and-Plugins-RU]]
- [[MsgContext-RU]]
- [[MessageContext-RU]]
- [[Middleware-RU]]
- [[Bot-Lifecycle-RU]]
+6 -6
@@ -1,11 +1,11 @@
# 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
- 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.
- Step handlers, scene-local commands, and a scene-level message fallback.
- JSON-backed scene state through `SceneContext.BindData(...)` and `SaveData(...)`.
@@ -67,13 +67,13 @@ plugin.NewScene("signup").
## Handler model
Normal commands use `*MsgContext`. Scene handlers use `*SceneContext`.
Normal commands use `*MessageContext`. Scene handlers use `*SceneContext`.
```go
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.Next(step)`
@@ -137,7 +137,7 @@ The store contract stays intentionally small because `Data []byte` is storage-ag
Command entry:
```go
func startSignup(ctx *laniakea.MsgContext, db *App) error {
func startSignup(ctx *laniakea.MessageContext, db *App) error {
return ctx.EnterScene("signup")
}
```
@@ -174,6 +174,6 @@ func cancelSignup(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, er
Related pages:
- [[Commands-and-Plugins]]
- [[MsgContext]]
- [[MessageContext]]
- [[Middleware]]
- [[Bot-Lifecycle]]
+3 -3
@@ -10,7 +10,7 @@ Laniakea хорошо тестируется обычными Go unit tests.
В репозитории уже используются паттерны вроде:
- fake HTTP transport для `tgapi`;
- прямые тесты для вспомогательных методов `MsgContext`;
- прямые тесты для вспомогательных методов `MessageContext`;
- routing tests для commands и payloads;
- runner tests.
@@ -35,7 +35,7 @@ Laniakea хорошо тестируется обычными Go unit tests.
## Тесты для логики обработчиков
Для тестов уровня обработчика обычно полезно:
- собрать `MsgContext`;
- собрать `MessageContext`;
- вызвать обработчик напрямую;
- проверить побочные эффекты и ответы.
@@ -63,5 +63,5 @@ Laniakea хорошо тестируется обычными Go unit tests.
## Что читать дальше
- [[Runners-RU]]
- [[MsgContext-RU]]
- [[MessageContext-RU]]
- [[Testing-Bots-with-Laniakea]]
+7 -7
@@ -21,15 +21,15 @@ Good test targets include:
The most practical approach is:
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;
4. assert the outgoing request shape or returned behavior directly.
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:
- a fake `tgapi.API` with a custom `http.Client`;
@@ -39,7 +39,7 @@ Common ingredients:
Example pattern:
```go
ctx := &laniakea.MsgContext{
ctx := &laniakea.MessageContext{
Api: api,
Msg: &tgapi.Message{Chat: &tgapi.Chat{ID: 42, Type: string(tgapi.ChatTypePrivate)}},
Logger: slog.CreateLogger(),
@@ -64,7 +64,7 @@ This is especially useful for:
- split long-message requests;
- 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
@@ -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:
- which handler was called;
- what `MsgContext` fields were populated;
- what `MessageContext` fields were populated;
- whether context mutations leaked across plugins.
This is particularly useful for:
@@ -184,4 +184,4 @@ When you fix a bug, consider adding a test for:
- [[Commands-and-Plugins]]
- [[Error-Handling]]
- [[Runners]]
- [[MsgContext]]
- [[MessageContext]]
+7 -7
@@ -2,9 +2,9 @@
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(...)`
До маршрутизации обработчиков Laniakea нормализует `MsgContext` из входящего Telegram update.
До маршрутизации обработчиков Laniakea нормализует `MessageContext` из входящего Telegram update.
Эта нормализация намеренно шире, чем command и payload routing:
- некоторые update types заполняют `ctx.Msg`
@@ -101,12 +101,12 @@ Generic update handlers регистрируются через `Plugin.AddUpdat
- `ctx.Text` не нормализуется
- `ctx.Args` не нормализуется
- `ctx.Prefix` не нормализуется
- каждый plugin update handler получает свою копию `MsgContext` struct
- каждый plugin update handler получает свою копию `MessageContext` struct
Важное ограничение:
- копируется сам `MsgContext`, но не обещается глубокая копия всех вложенных Telegram-структур
- копируется сам `MessageContext`, но не обещается глубокая копия всех вложенных Telegram-структур
## Нормализованные поля `MsgContext` по видам update
## Нормализованные поля `MessageContext` по видам update
### Update types, которые несут message
@@ -176,6 +176,6 @@ Generic update handlers регистрируются через `Plugin.AddUpdat
## Связанные страницы
- [[Commands-and-Plugins-RU]]
- [[MsgContext-RU]]
- [[MessageContext-RU]]
- [[Scenes-RU]]
- [[Bot-Lifecycle-RU]]
+6 -6
@@ -2,9 +2,9 @@
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
@@ -23,7 +23,7 @@ Important:
## 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:
- some update kinds populate `ctx.Msg`
@@ -101,12 +101,12 @@ General guarantees:
- `ctx.Text` is not normalized
- `ctx.Args` 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:
- 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
@@ -176,6 +176,6 @@ Important:
## Related pages
- [[Commands-and-Plugins]]
- [[MsgContext]]
- [[MessageContext]]
- [[Scenes]]
- [[Bot-Lifecycle]]
+14 -14
@@ -2,10 +2,10 @@
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:
- текущий публичный API использует историческое написание `WebHook` в идентификаторах вроде `RunWebHookWithContext(...)`, `RunWebHook(...)` и `BotWebHookOpts`;
- текущий публичный API использует историческое написание `WebHook` в идентификаторах вроде `RunWebhookWithContext(...)`, `RunWebhook(...)` и `BotWebhookOpts`;
- в тексте страницы используется обычное слово "webhook", но в примерах остаются реальные имена Go API.
## Когда использовать webhook runtime
@@ -25,11 +25,11 @@ English version: [[Webhook-Runtime]]
## Точки входа
Главные bot-level точки входа такие:
- `RunWebHookWithContext(ctx, opts, tlsFiles...)`
- `RunWebHook(opts, tlsFiles...)`
- `NewBotWebHookOpts()`
- `RunWebhookWithContext(ctx, opts, tlsFiles...)`
- `RunWebhook(opts, tlsFiles...)`
- `NewBotWebhookOpts()`
`RunWebHook(...)` — это просто короткая форма для `RunWebHookWithContext(context.Background(), ...)`.
`RunWebhook(...)` — это просто короткая форма для `RunWebhookWithContext(context.Background(), ...)`.
Обычный шаблон выглядит так:
@@ -46,20 +46,20 @@ defer bot.Close()
bot.SetAppData(app)
bot.AddPlugins(plugin)
webhookOpts := laniakea.NewBotWebHookOpts().
webhookOpts := laniakea.NewBotWebhookOpts().
SetURL("https://bot.example.com").
SetPath("/telegram").
SetLocalPort(8080).
SetSecretToken("shared-secret")
if err := bot.RunWebHookWithContext(ctx, webhookOpts); err != nil {
if err := bot.RunWebhookWithContext(ctx, webhookOpts); err != nil {
return err
}
```
## Что именно берет на себя bot-level runtime
`RunWebHookWithContext(...)` — это не просто обертка над Telegram `setWebhook`.
`RunWebhookWithContext(...)` — это не просто обертка над Telegram `setWebhook`.
Он:
- валидирует bot-level условия старта, например prefixes и наличие зарегистрированных plugins;
@@ -86,7 +86,7 @@ Webhook runtime использует те же основные гарантии
## Основные webhook options
`BotWebHookOpts` управляет и регистрацией webhook у Telegram, и локальным HTTP server.
`BotWebhookOpts` управляет и регистрацией webhook у Telegram, и локальным HTTP server.
Поля, которые важны в первую очередь:
@@ -167,12 +167,12 @@ Webhook runtime использует те же основные гарантии
## Поведение HTTP и TLS
По умолчанию `RunWebHookWithContext(...)` поднимает обычный HTTP server на `LocalPort`.
По умолчанию `RunWebhookWithContext(...)` поднимает обычный HTTP server на `LocalPort`.
Если передать два TLS-файла, локально стартует HTTPS.
Важно:
- текущий публичный API ожидает существующий порядок аргументов `key, cert` при вызове `RunWebHookWithContext(...)`;
- текущий публичный API ожидает существующий порядок аргументов `key, cert` при вызове `RunWebhookWithContext(...)`;
- это отличается от более привычной ментальной модели `cert, key`, которую многие Go-разработчики ожидают от `ListenAndServeTLS`.
Поэтому в реальном setup лучше писать этот вызов максимально явно.
@@ -202,7 +202,7 @@ Webhook runtime использует те же основные гарантии
- `URL` — это то, что видит Telegram;
- `Path` и `LocalPort` — это то, что реально обслуживает твой бот;
- в production эти значения часто относятся к разным слоям инфраструктуры.
- если ты переводишь работающий deployment с webhook-режима на polling, сначала удали webhook через `CloseWebHook()` или `tgapi.DeleteWebhook(...)`; Telegram не прекращает webhook-доставку автоматически.
- если ты переводишь работающий deployment с webhook-режима на polling, сначала удали webhook через `CloseWebhook()` или `tgapi.DeleteWebhook(...)`; Telegram не прекращает webhook-доставку автоматически.
## Связь с webhook methods в `tgapi`
@@ -221,7 +221,7 @@ Webhook runtime использует те же основные гарантии
когда тебе нужна собственная инфраструктура вокруг webhook path и ты не хочешь, чтобы сам бот владел HTTP server.
То есть:
- `RunWebHookWithContext(...)` — это framework runtime API;
- `RunWebhookWithContext(...)` — это framework runtime API;
- webhook methods из `tgapi` — это низкоуровневые transport primitives.
## Частые ошибки
+14 -14
@@ -2,10 +2,10 @@
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:
- 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.
## When to use webhook runtime
@@ -25,11 +25,11 @@ Use polling when:
## Entry points
The main bot-level entry points are:
- `RunWebHookWithContext(ctx, opts, tlsFiles...)`
- `RunWebHook(opts, tlsFiles...)`
- `NewBotWebHookOpts()`
- `RunWebhookWithContext(ctx, opts, tlsFiles...)`
- `RunWebhook(opts, tlsFiles...)`
- `NewBotWebhookOpts()`
`RunWebHook(...)` is only a shorthand for `RunWebHookWithContext(context.Background(), ...)`.
`RunWebhook(...)` is only a shorthand for `RunWebhookWithContext(context.Background(), ...)`.
The usual pattern looks like:
@@ -46,20 +46,20 @@ defer bot.Close()
bot.SetAppData(app)
bot.AddPlugins(plugin)
webhookOpts := laniakea.NewBotWebHookOpts().
webhookOpts := laniakea.NewBotWebhookOpts().
SetURL("https://bot.example.com").
SetPath("/telegram").
SetLocalPort(8080).
SetSecretToken("shared-secret")
if err := bot.RunWebHookWithContext(ctx, webhookOpts); err != nil {
if err := bot.RunWebhookWithContext(ctx, webhookOpts); err != nil {
return err
}
```
## What the bot-level runtime owns
`RunWebHookWithContext(...)` is more than a wrapper around Telegram's `setWebhook`.
`RunWebhookWithContext(...)` is more than a wrapper around Telegram's `setWebhook`.
It:
- 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
`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:
@@ -167,12 +167,12 @@ Use this only when you specifically need Telegram's `ip_address` webhook option.
## HTTP and TLS behavior
By default, `RunWebHookWithContext(...)` starts a plain HTTP server on `LocalPort`.
By default, `RunWebhookWithContext(...)` starts a plain HTTP server on `LocalPort`.
If you pass two TLS files, it starts HTTPS locally instead.
Important:
- the current public API expects the existing key-then-cert argument order when calling `RunWebHookWithContext(...)`;
- the current public API expects the existing key-then-cert argument order when calling `RunWebhookWithContext(...)`;
- that differs from the more common `cert, key` mental model many Go developers expect from `ListenAndServeTLS`.
Be explicit in your own setup code so this does not become a deployment footgun.
@@ -202,7 +202,7 @@ Also keep in mind:
- `URL` is what Telegram sees;
- `Path` and `LocalPort` are what your bot actually serves;
- these are often not the same thing in production.
- if you switch a running deployment from webhook mode to polling, delete the webhook first with `CloseWebHook()` or `tgapi.DeleteWebhook(...)`; Telegram does not stop webhook delivery automatically.
- 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
@@ -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.
In other words:
- `RunWebHookWithContext(...)` is the framework runtime API;
- `RunWebhookWithContext(...)` is the framework runtime API;
- `tgapi` webhook methods are the lower-level transport primitives.
## Common mistakes
+1 -1
@@ -6,7 +6,7 @@
- [[Getting-Started]]
- [[Bot-Options-and-Configuration]]
- [[Commands-and-Plugins]]
- [[MsgContext]]
- [[MessageContext]]
## Runtime and Architecture
- [[Bot-Lifecycle]]
+4 -4
@@ -21,14 +21,14 @@ English version: [[tgapi-Overview]]
Это разделение специально сделано, чтобы JSON methods и file upload methods не смешивались в одну слишком размытую abstraction.
## Когда использовать `MsgContext`, а когда `tgapi`
## Когда использовать `MessageContext`, а когда `tgapi`
Используй `MsgContext`, когда:
Используй `MessageContext`, когда:
- ты уже внутри handler'а;
- нужен обычный поток reply/edit/delete/callback.
Используй `tgapi`, когда:
- у `MsgContext` нет нужного вспомогательного метода;
- у `MessageContext` нет нужного вспомогательного метода;
- ты работаешь вне потока обработчика;
- нужен более низкоуровневый контроль;
- нужно работать с uploads/downloads напрямую.
@@ -100,7 +100,7 @@ _, err := uploader.SendPhoto(tgapi.UploadPhoto{
## Что читать дальше
- [[MsgContext-RU]]
- [[MessageContext-RU]]
- [[Inline-Keyboards-and-Payloads-RU]]
- [[Rate-Limiting-RU]]
- [[tgapi-Overview]]
+6 -6
@@ -2,7 +2,7 @@
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
@@ -20,7 +20,7 @@ If you stay on the typed method surface, you usually do not need to think about
## The normal layering
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;
- low-level Telegram access in `tgapi`.
@@ -29,7 +29,7 @@ In practice, Laniakea has three levels:
## When to use `tgapi` directly
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 want explicit control over params, parse modes, message edits, or uploads;
- 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;
- file downloads and streaming;
- 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
@@ -244,7 +244,7 @@ Related page:
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 `tgapi` when you need direct Telegram method control;
- 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
- [[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.
- [[Bot-Lifecycle]] for runtime startup and shutdown responsibilities around `Bot`, `API`, and `Uploader`.
- [[Rate-Limiting]] for limiter and `retry_after` behavior.