From eafeebb69c26b7e0564fd3d4066bee3b6e83bd10 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Fri, 4 Sep 2026 11:46:59 +0700 Subject: [PATCH] fix(telegram): allow inline_query through the getUpdates filter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The alias module registers an inline-query handler, but pollingAllowedUpdates listed only message and callback_query. Telegram filters getUpdates on its side, so inline queries were dropped before reaching the bot — the handler never ran, and the omission left no log line or error to debug from. Adds a test tying the list to the kinds actually handled, since nothing else would catch the next such gap. --- docs/aliases.md | 12 +++++++++--- internal/telegram/client.go | 13 +++++++++---- internal/telegram/client_test.go | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 internal/telegram/client_test.go 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) + } +}