diff --git a/Home-RU.md b/Home-RU.md index 7a2c897..07c81b5 100644 --- a/Home-RU.md +++ b/Home-RU.md @@ -19,6 +19,7 @@ English version: [[Home]] - [[Runners-RU]] - [[Error-Handling-RU]] - [[Logging-RU]] +- [[Update-Routing-Model-RU]] - [[Scenes-RU]] ## Telegram API и взаимодействие diff --git a/Home.md b/Home.md index e4de2db..ccc5ed6 100644 --- a/Home.md +++ b/Home.md @@ -23,6 +23,7 @@ Use this wiki as the structured companion to the README: start with setup, then - [[Runners]] - [[Error-Handling]] - [[Logging]] +- [[Update-Routing-Model]] - [[Scenes]] ## Telegram API and Interaction diff --git a/MsgContext-RU.md b/MsgContext-RU.md index 9e5fa86..13a2e4c 100644 --- a/MsgContext-RU.md +++ b/MsgContext-RU.md @@ -18,6 +18,8 @@ English version: [[MsgContext]] - разобранные аргументы команд и callback; - вспомогательные методы для reply, edit, delete, callback, drafts и localization. +Полную матрицу маршрутизации и гарантий по полям `MsgContext` для разных update types смотри в [[Update-Routing-Model-RU]]. + ## Поля, которые используются чаще всего ### `Text` diff --git a/MsgContext.md b/MsgContext.md index cee41c2..ab8d3e9 100644 --- a/MsgContext.md +++ b/MsgContext.md @@ -12,6 +12,8 @@ It gives you access to: 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 ### `Text` diff --git a/Update-Routing-Model-RU.md b/Update-Routing-Model-RU.md new file mode 100644 index 0000000..f297047 --- /dev/null +++ b/Update-Routing-Model-RU.md @@ -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]] diff --git a/Update-Routing-Model.md b/Update-Routing-Model.md new file mode 100644 index 0000000..c66a2d0 --- /dev/null +++ b/Update-Routing-Model.md @@ -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]]