diff --git a/docs/aliases.md b/docs/aliases.md index f76265f..cb59b6b 100644 --- a/docs/aliases.md +++ b/docs/aliases.md @@ -68,9 +68,15 @@ is the payoff for storing a `file_id` rather than bytes. `InlineQueryResultCachedVideoNote`, and substituting a plain video would change what was saved. They stay reachable through `/insert` and `/`. -**Inline mode must be enabled in BotFather** (`/setinline`) or Telegram never -delivers these updates and the handler is never called. This is a one-time -operational step that cannot be done from code. +**Two things gate inline mode, and both fail silently.** + +1. `inline_query` must be in `pollingAllowedUpdates` + (`internal/telegram/client.go`). Telegram filters getUpdates server-side, so + a missing kind means the handler is never called — no log line, no error. +2. Inline mode must be enabled for the bot in BotFather (`/setinline`). Until + it is, Telegram does not offer the bot for inline use at all, so typing + `@botname` shows nothing. This is a one-time operational step that cannot be + done from code. ## Names diff --git a/internal/telegram/client.go b/internal/telegram/client.go index a4e2cc3..8f4dbc0 100644 --- a/internal/telegram/client.go +++ b/internal/telegram/client.go @@ -5,10 +5,15 @@ import ( ) // pollingAllowedUpdates restricts getUpdates to the update kinds the modules -// actually handle (text commands + inline-keyboard callbacks), matching the -// allowed_updates the old webhook registration set. Anything else (channel -// posts, edited messages, etc.) is dropped server-side by Telegram. -var pollingAllowedUpdates = bot.AllowedUpdates{"message", "callback_query"} +// actually handle: text commands, inline-keyboard callbacks, and inline-mode +// queries. Anything else (channel posts, edited messages, etc.) is dropped +// server-side by Telegram. +// +// This list is a hard gate, not an optimisation. Telegram filters on its side, +// so a handler for a kind missing here is never called and leaves no trace — +// no log line, no error, nothing to debug from. Any module that starts handling +// a new update kind must add it here in the same change. +var pollingAllowedUpdates = bot.AllowedUpdates{"message", "callback_query", "inline_query"} // NewBot constructs a Telegram bot for long-polling mode (the sole transport // on self-host — b.Start runs the getUpdates loop in cmd/server): diff --git a/internal/telegram/client_test.go b/internal/telegram/client_test.go new file mode 100644 index 0000000..4e0f0a2 --- /dev/null +++ b/internal/telegram/client_test.go @@ -0,0 +1,32 @@ +package telegram + +import ( + "slices" + "testing" +) + +// pollingAllowedUpdates is a silent gate: Telegram filters getUpdates on its +// side, so a handler for a kind missing from this list is never called and +// leaves no log line, no error, and nothing to debug from. Inline queries were +// dropped exactly this way once — the handler was registered and the list was +// not updated. +// +// This test is the tripwire. A module that starts handling a new update kind +// belongs in both places, and removing a kind here must be deliberate enough to +// require editing this list too. +func TestPollingAllowedUpdates_CoversEveryHandledKind(t *testing.T) { + // message — every /command, and the alias fallback + // callback_query — inline-keyboard buttons (stock dividends, loldle) + // inline_query — "@botname " alias lookups + want := []string{"message", "callback_query", "inline_query"} + + for _, kind := range want { + if !slices.Contains(pollingAllowedUpdates, kind) { + t.Errorf("%q missing from pollingAllowedUpdates; its handlers will never fire", kind) + } + } + if len(pollingAllowedUpdates) != len(want) { + t.Errorf("pollingAllowedUpdates = %v, want exactly %v — an unhandled kind costs bandwidth for nothing", + pollingAllowedUpdates, want) + } +}