REPOSITORY / ScuroNeko/Laniakea

Wiki

KNOWLEDGE REPOSITORY

(doc): merge conflict resolution + restore v1.0.0 backlog checklist

- accept remote (beee616) for all API rename conflicts
- restore [1.0.0] pre-release checklist (M1-M11, minor items, tests) to both Framework-Backlog pages
2026-05-20 13:28:29 +03:00
38 changed files with 471 additions and 350 deletions
+3 -3
@@ -43,7 +43,7 @@ The normal pattern is to finish all structural configuration before starting the
- `SetDraftProvider(...)`, `SetSessionStore(...)`, and `SetSceneScopePriority(...)` replace runtime helpers. - `SetDraftProvider(...)`, `SetSessionStore(...)`, and `SetSceneScopePriority(...)` replace runtime helpers.
- `SetErrorTemplate(...)` adjusts centralized user-facing error text. - `SetErrorTemplate(...)` adjusts centralized user-facing error text.
For an overview of handlers and plugins, see [[Commands-and-Plugins]]. For context helpers available inside handlers, see [[MsgContext]]. For an overview of handlers and plugins, see [[Commands-and-Plugins]]. For context helpers available inside handlers, see [[MessageContext]].
## `AddPlugins(...)` is a configuration commit point ## `AddPlugins(...)` is a configuration commit point
@@ -97,10 +97,10 @@ if err != nil {
defer bot.Close() defer bot.Close()
plugin := laniakea.NewPlugin[laniakea.NoData]("main") plugin := laniakea.NewPlugin[laniakea.NoData]("main")
plugin.Command("ping", func(ctx *laniakea.MsgContext, db laniakea.NoData) error { plugin.NewCommand(func(ctx *laniakea.MessageContext, db laniakea.NoData) error {
ctx.Answer("pong") ctx.Answer("pong")
return nil return nil
}) }, "ping")
bot.AddPlugins(plugin) bot.AddPlugins(plugin)
+7 -7
@@ -47,11 +47,11 @@ plugin := laniakea.NewPlugin[laniakea.NoData]("admin")
Сигнатура обработчика команды такая: Сигнатура обработчика команды такая:
```go ```go
func(ctx *laniakea.MsgContext, db T) error func(ctx *laniakea.MessageContext, db T) error
``` ```
Где: Где:
- `ctx` — текущий `MsgContext`; - `ctx` — текущий `MessageContext`;
- `db` — значение generic-параметра `T`, которое ты передал в `Bot`. - `db` — значение generic-параметра `T`, которое ты передал в `Bot`.
Возвращай: Возвращай:
@@ -61,7 +61,7 @@ func(ctx *laniakea.MsgContext, db T) error
Пример: Пример:
```go ```go
func start(ctx *laniakea.MsgContext, db *App) error { func start(ctx *laniakea.MessageContext, db *App) error {
ctx.Answer("Welcome") ctx.Answer("Welcome")
return nil return nil
} }
@@ -125,7 +125,7 @@ plugin.Command(
Пример: Пример:
```go ```go
func confirmDelete(ctx *laniakea.MsgContext, db *App) error { func confirmDelete(ctx *laniakea.MessageContext, db *App) error {
ctx.EditCallback("Deleted", nil) ctx.EditCallback("Deleted", nil)
return nil return nil
} }
@@ -147,7 +147,7 @@ plugin.Payload("delete.confirm", confirmDelete)
Пример: Пример:
```go ```go
plugin.AddUpdateHandler(tgapi.UpdateTypeInlineQuery, func(ctx *laniakea.MsgContext, db *App) error { plugin.AddUpdateHandler(tgapi.UpdateTypeInlineQuery, func(ctx *laniakea.MessageContext, db *App) error {
return nil return nil
}) })
``` ```
@@ -170,7 +170,7 @@ plugin.AddUpdateHandler(tgapi.UpdateTypeInlineQuery, func(ctx *laniakea.MsgConte
Для текстовой команды поток примерно такой: Для текстовой команды поток примерно такой:
1. Приходит Telegram update. 1. Приходит Telegram update.
2. `Bot` готовит `MsgContext`. 2. `Bot` готовит `MessageContext`.
3. Выполняется middleware бота. 3. Выполняется middleware бота.
4. Находится подходящий плагин. 4. Находится подходящий плагин.
5. Выполняется middleware плагина. 5. Выполняется middleware плагина.
@@ -258,5 +258,5 @@ return nil
- [[Getting-Started-RU]] - [[Getting-Started-RU]]
- [[Bot-Lifecycle-RU]] - [[Bot-Lifecycle-RU]]
- [[Commands-and-Plugins]] - [[Commands-and-Plugins]]
- [[MsgContext]] - [[MessageContext]]
- [[Inline-Keyboards-and-Payloads]] - [[Inline-Keyboards-and-Payloads]]
+27 -27
@@ -49,7 +49,7 @@ That keeps command registration and middleware ownership clear.
The command handler signature is: The command handler signature is:
```go ```go
func(ctx *laniakea.MsgContext, db T) error func(ctx *laniakea.MessageContext, db T) error
``` ```
Where: Where:
@@ -63,7 +63,7 @@ Return:
Example: Example:
```go ```go
func start(ctx *laniakea.MsgContext, db *App) error { func start(ctx *laniakea.MessageContext, db *App) error {
ctx.Answer("Welcome") ctx.Answer("Welcome")
return nil return nil
} }
@@ -71,11 +71,11 @@ func start(ctx *laniakea.MsgContext, db *App) error {
## Registering commands ## Registering commands
Create and register a command with `Plugin.Command(...)`: Create a command with `NewCommand(...)` and add it to a plugin:
```go ```go
plugin := laniakea.NewPlugin[*App]("main") plugin := laniakea.NewPlugin[*App]("main")
plugin.Command("start", start) plugin.AddCommand(plugin.NewCommand(start, "start"))
``` ```
The command name: The command name:
@@ -89,7 +89,7 @@ So:
## The easiest command example ## The easiest command example
```go ```go
func echo(ctx *laniakea.MsgContext, db laniakea.NoData) error { func echo(ctx *laniakea.MessageContext, db laniakea.NoData) error {
if ctx.Text == "" { if ctx.Text == "" {
ctx.Answer("Usage: /echo <text>") ctx.Answer("Usage: /echo <text>")
return nil return nil
@@ -99,7 +99,7 @@ func echo(ctx *laniakea.MsgContext, db laniakea.NoData) error {
return nil return nil
} }
plugin.Command("echo", echo) plugin.AddCommand(plugin.NewCommand(echo, "echo"))
``` ```
For `/echo hello world`: For `/echo hello world`:
@@ -113,12 +113,12 @@ You can declare command arguments using `CommandArg`.
Example: Example:
```go ```go
plugin.Command( plugin.AddCommand(
"ban", plugin.NewCommand(banUser, "ban",
banUser, laniakea.NewCommandArg("user_id").
laniakea.NewCommandArg("user_id"). SetValueType(laniakea.CommandValueIntType).
SetValueType(laniakea.CommandValueInt). SetRequired(),
SetRequired(), ),
) )
``` ```
@@ -133,15 +133,15 @@ If validation fails, the command does not run and the bot error path is used.
Payload handlers are for callback data coming from inline keyboard buttons. Payload handlers are for callback data coming from inline keyboard buttons.
Register them with `Plugin.Payload(...)` or `AddPayload(...)`: Register them with `NewPayload(...)` or `AddPayload(...)`:
```go ```go
func confirmDelete(ctx *laniakea.MsgContext, db *App) error { func confirmDelete(ctx *laniakea.MessageContext, db *App) error {
ctx.EditCallback("Deleted", nil) ctx.EditCallback("Deleted", nil)
return nil return nil
} }
plugin.Payload("delete.confirm", confirmDelete) plugin.AddPayload(plugin.NewPayload(confirmDelete, "delete.confirm"))
``` ```
Payload handlers: Payload handlers:
@@ -158,7 +158,7 @@ Update handlers are for Telegram updates outside the normal command/payload flow
Register them with `AddUpdateHandler(...)`: Register them with `AddUpdateHandler(...)`:
```go ```go
plugin.AddUpdateHandler(tgapi.UpdateTypeInlineQuery, func(ctx *laniakea.MsgContext, db *App) error { plugin.AddUpdateHandler(tgapi.UpdateTypeInlineQuery, func(ctx *laniakea.MessageContext, db *App) error {
// handle inline query here // handle inline query here
return nil return nil
}) })
@@ -183,7 +183,7 @@ stay on the command/payload flow and are not meant to be registered through `Add
For text commands: For text commands:
1. Telegram update arrives 1. Telegram update arrives
2. bot prepares `MsgContext` 2. bot prepares `MessageContext`
3. bot middleware runs 3. bot middleware runs
4. matching plugin is found 4. matching plugin is found
5. plugin middleware runs 5. plugin middleware runs
@@ -213,7 +213,7 @@ Use this for logic shared by most handlers in the plugin.
Added with: Added with:
```go ```go
plugin.Command("name", handler).Use(middleware) plugin.NewCommand(handler, "name").Use(middleware)
``` ```
Use this when only one command or payload needs the check. Use this when only one command or payload needs the check.
@@ -235,7 +235,7 @@ Avoid one giant plugin for the entire bot unless the bot is very small.
```go ```go
admin := laniakea.NewPlugin[*App]("admin") admin := laniakea.NewPlugin[*App]("admin")
admin.AddMiddleware(laniakea.NewMiddleware("admin-only", func(ctx *laniakea.MsgContext, app *App) bool { admin.AddMiddleware(laniakea.NewMiddleware("admin-only", func(ctx *laniakea.MessageContext, app *App) bool {
if !app.IsAdmin(ctx.FromID) { if !app.IsAdmin(ctx.FromID) {
ctx.Answer("Access denied") ctx.Answer("Access denied")
return false return false
@@ -243,20 +243,20 @@ admin.AddMiddleware(laniakea.NewMiddleware("admin-only", func(ctx *laniakea.MsgC
return true return true
})) }))
admin.Command("ban", func(ctx *laniakea.MsgContext, app *App) error { admin.AddCommand(admin.NewCommand(func(ctx *laniakea.MessageContext, app *App) error {
ctx.Answer("Banned") ctx.Answer("Banned")
return nil return nil
}) }, "ban"))
``` ```
### Example: payload handler for inline keyboard callback ### Example: payload handler for inline keyboard callback
```go ```go
plugin.Payload("approve", func(ctx *laniakea.MsgContext, app *App) error { plugin.AddPayload(plugin.NewPayload(func(ctx *laniakea.MessageContext, app *App) error {
ctx.AnswerCallbackText("Accepted") ctx.AnswerCbQueryText("Accepted")
ctx.EditCallback("Done", nil) ctx.EditCallback("Done", nil)
return nil return nil
}) }, "approve"))
``` ```
## Common mistakes ## Common mistakes
@@ -266,13 +266,13 @@ plugin.Payload("approve", func(ctx *laniakea.MsgContext, app *App) error {
Wrong: Wrong:
```go ```go
plugin.Command("/start", start) plugin.NewCommand(start, "/start")
``` ```
Right: Right:
```go ```go
plugin.Command("start", start) plugin.NewCommand(start, "start")
``` ```
### Treating payloads like commands ### Treating payloads like commands
@@ -304,6 +304,6 @@ Use:
- command middleware for narrow, local checks - command middleware for narrow, local checks
## Where to go next ## Where to go next
- Read [[MsgContext]] next to understand what handlers can do once they are triggered. - Read [[MessageContext]] next to understand what handlers can do once they are triggered.
- Read [[Inline-Keyboards-and-Payloads]] if you are starting to use buttons and callback data. - Read [[Inline-Keyboards-and-Payloads]] if you are starting to use buttons and callback data.
- Read [[Middleware]] for execution-order and async details. - Read [[Middleware]] for execution-order and async details.
+1 -1
@@ -75,6 +75,6 @@ draft.Flush()
## Что читать дальше ## Что читать дальше
- [[MsgContext-RU]] - [[MessageContext-RU]]
- [[Bot-Lifecycle-RU]] - [[Bot-Lifecycle-RU]]
- [[Drafts]] - [[Drafts]]
+4 -4
@@ -23,9 +23,9 @@ The draft system has two layers:
The provider is safe for concurrent use. Individual drafts are intended for single-goroutine use unless you add your own synchronization. The provider is safe for concurrent use. Individual drafts are intended for single-goroutine use unless you add your own synchronization.
## The easiest entry point: `MsgContext.NewDraft()` ## The easiest entry point: `MessageContext.NewDraft()`
Inside a handler, the usual entry point is `MsgContext.NewDraft()` or `MsgContext.NewDraftMarkdown()`. Inside a handler, the usual entry point is `MessageContext.NewDraft()` or `MessageContext.NewDraftMarkdown()`.
Those helpers: Those helpers:
- create a draft from the bot's configured `DraftProvider`; - create a draft from the bot's configured `DraftProvider`;
@@ -35,7 +35,7 @@ Those helpers:
Typical usage: Typical usage:
```go ```go
func report(ctx *laniakea.MsgContext, db *App) error { func report(ctx *laniakea.MessageContext, db *App) error {
draft := ctx.NewDraft() draft := ctx.NewDraft()
if draft == nil { if draft == nil {
return nil return nil
@@ -174,5 +174,5 @@ Prefer direct reply helpers such as `Answer(...)` or `AnswerLong(...)` when:
## Related pages ## Related pages
- [[MsgContext]] for handler-scoped reply and draft helpers - [[MessageContext]] for handler-scoped reply and draft helpers
- [[Bot-Lifecycle]] for draft-provider attachment through the bot - [[Bot-Lifecycle]] for draft-provider attachment through the bot
+1 -1
@@ -25,7 +25,7 @@ English version: [[Error-Handling]]
## Как это выглядит ## Как это выглядит
```go ```go
func ping(ctx *laniakea.MsgContext, db *App) error { func ping(ctx *laniakea.MessageContext, db *App) error {
ctx.Answer("pong") ctx.Answer("pong")
return nil return nil
} }
+12 -12
@@ -16,7 +16,7 @@ That applies to:
The basic pattern is: The basic pattern is:
```go ```go
func ping(ctx *laniakea.MsgContext, db *App) error { func ping(ctx *laniakea.MessageContext, db *App) error {
ctx.Answer("pong") ctx.Answer("pong")
return nil return nil
} }
@@ -133,10 +133,10 @@ This matters because callback error UX is different:
- no new chat message is posted for the error path. - no new chat message is posted for the error path.
If you want a different callback UX, answer manually with: If you want a different callback UX, answer manually with:
- `AnswerCallback()` - `AnswerCbQuery()`
- `AnswerCallbackText(...)` - `AnswerCbQueryText(...)`
- `AnswerCallbackAlert(...)` - `AnswerCbQueryAlert(...)`
- `AnswerCallbackURL(...)` - `AnswerCbQueryUrl(...)`
and return `nil`. and return `nil`.
@@ -183,38 +183,38 @@ Related page:
### Centralized command failure ### Centralized command failure
```go ```go
plugin.Command("report", func(ctx *laniakea.MsgContext, db *App) error { plugin.NewCommand(func(ctx *laniakea.MessageContext, db *App) error {
result, err := db.DoWork() result, err := db.DoWork()
if err != nil { if err != nil {
return fmt.Errorf("failed to build report: %w", err) return fmt.Errorf("failed to build report: %w", err)
} }
ctx.Answer(result) ctx.Answer(result)
return nil return nil
}) }, "report")
``` ```
### Manual denial response ### Manual denial response
```go ```go
plugin.Command("admin", func(ctx *laniakea.MsgContext, db *App) error { plugin.NewCommand(func(ctx *laniakea.MessageContext, db *App) error {
if ctx.From == nil || !db.Allowed(ctx.From.ID) { if ctx.From == nil || !db.Allowed(ctx.From.ID) {
ctx.Answer("Access denied") ctx.Answer("Access denied")
return nil return nil
} }
return doProtectedWork(ctx, db) return doProtectedWork(ctx, db)
}) }, "admin")
``` ```
### Callback-specific manual alert ### Callback-specific manual alert
```go ```go
plugin.Payload("start", func(ctx *laniakea.MsgContext, db *App) error { plugin.NewPayload(func(ctx *laniakea.MessageContext, db *App) error {
if !ready { if !ready {
ctx.AnswerCallbackAlert("This action is not available yet") ctx.AnswerCbQueryAlert("This action is not available yet")
return nil return nil
} }
return nil return nil
}) }, "start")
``` ```
## Recommendations ## Recommendations
+56 -17
@@ -4,6 +4,45 @@
## Done ## Done
### [1.0.0]
#### Major — закрыто до тега 1.0.0
- [X] **M1. `BotPayloadType*` были `var`, должны быть `const`.**
- [X] **M2. Асимметрия именования методов `Observer`**`OnReceiveUpdate``OnUpdateReceived`; `OnHandledUpdate``OnUpdateHandled`.
- [X] **M3. Uploader возвращал ad-hoc строку ошибки вместо `*ResponseError`.**
- [X] **M4. `BotOptsFileJSON` не сохранял `PollTimeout`** — поле терялось при round-trip.
- [X] **M5. Устаревший godoc `Bot.Updates`** — утверждал "30-second timeout" и "empty slice if none".
- [X] **M6. Противоречивый godoc `NewRandomDraftProvider`** — говорил "cryptographically secure", но использовал `math/rand/v2`.
- [X] **M7. Godoc `Draft.Delete` говорил "internal method"** — метод экспортирован.
- [X] **M8. Русские комментарии в production-коде**`msg_handler.go`, `tgapi/uploader_api.go`.
- [X] **M9. Godoc `MessageContext.Error` ссылался на неэкспортированный хелпер.**
- [X] **M10. `Scene` и `SceneSession` смешивали экспортированные поля с setter-ами**`Scene.PluginName` закрыт, `SceneSession.Data` закрыт.
- [X] **M11. Constant-time compare для webhook secret** — использован `subtle.ConstantTimeCompare`.
#### Minor — закрыто до тега 1.0.0
- [X] Убраны godoc-комментарии с неэкспортированных функций.
- [X] Godoc `Plugin.AddCommand` ссылался на `.command`.
- [X] Builder раннеров: `Onetime`/`Timeout``Every`/`Async`; `Once()` удалён.
- [X] Опечатка в webhook: "must between" → "must be between".
- [X] Inline `errors.New(...)` в webhook → `Err*` sentinel-ы.
- [X] Добавлен godoc для `tgapi.UpdateTypeManagedBot`, `Bot.GetAPI`, `Bot.GetUploader`, `InlineKeyboard.GetMaxRow`.
- [X] Godoc `Bot.L10n` исправлен — возвращает ключ, а не пустую строку.
- [X] Panic в `Bot.handle` теперь эмитирует `ErrorEvent` через observer.
- [X] Выравнена логика plugin-logger в `handleCallback` и `handleMessage`.
- [X] Godoc `SetCallbackData` уточняет поведение zero `BotPayloadType`.
- [X] `commands.go` пустой `case CommandValueAny:` объединён с `default`.
- [X] `Bot.SetDebug` не вызывает `configMutable` — задокументировано в godoc.
#### Тесты, добавленные после правок
- [X] Round-trip `BotOptsFileJSON` для `PollTimeout`.
- [X] Uploader 4xx/429 возвращает `*tgapi.ResponseError`.
- [X] Panic в `Bot.handle` → observer получает `ErrorEvent`.
- [X] Webhook `/status` с неверным `SecretToken` возвращает 404, smoke-тест constant-time compare.
- [X] Table-driven тесты `parseCommand` для `/cmd@botname`.
### [1.0.0-rc.14] Модель выполнения webhook ### [1.0.0-rc.14] Модель выполнения webhook
Текущее состояние: Текущее состояние:
@@ -65,7 +104,7 @@
Текущее состояние: Текущее состояние:
- Во фреймворке теперь есть `Policy[T]` как явная переиспользуемая модель правила доступа, работающая поверх нормализованного `MsgContext` и общих данных приложения. - Во фреймворке теперь есть `Policy[T]` как явная переиспользуемая модель правила доступа, работающая поверх нормализованного `MessageContext` и общих данных приложения.
- Политики интегрируются в уже существующую модель выполнения через `RequirePolicy(...)`, поэтому авторизация остаётся на middleware-пути и не создаёт второй pipeline маршрутизации. - Политики интегрируются в уже существующую модель выполнения через `RequirePolicy(...)`, поэтому авторизация остаётся на middleware-пути и не создаёт второй pipeline маршрутизации.
- У бота и плагинов появились явные helpers для регистрации политик на уровне конфигурации. - У бота и плагинов появились явные helpers для регистрации политик на уровне конфигурации.
@@ -82,7 +121,7 @@
- `Bot.UsePolicy(...)` и `Plugin.UsePolicy(...)` для удобной регистрации. - `Bot.UsePolicy(...)` и `Plugin.UsePolicy(...)` для удобной регистрации.
- Встроенные Telegram-aware helpers: `RequirePrivateChat(...)`, `RequireGroupChat(...)`, `RequireSupergroupChat(...)`, `RequireChatAdmin(...)`, `RequireChatCreator(...)`, `RequireBotAdmin(...)` и `RequireCallbackFromUser(...)`. - Встроенные Telegram-aware helpers: `RequirePrivateChat(...)`, `RequireGroupChat(...)`, `RequireSupergroupChat(...)`, `RequireChatAdmin(...)`, `RequireChatCreator(...)`, `RequireBotAdmin(...)` и `RequireCallbackFromUser(...)`.
- Комбинаторы `AllPolicies(...)`, `AnyPolicy(...)` и `NotPolicy(...)`. - Комбинаторы `AllPolicies(...)`, `AnyPolicy(...)` и `NotPolicy(...)`.
- Расширенная нормализация `MsgContext` для `Chat` и `ChatID`, а также регрессионные тесты на поведение политик и нормализованного контекста. - Расширенная нормализация `MessageContext` для `Chat` и `ChatID`, а также регрессионные тесты на поведение политик и нормализованного контекста.
Практическая цель: Практическая цель:
@@ -106,7 +145,7 @@
- `AsUserError(...)` и `AsInternalError(...)` для явной классификации возвращаемых ошибок. - `AsUserError(...)` и `AsInternalError(...)` для явной классификации возвращаемых ошибок.
- `IsUserError(...)` и `IsInternalError(...)` для проверки этой классификации на стороне фреймворка. - `IsUserError(...)` и `IsInternalError(...)` для проверки этой классификации на стороне фреймворка.
- Обновлённое поведение `MsgContext.Error(...)`: все ошибки по-прежнему логируются, но для внутренних ошибок автоматический ответ пользователю подавляется. - Обновлённое поведение `MessageContext.Error(...)`: все ошибки по-прежнему логируются, но для внутренних ошибок автоматический ответ пользователю подавляется.
- Регрессионные тесты для message и callback потоков. - Регрессионные тесты для message и callback потоков.
Практическая цель: Практическая цель:
@@ -141,30 +180,30 @@
Текущее состояние: Текущее состояние:
- Нормализация обновлений уже существовала, но теперь она описана и протестирована как явный контракт уровня фреймворка. - Нормализация обновлений уже существовала, но теперь она описана и протестирована как явный контракт уровня фреймворка.
- Категории маршрутизации и гарантии заполнения `MsgContext` теперь рассматриваются как полноценная часть публичной модели. - Категории маршрутизации и гарантии заполнения `MessageContext` теперь рассматриваются как полноценная часть публичной модели.
Почему это важно: Почему это важно:
- Код обработчиков должен понимать, на какие поля `MsgContext` можно безопасно опираться в каждом потоке обновлений. - Код обработчиков должен понимать, на какие поля `MessageContext` можно безопасно опираться в каждом потоке обновлений.
- Без явного контракта обработка обновлений остаётся понятной только через чтение реализации. - Без явного контракта обработка обновлений остаётся понятной только через чтение реализации.
Что теперь есть: Что теперь есть:
- Задокументированная модель маршрутизации для command flow, payload flow и generic update handlers. - Задокументированная модель маршрутизации для command flow, payload flow и generic update handlers.
- Явные комментарии на полях `MsgContext`, описывающие гарантии для update-backed, callback-backed и message-backed контекстов. - Явные комментарии на полях `MessageContext`, описывающие гарантии для update-backed, callback-backed и message-backed контекстов.
- Table-driven регрессионные тесты для нормализованного update contract, включая callback target semantics и non-command update flows. - Table-driven регрессионные тесты для нормализованного update contract, включая callback target semantics и non-command update flows.
Практическая цель: Практическая цель:
- Сделать маршрутизацию обновлений и гарантии `MsgContext` достаточно явными, чтобы на них можно было опираться как на стабильный контракт `1.0`. - Сделать маршрутизацию обновлений и гарантии `MessageContext` достаточно явными, чтобы на них можно было опираться как на стабильный контракт `1.0`.
### [1.0.0-rc.12] Conversation / Scene Model ### [1.0.0-rc.12] Conversation / Scene Model
Текущее состояние: Текущее состояние:
- Фреймворк хорошо обрабатывает одно обновление через команды, данные callback, middleware и обработчики обновлений. - Фреймворк хорошо обрабатывает одно обновление через команды, данные callback, middleware и обработчики обновлений.
- В нём уже есть полезные низкоуровневые строительные блоки: `MsgContext`, черновики, маршрутизация данных callback, плагины и обработчики обновлений. - В нём уже есть полезные низкоуровневые строительные блоки: `MessageContext`, черновики, маршрутизация данных callback, плагины и обработчики обновлений.
- Теперь в нём уже есть реализованная начальная модель сцен для долгоживущих интерактивных сценариев: сцены можно регистрировать в плагинах, запускать через `MsgContext`, сохранять через `SessionStore` и маршрутизировать раньше обычной обработки команд. - Теперь в нём уже есть реализованная начальная модель сцен для долгоживущих интерактивных сценариев: сцены можно регистрировать в плагинах, запускать через `MessageContext`, сохранять через `SessionStore` и маршрутизировать раньше обычной обработки команд.
Почему это важно: Почему это важно:
@@ -176,7 +215,7 @@
- Маршрутизация активной сцены раньше обычной маршрутизации команд. - Маршрутизация активной сцены раньше обычной маршрутизации команд.
- Области действия сессии на пользователя, чат и пару пользователь-чат. - Области действия сессии на пользователя, чат и пару пользователь-чат.
- Явный вход и выход через `MsgContext`. - Явный вход и выход через `MessageContext`.
- Обработчики шагов, локальные команды сцены и `OnMessage(...)`. - Обработчики шагов, локальные команды сцены и `OnMessage(...)`.
- Встроенное in-memory-хранилище по умолчанию и интерфейс `SessionStore` для собственного постоянного хранения. - Встроенное in-memory-хранилище по умолчанию и интерфейс `SessionStore` для собственного постоянного хранения.
@@ -189,8 +228,8 @@
Текущее направление API: Текущее направление API:
- `Scene`, `SceneContext`, `SceneSession` и `SessionStore`. - `Scene`, `SceneContext`, `SceneSession` и `SessionStore`.
- `Plugin.Scene(...)` и `Plugin.AddScene(...)`. - `Plugin.NewScene(...)` и `Plugin.AddScene(...)`.
- `MsgContext.EnterScene(...)`, `EnterSceneStep(...)` и `ExitScene(...)`. - `MessageContext.EnterScene(...)`, `EnterSceneStep(...)` и `ExitScene(...)`.
- `SceneContext.Stay()`, `Next(...)`, `Exit()`, `Pass()`, `BindData(...)` и `SaveData(...)`. - `SceneContext.Stay()`, `Next(...)`, `Exit()`, `Pass()`, `BindData(...)` и `SaveData(...)`.
- Состояние на пользователя или чат с хранением в `SessionStore` и чистым интерфейсом для собственного постоянного хранения. - Состояние на пользователя или чат с хранением в `SessionStore` и чистым интерфейсом для собственного постоянного хранения.
@@ -198,7 +237,7 @@
- Это должно быть опциональным и расширяющим текущую модель. - Это должно быть опциональным и расширяющим текущую модель.
- Это не должно заменять плагины, команды или обработчики как обычные точки входа во фреймворк. - Это не должно заменять плагины, команды или обработчики как обычные точки входа во фреймворк.
- Это должно работать поверх существующих middleware и `MsgContext`, а не вводить вторую несовместимую модель выполнения. - Это должно работать поверх существующих middleware и `MessageContext`, а не вводить вторую несовместимую модель выполнения.
Практическая цель: Практическая цель:
@@ -240,7 +279,7 @@ type BanInput struct {
Reason string Reason string
} }
func ban(ctx *laniakea.MsgContext, db *App) error { func ban(ctx *laniakea.MessageContext, db *App) error {
var input BanInput var input BanInput
if err := ctx.BindArgs(&input); err != nil { if err := ctx.BindArgs(&input); err != nil {
return err return err
@@ -281,14 +320,14 @@ func ban(ctx *laniakea.MsgContext, db *App) error {
Возможное направление API: Возможное направление API:
- Предпочесть non-breaking подход и выдавать context через `MsgContext`, например `ctx.Context()`. - Предпочесть non-breaking подход и выдавать context через `MessageContext`, например `ctx.Context()`.
- Строить context из жизненного цикла обработки обновления, чтобы он оставался полезным во время корректного завершения. - Строить context из жизненного цикла обработки обновления, чтобы он оставался полезным во время корректного завершения.
- Сделать естественной передачу этого context в методы базы данных, HTTP-клиенты и `tgapi.WithContext(...)`. - Сделать естественной передачу этого context в методы базы данных, HTTP-клиенты и `tgapi.WithContext(...)`.
Почему это, скорее всего, не должно быть изменением сигнатуры: Почему это, скорее всего, не должно быть изменением сигнатуры:
- Изменение сигнатур обработчиков на прямой `context.Context` было бы публичным ломающим изменением. - Изменение сигнатур обработчиков на прямой `context.Context` было бы публичным ломающим изменением.
- Аксессор на `MsgContext` сохраняет совместимость и при этом даёт обработчикам идиоматичный Go-путь для отмены выполнения. - Аксессор на `MessageContext` сохраняет совместимость и при этом даёт обработчикам идиоматичный Go-путь для отмены выполнения.
Практическая цель: Практическая цель:
@@ -297,7 +336,7 @@ func ban(ctx *laniakea.MsgContext, db *App) error {
Связанные страницы: Связанные страницы:
- [[Scenes]] - [[Scenes]]
- [[MsgContext]] - [[MessageContext]]
- [[Bot-Lifecycle]] - [[Bot-Lifecycle]]
- [[Migration]] - [[Migration]]
+42 -47
@@ -6,7 +6,7 @@ This page tracks framework-level backlog items that are about missing concepts i
### [1.0.0] ### [1.0.0]
### Major — close before 1.0.0 tag #### Major — closed before 1.0.0 tag
- [X] **M1. `BotPayloadType*` are `var`, must be `const`**`bot.go:50-59`. Public sentinels are user-mutable globals. `KeyboardButtonStyle*` in `keyboard.go:10-17` already uses `const`; match the pattern. - [X] **M1. `BotPayloadType*` are `var`, must be `const`**`bot.go:50-59`. Public sentinels are user-mutable globals. `KeyboardButtonStyle*` in `keyboard.go:10-17` already uses `const`; match the pattern.
- [X] **M2. `Observer` method naming asymmetry**`observer.go:147-157`. `OnReceiveUpdate``OnUpdateReceived`; `OnHandledUpdate``OnUpdateHandled` to match `UpdateReceivedEvent` / `UpdateHandledEvent` and the rest of the `OnX` pattern. Breaking after 1.0. - [X] **M2. `Observer` method naming asymmetry**`observer.go:147-157`. `OnReceiveUpdate``OnUpdateReceived`; `OnHandledUpdate``OnUpdateHandled` to match `UpdateReceivedEvent` / `UpdateHandledEvent` and the rest of the `OnX` pattern. Breaking after 1.0.
@@ -15,39 +15,34 @@ This page tracks framework-level backlog items that are about missing concepts i
- [X] **M5. Stale `Bot.Updates` godoc**`methods.go:11-44`. Claims "30-second timeout" and "empty slice if none"; in reality timeout is `bot.pollTimeout` and the function returns `nil` on error. - [X] **M5. Stale `Bot.Updates` godoc**`methods.go:11-44`. Claims "30-second timeout" and "empty slice if none"; in reality timeout is `bot.pollTimeout` and the function returns `nil` on error.
- [X] **M6. Self-contradicting `NewRandomDraftProvider` godoc**`drafts.go:50-59`. Says "cryptographically secure random numbers" but uses `math/rand/v2` (the underlying generator type correctly notes it is not crypto-secure). - [X] **M6. Self-contradicting `NewRandomDraftProvider` godoc**`drafts.go:50-59`. Says "cryptographically secure random numbers" but uses `math/rand/v2` (the underlying generator type correctly notes it is not crypto-secure).
- [X] **M7. `Draft.Delete` godoc says "internal method"**`drafts.go:190-201`. Method is exported; either rewrite the godoc with a public-intent description or unexport. - [X] **M7. `Draft.Delete` godoc says "internal method"**`drafts.go:190-201`. Method is exported; either rewrite the godoc with a public-intent description or unexport.
- [X] **M8. Russian comments in production code** - [X] **M8. Russian comments in production code**`msg_handler.go:28`, `tgapi/uploader_api.go:181`.
- `msg_handler.go:28` — "Ищем команду по точному совпадению" - [X] **M9. `MessageContext.Error` godoc references unexported helper**`msg_context.go:540`. Rewrite to describe the centralized handler error path and `IsUserError` gating.
- `tgapi/uploader_api.go:181` — "Повторяем запрос" - [X] **M10. `Scene` and `SceneSession` mix exported fields with setters**`Scene.PluginName` unexported; `SceneSession.Data` unexported, use accessor helpers.
- [X] **M9. `MessageContext.Error` godoc references unexported helper**`msg_context.go:540`. "Error is an alias for error()" — rewrite to describe the centralized handler error path and `IsUserError` gating. - [X] **M11. Constant-time compare for webhook secret**`bot_webhook.go`. Use `subtle.ConstantTimeCompare`.
- [X] **M10. `Scene` and `SceneSession` mix exported fields with setters**
- `Scene` exports `Name/Scope/Entry/PluginName` and also has `SetScope/SetEntry`; `PluginName` is framework-assigned but publicly mutable.
- `SceneSession` exports `Data []byte` and also has `Set/Get/HasData/ClearData/BindData/SaveData`.
- Pick one model per type before 1.0.0.
- [X] **M11. Constant-time compare for webhook secret**`bot_webhook.go:296` (update handler) and `bot_webhook.go:341` (`/status`). Use `subtle.ConstantTimeCompare`.
### Minor — can slip to 1.0.x #### Minor — closed before 1.0.0 tag
- [X] Strip `// Internal helper …` godoc from unexported funcs (~23 occurrences in repo); `AGENTS.md` explicitly forbids godoc-style comments on unexported declarations without a strong reason. - [X] Strip `// Internal helper …` godoc from unexported funcs.
- [X] `Plugin.AddCommand` godoc references unexported field `.command``plugins.go:48-49`. - [X] `Plugin.AddCommand` godoc references unexported field `.command`.
- [X] `Runner` builder naming: `runner.Once(true)`, `runner.Async(true)` read awkwardly; consider `SetOnce`/`SetAsync` to match `Set*` on other types, or zero-arg `Once()` + paired `Repeat(every)`. - [X] `Runner` builder: `Onetime`/`Timeout``Every`/`Async`; `Once()` removed.
- [X] Typo in webhook error string: `bot_webhook.go:143` "MaxConnections must between 1 and 100" (missing `be`). - [X] Typo in webhook error string: "MaxConnections must between 1 and 100" → "must be between".
- [X] `RunWebhookWithContext` uses inline `errors.New(...)` instead of `Err*` sentinels (`bot_webhook.go:131-156`); rest of the package uses sentinels from `errors.go`. - [X] `RunWebhookWithContext` inline `errors.New(...)` `Err*` sentinels.
- [X] `tgapi.UpdateTypeManagedBot` (`tgapi/types.go:61`) has no godoc. - [X] `tgapi.UpdateTypeManagedBot` missing godoc.
- [X] `Bot.GetAPI`, `Bot.GetUploader`, `InlineKeyboard.GetMaxRow` have no godoc. - [X] `Bot.GetAPI`, `Bot.GetUploader`, `InlineKeyboard.GetMaxRow` missing godoc.
- [X] `Bot.L10n` godoc says "Returns empty string if translation not found"; actually returns the key (`l10n.go:48-59`). - [X] `Bot.L10n` godoc says "Returns empty string if translation not found"; actually returns the key.
- [X] `Bot.handle` panic recovery only logs — emit `ErrorEvent` so observers see panics (`handler.go:18-23`). - [X] `Bot.handle` panic recovery only logs — emit `ErrorEvent` so observers see panics.
- [X] `handleCallback` vs `handleMessage` differ in plugin-logger assignment: callback assigns unconditionally then falls back to bot logger (`msg_handler.go:209-212`); message only assigns if non-nil (`msg_handler.go:35-37`). Align. - [X] `handleCallback` vs `handleMessage` plugin-logger assignment asymmetry — aligned.
- [X] `SetCallbackData` godoc says "default payload type is JSON" — actually the zero `BotPayloadType` falls through to the `default` branch (which happens to be JSON). Either document the zero-value behavior explicitly or initialize the builder with the bot's default (`keyboard.go:106-122`). - [X] `SetCallbackData` godoc: zero `BotPayloadType` behavior documented explicitly.
- [X] `commands.go:62-66` empty `case CommandValueAny:` next to `default: regex = nil` looks like an incomplete switch. Merge or add a one-line comment. - [X] `commands.go` empty `case CommandValueAny:` merged with `default`.
- [X] `Bot.SetDebug` does not call `configMutable` unlike sibling setters; if intentional, note it in godoc. - [X] `Bot.SetDebug` does not call `configMutable` noted in godoc.
### Tests to add after the fixes #### Tests added after the fixes
- [X] `BotOptsFileJSON` round-trip for `PollTimeout` (after M4). - [X] `BotOptsFileJSON` round-trip for `PollTimeout`.
- [X] Uploader 4xx/429 surfaces `*tgapi.ResponseError` (after M3). - [X] Uploader 4xx/429 surfaces `*tgapi.ResponseError`.
- [X] `Bot.handle` panic → observer receives `ErrorEvent` (after panic-recovery fix). - [X] `Bot.handle` panic → observer receives `ErrorEvent`.
- [X] Webhook `/status` with wrong `SecretToken` returns 403 / `403`-equivalent (after M11), incl. a constant-time-compare smoke. - [X] Webhook `/status` with wrong `SecretToken` returns 404, constant-time-compare smoke test.
- [X] Table-driven `parseCommand` cases for `/cmd@botname` and stripping behavior. - [X] Table-driven `parseCommand` cases for `/cmd@botname` stripping.
### [1.0.0-rc.14] Webhook runtime model ### [1.0.0-rc.14] Webhook runtime model
@@ -110,7 +105,7 @@ Practical target:
Current state: Current state:
- The framework now exposes `Policy[T]` as a first-class reusable authorization rule that runs against the normalized `MsgContext` and injected app data. - The framework now exposes `Policy[T]` as a first-class reusable authorization rule that runs against the normalized `MessageContext` and injected app data.
- Policies integrate with the existing execution model through `RequirePolicy(...)`, so authorization stays on the middleware path instead of introducing a second routing pipeline. - Policies integrate with the existing execution model through `RequirePolicy(...)`, so authorization stays on the middleware path instead of introducing a second routing pipeline.
- Bot-level and plugin-level registration helpers now make policy usage explicit in configuration. - Bot-level and plugin-level registration helpers now make policy usage explicit in configuration.
@@ -127,7 +122,7 @@ What is now present:
- `Bot.UsePolicy(...)` and `Plugin.UsePolicy(...)` for registration ergonomics. - `Bot.UsePolicy(...)` and `Plugin.UsePolicy(...)` for registration ergonomics.
- Built-in Telegram-aware helpers such as `RequirePrivateChat(...)`, `RequireGroupChat(...)`, `RequireSupergroupChat(...)`, `RequireChatAdmin(...)`, `RequireChatCreator(...)`, `RequireBotAdmin(...)`, and `RequireCallbackFromUser(...)`. - Built-in Telegram-aware helpers such as `RequirePrivateChat(...)`, `RequireGroupChat(...)`, `RequireSupergroupChat(...)`, `RequireChatAdmin(...)`, `RequireChatCreator(...)`, `RequireBotAdmin(...)`, and `RequireCallbackFromUser(...)`.
- Composition helpers `AllPolicies(...)`, `AnyPolicy(...)`, and `NotPolicy(...)`. - Composition helpers `AllPolicies(...)`, `AnyPolicy(...)`, and `NotPolicy(...)`.
- Extended `MsgContext` normalization for `Chat` and `ChatID`, plus regression coverage for policy and normalization behavior. - Extended `MessageContext` normalization for `Chat` and `ChatID`, plus regression coverage for policy and normalization behavior.
Practical target: Practical target:
@@ -151,7 +146,7 @@ What is now present:
- `AsUserError(...)` and `AsInternalError(...)` to classify returned handler errors explicitly. - `AsUserError(...)` and `AsInternalError(...)` to classify returned handler errors explicitly.
- `IsUserError(...)` and `IsInternalError(...)` for framework-side inspection. - `IsUserError(...)` and `IsInternalError(...)` for framework-side inspection.
- Updated centralized `MsgContext.Error(...)` behavior that still logs all errors but suppresses the automatic user reply for internal-only failures. - Updated centralized `MessageContext.Error(...)` behavior that still logs all errors but suppresses the automatic user reply for internal-only failures.
- Regression coverage for both message and callback flows. - Regression coverage for both message and callback flows.
Practical target: Practical target:
@@ -186,30 +181,30 @@ Practical target:
Current state: Current state:
- Update normalization already existed, but it is now described and tested as an explicit framework-level contract. - Update normalization already existed, but it is now described and tested as an explicit framework-level contract.
- Routing categories and `MsgContext` population guarantees are now treated as a first-class part of the public model. - Routing categories and `MessageContext` population guarantees are now treated as a first-class part of the public model.
Why this matters: Why this matters:
- Handler code needs to know which `MsgContext` fields are safe to rely on for each update path. - Handler code needs to know which `MessageContext` fields are safe to rely on for each update path.
- Without a formal contract, update handling remains understandable only by reading implementation details. - Without a formal contract, update handling remains understandable only by reading implementation details.
What is now present: What is now present:
- A documented routing model for command flow, payload flow, and generic update handlers. - A documented routing model for command flow, payload flow, and generic update handlers.
- Explicit `MsgContext` field comments for update-backed, callback-backed, and message-backed contexts. - Explicit `MessageContext` field comments for update-backed, callback-backed, and message-backed contexts.
- Table-driven regression coverage for the normalized update contract, including callback target semantics and non-command update flows. - Table-driven regression coverage for the normalized update contract, including callback target semantics and non-command update flows.
Practical target: Practical target:
- Make update routing and `MsgContext` guarantees explicit enough to serve as a stable `1.0` public contract. - Make update routing and `MessageContext` guarantees explicit enough to serve as a stable `1.0` public contract.
### [1.0.0-rc.12] Conversation / Scene Model ### [1.0.0-rc.12] Conversation / Scene Model
Current state: Current state:
- The framework is strong at handling a single update through commands, payloads, middleware, and update handlers. - The framework is strong at handling a single update through commands, payloads, middleware, and update handlers.
- It already has useful lower-level building blocks such as `MsgContext`, drafts, payload routing, plugins, and update handlers. - It already has useful lower-level building blocks such as `MessageContext`, drafts, payload routing, plugins, and update handlers.
- It now provides an implemented initial scene model for long-lived user interaction flows: scenes can be registered in plugins, entered through `MsgContext`, persisted through `SessionStore`, and routed before normal command handling. - It now provides an implemented initial scene model for long-lived user interaction flows: scenes can be registered in plugins, entered through `MessageContext`, persisted through `SessionStore`, and routed before normal command handling.
Why this matters: Why this matters:
@@ -221,7 +216,7 @@ What is already present:
- Active-scene routing before normal command flow. - Active-scene routing before normal command flow.
- Per-user, per-chat, and per-user-chat session scopes. - Per-user, per-chat, and per-user-chat session scopes.
- Explicit scene entry and exit through `MsgContext`. - Explicit scene entry and exit through `MessageContext`.
- Step handlers, scene-local commands, and `OnMessage(...)`. - Step handlers, scene-local commands, and `OnMessage(...)`.
- In-memory session storage by default, plus the `SessionStore` interface for custom persistence. - In-memory session storage by default, plus the `SessionStore` interface for custom persistence.
@@ -234,8 +229,8 @@ What is still missing or not yet settled:
Current API direction: Current API direction:
- `Scene`, `SceneContext`, `SceneSession`, and `SessionStore`. - `Scene`, `SceneContext`, `SceneSession`, and `SessionStore`.
- `Plugin.Scene(...)` and `Plugin.AddScene(...)`. - `Plugin.NewScene(...)` and `Plugin.AddScene(...)`.
- `MsgContext.EnterScene(...)`, `EnterSceneStep(...)`, and `ExitScene(...)`. - `MessageContext.EnterScene(...)`, `EnterSceneStep(...)`, and `ExitScene(...)`.
- `SceneContext.Stay()`, `Next(...)`, `Exit()`, `Pass()`, `BindData(...)`, and `SaveData(...)`. - `SceneContext.Stay()`, `Next(...)`, `Exit()`, `Pass()`, `BindData(...)`, and `SaveData(...)`.
- Storage-backed per-user or per-chat state with a clean interface for custom persistence. - Storage-backed per-user or per-chat state with a clean interface for custom persistence.
@@ -243,7 +238,7 @@ Important design constraints:
- This should be additive and optional. - This should be additive and optional.
- It should not replace plugins, commands, or handlers as the normal framework entry points. - It should not replace plugins, commands, or handlers as the normal framework entry points.
- It should work with existing middleware and `MsgContext` instead of introducing a second incompatible execution model. - It should work with existing middleware and `MessageContext` instead of introducing a second incompatible execution model.
Practical target: Practical target:
@@ -285,7 +280,7 @@ type BanInput struct {
Reason string Reason string
} }
func ban(ctx *laniakea.MsgContext, db *App) error { func ban(ctx *laniakea.MessageContext, db *App) error {
var input BanInput var input BanInput
if err := ctx.BindArgs(&input); err != nil { if err := ctx.BindArgs(&input); err != nil {
return err return err
@@ -326,14 +321,14 @@ What is missing:
Possible API direction: Possible API direction:
- Prefer a non-breaking approach by exposing context through `MsgContext`, for example `ctx.Context()`. - Prefer a non-breaking approach by exposing context through `MessageContext`, for example `ctx.Context()`.
- Build the context from the update-processing lifecycle so it is meaningful during graceful shutdown. - Build the context from the update-processing lifecycle so it is meaningful during graceful shutdown.
- Make it natural to pass that context into database methods, HTTP clients, and `tgapi.WithContext(...)` calls. - Make it natural to pass that context into database methods, HTTP clients, and `tgapi.WithContext(...)` calls.
Why this should probably not be a signature change: Why this should probably not be a signature change:
- Changing handler signatures to accept `context.Context` directly would be a public breaking change. - Changing handler signatures to accept `context.Context` directly would be a public breaking change.
- A `MsgContext` accessor would preserve compatibility while still giving handlers an idiomatic Go cancellation path. - A `MessageContext` accessor would preserve compatibility while still giving handlers an idiomatic Go cancellation path.
Practical target: Practical target:
@@ -342,7 +337,7 @@ Practical target:
Related pages: Related pages:
- [[Scenes]] - [[Scenes]]
- [[MsgContext]] - [[MessageContext]]
- [[Bot-Lifecycle]] - [[Bot-Lifecycle]]
- [[Migration]] - [[Migration]]
+4 -4
@@ -33,7 +33,7 @@ import (
"git.scuroneko.dev/scuroneko/laniakea" "git.scuroneko.dev/scuroneko/laniakea"
) )
func ping(ctx *laniakea.MsgContext, db laniakea.NoData) error { func ping(ctx *laniakea.MessageContext, db laniakea.NoData) error {
ctx.Answer("Pong") ctx.Answer("Pong")
return nil return nil
} }
@@ -111,7 +111,7 @@ bot.AddPlugins(plugin)
Сигнатура хендлера: Сигнатура хендлера:
```go ```go
func(ctx *laniakea.MsgContext, db T) error func(ctx *laniakea.MessageContext, db T) error
``` ```
То есть: То есть:
@@ -121,7 +121,7 @@ func(ctx *laniakea.MsgContext, db T) error
Пример: Пример:
```go ```go
func profile(ctx *laniakea.MsgContext, db *App) error { func profile(ctx *laniakea.MessageContext, db *App) error {
user, err := db.LoadUser(ctx.FromID) user, err := db.LoadUser(ctx.FromID)
if err != nil { if err != nil {
return err return err
@@ -199,4 +199,4 @@ defer bot.Close()
- [[Bot-Lifecycle-RU]] - [[Bot-Lifecycle-RU]]
- [[Getting-Started]] - [[Getting-Started]]
- [[Commands-and-Plugins]] - [[Commands-and-Plugins]]
- [[MsgContext]] - [[MessageContext]]
+5 -5
@@ -34,7 +34,7 @@ import (
"git.scuroneko.dev/scuroneko/laniakea" "git.scuroneko.dev/scuroneko/laniakea"
) )
func ping(ctx *laniakea.MsgContext, db laniakea.NoData) error { func ping(ctx *laniakea.MessageContext, db laniakea.NoData) error {
ctx.Answer("Pong") ctx.Answer("Pong")
return nil return nil
} }
@@ -113,7 +113,7 @@ See [[Commands-and-Plugins]] for the full model.
The command handler signature is: The command handler signature is:
```go ```go
func(ctx *laniakea.MsgContext, db T) error func(ctx *laniakea.MessageContext, db T) error
``` ```
This means: This means:
@@ -124,7 +124,7 @@ This means:
Example: Example:
```go ```go
func profile(ctx *laniakea.MsgContext, db *App) error { func profile(ctx *laniakea.MessageContext, db *App) error {
user, err := db.LoadUser(ctx.FromID) user, err := db.LoadUser(ctx.FromID)
if err != nil { if err != nil {
return err return err
@@ -184,7 +184,7 @@ import (
type App struct{} type App struct{}
func echo(ctx *laniakea.MsgContext, app *App) error { func echo(ctx *laniakea.MessageContext, app *App) error {
if ctx.Text == "" { if ctx.Text == "" {
ctx.Answer("Send some text after the command.") ctx.Answer("Send some text after the command.")
return nil return nil
@@ -259,5 +259,5 @@ Prefer pointer types unless you have a strong reason not to.
## Where to go next ## Where to go next
- Read [[Commands-and-Plugins]] next if you want to build the handler layer correctly. - Read [[Commands-and-Plugins]] next if you want to build the handler layer correctly.
- Read [[MsgContext]] next if you want to understand reply, edit, callback, and draft helpers. - Read [[MessageContext]] next if you want to understand reply, edit, callback, and draft helpers.
- Read [[Bot-Lifecycle]] if you need shutdown, worker, or startup details. - Read [[Bot-Lifecycle]] if you need shutdown, worker, or startup details.
+3 -3
@@ -9,7 +9,7 @@ English version: [[Home]]
- [[Getting-Started-RU]] - [[Getting-Started-RU]]
- [[Bot-Options-and-Configuration-RU]] - [[Bot-Options-and-Configuration-RU]]
- [[Commands-and-Plugins-RU]] - [[Commands-and-Plugins-RU]]
- [[MsgContext-RU]] - [[MessageContext-RU]]
## Выполнение и архитектура ## Выполнение и архитектура
@@ -50,7 +50,7 @@ English version: [[Home]]
- [[Getting-Started]] - [[Getting-Started]]
- [[Bot-Options-and-Configuration]] - [[Bot-Options-and-Configuration]]
- [[Commands-and-Plugins]] - [[Commands-and-Plugins]]
- [[MsgContext]] - [[MessageContext]]
- [[Bot-Lifecycle]] - [[Bot-Lifecycle]]
- [[Inline-Keyboards-and-Payloads]] - [[Inline-Keyboards-and-Payloads]]
- [[tgapi-Overview]] - [[tgapi-Overview]]
@@ -60,7 +60,7 @@ English version: [[Home]]
Рекомендуемый маршрут для русскоязычного пользователя: Рекомендуемый маршрут для русскоязычного пользователя:
1. Прочитать [[Getting-Started-RU]]. 1. Прочитать [[Getting-Started-RU]].
2. Прочитать [[Commands-and-Plugins-RU]] и [[MsgContext-RU]]. 2. Прочитать [[Commands-and-Plugins-RU]] и [[MessageContext-RU]].
3. Перейти в страницы про выполнение и поведение во время работы, например [[Bot-Lifecycle-RU]] и [[Middleware-RU]]. 3. Перейти в страницы про выполнение и поведение во время работы, например [[Bot-Lifecycle-RU]] и [[Middleware-RU]].
4. При необходимости открыть специализированные страницы вроде [[Drafts-RU]], [[Rate-Limiting-RU]] или [[tgapi-Overview-RU]]. 4. При необходимости открыть специализированные страницы вроде [[Drafts-RU]], [[Rate-Limiting-RU]] или [[tgapi-Overview-RU]].
5. Для максимальной точности переходить в соответствующую англоязычную страницу. 5. Для максимальной точности переходить в соответствующую англоязычную страницу.
+1 -1
@@ -15,7 +15,7 @@ Use this wiki as the structured companion to the README: start with setup, then
- [[Getting-Started]] - [[Getting-Started]]
- [[Bot-Options-and-Configuration]] - [[Bot-Options-and-Configuration]]
- [[Commands-and-Plugins]] - [[Commands-and-Plugins]]
- [[MsgContext]] - [[MessageContext]]
## Bot Runtime ## Bot Runtime
- [[Bot-Lifecycle]] - [[Bot-Lifecycle]]
+1 -1
@@ -99,5 +99,5 @@ kb := ctx.NewInlineKeyboard(2).
## Что читать дальше ## Что читать дальше
- [[Commands-and-Plugins-RU]] - [[Commands-and-Plugins-RU]]
- [[MsgContext-RU]] - [[MessageContext-RU]]
- [[Inline-Keyboards-and-Payloads]] - [[Inline-Keyboards-and-Payloads]]
+22 -22
@@ -16,7 +16,7 @@ Inline keyboards in Laniakea are built explicitly: you choose a row width, add U
## Building a keyboard ## Building a keyboard
The most direct constructors are: The most direct constructors are:
- `laniakea.NewInlineKeyboardJSON(maxRow)` - `laniakea.NewInlineKeyboardJson(maxRow)`
- `laniakea.NewInlineKeyboardBase64(maxRow)` - `laniakea.NewInlineKeyboardBase64(maxRow)`
- `laniakea.NewInlineKeyboard(payloadType, maxRow)` - `laniakea.NewInlineKeyboard(payloadType, maxRow)`
@@ -25,10 +25,10 @@ The most direct constructors are:
Example: Example:
```go ```go
kb := laniakea.NewInlineKeyboardJSON(2). kb := laniakea.NewInlineKeyboardJson(2).
AddCallbackButton("Open", "open", 42). AddCallbackButton("Open", "open", 42).
AddCallbackButton("Delete", "delete", 42). AddCallbackButton("Delete", "delete", 42).
AddURLButton("Docs", "https://example.com/docs") AddUrlButton("Docs", "https://example.com/docs")
``` ```
In that example: In that example:
@@ -77,7 +77,7 @@ All payload arguments are converted with `fmt.Sprint`, so handlers receive strin
Example: Example:
```go ```go
kb := laniakea.NewInlineKeyboardJSON(1). kb := laniakea.NewInlineKeyboardJson(1).
AddCallbackButton("Ban", "ban_user", 12345, "spam") AddCallbackButton("Ban", "ban_user", 12345, "spam")
``` ```
@@ -91,13 +91,13 @@ Inside the payload handler, those values are available through:
- `ctx.Args` for the decoded argument list; - `ctx.Args` for the decoded argument list;
- the payload command name used to choose the matched handler. - the payload command name used to choose the matched handler.
There is no separate `ctx.Payload` object in the current API. Payload handlers receive the same `MsgContext` structure used elsewhere, with `ctx.Args` populated from callback data. There is no separate `ctx.Payload` object in the current API. Payload handlers receive the same `MessageContext` structure used elsewhere, with `ctx.Args` populated from callback data.
## JSON vs Base64 payloads ## JSON vs Base64 payloads
Laniakea supports two payload encodings: Laniakea supports two payload encodings:
- `BotPayloadJSON` - `BotPayloadJson`
- `BotPayloadBase64` - `BotPayloadBase64`
JSON is easier to inspect in logs and tests. JSON is easier to inspect in logs and tests.
@@ -126,13 +126,13 @@ The bot has a default callback encoding:
bot.SetPayloadType(laniakea.BotPayloadBase64) bot.SetPayloadType(laniakea.BotPayloadBase64)
``` ```
That default is copied into `MsgContext`, so `ctx.NewInlineKeyboard(...)` starts with the bots current payload type. That default is copied into `MessageContext`, so `ctx.NewInlineKeyboard(...)` starts with the bots current payload type.
For a single keyboard, you can override it locally: For a single keyboard, you can override it locally:
```go ```go
kb := ctx.NewInlineKeyboard(2). kb := ctx.NewInlineKeyboard(2).
SetPayloadType(laniakea.BotPayloadJSON). SetPayloadType(laniakea.BotPayloadJson).
AddCallbackButton("Inspect", "inspect", 7) AddCallbackButton("Inspect", "inspect", 7)
``` ```
@@ -194,23 +194,23 @@ In both cases, keep payloads short and intentional. Telegram callback data is li
## Button builder for advanced cases ## Button builder for advanced cases
`InlineKeyboardButtonBuilder` is the flexible path when you want button-specific styling or custom emoji icons. `InlineKbButtonBuilder` is the flexible path when you want button-specific styling or custom emoji icons.
Example: Example:
```go ```go
button := laniakea.NewInlineKeyboardButton("Confirm"). button := laniakea.NewInlineKbButton("Confirm").
SetStyle(laniakea.ButtonStyleSuccess). SetStyle(laniakea.ButtonStyleSuccess).
SetCallbackDataJSON("confirm_order", 99) SetCallbackDataJson("confirm_order", 99)
kb := laniakea.NewInlineKeyboardJSON(2).AddButton(button) kb := laniakea.NewInlineKeyboardJson(2).AddButton(button)
``` ```
The builder supports: The builder supports:
- `SetStyle(...)` - `SetStyle(...)`
- `SetURL(...)` - `SetUrl(...)`
- `SetIconCustomEmojiID(...)` - `SetIconCustomEmojiId(...)`
- `SetCallbackDataJSON(...)` - `SetCallbackDataJson(...)`
- `SetCallbackDataBase64(...)` - `SetCallbackDataBase64(...)`
Use it when `AddCallbackButton(...)` is not expressive enough. Use it when `AddCallbackButton(...)` is not expressive enough.
@@ -223,9 +223,9 @@ Laniakea exposes three convenience style constants:
- `ButtonStyleDanger` - `ButtonStyleDanger`
You can use them with: You can use them with:
- `AddURLButtonStyle(...)` - `AddUrlButtonStyle(...)`
- `AddCallbackButtonStyle(...)` - `AddCallbackButtonStyle(...)`
- `InlineKeyboardButtonBuilder.SetStyle(...)` - `InlineKbButtonBuilder.SetStyle(...)`
URL buttons and callback buttons can live in the same keyboard. Use URL buttons for external navigation and callback buttons for bot-side actions. URL buttons and callback buttons can live in the same keyboard. Use URL buttons for external navigation and callback buttons for bot-side actions.
@@ -241,17 +241,17 @@ If your plain-text reply may exceed Telegrams message length limit, use:
## Routing payloads to handlers ## Routing payloads to handlers
Payloads are registered on plugins with: Payloads are registered on plugins with:
- `Plugin.Payload(...)` - `Plugin.NewPayload(...)`
- `Plugin.AddPayload(...)` - `Plugin.AddPayload(...)`
Example: Example:
```go ```go
plugin.Payload("inspect", func(ctx *laniakea.MsgContext, db *App) error { plugin.NewPayload(func(ctx *laniakea.MessageContext, db *App) error {
id := ctx.Args[0] id := ctx.Args[0]
ctx.AnswerCallbackText("Handled " + id) ctx.AnswerCbQueryText("Handled " + id)
return nil return nil
}) }, "inspect")
``` ```
When a callback arrives: When a callback arrives:
@@ -275,5 +275,5 @@ Related page:
## Related pages ## Related pages
- [[Commands-and-Plugins]] for payload handler registration and routing. - [[Commands-and-Plugins]] for payload handler registration and routing.
- [[MsgContext]] for reply, edit, and callback helpers. - [[MessageContext]] for reply, edit, and callback helpers.
- [[tgapi-Overview]] for lower-level Telegram method access when keyboard helpers are not enough. - [[tgapi-Overview]] for lower-level Telegram method access when keyboard helpers are not enough.
+5 -5
@@ -77,11 +77,11 @@ bot.SetL10n(l10n)
From then on: From then on:
- `bot.L10n(lang, key)` is available for manual lookups; - `bot.L10n(lang, key)` is available for manual lookups;
- `MsgContext.Translate(key)` becomes the ergonomic handler-level helper. - `MessageContext.Translate(key)` becomes the ergonomic handler-level helper.
If `SetL10n(nil)` is called, the bot logs a warning and keeps the existing localization provider unchanged. If `SetL10n(nil)` is called, the bot logs a warning and keeps the existing localization provider unchanged.
## `MsgContext.Translate` ## `MessageContext.Translate`
`ctx.Translate(key)` is the usual choice inside handlers. `ctx.Translate(key)` is the usual choice inside handlers.
@@ -93,7 +93,7 @@ It:
Example: Example:
```go ```go
func start(ctx *laniakea.MsgContext, db *App) error { func start(ctx *laniakea.MessageContext, db *App) error {
ctx.Answer(ctx.Translate("greeting")) ctx.Answer(ctx.Translate("greeting"))
return nil return nil
} }
@@ -111,7 +111,7 @@ Use `bot.L10n(lang, key)` when you need a translation outside handler flow or wh
Examples: Examples:
- preparing background-runner messages; - preparing background-runner messages;
- rendering text for stored user preferences; - rendering text for stored user preferences;
- translating outside `MsgContext`. - translating outside `MessageContext`.
## Concurrency and mutation safety ## Concurrency and mutation safety
@@ -150,5 +150,5 @@ l10n.
## Related pages ## Related pages
- [[Getting-Started]] for basic bot setup - [[Getting-Started]] for basic bot setup
- [[MsgContext]] for handler helpers - [[MessageContext]] for handler helpers
- [[Recipes]] for localized handler examples - [[Recipes]] for localized handler examples
+2 -2
@@ -55,13 +55,13 @@ Ways plugin loggers are set:
- explicitly with `Plugin.SetLogger(...)` before `AddPlugins(...)`; - explicitly with `Plugin.SetLogger(...)` before `AddPlugins(...)`;
- implicitly by the bot, which creates a default logger if the plugin has none at registration time. - implicitly by the bot, which creates a default logger if the plugin has none at registration time.
During handler execution, `MsgContext.Logger` is set to: During handler execution, `MessageContext.Logger` is set to:
- the matched plugin logger when one exists; - the matched plugin logger when one exists;
- otherwise the bot logger. - otherwise the bot logger.
That means handler-local logs naturally follow plugin boundaries when possible. That means handler-local logs naturally follow plugin boundaries when possible.
## `MsgContext.Logger` ## `MessageContext.Logger`
Inside handlers and middleware, the easiest logger to use is `ctx.Logger`. Inside handlers and middleware, the easiest logger to use is `ctx.Logger`.
+4 -4
@@ -45,7 +45,7 @@ Middleware позволяет запускать логику до обрабо
Сигнатура: Сигнатура:
```go ```go
func(ctx *laniakea.MsgContext, db T) bool func(ctx *laniakea.MessageContext, db T) bool
``` ```
Возвращает: Возвращает:
@@ -66,7 +66,7 @@ func(ctx *laniakea.MsgContext, db T) bool
- middleware идет в goroutine; - middleware идет в goroutine;
- выполнение цепочки продолжается сразу; - выполнение цепочки продолжается сразу;
- возвращаемое значение `bool` игнорируется; - возвращаемое значение `bool` игнорируется;
- middleware получает копию `MsgContext`. - middleware получает копию `MessageContext`.
Поэтому async middleware подходит только для: Поэтому async middleware подходит только для:
- телеметрии; - телеметрии;
@@ -79,7 +79,7 @@ func(ctx *laniakea.MsgContext, db T) bool
- обязательной валидации; - обязательной валидации;
- логики, которая должна переписать `ctx` и повлиять на обработчик. - логики, которая должна переписать `ctx` и повлиять на обработчик.
## Почему async middleware получает копию `MsgContext` ## Почему async middleware получает копию `MessageContext`
Так библиотека избегает очевидных гонок данных между goroutine middleware и основной цепочкой обработки. Так библиотека избегает очевидных гонок данных между goroutine middleware и основной цепочкой обработки.
@@ -105,5 +105,5 @@ Middleware уровня плагина и команды сохраняют по
## Что читать дальше ## Что читать дальше
- [[Commands-and-Plugins-RU]] - [[Commands-and-Plugins-RU]]
- [[MsgContext-RU]] - [[MessageContext-RU]]
- [[Middleware]] - [[Middleware]]
+13 -13
@@ -11,11 +11,11 @@ Middleware is useful when the same check or side effect should apply in more tha
Typical uses: Typical uses:
- reject updates from unauthorized users; - reject updates from unauthorized users;
- log incoming commands and callback payloads; - log incoming commands and callback payloads;
- attach derived values to `MsgContext`; - attach derived values to `MessageContext`;
- stop processing early when a precondition is not met; - stop processing early when a precondition is not met;
- run non-blocking side effects such as analytics or audit logging. - run non-blocking side effects such as analytics or audit logging.
For the handler and plugin model around middleware, see [[Commands-and-Plugins]]. For the fields you can read or update on the context, see [[MsgContext]]. For the handler and plugin model around middleware, see [[Commands-and-Plugins]]. For the fields you can read or update on the context, see [[MessageContext]].
## Middleware levels ## Middleware levels
@@ -47,7 +47,7 @@ For non-command update handlers registered through `AddUpdateHandler(...)`, the
Important detail: Important detail:
- bot-level middleware runs once per update before routing; - bot-level middleware runs once per update before routing;
- for non-command update handlers, each matching plugin receives its own cloned `MsgContext`, so one plugin's mutations do not leak into the next plugin's handler chain. - for non-command update handlers, each matching plugin receives its own cloned `MessageContext`, so one plugin's mutations do not leak into the next plugin's handler chain.
## Synchronous middleware ## Synchronous middleware
@@ -56,7 +56,7 @@ By default, middleware is synchronous.
The executor signature is: The executor signature is:
```go ```go
func(ctx *laniakea.MsgContext, db T) bool func(ctx *laniakea.MessageContext, db T) bool
``` ```
Return values mean: Return values mean:
@@ -72,7 +72,7 @@ This makes synchronous middleware the right choice for:
Example: Example:
```go ```go
auth := laniakea.NewMiddleware("auth", func(ctx *laniakea.MsgContext, db *App) bool { auth := laniakea.NewMiddleware("auth", func(ctx *laniakea.MessageContext, db *App) bool {
if ctx.From == nil || !db.Allowed(ctx.From.ID) { if ctx.From == nil || !db.Allowed(ctx.From.ID) {
ctx.Answer("Access denied") ctx.Answer("Access denied")
return false return false
@@ -86,7 +86,7 @@ auth := laniakea.NewMiddleware("auth", func(ctx *laniakea.MsgContext, db *App) b
Middleware can also run asynchronously with `SetAsync(true)`. Middleware can also run asynchronously with `SetAsync(true)`.
```go ```go
audit := laniakea.NewMiddleware("audit", func(ctx *laniakea.MsgContext, db *App) bool { audit := laniakea.NewMiddleware("audit", func(ctx *laniakea.MessageContext, db *App) bool {
db.Audit(ctx.Update.UpdateID, ctx.Text) db.Audit(ctx.Update.UpdateID, ctx.Text)
return true return true
}).SetAsync(true) }).SetAsync(true)
@@ -96,7 +96,7 @@ Async middleware behaves differently:
- it runs in a goroutine; - it runs in a goroutine;
- execution always continues immediately; - execution always continues immediately;
- its boolean return value is ignored; - its boolean return value is ignored;
- it receives a copied `MsgContext`, not the original pointer. - it receives a copied `MessageContext`, not the original pointer.
That means async middleware is appropriate for: That means async middleware is appropriate for:
- fire-and-forget logging; - fire-and-forget logging;
@@ -112,7 +112,7 @@ It is not appropriate for:
## Why async middleware gets a copied context ## Why async middleware gets a copied context
When middleware is async, the library copies `MsgContext` before starting the goroutine. This prevents obvious data races against the handler path. When middleware is async, the library copies `MessageContext` before starting the goroutine. This prevents obvious data races against the handler path.
Practical consequence: Practical consequence:
- changes you make to the copied `ctx` inside async middleware are local to that goroutine; - changes you make to the copied `ctx` inside async middleware are local to that goroutine;
@@ -121,7 +121,7 @@ Practical consequence:
So this pattern does not work: So this pattern does not work:
```go ```go
bad := laniakea.NewMiddleware("bad", func(ctx *laniakea.MsgContext, db *App) bool { bad := laniakea.NewMiddleware("bad", func(ctx *laniakea.MessageContext, db *App) bool {
ctx.Text = "rewritten" ctx.Text = "rewritten"
return false return false
}).SetAsync(true) }).SetAsync(true)
@@ -177,7 +177,7 @@ This usually means:
```go ```go
bot.AddMiddleware( bot.AddMiddleware(
laniakea.NewMiddleware("private-only", func(ctx *laniakea.MsgContext, db *App) bool { laniakea.NewMiddleware("private-only", func(ctx *laniakea.MessageContext, db *App) bool {
if ctx.Chat == nil || ctx.Chat.Type != "private" { if ctx.Chat == nil || ctx.Chat.Type != "private" {
return false return false
} }
@@ -191,7 +191,7 @@ bot.AddMiddleware(
```go ```go
admin := laniakea.NewPlugin[*App]("admin") admin := laniakea.NewPlugin[*App]("admin")
admin.AddMiddleware( admin.AddMiddleware(
laniakea.NewMiddleware("admin-only", func(ctx *laniakea.MsgContext, db *App) bool { laniakea.NewMiddleware("admin-only", func(ctx *laniakea.MessageContext, db *App) bool {
return ctx.From != nil && db.IsAdmin(ctx.From.ID) return ctx.From != nil && db.IsAdmin(ctx.From.ID)
}), }),
) )
@@ -200,8 +200,8 @@ admin.AddMiddleware(
### Command-specific validation ### Command-specific validation
```go ```go
ban := admin.Command("ban", banUser) ban := admin.NewCommand(banUser, "ban")
ban.Use(laniakea.NewMiddleware("require-reply", func(ctx *laniakea.MsgContext, db *App) bool { ban.Use(laniakea.NewMiddleware("require-reply", func(ctx *laniakea.MessageContext, db *App) bool {
if ctx.Msg == nil || ctx.Msg.ReplyToMessage == nil { if ctx.Msg == nil || ctx.Msg.ReplyToMessage == nil {
ctx.Answer("Reply to a user message first") ctx.Answer("Reply to a user message first")
return false return false
+64 -1
@@ -25,7 +25,70 @@ When jumping across multiple RC versions:
5. Revisit any direct `tgapi` calls and renamed types. 5. Revisit any direct `tgapi` calls and renamed types.
6. Run tests against realistic update payloads and callback data. 6. Run tests against realistic update payloads and callback data.
The largest migration points in the current history are `rc.4`, `rc.7`, `rc.10`, and `rc.12`. The largest migration points in the current history are `rc.4`, `rc.7`, `rc.10`, `rc.12`, and the `v1.0.0` stable release.
## `v1.0.0`
`v1.0.0` is a public-API hygiene release. Most changes are renames and type-system tightenings that cause compile errors and are easy to fix mechanically.
### Handler type rename: `MsgContext` → `MessageContext`
Every handler signature and every explicit type reference to `MsgContext` must be renamed to `MessageContext`.
```go
// before
func ping(ctx *laniakea.MsgContext, db *App) error { ... }
// after
func ping(ctx *laniakea.MessageContext, db *App) error { ... }
```
This includes `CommandExecutor`, `MiddlewareExecutor`, `SceneContext.MessageContext`, and any local variable type annotations.
### Runner builder change: `Onetime` and `Timeout` removed
The `Onetime(bool)` and `Timeout(duration)` builder methods have been replaced by `Every(duration)` and `Async(bool)`.
```go
// before — one-time sync
runner.Onetime(true).Async(false)
// after — one-time sync
runner.Async(false)
// before — periodic
runner.Timeout(5 * time.Minute)
// after — periodic
runner.Every(5 * time.Minute)
```
The default remains async one-shot (`Every(0).Async(true)`), so runners without a builder call are unaffected.
### Observer method renames
If you implement the `Observer` interface directly, rename the two affected methods:
| Before | After |
|---|---|
| `OnReceiveUpdate(UpdateReceivedEvent)` | `OnUpdateReceived(UpdateReceivedEvent)` |
| `OnHandledUpdate(UpdateHandledEvent)` | `OnUpdateHandled(UpdateHandledEvent)` |
### `Scene.PluginName` unexported
`Scene.PluginName` was a mutable public field. It is now unexported. Remove any reads or writes to this field; the framework assigns it during `AddPlugins(...)` registration.
### `SceneSession.Data` unexported
`SceneSession.Data []byte` was a public field. It is now unexported. Use the accessor helpers: `HasData()`, `BindData(...)`, `SaveData(...)`, `ClearData()`.
### `BotPayloadType*` constants are now `const`
`BotPayloadTypeJSON`, `BotPayloadTypeBase64`, `BotPayloadTypeCompact`, and `BotPayloadTypeCompactBase64` were `var`. They are now `const`. Any code assigning to them will fail to compile.
### Webhook error sentinels
Inline `errors.New(...)` error values returned from webhook startup have been replaced by exported sentinels. If you were comparing webhook startup errors with `==`, switch to `errors.Is(...)`.
## `v1.0.0-rc.12` ## `v1.0.0-rc.12`
+9 -9
@@ -1,12 +1,12 @@
# MsgContext RU # MessageContext RU
English version: [[MsgContext]] English version: [[MessageContext]]
Это краткая русскоязычная версия страницы про `MsgContext`. Полная и наиболее актуальная страница: [[MsgContext]]. Это краткая русскоязычная версия страницы про `MessageContext`. Полная и наиболее актуальная страница: [[MessageContext]].
## Что такое `MsgContext` ## Что такое `MessageContext`
`MsgContext` — это объект времени выполнения, который приходит в: `MessageContext` — это объект времени выполнения, который приходит в:
- обработчики команд; - обработчики команд;
- обработчики данных callback; - обработчики данных callback;
- middleware; - middleware;
@@ -18,7 +18,7 @@ English version: [[MsgContext]]
- разобранные аргументы команд и callback; - разобранные аргументы команд и callback;
- вспомогательные методы для reply, edit, delete, callback, drafts и localization. - вспомогательные методы для reply, edit, delete, callback, drafts и localization.
Полную матрицу маршрутизации и гарантий по полям `MsgContext` для разных update types смотри в [[Update-Routing-Model-RU]]. Полную матрицу маршрутизации и гарантий по полям `MessageContext` для разных update types смотри в [[Update-Routing-Model-RU]].
## Поля, которые используются чаще всего ## Поля, которые используются чаще всего
@@ -107,12 +107,12 @@ English version: [[MsgContext]]
## Drafts и localization ## Drafts и localization
У `MsgContext` есть: У `MessageContext` есть:
- `NewDraft()` - `NewDraft()`
- `NewDraftMarkdown()` - `NewDraftMarkdown()`
- `Translate(key)` - `Translate(key)`
Это делает `MsgContext` основной удобной точкой доступа почти для всего кода обработчиков. Это делает `MessageContext` основной удобной точкой доступа почти для всего кода обработчиков.
## `NewInlineKeyboard(...)` ## `NewInlineKeyboard(...)`
@@ -130,4 +130,4 @@ kb := ctx.NewInlineKeyboard(2)
- [[Inline-Keyboards-and-Payloads-RU]] - [[Inline-Keyboards-and-Payloads-RU]]
- [[Drafts-RU]] - [[Drafts-RU]]
- [[Localization-RU]] - [[Localization-RU]]
- [[MsgContext]] - [[MessageContext]]
+23 -23
@@ -1,8 +1,8 @@
# MsgContext # MessageContext
Russian version: [[MsgContext-RU]] Russian version: [[MessageContext-RU]]
`MsgContext` is the runtime object passed into command handlers, payload handlers, middleware, and update handlers. `MessageContext` is the runtime object passed into command handlers, payload handlers, middleware, and update handlers.
It gives you access to: It gives you access to:
- the incoming update - the incoming update
@@ -10,7 +10,7 @@ It gives you access to:
- parsed command or payload arguments - parsed command or payload arguments
- reply, edit, delete, callback, draft, and localization helpers - reply, edit, delete, callback, draft, and localization helpers
If you write handlers, `MsgContext` is the API surface you will use most often. If you write handlers, `MessageContext` is the API surface you will use most often.
For the full routing and field-guarantee matrix by update kind, see [[Update-Routing-Model]]. For the full routing and field-guarantee matrix by update kind, see [[Update-Routing-Model]].
@@ -63,7 +63,7 @@ Use `FromID` when you only need the identifier and do not want to keep checking
Use `Answer(...)` for the normal “reply with text” case. Use `Answer(...)` for the normal “reply with text” case.
```go ```go
func start(ctx *laniakea.MsgContext, db laniakea.NoData) error { func start(ctx *laniakea.MessageContext, db laniakea.NoData) error {
ctx.Answer("Welcome") ctx.Answer("Welcome")
return nil return nil
} }
@@ -76,7 +76,7 @@ This is the default high-level reply helper for plain text.
Use `AnswerLong(...)` when plain text may exceed Telegrams message limit. Use `AnswerLong(...)` when plain text may exceed Telegrams message limit.
```go ```go
func help(ctx *laniakea.MsgContext, db laniakea.NoData) error { func help(ctx *laniakea.MessageContext, db laniakea.NoData) error {
ctx.AnswerLong(buildLargeHelpText()) ctx.AnswerLong(buildLargeHelpText())
return nil return nil
} }
@@ -92,7 +92,7 @@ Important:
Use `Keyboard(...)` when you want to send a message with an inline keyboard. Use `Keyboard(...)` when you want to send a message with an inline keyboard.
```go ```go
func menu(ctx *laniakea.MsgContext, db laniakea.NoData) error { func menu(ctx *laniakea.MessageContext, db laniakea.NoData) error {
kb := ctx.NewInlineKeyboard(2). kb := ctx.NewInlineKeyboard(2).
AddCallbackButton("Profile", "profile.open"). AddCallbackButton("Profile", "profile.open").
AddCallbackButton("Settings", "settings.open") AddCallbackButton("Settings", "settings.open")
@@ -127,7 +127,7 @@ Use `laniakea.EscapeMarkdownV2(...)` for this.
Example: Example:
```go ```go
func whoami(ctx *laniakea.MsgContext, db laniakea.NoData) error { func whoami(ctx *laniakea.MessageContext, db laniakea.NoData) error {
name := laniakea.EscapeMarkdownV2(ctx.From.FirstName) name := laniakea.EscapeMarkdownV2(ctx.From.FirstName)
ctx.AnswerMarkdown("*User:* " + name) ctx.AnswerMarkdown("*User:* " + name)
return nil return nil
@@ -141,7 +141,7 @@ Once you already have an `AnswerMessage`, you can edit or delete it.
Example: Example:
```go ```go
func slowTask(ctx *laniakea.MsgContext, db laniakea.NoData) error { func slowTask(ctx *laniakea.MessageContext, db laniakea.NoData) error {
msg := ctx.Answer("Working...") msg := ctx.Answer("Working...")
if msg == nil { if msg == nil {
return nil return nil
@@ -173,27 +173,27 @@ When handling inline button callbacks, these helpers are especially useful.
Edits the callback-linked message. Edits the callback-linked message.
```go ```go
func approve(ctx *laniakea.MsgContext, db laniakea.NoData) error { func approve(ctx *laniakea.MessageContext, db laniakea.NoData) error {
ctx.EditCallback("Approved", nil) ctx.EditCallback("Approved", nil)
return nil return nil
} }
``` ```
### `AnswerCallback` ### `AnswerCbQuery`
Acknowledges the callback query itself. Acknowledges the callback query itself.
Use: Use:
- `AnswerCallback()` for empty acknowledgement - `AnswerCbQuery()` for empty acknowledgement
- `AnswerCallbackText(...)` for a short notice - `AnswerCbQueryText(...)` for a short notice
- `AnswerCallbackAlert(...)` for a visible alert - `AnswerCbQueryAlert(...)` for a visible alert
- `AnswerCallbackURL(...)` for redirect behavior - `AnswerCbQueryUrl(...)` for redirect behavior
Example: Example:
```go ```go
func approve(ctx *laniakea.MsgContext, db laniakea.NoData) error { func approve(ctx *laniakea.MessageContext, db laniakea.NoData) error {
ctx.AnswerCallbackText("Saved") ctx.AnswerCbQueryText("Saved")
ctx.EditCallback("Saved", nil) ctx.EditCallback("Saved", nil)
return nil return nil
} }
@@ -220,7 +220,7 @@ Caption rules differ from normal message text:
## Drafts ## Drafts
`MsgContext` also exposes draft creation helpers: `MessageContext` also exposes draft creation helpers:
- `NewDraft()` - `NewDraft()`
- `NewDraftMarkdown()` - `NewDraftMarkdown()`
@@ -246,7 +246,7 @@ This looks up text using the current users language when available and falls
Example: Example:
```go ```go
func ping(ctx *laniakea.MsgContext, db laniakea.NoData) error { func ping(ctx *laniakea.MessageContext, db laniakea.NoData) error {
ctx.Answer(ctx.Translate("ping.answer")) ctx.Answer(ctx.Translate("ping.answer"))
return nil return nil
} }
@@ -275,7 +275,7 @@ Use `SendAction(...)` to show activity like typing or uploading.
Example: Example:
```go ```go
func report(ctx *laniakea.MsgContext, db *App) error { func report(ctx *laniakea.MessageContext, db *App) error {
ctx.SendAction(tgapi.ChatActionTyping) ctx.SendAction(tgapi.ChatActionTyping)
text, err := db.BuildReport(ctx.FromID) text, err := db.BuildReport(ctx.FromID)
if err != nil { if err != nil {
@@ -293,7 +293,7 @@ This is especially useful for slower handlers.
A common pattern is: A common pattern is:
```go ```go
func profile(ctx *laniakea.MsgContext, db *App) error { func profile(ctx *laniakea.MessageContext, db *App) error {
user, err := db.LoadUser(ctx.FromID) user, err := db.LoadUser(ctx.FromID)
if err != nil { if err != nil {
return err return err
@@ -333,12 +333,12 @@ Do not pass raw user input to MarkdownV2 methods without escaping.
### Callback helpers only make sense in callback flow ### Callback helpers only make sense in callback flow
Methods like `EditCallback(...)` and `AnswerCallbackText(...)` depend on callback-specific context. Methods like `EditCallback(...)` and `AnswerCbQueryText(...)` depend on callback-specific context.
## A practical example ## A practical example
```go ```go
func settings(ctx *laniakea.MsgContext, db *App) error { func settings(ctx *laniakea.MessageContext, db *App) error {
kb := ctx.NewInlineKeyboard(1). kb := ctx.NewInlineKeyboard(1).
AddCallbackButton("Enable notifications", "settings.notifications.enable"). AddCallbackButton("Enable notifications", "settings.notifications.enable").
AddCallbackButton("Disable notifications", "settings.notifications.disable") AddCallbackButton("Disable notifications", "settings.notifications.disable")
+4 -4
@@ -1,6 +1,6 @@
# Policies # Policies
Policies — это полноценная модель правил авторизации в Laniakea. Политика работает поверх нормализованного `MsgContext` и переданных `AppData`, возвращает `nil`, если доступ разрешён, и возвращает ошибку, если действие нужно запретить или сама проверка не смогла корректно выполниться. Policies — это полноценная модель правил авторизации в Laniakea. Политика работает поверх нормализованного `MessageContext` и переданных `AppData`, возвращает `nil`, если доступ разрешён, и возвращает ошибку, если действие нужно запретить или сама проверка не смогла корректно выполниться.
Policies не вводят вторую модель выполнения. Они встраиваются в уже существующий middleware pipeline через `RequirePolicy(...)`, поэтому авторизация остаётся на том же пути маршрутизации, что и остальной фреймворк. Policies не вводят вторую модель выполнения. Они встраиваются в уже существующий middleware pipeline через `RequirePolicy(...)`, поэтому авторизация остаётся на том же пути маршрутизации, что и остальной фреймворк.
@@ -15,7 +15,7 @@ Policies не вводят вторую модель выполнения. Он
## Основной API ## Основной API
```go ```go
type Policy[T laniakea.AppData] func(ctx *laniakea.MsgContext, data T) error type Policy[T laniakea.AppData] func(ctx *laniakea.MessageContext, data T) error
func RequirePolicy[T laniakea.AppData](name string, p Policy[T]) Middleware[T] func RequirePolicy[T laniakea.AppData](name string, p Policy[T]) Middleware[T]
@@ -62,7 +62,7 @@ plugin.UsePolicy(
- `RequireBotAdmin(...)` - `RequireBotAdmin(...)`
- `RequireCallbackFromUser(...)` - `RequireCallbackFromUser(...)`
Эти helpers используют нормализованные данные `MsgContext`. В частности, chat-aware политики опираются на `Chat` и `ChatID`, которые теперь заполняются не только для message-backed обновлений. Эти helpers используют нормализованные данные `MessageContext`. В частности, chat-aware политики опираются на `Chat` и `ChatID`, которые теперь заполняются не только для message-backed обновлений.
## Композиция ## Композиция
@@ -133,6 +133,6 @@ policy := laniakea.NotPolicy(laniakea.RequirePrivateChat())
Связанные страницы: Связанные страницы:
- [[Middleware-RU]] - [[Middleware-RU]]
- [[MsgContext-RU]] - [[MessageContext-RU]]
- [[Commands-and-Plugins-RU]] - [[Commands-and-Plugins-RU]]
- [[Framework-Backlog-RU]] - [[Framework-Backlog-RU]]
+4 -4
@@ -1,6 +1,6 @@
# Policies # Policies
Policies are Laniakea's first-class authorization rules. A policy runs against the normalized `MsgContext` and injected `AppData`, returns `nil` when access is allowed, and returns an error when access should be denied or when the check itself fails. Policies are Laniakea's first-class authorization rules. A policy runs against the normalized `MessageContext` and injected `AppData`, returns `nil` when access is allowed, and returns an error when access should be denied or when the check itself fails.
Policies do not introduce a second execution model. They plug into the existing middleware pipeline through `RequirePolicy(...)`, so authorization stays on the same routing path as the rest of the framework. Policies do not introduce a second execution model. They plug into the existing middleware pipeline through `RequirePolicy(...)`, so authorization stays on the same routing path as the rest of the framework.
@@ -15,7 +15,7 @@ Policies do not introduce a second execution model. They plug into the existing
## Core API ## Core API
```go ```go
type Policy[T laniakea.AppData] func(ctx *laniakea.MsgContext, data T) error type Policy[T laniakea.AppData] func(ctx *laniakea.MessageContext, data T) error
func RequirePolicy[T laniakea.AppData](name string, p Policy[T]) Middleware[T] func RequirePolicy[T laniakea.AppData](name string, p Policy[T]) Middleware[T]
@@ -62,7 +62,7 @@ The current built-in helpers focus on common Telegram-specific access checks:
- `RequireBotAdmin(...)` - `RequireBotAdmin(...)`
- `RequireCallbackFromUser(...)` - `RequireCallbackFromUser(...)`
These helpers use normalized `MsgContext` data. In particular, chat-aware policies rely on `Chat` and `ChatID`, which are now populated for more update kinds than only message-backed ones. These helpers use normalized `MessageContext` data. In particular, chat-aware policies rely on `Chat` and `ChatID`, which are now populated for more update kinds than only message-backed ones.
## Composition ## Composition
@@ -133,6 +133,6 @@ Those features can be added later if the existing `Policy[T]` model proves too s
Related pages: Related pages:
- [[Middleware]] - [[Middleware]]
- [[MsgContext]] - [[MessageContext]]
- [[Commands-and-Plugins]] - [[Commands-and-Plugins]]
- [[Framework-Backlog]] - [[Framework-Backlog]]
+17 -17
@@ -14,14 +14,14 @@ type App struct{}
func (a *App) IsAdmin(userID int64) bool { return userID == 42 } func (a *App) IsAdmin(userID int64) bool { return userID == 42 }
admin := laniakea.NewPlugin[*App]("admin") admin := laniakea.NewPlugin[*App]("admin")
admin.AddMiddleware(laniakea.NewMiddleware("admin-only", func(ctx *laniakea.MsgContext, app *App) bool { admin.AddMiddleware(laniakea.NewMiddleware("admin-only", func(ctx *laniakea.MessageContext, app *App) bool {
return ctx.From != nil && app.IsAdmin(ctx.From.ID) return ctx.From != nil && app.IsAdmin(ctx.From.ID)
})) }))
admin.Command("reload", func(ctx *laniakea.MsgContext, app *App) error { admin.NewCommand(func(ctx *laniakea.MessageContext, app *App) error {
ctx.Answer("Admin command executed") ctx.Answer("Admin command executed")
return nil return nil
}) }, "reload")
``` ```
## Callback button flow ## Callback button flow
@@ -31,17 +31,17 @@ Use a payload handler for inline keyboard callbacks.
```go ```go
menu := laniakea.NewPlugin[laniakea.NoData]("menu") menu := laniakea.NewPlugin[laniakea.NoData]("menu")
menu.Command("menu", func(ctx *laniakea.MsgContext, db laniakea.NoData) error { menu.NewCommand(func(ctx *laniakea.MessageContext, db laniakea.NoData) error {
kb := ctx.NewInlineKeyboard(1) kb := ctx.NewInlineKeyboard(1)
kb.AddCallbackButton("Open settings", "settings") kb.AddCallbackButton("Open settings", "settings")
ctx.Keyboard("Choose an action", kb) ctx.Keyboard("Choose an action", kb)
return nil return nil
}) }, "menu")
menu.Payload("settings", func(ctx *laniakea.MsgContext, db laniakea.NoData) error { menu.NewPayload(func(ctx *laniakea.MessageContext, db laniakea.NoData) error {
ctx.EditCallback("Settings screen", nil) ctx.EditCallback("Settings screen", nil)
return nil return nil
}) }, "settings")
``` ```
## Long plain-text reply ## Long plain-text reply
@@ -49,11 +49,11 @@ menu.Payload("settings", func(ctx *laniakea.MsgContext, db laniakea.NoData) erro
Use `AnswerLong(...)` when you want explicit splitting into multiple safe Telegram messages. Use `AnswerLong(...)` when you want explicit splitting into multiple safe Telegram messages.
```go ```go
plugin.Command("report", func(ctx *laniakea.MsgContext, db *App) error { plugin.NewCommand(func(ctx *laniakea.MessageContext, db *App) error {
report := buildLargePlainTextReport() report := buildLargePlainTextReport()
ctx.AnswerLong(report) ctx.AnswerLong(report)
return nil return nil
}) }, "report")
``` ```
If you need an inline keyboard on the final chunk, use `KeyboardLong(...)`. If you need an inline keyboard on the final chunk, use `KeyboardLong(...)`.
@@ -71,10 +71,10 @@ l10n := laniakea.NewL10n("en").
bot.SetL10n(l10n) bot.SetL10n(l10n)
plugin.Command("start", func(ctx *laniakea.MsgContext, db *App) error { plugin.NewCommand(func(ctx *laniakea.MessageContext, db *App) error {
ctx.Answer(ctx.Translate("greeting")) ctx.Answer(ctx.Translate("greeting"))
return nil return nil
}) }, "start")
``` ```
## Non-command update handler ## Non-command update handler
@@ -82,7 +82,7 @@ plugin.Command("start", func(ctx *laniakea.MsgContext, db *App) error {
Use `AddUpdateHandler(...)` for Telegram update types that are outside the command and payload flow. Use `AddUpdateHandler(...)` for Telegram update types that are outside the command and payload flow.
```go ```go
plugin.AddUpdateHandler(tgapi.UpdateTypeInlineQuery, func(ctx *laniakea.MsgContext, db *App) error { plugin.AddUpdateHandler(tgapi.UpdateTypeInlineQuery, func(ctx *laniakea.MessageContext, db *App) error {
if ctx.From != nil { if ctx.From != nil {
ctx.Logger.Infoln("inline query from", ctx.From.ID) ctx.Logger.Infoln("inline query from", ctx.From.ID)
} }
@@ -97,7 +97,7 @@ This is usually cleaner than forcing non-command traffic through a command parse
Use drafts when you want to build a reply progressively and publish it once at the end. Use drafts when you want to build a reply progressively and publish it once at the end.
```go ```go
plugin.Command("build", func(ctx *laniakea.MsgContext, db *App) error { plugin.NewCommand(func(ctx *laniakea.MessageContext, db *App) error {
draft := ctx.NewDraft() draft := ctx.NewDraft()
if draft == nil { if draft == nil {
return nil return nil
@@ -111,7 +111,7 @@ plugin.Command("build", func(ctx *laniakea.MsgContext, db *App) error {
} }
return draft.Flush() return draft.Flush()
}) }, "build")
``` ```
## File upload with `tgapi` ## File upload with `tgapi`
@@ -119,15 +119,15 @@ plugin.Command("build", func(ctx *laniakea.MsgContext, db *App) error {
Use the higher-level handler flow for routing, but drop down to `tgapi` uploader methods when you need multipart upload behavior. Use the higher-level handler flow for routing, but drop down to `tgapi` uploader methods when you need multipart upload behavior.
```go ```go
plugin.Command("upload", func(ctx *laniakea.MsgContext, db *App) error { plugin.NewCommand(func(ctx *laniakea.MessageContext, db *App) error {
uploader := tgapi.NewUploader(ctx.API) uploader := tgapi.NewUploader(ctx.Api)
defer uploader.Close() defer uploader.Close()
_, err := uploader.SendPhoto(tgapi.UploadPhoto{ _, err := uploader.SendPhoto(tgapi.UploadPhoto{
ChatID: ctx.Msg.Chat.ID, ChatID: ctx.Msg.Chat.ID,
}, tgapi.NewUploaderFile("report.jpg", []byte("hello"))) }, tgapi.NewUploaderFile("report.jpg", []byte("hello")))
return err return err
}) }, "upload")
``` ```
If your exact uploader call differs, keep the general rule in mind: handler routing can stay high-level even when the send path needs `tgapi`. If your exact uploader call differs, keep the general rule in mind: handler routing can stay high-level even when the send path needs `tgapi`.
+49 -15
@@ -22,36 +22,45 @@ Runner — это фоновая или одноразовая задача, к
runner := laniakea.NewRunner("cleanup", fn) runner := laniakea.NewRunner("cleanup", fn)
``` ```
Потом конфигурируются методы builder: Методы builder:
- `Once(bool)` - `Every(time.Duration)` — интервал повторного запуска. Ноль (по умолчанию) означает одноразовый запуск; положительное значение — периодический.
- `Async(bool)` - `Async(bool)` — если `true` (по умолчанию), запускается в goroutine; если `false`, блокирует запуск runtime.
- `Every(duration)`
## Основные режимы ## Основные режимы
### Одноразовый sync ### Одноразовый async (по умолчанию)
- выполняется один раз; ```go
- блокирует запуск; runner := laniakea.NewRunner("prefetch", fn)
- полезен для работы, критичной на старте. ```
### Одноразовый async
- выполняется один раз; - выполняется один раз;
- стартует в goroutine; - стартует в goroutine;
- не блокирует запуск. - не блокирует запуск.
### Одноразовый sync
```go
runner := laniakea.NewRunner("warmup", fn).Async(false)
```
- выполняется один раз;
- блокирует запуск до завершения;
- полезен для работы, критичной на старте.
### Повторяющийся async ### Повторяющийся async
- работает циклически; ```go
runner := laniakea.NewRunner("cleanup", fn).Every(time.Minute)
```
- работает циклически с заданным интервалом;
- использует ticker; - использует ticker;
- живет до `ctx.Done()`. - живет до `ctx.Done()`.
## Невалидная конфигурация ## Невалидная конфигурация
Повторяющийся synchronous runner считается невалидным и пропускается с предупреждением. Повторяющийся synchronous runner (`Every(d > 0).Async(false)`) считается невалидным и пропускается с предупреждением — блокировать запуск бесконечно никогда не имеет смысла.
Также повторяющийся async runner без `Every(...)` пропускается.
## Когда стартуют фоновые задачи ## Когда стартуют фоновые задачи
@@ -59,6 +68,13 @@ runner := laniakea.NewRunner("cleanup", fn)
Это часть фазы выполнения, а не фазы сборки конфигурации. Это часть фазы выполнения, а не фазы сборки конфигурации.
## Обработка ошибок
Если runner возвращает ошибку:
- бот логирует предупреждение;
- через observer отправляется `ErrorEvent`;
- бот продолжает работу.
## Семантика остановки ## Семантика остановки
При корректной остановке бот: При корректной остановке бот:
@@ -67,9 +83,27 @@ runner := laniakea.NewRunner("cleanup", fn)
Поэтому код runner должен завершаться достаточно быстро. Поэтому код runner должен завершаться достаточно быстро.
## Практические примеры
### Периодическая очистка
```go
cleanup := laniakea.NewRunner("cleanup", func(bot *laniakea.Bot[*App]) error {
return bot.GetAppData().CleanupExpired()
}).Every(5 * time.Minute)
```
### Блокирующий запуск при старте
```go
warmup := laniakea.NewRunner("warmup", func(bot *laniakea.Bot[*App]) error {
return bot.GetAppData().WarmCaches()
}).Async(false)
```
## Рекомендации ## Рекомендации
- Для периодических задач используй повторяющийся async runner с timeout. - Для периодических задач используй повторяющийся async runner с `Every(...)`.
- Для критичной стартовой работы используй одноразовый sync runner. - Для критичной стартовой работы используй одноразовый sync runner.
- Не держи сложную бизнес-логику внутри runner; лучше делегируй ее в обычные сервисы приложения. - Не держи сложную бизнес-логику внутри runner; лучше делегируй ее в обычные сервисы приложения.
+34 -44
@@ -13,10 +13,9 @@ Each runner is built from:
- a function `func(*Bot[T]) error`; - a function `func(*Bot[T]) error`;
- execution flags configured through builder methods. - execution flags configured through builder methods.
Main builder methods: Builder methods:
- `Once(bool)` - `Every(time.Duration)` — sets the repeat interval. Zero (default) means run once; positive means repeat.
- `Async(bool)` - `Async(bool)` — if `true` (default), the runner runs in a goroutine; if `false`, it blocks runtime startup.
- `Every(duration)`
## Creating a runner ## Creating a runner
@@ -29,36 +28,21 @@ cleanup := laniakea.NewRunner("cleanup", func(bot *laniakea.Bot[*App]) error {
``` ```
By default, a new runner is: By default, a new runner is:
- asynchronous; - asynchronous (`Async(true)`);
- not one-time; - one-shot (`Every(0)`).
- configured with zero timeout.
That default means you almost always want to finish configuration before adding it to the bot. That default fires the runner once in a goroutine when the bot starts.
## Runner execution modes ## Runner execution modes
There are three meaningful configurations. There are three meaningful configurations.
### One-time synchronous ### One-time asynchronous (default)
```go ```go
runner := laniakea.NewRunner("warmup", fn). runner := laniakea.NewRunner("prefetch", fn)
Once(true). // or explicitly:
Async(false) runner := laniakea.NewRunner("prefetch", fn).Every(0).Async(true)
```
Behavior:
- runs once;
- blocks startup until it finishes;
- logs a warning if it takes longer than two seconds.
Use this for startup work that must complete before the bot is considered ready.
### One-time asynchronous
```go
runner := laniakea.NewRunner("prefetch", fn).
Once(true)
``` ```
Behavior: Behavior:
@@ -68,31 +52,37 @@ Behavior:
Use this for fire-and-forget startup work that is useful but not required before handling updates. Use this for fire-and-forget startup work that is useful but not required before handling updates.
### Repeating asynchronous ### One-time synchronous
```go ```go
runner := laniakea.NewRunner("cleanup", fn). runner := laniakea.NewRunner("warmup", fn).Async(false)
Every(time.Minute)
``` ```
Behavior: Behavior:
- runs on a ticker; - runs once;
- blocks startup until it finishes;
- logs a warning if it takes longer than two seconds.
Use this for startup work that must complete before the bot is considered ready.
### Repeating asynchronous
```go
runner := laniakea.NewRunner("cleanup", fn).Every(time.Minute)
```
Behavior:
- runs on a ticker with the configured interval;
- keeps running until `ctx.Done()` from the bot runtime context; - keeps running until `ctx.Done()` from the bot runtime context;
- is awaited during graceful shutdown. - is awaited during graceful shutdown.
Use this for recurring background jobs. Use this for recurring background jobs.
## Invalid configuration ## Invalid configurations
One configuration is intentionally treated as invalid: One configuration is intentionally treated as invalid and skipped with a warning:
- `Once(false).Async(false)` - `Every(d > 0).Async(false)` — a periodic sync runner blocks startup indefinitely, which is never correct.
That means:
- synchronous repeating runners are skipped;
- the bot logs a warning instead of trying to run them inline forever.
Also, repeating async runners with `Every(0)` are skipped with a warning.
## Registration ## Registration
@@ -125,6 +115,7 @@ Runner functions return `error`.
When a runner returns a non-nil error: When a runner returns a non-nil error:
- the bot logs a warning; - the bot logs a warning;
- an `ErrorEvent` is emitted through the observer;
- the process continues; - the process continues;
- the bot does not crash automatically. - the bot does not crash automatically.
@@ -152,12 +143,12 @@ cleanup := laniakea.NewRunner("cleanup", func(bot *laniakea.Bot[*App]) error {
}).Every(5 * time.Minute) }).Every(5 * time.Minute)
``` ```
### Startup warmup ### Startup warmup (blocking)
```go ```go
warmup := laniakea.NewRunner("warmup", func(bot *laniakea.Bot[*App]) error { warmup := laniakea.NewRunner("warmup", func(bot *laniakea.Bot[*App]) error {
return bot.GetAppData().WarmCaches() return bot.GetAppData().WarmCaches()
}).Once(true).Async(false) }).Async(false)
``` ```
### Background metrics push ### Background metrics push
@@ -179,11 +170,10 @@ metrics := laniakea.NewRunner("metrics", func(bot *laniakea.Bot[*App]) error {
## Caveats ## Caveats
- Runners do not receive `context.Context` directly; they receive `*Bot[T]`. - Runners do not receive `context.Context` directly; they receive `*Bot[T]`.
- Repeating sync runners are skipped. - Periodic sync runners are skipped with a warning.
- Repeating async runners without timeout are skipped.
- Slow one-time sync runners delay bot startup. - Slow one-time sync runners delay bot startup.
## Related pages ## Related pages
- [[Bot-Lifecycle]] - [[Bot-Lifecycle]]
- [[Testing-Bots-with-Laniakea]] - [[Testing-Bots-with-Laniakea]]
+8 -8
@@ -1,11 +1,11 @@
# Scenes # Scenes
Scenes — это слой маршрутизации Laniakea с сохранением состояния для многошаговых и модальных диалогов. Сцена регистрируется внутри плагина, запускается через `MsgContext`, хранится через `SessionStore` и получает обновления раньше обычной маршрутизации команд, пока её сессия активна. Scenes — это слой маршрутизации Laniakea с сохранением состояния для многошаговых и модальных диалогов. Сцена регистрируется внутри плагина, запускается через `MessageContext`, хранится через `SessionStore` и получает обновления раньше обычной маршрутизации команд, пока её сессия активна.
## Что дают сцены ## Что дают сцены
- Регистрацию через `Plugin.Scene(...)` и `Plugin.AddScene(...)`. - Регистрацию через `Plugin.NewScene(...)` и `Plugin.AddScene(...)`.
- Явный вход и выход через `MsgContext.EnterScene(...)`, `EnterSceneStep(...)` и `ExitScene()`. - Явный вход и выход через `MessageContext.EnterScene(...)`, `EnterSceneStep(...)` и `ExitScene()`.
- Области действия сессии на пользователя, чат или пару пользователь-чат. - Области действия сессии на пользователя, чат или пару пользователь-чат.
- Обработчики шагов, локальные команды сцены и резервный обработчик сообщений на уровне сцены. - Обработчики шагов, локальные команды сцены и резервный обработчик сообщений на уровне сцены.
- JSON-состояние сцены через `SceneContext.BindData(...)` и `SaveData(...)`. - JSON-состояние сцены через `SceneContext.BindData(...)` и `SaveData(...)`.
@@ -54,7 +54,7 @@ type SessionStore interface {
Сцены регистрируются внутри плагина в том же стиле, что и команды с данными callback. Сцены регистрируются внутри плагина в том же стиле, что и команды с данными callback.
```go ```go
plugin.Scene("signup"). plugin.NewScene("signup").
SetScope(laniakea.SceneScopeUserChat). SetScope(laniakea.SceneScopeUserChat).
SetEntry("ask_name"). SetEntry("ask_name").
OnStep("ask_name", askName). OnStep("ask_name", askName).
@@ -67,13 +67,13 @@ plugin.Scene("signup").
## Модель обработчиков ## Модель обработчиков
Обычные команды используют `*MsgContext`. Обработчики сцен используют `*SceneContext`. Обычные команды используют `*MessageContext`. Обработчики сцен используют `*SceneContext`.
```go ```go
type SceneHandler[T any] func(ctx *SceneContext, db T) (SceneResult, error) type SceneHandler[T any] func(ctx *SceneContext, db T) (SceneResult, error)
``` ```
`SceneContext` встраивает `*MsgContext` и добавляет вспомогательные методы для сцен: `SceneContext` встраивает `*MessageContext` и добавляет вспомогательные методы для сцен:
- `ctx.Stay()` - `ctx.Stay()`
- `ctx.Next(step)` - `ctx.Next(step)`
@@ -137,7 +137,7 @@ func askName(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error)
Точка входа из команды: Точка входа из команды:
```go ```go
func startSignup(ctx *laniakea.MsgContext, db *App) error { func startSignup(ctx *laniakea.MessageContext, db *App) error {
return ctx.EnterScene("signup") return ctx.EnterScene("signup")
} }
``` ```
@@ -174,6 +174,6 @@ func cancelSignup(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, er
Связанные страницы: Связанные страницы:
- [[Commands-and-Plugins-RU]] - [[Commands-and-Plugins-RU]]
- [[MsgContext-RU]] - [[MessageContext-RU]]
- [[Middleware-RU]] - [[Middleware-RU]]
- [[Bot-Lifecycle-RU]] - [[Bot-Lifecycle-RU]]
+8 -8
@@ -1,11 +1,11 @@
# Scenes # Scenes
Scenes are Laniakea's stateful routing layer for multi-step and modal bot flows. A scene is registered inside a plugin, entered through `MsgContext`, stored through `SessionStore`, and routed before normal command handling while the session is active. Scenes are Laniakea's stateful routing layer for multi-step and modal bot flows. A scene is registered inside a plugin, entered through `MessageContext`, stored through `SessionStore`, and routed before normal command handling while the session is active.
## What scenes give you ## What scenes give you
- Scene registration through `Plugin.Scene(...)` and `Plugin.AddScene(...)`. - Scene registration through `Plugin.NewScene(...)` and `Plugin.AddScene(...)`.
- Explicit entry and exit through `MsgContext.EnterScene(...)`, `EnterSceneStep(...)`, and `ExitScene()`. - Explicit entry and exit through `MessageContext.EnterScene(...)`, `EnterSceneStep(...)`, and `ExitScene()`.
- Per-user, per-chat, or per-user-chat session scopes. - Per-user, per-chat, or per-user-chat session scopes.
- Step handlers, scene-local commands, and a scene-level message fallback. - Step handlers, scene-local commands, and a scene-level message fallback.
- JSON-backed scene state through `SceneContext.BindData(...)` and `SaveData(...)`. - JSON-backed scene state through `SceneContext.BindData(...)` and `SaveData(...)`.
@@ -54,7 +54,7 @@ Recommended default:
Scenes are registered inside plugins in the same style as commands and payloads. Scenes are registered inside plugins in the same style as commands and payloads.
```go ```go
plugin.Scene("signup"). plugin.NewScene("signup").
SetScope(laniakea.SceneScopeUserChat). SetScope(laniakea.SceneScopeUserChat).
SetEntry("ask_name"). SetEntry("ask_name").
OnStep("ask_name", askName). OnStep("ask_name", askName).
@@ -67,13 +67,13 @@ plugin.Scene("signup").
## Handler model ## Handler model
Normal commands use `*MsgContext`. Scene handlers use `*SceneContext`. Normal commands use `*MessageContext`. Scene handlers use `*SceneContext`.
```go ```go
type SceneHandler[T any] func(ctx *SceneContext, db T) (SceneResult, error) type SceneHandler[T any] func(ctx *SceneContext, db T) (SceneResult, error)
``` ```
`SceneContext` embeds `*MsgContext` and adds scene helpers: `SceneContext` embeds `*MessageContext` and adds scene helpers:
- `ctx.Stay()` - `ctx.Stay()`
- `ctx.Next(step)` - `ctx.Next(step)`
@@ -137,7 +137,7 @@ The store contract stays intentionally small because `Data []byte` is storage-ag
Command entry: Command entry:
```go ```go
func startSignup(ctx *laniakea.MsgContext, db *App) error { func startSignup(ctx *laniakea.MessageContext, db *App) error {
return ctx.EnterScene("signup") return ctx.EnterScene("signup")
} }
``` ```
@@ -174,6 +174,6 @@ func cancelSignup(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, er
Related pages: Related pages:
- [[Commands-and-Plugins]] - [[Commands-and-Plugins]]
- [[MsgContext]] - [[MessageContext]]
- [[Middleware]] - [[Middleware]]
- [[Bot-Lifecycle]] - [[Bot-Lifecycle]]
+3 -3
@@ -10,7 +10,7 @@ Laniakea хорошо тестируется обычными Go unit tests.
В репозитории уже используются паттерны вроде: В репозитории уже используются паттерны вроде:
- fake HTTP transport для `tgapi`; - fake HTTP transport для `tgapi`;
- прямые тесты для вспомогательных методов `MsgContext`; - прямые тесты для вспомогательных методов `MessageContext`;
- routing tests для commands и payloads; - routing tests для commands и payloads;
- runner tests. - runner tests.
@@ -35,7 +35,7 @@ Laniakea хорошо тестируется обычными Go unit tests.
## Тесты для логики обработчиков ## Тесты для логики обработчиков
Для тестов уровня обработчика обычно полезно: Для тестов уровня обработчика обычно полезно:
- собрать `MsgContext`; - собрать `MessageContext`;
- вызвать обработчик напрямую; - вызвать обработчик напрямую;
- проверить побочные эффекты и ответы. - проверить побочные эффекты и ответы.
@@ -63,5 +63,5 @@ Laniakea хорошо тестируется обычными Go unit tests.
## Что читать дальше ## Что читать дальше
- [[Runners-RU]] - [[Runners-RU]]
- [[MsgContext-RU]] - [[MessageContext-RU]]
- [[Testing-Bots-with-Laniakea]] - [[Testing-Bots-with-Laniakea]]
+7 -7
@@ -21,15 +21,15 @@ Good test targets include:
The most practical approach is: The most practical approach is:
1. isolate one behavior; 1. isolate one behavior;
2. create a small bot, plugin, or `MsgContext`; 2. create a small bot, plugin, or `MessageContext`;
3. use a fake HTTP client when you need to inspect Telegram requests; 3. use a fake HTTP client when you need to inspect Telegram requests;
4. assert the outgoing request shape or returned behavior directly. 4. assert the outgoing request shape or returned behavior directly.
This keeps tests fast and independent from real Telegram infrastructure. This keeps tests fast and independent from real Telegram infrastructure.
## Testing `MsgContext` helpers ## Testing `MessageContext` helpers
Many helper methods can be tested by constructing a `MsgContext` directly. Many helper methods can be tested by constructing a `MessageContext` directly.
Common ingredients: Common ingredients:
- a fake `tgapi.API` with a custom `http.Client`; - a fake `tgapi.API` with a custom `http.Client`;
@@ -39,7 +39,7 @@ Common ingredients:
Example pattern: Example pattern:
```go ```go
ctx := &laniakea.MsgContext{ ctx := &laniakea.MessageContext{
Api: api, Api: api,
Msg: &tgapi.Message{Chat: &tgapi.Chat{ID: 42, Type: string(tgapi.ChatTypePrivate)}}, Msg: &tgapi.Message{Chat: &tgapi.Chat{ID: 42, Type: string(tgapi.ChatTypePrivate)}},
Logger: slog.CreateLogger(), Logger: slog.CreateLogger(),
@@ -64,7 +64,7 @@ This is especially useful for:
- split long-message requests; - split long-message requests;
- keyboard attachment behavior. - keyboard attachment behavior.
The repository already uses this pattern extensively for `MsgContext` helper tests. The repository already uses this pattern extensively for `MessageContext` helper tests.
## Testing long replies ## Testing long replies
@@ -96,7 +96,7 @@ For routing tests, create a small bot with:
Then call the bot's handling path in a focused test and assert: Then call the bot's handling path in a focused test and assert:
- which handler was called; - which handler was called;
- what `MsgContext` fields were populated; - what `MessageContext` fields were populated;
- whether context mutations leaked across plugins. - whether context mutations leaked across plugins.
This is particularly useful for: This is particularly useful for:
@@ -184,4 +184,4 @@ When you fix a bug, consider adding a test for:
- [[Commands-and-Plugins]] - [[Commands-and-Plugins]]
- [[Error-Handling]] - [[Error-Handling]]
- [[Runners]] - [[Runners]]
- [[MsgContext]] - [[MessageContext]]
+7 -7
@@ -2,9 +2,9 @@
English version: [[Update-Routing-Model]] English version: [[Update-Routing-Model]]
Эта страница фиксирует текущий контракт маршрутизации обновлений в Laniakea: какие Telegram update types идут через команды, какие через данные callback, какие через generic update handlers, и какие гарантии по `MsgContext` существуют в каждом потоке. Эта страница фиксирует текущий контракт маршрутизации обновлений в Laniakea: какие Telegram update types идут через команды, какие через данные callback, какие через generic update handlers, и какие гарантии по `MessageContext` существуют в каждом потоке.
Это описание текущего поведения фреймворка, а не отдельная новая API-поверхность. Цель страницы — сделать уже существующую маршрутизацию и нормализацию `MsgContext` явной и тестируемой. Это описание текущего поведения фреймворка, а не отдельная новая API-поверхность. Цель страницы — сделать уже существующую маршрутизацию и нормализацию `MessageContext` явной и тестируемой.
## Верхнеуровневая маршрутизация ## Верхнеуровневая маршрутизация
@@ -23,7 +23,7 @@ English version: [[Update-Routing-Model]]
## Что делает `prepareUpdateCtx(...)` ## Что делает `prepareUpdateCtx(...)`
До маршрутизации обработчиков Laniakea нормализует `MsgContext` из входящего Telegram update. До маршрутизации обработчиков Laniakea нормализует `MessageContext` из входящего Telegram update.
Эта нормализация намеренно шире, чем command и payload routing: Эта нормализация намеренно шире, чем command и payload routing:
- некоторые update types заполняют `ctx.Msg` - некоторые update types заполняют `ctx.Msg`
@@ -101,12 +101,12 @@ Generic update handlers регистрируются через `Plugin.AddUpdat
- `ctx.Text` не нормализуется - `ctx.Text` не нормализуется
- `ctx.Args` не нормализуется - `ctx.Args` не нормализуется
- `ctx.Prefix` не нормализуется - `ctx.Prefix` не нормализуется
- каждый plugin update handler получает свою копию `MsgContext` struct - каждый plugin update handler получает свою копию `MessageContext` struct
Важное ограничение: Важное ограничение:
- копируется сам `MsgContext`, но не обещается глубокая копия всех вложенных Telegram-структур - копируется сам `MessageContext`, но не обещается глубокая копия всех вложенных Telegram-структур
## Нормализованные поля `MsgContext` по видам update ## Нормализованные поля `MessageContext` по видам update
### Update types, которые несут message ### Update types, которые несут message
@@ -176,6 +176,6 @@ Generic update handlers регистрируются через `Plugin.AddUpdat
## Связанные страницы ## Связанные страницы
- [[Commands-and-Plugins-RU]] - [[Commands-and-Plugins-RU]]
- [[MsgContext-RU]] - [[MessageContext-RU]]
- [[Scenes-RU]] - [[Scenes-RU]]
- [[Bot-Lifecycle-RU]] - [[Bot-Lifecycle-RU]]
+6 -6
@@ -2,9 +2,9 @@
Russian version: [[Update-Routing-Model-RU]] Russian version: [[Update-Routing-Model-RU]]
This page defines the current update-routing contract in Laniakea: which Telegram update kinds go through command routing, which go through payload routing, which go through generic update handlers, and what `MsgContext` guarantees exist in each path. This page defines the current update-routing contract in Laniakea: which Telegram update kinds go through command routing, which go through payload routing, which go through generic update handlers, and what `MessageContext` guarantees exist in each path.
This is a model of current framework behavior, not a second API surface. The goal is to make the existing routing and `MsgContext` normalization explicit and testable. This is a model of current framework behavior, not a second API surface. The goal is to make the existing routing and `MessageContext` normalization explicit and testable.
## Top-level routing ## Top-level routing
@@ -23,7 +23,7 @@ Important:
## What `prepareUpdateCtx(...)` does ## What `prepareUpdateCtx(...)` does
Before handler routing, Laniakea normalizes a `MsgContext` from the incoming Telegram update. Before handler routing, Laniakea normalizes a `MessageContext` from the incoming Telegram update.
That normalization is intentionally broader than command and payload routing: That normalization is intentionally broader than command and payload routing:
- some update kinds populate `ctx.Msg` - some update kinds populate `ctx.Msg`
@@ -101,12 +101,12 @@ General guarantees:
- `ctx.Text` is not normalized - `ctx.Text` is not normalized
- `ctx.Args` is not normalized - `ctx.Args` is not normalized
- `ctx.Prefix` is not normalized - `ctx.Prefix` is not normalized
- each plugin update handler receives its own copied `MsgContext` struct - each plugin update handler receives its own copied `MessageContext` struct
Important limitation: Important limitation:
- the copied context is an isolated struct copy, not a deep copy of all nested Telegram payload objects - the copied context is an isolated struct copy, not a deep copy of all nested Telegram payload objects
## Normalized `MsgContext` fields by update kind ## Normalized `MessageContext` fields by update kind
### Message-backed update kinds ### Message-backed update kinds
@@ -176,6 +176,6 @@ Important:
## Related pages ## Related pages
- [[Commands-and-Plugins]] - [[Commands-and-Plugins]]
- [[MsgContext]] - [[MessageContext]]
- [[Scenes]] - [[Scenes]]
- [[Bot-Lifecycle]] - [[Bot-Lifecycle]]
+2 -2
@@ -5,8 +5,8 @@ English version: [[Webhook-Runtime]]
Эта страница объясняет bot-level webhook runtime в Laniakea: как работает `RunWebhookWithContext(...)`, чем он отличается от низкоуровневых webhook-вызовов в `tgapi` и какие runtime-гарантии он делит с polling-режимом. Эта страница объясняет bot-level webhook runtime в Laniakea: как работает `RunWebhookWithContext(...)`, чем он отличается от низкоуровневых webhook-вызовов в `tgapi` и какие runtime-гарантии он делит с polling-режимом.
Важно про naming: Важно про naming:
- публичный API использует идиоматичное написание `Webhook` в идентификаторах вроде `RunWebhookWithContext(...)`, `RunWebhook(...)` и `BotWebhookOpts`; - текущий публичный API использует историческое написание `WebHook` в идентификаторах вроде `RunWebhookWithContext(...)`, `RunWebhook(...)` и `BotWebhookOpts`;
- в примерах остаются реальные имена Go API. - в тексте страницы используется обычное слово "webhook", но в примерах остаются реальные имена Go API.
## Когда использовать webhook runtime ## Когда использовать webhook runtime
+2 -2
@@ -5,8 +5,8 @@ Russian version: [[Webhook-Runtime-RU]]
This page explains the bot-level webhook runtime in Laniakea: how `RunWebhookWithContext(...)` works, what it owns, how it differs from low-level `tgapi` webhook calls, and what runtime guarantees it shares with polling mode. This page explains the bot-level webhook runtime in Laniakea: how `RunWebhookWithContext(...)` works, what it owns, how it differs from low-level `tgapi` webhook calls, and what runtime guarantees it shares with polling mode.
Important naming note: Important naming note:
- the public API uses idiomatic `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`;
- examples keep the actual Go API names. - this page uses the more common English term "webhook" for readability, but examples keep the actual Go API names.
## When to use webhook runtime ## When to use webhook runtime
+1 -1
@@ -6,7 +6,7 @@
- [[Getting-Started]] - [[Getting-Started]]
- [[Bot-Options-and-Configuration]] - [[Bot-Options-and-Configuration]]
- [[Commands-and-Plugins]] - [[Commands-and-Plugins]]
- [[MsgContext]] - [[MessageContext]]
## Runtime and Architecture ## Runtime and Architecture
- [[Bot-Lifecycle]] - [[Bot-Lifecycle]]
+4 -4
@@ -21,14 +21,14 @@ English version: [[tgapi-Overview]]
Это разделение специально сделано, чтобы JSON methods и file upload methods не смешивались в одну слишком размытую abstraction. Это разделение специально сделано, чтобы JSON methods и file upload methods не смешивались в одну слишком размытую abstraction.
## Когда использовать `MsgContext`, а когда `tgapi` ## Когда использовать `MessageContext`, а когда `tgapi`
Используй `MsgContext`, когда: Используй `MessageContext`, когда:
- ты уже внутри handler'а; - ты уже внутри handler'а;
- нужен обычный поток reply/edit/delete/callback. - нужен обычный поток reply/edit/delete/callback.
Используй `tgapi`, когда: Используй `tgapi`, когда:
- у `MsgContext` нет нужного вспомогательного метода; - у `MessageContext` нет нужного вспомогательного метода;
- ты работаешь вне потока обработчика; - ты работаешь вне потока обработчика;
- нужен более низкоуровневый контроль; - нужен более низкоуровневый контроль;
- нужно работать с uploads/downloads напрямую. - нужно работать с uploads/downloads напрямую.
@@ -100,7 +100,7 @@ _, err := uploader.SendPhoto(tgapi.UploadPhoto{
## Что читать дальше ## Что читать дальше
- [[MsgContext-RU]] - [[MessageContext-RU]]
- [[Inline-Keyboards-and-Payloads-RU]] - [[Inline-Keyboards-and-Payloads-RU]]
- [[Rate-Limiting-RU]] - [[Rate-Limiting-RU]]
- [[tgapi-Overview]] - [[tgapi-Overview]]
+6 -6
@@ -2,7 +2,7 @@
Russian version: [[tgapi-Overview-RU]] Russian version: [[tgapi-Overview-RU]]
`tgapi` is the low-level Telegram Bot API layer used under Laniakeas higher-level bot runtime. Use it when you need direct access to Telegram methods, explicit parameter structs, upload control, or raw request building that sits below plugins and `MsgContext` helpers. `tgapi` is the low-level Telegram Bot API layer used under Laniakeas higher-level bot runtime. Use it when you need direct access to Telegram methods, explicit parameter structs, upload control, or raw request building that sits below plugins and `MessageContext` helpers.
## The important split first ## The important split first
@@ -20,7 +20,7 @@ If you stay on the typed method surface, you usually do not need to think about
## The normal layering ## The normal layering
In practice, Laniakea has three levels: In practice, Laniakea has three levels:
- high-level handler helpers on `MsgContext`; - high-level handler helpers on `MessageContext`;
- runtime structure on `Bot`, plugins, and middleware; - runtime structure on `Bot`, plugins, and middleware;
- low-level Telegram access in `tgapi`. - low-level Telegram access in `tgapi`.
@@ -29,7 +29,7 @@ In practice, Laniakea has three levels:
## When to use `tgapi` directly ## When to use `tgapi` directly
Use `tgapi` directly when: Use `tgapi` directly when:
- a `MsgContext` helper does not expose the Telegram feature you need; - a `MessageContext` helper does not expose the Telegram feature you need;
- you need a Telegram method outside the high-level command/payload flow; - you need a Telegram method outside the high-level command/payload flow;
- you want explicit control over params, parse modes, message edits, or uploads; - you want explicit control over params, parse modes, message edits, or uploads;
- you are writing infrastructure code rather than command logic. - you are writing infrastructure code rather than command logic.
@@ -43,7 +43,7 @@ Typical examples where `tgapi` is the better tool:
- setting bot metadata or command scopes directly; - setting bot metadata or command scopes directly;
- file downloads and streaming; - file downloads and streaming;
- multipart uploads; - multipart uploads;
- one-off Telegram methods that do not have a `MsgContext` wrapper. - one-off Telegram methods that do not have a `MessageContext` wrapper.
## Typed methods first ## Typed methods first
@@ -244,7 +244,7 @@ Related page:
As a rule of thumb: As a rule of thumb:
- use `MsgContext` when responding to the current update; - use `MessageContext` when responding to the current update;
- use `Bot` and plugins when structuring runtime behavior; - use `Bot` and plugins when structuring runtime behavior;
- use `tgapi` when you need direct Telegram method control; - use `tgapi` when you need direct Telegram method control;
- use raw `NewRequest` or `NewUploaderRequest` only as the final fallback. - use raw `NewRequest` or `NewUploaderRequest` only as the final fallback.
@@ -254,7 +254,7 @@ That boundary keeps normal bot code ergonomic without hiding Telegram-specific c
## Related pages ## Related pages
- [[Getting-Started]] for the normal high-level bot setup path. - [[Getting-Started]] for the normal high-level bot setup path.
- [[MsgContext]] for handler-time reply helpers. - [[MessageContext]] for handler-time reply helpers.
- [[Inline-Keyboards-and-Payloads]] for callback button construction. - [[Inline-Keyboards-and-Payloads]] for callback button construction.
- [[Bot-Lifecycle]] for runtime startup and shutdown responsibilities around `Bot`, `API`, and `Uploader`. - [[Bot-Lifecycle]] for runtime startup and shutdown responsibilities around `Bot`, `API`, and `Uploader`.
- [[Rate-Limiting]] for limiter and `retry_after` behavior. - [[Rate-Limiting]] for limiter and `retry_after` behavior.