REPOSITORY / ScuroNeko/Laniakea

Wiki

KNOWLEDGE REPOSITORY

Add update routing contract pages

Document the current update schema contract in EN and RU

Link the new routing model from Home and MsgContext pages
2026-03-30 00:38:51 +03:00
parent 72439e8224
commit 443045d509
6 changed files with 368 additions and 0 deletions
+1
@@ -19,6 +19,7 @@ English version: [[Home]]
- [[Runners-RU]] - [[Runners-RU]]
- [[Error-Handling-RU]] - [[Error-Handling-RU]]
- [[Logging-RU]] - [[Logging-RU]]
- [[Update-Routing-Model-RU]]
- [[Scenes-RU]] - [[Scenes-RU]]
## Telegram API и взаимодействие ## Telegram API и взаимодействие
+1
@@ -23,6 +23,7 @@ Use this wiki as the structured companion to the README: start with setup, then
- [[Runners]] - [[Runners]]
- [[Error-Handling]] - [[Error-Handling]]
- [[Logging]] - [[Logging]]
- [[Update-Routing-Model]]
- [[Scenes]] - [[Scenes]]
## Telegram API and Interaction ## Telegram API and Interaction
+2
@@ -18,6 +18,8 @@ English version: [[MsgContext]]
- разобранные аргументы команд и callback; - разобранные аргументы команд и callback;
- вспомогательные методы для reply, edit, delete, callback, drafts и localization. - вспомогательные методы для reply, edit, delete, callback, drafts и localization.
Полную матрицу маршрутизации и гарантий по полям `MsgContext` для разных update types смотри в [[Update-Routing-Model-RU]].
## Поля, которые используются чаще всего ## Поля, которые используются чаще всего
### `Text` ### `Text`
+2
@@ -12,6 +12,8 @@ It gives you access to:
If you write handlers, `MsgContext` is the API surface you will use most often. If you write handlers, `MsgContext` is the API surface you will use most often.
For the full routing and field-guarantee matrix by update kind, see [[Update-Routing-Model]].
## The fields you will use first ## The fields you will use first
### `Text` ### `Text`
+181
@@ -0,0 +1,181 @@
# Update Routing Model RU
English version: [[Update-Routing-Model]]
Эта страница фиксирует текущий контракт маршрутизации обновлений в Laniakea: какие Telegram update types идут через команды, какие через данные callback, какие через generic update handlers, и какие гарантии по `MsgContext` существуют в каждом потоке.
Это описание текущего поведения фреймворка, а не отдельная новая API-поверхность. Цель страницы — сделать уже существующую маршрутизацию и нормализацию `MsgContext` явной и тестируемой.
## Верхнеуровневая маршрутизация
Сейчас Laniakea маршрутизирует обновления так:
| Update type | Routing path |
|---|---|
| `message` | command flow |
| `channel_post` | command flow |
| `callback_query` | payload flow |
| всё остальное | `AddUpdateHandler(...)` |
Важно:
- `message`, `channel_post` и `callback_query` зарезервированы и не должны регистрироваться через `AddUpdateHandler(...)`.
- сцены проверяются раньше обычной маршрутизации, но текущая модель сцен всё ещё по сути ориентирована на сообщения.
## Что делает `prepareUpdateCtx(...)`
До маршрутизации обработчиков Laniakea нормализует `MsgContext` из входящего Telegram update.
Эта нормализация намеренно шире, чем command и payload routing:
- некоторые update types заполняют `ctx.Msg`
- некоторые заполняют `ctx.From` и `ctx.FromID`
- callback queries могут заполнять callback target поля
- `ctx.Text`, `ctx.Args` и `ctx.Prefix` на этом этапе **не** заполняются
Это важное разделение:
- `prepareUpdateCtx(...)` задаёт сырой нормализованный shape контекста
- routing потом определяет, какой handler path получит этот контекст
## Контракт command flow
Command flow применяется только к:
- `message`
- `channel_post`
Когда команда совпала, действуют такие гарантии:
- `ctx.Update` всегда заполнен
- `ctx.Msg` заполнен
- `ctx.Prefix` содержит совпавший префикс команды
- `ctx.Text` содержит хвост после имени команды
- `ctx.Args` содержит `strings.Fields(ctx.Text)`
- `ctx.Logger` переключается на logger совпавшего плагина, если он задан
Не гарантируется:
- `ctx.From`
- `ctx.FromID`
Показательный edge case:
- `channel_post`, пришедший через `sender_chat`, всё равно даёт `ctx.Msg`
- но `ctx.From` может быть `nil`
- а `ctx.FromID` может оставаться `0`
## Контракт payload flow
Payload flow применяется только к:
- `callback_query`
Базовые гарантии:
- `ctx.Update` всегда заполнен
- `ctx.CallbackQueryId` заполнен
- `ctx.From` и `ctx.FromID` заполняются, если Telegram прислал user
- `ctx.Args` заполняется из декодированных данных callback
- `ctx.Logger` переключается на logger совпавшего плагина, если он задан
Не гарантируется:
- `ctx.Text`
- `ctx.Prefix`
У payload flow есть две формы target.
### Callback query, привязанный к обычному сообщению
Гарантии:
- `ctx.Msg` заполнен
- `ctx.CallbackMsgId` заполнен
- `ctx.InlineMsgId == ""`
### Callback query, привязанный к inline message
Гарантии:
- `ctx.Msg == nil`
- `ctx.CallbackMsgId == 0`
- `ctx.InlineMsgId` заполнен
## Контракт generic update handlers
Generic update handlers регистрируются через `Plugin.AddUpdateHandler(...)`.
Они применяются ко всем update types вне зарезервированного command/payload flow.
Общие гарантии:
- `ctx.Update` всегда заполнен
- `ctx.Text` не нормализуется
- `ctx.Args` не нормализуется
- `ctx.Prefix` не нормализуется
- каждый plugin update handler получает свою копию `MsgContext` struct
Важное ограничение:
- копируется сам `MsgContext`, но не обещается глубокая копия всех вложенных Telegram-структур
## Нормализованные поля `MsgContext` по видам update
### Update types, которые несут message
| Update type | `ctx.Msg` | `ctx.From` / `ctx.FromID` |
|---|---|---|
| `message` | да | да, если есть `Msg.From` |
| `edited_message` | да | да, если есть `Msg.From` |
| `channel_post` | да | только если есть `Msg.From` |
| `edited_channel_post` | да | только если есть `Msg.From` |
| `business_message` | да | да, если есть `Msg.From` |
| `edited_business_message` | да | да, если есть `Msg.From` |
Важно:
- message-backed не означает command-routed
- `edited_message`, `edited_channel_post`, `business_message` и `edited_business_message` сейчас автоматически не попадают в command flow
### Update types с user, но без message
| Update type | `ctx.Msg` | `ctx.From` / `ctx.FromID` |
|---|---|---|
| `inline_query` | нет | да |
| `chosen_inline_result` | нет | да |
| `shipping_query` | нет | да |
| `pre_checkout_query` | нет | да |
| `purchased_paid_media` | нет | да |
| `my_chat_member` | нет | да |
| `chat_member` | нет | да |
| `chat_join_request` | нет | да |
| `business_connection` | нет | да |
| `poll_answer` | нет | да |
| `message_reaction` | нет | да, если Telegram прислал `User` |
| `chat_boost` | нет | да |
| `removed_chat_boost` | нет | да |
### Callback-specific update type
| Update type | `ctx.Msg` | `ctx.From` / `ctx.FromID` | Спецполя |
|---|---|---|---|
| `callback_query` с `Message` | да | да | `CallbackQueryId`, `CallbackMsgId` |
| `callback_query` с `InlineMessageID` | нет | да | `CallbackQueryId`, `InlineMsgId` |
### Update types без нормализованных гарантий по user/message
| Update type | `ctx.Msg` | `ctx.From` / `ctx.FromID` |
|---|---|---|
| `poll` | нет | нет |
| `message_reaction_count` | нет | нет |
| `deleted_business_messages` | нет | нет |
| `unknown` | нет | нет |
## Что уже достаточно стабильно
- `message` и `channel_post` остаются в command flow
- `callback_query` остаётся в payload flow
- `prepareUpdateCtx(...)` сам по себе не заполняет `Text`, `Args` и `Prefix`
- command parsing заполняет `Text`, `Args` и `Prefix`
- payload decoding заполняет `Args`, но не `Text`
- callback target разделяется на chat-message callbacks и inline-message callbacks
- `edited_message` и `edited_channel_post` не входят в command routing
## Что пока намеренно не закрыто
- должны ли сцены оставаться только message-driven или получить более широкую update model
- должны ли edited/business message-backed updates когда-нибудь получить command-style routing
- стоит ли оформлять “message-backed update” как отдельную внутреннюю концепцию фреймворка
## Связанные страницы
- [[Commands-and-Plugins-RU]]
- [[MsgContext-RU]]
- [[Scenes-RU]]
- [[Bot-Lifecycle-RU]]
+181
@@ -0,0 +1,181 @@
# Update Routing Model
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 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.
## Top-level routing
Laniakea currently routes updates like this:
| Update type | Routing path |
|---|---|
| `message` | command flow |
| `channel_post` | command flow |
| `callback_query` | payload flow |
| everything else | `AddUpdateHandler(...)` |
Important:
- `message`, `channel_post`, and `callback_query` are reserved from `AddUpdateHandler(...)`.
- scenes are checked before normal routing, but the current scene model is still fundamentally message-driven.
## What `prepareUpdateCtx(...)` does
Before handler routing, Laniakea normalizes a `MsgContext` from the incoming Telegram update.
That normalization is intentionally broader than command and payload routing:
- some update kinds populate `ctx.Msg`
- some populate `ctx.From` and `ctx.FromID`
- callback queries may populate callback-target fields
- `ctx.Text`, `ctx.Args`, and `ctx.Prefix` are **not** populated by this stage
This is an important distinction:
- `prepareUpdateCtx(...)` defines the raw normalized context shape
- routing then decides which handler path receives that context
## Command flow contract
Command flow applies only to:
- `message`
- `channel_post`
When a command matches, these guarantees apply:
- `ctx.Update` is always present
- `ctx.Msg` is present
- `ctx.Prefix` contains the matched command prefix
- `ctx.Text` contains the parsed tail after the command name
- `ctx.Args` contains `strings.Fields(ctx.Text)`
- `ctx.Logger` switches to the matched plugin logger when one exists
Not guaranteed:
- `ctx.From`
- `ctx.FromID`
Example edge case:
- a `channel_post` sent via `sender_chat` still has `ctx.Msg`
- but `ctx.From` may be `nil`
- and `ctx.FromID` may stay `0`
## Payload flow contract
Payload flow applies only to:
- `callback_query`
Base guarantees:
- `ctx.Update` is always present
- `ctx.CallbackQueryId` is populated
- `ctx.From` and `ctx.FromID` are populated when Telegram includes a user
- `ctx.Args` is populated from decoded payload args
- `ctx.Logger` switches to the matched plugin logger when one exists
Not guaranteed:
- `ctx.Text`
- `ctx.Prefix`
There are two payload target shapes.
### Callback query targeting a chat message
Guarantees:
- `ctx.Msg` is present
- `ctx.CallbackMsgId` is populated
- `ctx.InlineMsgId == ""`
### Callback query targeting an inline message
Guarantees:
- `ctx.Msg == nil`
- `ctx.CallbackMsgId == 0`
- `ctx.InlineMsgId` is populated
## Generic update handler contract
Generic update handlers are registered through `Plugin.AddUpdateHandler(...)`.
They apply to all update kinds outside the reserved command/payload flow.
General guarantees:
- `ctx.Update` is always present
- `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
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
### Message-backed update kinds
| Update type | `ctx.Msg` | `ctx.From` / `ctx.FromID` |
|---|---|---|
| `message` | yes | yes, when `Msg.From` exists |
| `edited_message` | yes | yes, when `Msg.From` exists |
| `channel_post` | yes | only when `Msg.From` exists |
| `edited_channel_post` | yes | only when `Msg.From` exists |
| `business_message` | yes | yes, when `Msg.From` exists |
| `edited_business_message` | yes | yes, when `Msg.From` exists |
Important:
- message-backed does **not** mean command-routed
- `edited_message`, `edited_channel_post`, `business_message`, and `edited_business_message` currently do not enter command flow automatically
### User-backed but not message-backed update kinds
| Update type | `ctx.Msg` | `ctx.From` / `ctx.FromID` |
|---|---|---|
| `inline_query` | no | yes |
| `chosen_inline_result` | no | yes |
| `shipping_query` | no | yes |
| `pre_checkout_query` | no | yes |
| `purchased_paid_media` | no | yes |
| `my_chat_member` | no | yes |
| `chat_member` | no | yes |
| `chat_join_request` | no | yes |
| `business_connection` | no | yes |
| `poll_answer` | no | yes |
| `message_reaction` | no | yes, when Telegram includes `User` |
| `chat_boost` | no | yes |
| `removed_chat_boost` | no | yes |
### Callback-specific update kind
| Update type | `ctx.Msg` | `ctx.From` / `ctx.FromID` | Special fields |
|---|---|---|---|
| `callback_query` with `Message` | yes | yes | `CallbackQueryId`, `CallbackMsgId` |
| `callback_query` with `InlineMessageID` | no | yes | `CallbackQueryId`, `InlineMsgId` |
### Update kinds without normalized user/message guarantees
| Update type | `ctx.Msg` | `ctx.From` / `ctx.FromID` |
|---|---|---|
| `poll` | no | no |
| `message_reaction_count` | no | no |
| `deleted_business_messages` | no | no |
| `unknown` | no | no |
## What is already stable enough to rely on
- `message` and `channel_post` stay on command flow
- `callback_query` stays on payload flow
- `prepareUpdateCtx(...)` itself does not populate `Text`, `Args`, or `Prefix`
- command parsing populates `Text`, `Args`, and `Prefix`
- payload decoding populates `Args`, not `Text`
- callback target shape is split between chat-message callbacks and inline-message callbacks
- `edited_message` and `edited_channel_post` stay out of command routing
## What is still intentionally open
- whether scenes should remain message-driven only or grow a broader update model
- whether message-backed edited/business updates should ever gain command-style routing
- whether “message-backed update” should become an explicit documented framework concept
## Related pages
- [[Commands-and-Plugins]]
- [[MsgContext]]
- [[Scenes]]
- [[Bot-Lifecycle]]