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