From 1ce2b724190ebc327501c523178f7c437f92cd83 Mon Sep 17 00:00:00 2001 From: elphamale Date: Mon, 20 Jul 2026 16:40:29 +0300 Subject: [PATCH] fix(telegram): request guest_message in allowed_updates for guest mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PTB's Update.ALL_TYPES is a hardcoded enumeration of update kinds PTB has a typed field for. Bot API 10.0's guest_message isn't one of them (see _handle_guest_message_update's docstring — PTB doesn't have a typed field for it yet, the raw payload arrives via update.api_kwargs["guest_message"]). Passing Update.ALL_TYPES verbatim as allowed_updates to start_polling()/start_webhook() tells Telegram to send every OTHER update type and drop guest_message server-side, before it ever reaches this process. The _handle_guest_message_update TypeHandler registers without error and no exception fires anywhere — the bot just silently never receives a guest @mention, from any chat, indistinguishable from an outage. Confirmed live via getWebhookInfo: allowed_updates was missing guest_message even though getMe reported supports_guest_queries: true. Fixes it by explicitly appending "guest_message" to the allowed_updates list at both call sites (polling and webhook), getattr-guarded since some tests inject a minimal fake `telegram` module (Update = object) that has no ALL_TYPES attribute. No test coverage existed for allowed_updates at all before this, which is how it shipped unnoticed; added a regression test pinning both the constant's content and that no call site bypasses it. --- plugins/platforms/telegram/adapter.py | 20 +++++++++- ..._telegram_guest_message_allowed_updates.py | 40 +++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 tests/gateway/test_telegram_guest_message_allowed_updates.py diff --git a/plugins/platforms/telegram/adapter.py b/plugins/platforms/telegram/adapter.py index d91f2a799828..fa355bb9d458 100644 --- a/plugins/platforms/telegram/adapter.py +++ b/plugins/platforms/telegram/adapter.py @@ -246,6 +246,22 @@ class _MockContextTypes: DEFAULT_TYPE = Any ContextTypes = _MockContextTypes +# PTB's Update.ALL_TYPES is a hardcoded enumeration of update kinds PTB has a +# typed field for. Bot API 10.0's guest_message isn't one of them yet (see +# _handle_guest_message_update's docstring), so passing Update.ALL_TYPES +# verbatim as allowed_updates tells Telegram to send everything EXCEPT +# guest_message -- the update is dropped server-side, before it ever reaches +# this process, so guest mode fails with zero client-side error or log trace. +# Older PTB releases used an empty list for ALL_TYPES, which Telegram treats +# as "send every type including ones the client doesn't know about" -- this +# explicit append restores that behavior for the one type PTB can't name yet. +# getattr-guarded: some tests inject a minimal fake `telegram` module (e.g. +# Update = object) into sys.modules before importing this adapter, which +# satisfies `from telegram import Update` without raising ImportError but +# has no ALL_TYPES attribute -- this must degrade to [] there, not crash +# the whole module import. +_ALLOWED_UPDATES_WITH_GUEST = [*getattr(Update, "ALL_TYPES", []), "guest_message"] if TELEGRAM_AVAILABLE else [] + import sys from pathlib import Path as _Path sys.path.insert(0, str(_Path(__file__).resolve().parents[3])) @@ -2147,7 +2163,7 @@ def _generation_error_callback(error: Exception) -> None: try: await asyncio.wait_for( app.updater.start_polling( - allowed_updates=Update.ALL_TYPES, + allowed_updates=_ALLOWED_UPDATES_WITH_GUEST, drop_pending_updates=drop_pending_updates, error_callback=_generation_error_callback, ), @@ -3693,7 +3709,7 @@ def _with_limits(httpx_kwargs: Optional[dict] = None) -> dict: url_path=webhook_path, webhook_url=webhook_url, secret_token=webhook_secret, - allowed_updates=Update.ALL_TYPES, + allowed_updates=_ALLOWED_UPDATES_WITH_GUEST, # Webhooks are push-based — Telegram does not hold a # server-side getUpdates queue, so this flag is a no-op # in practice. Mirror the polling path's reconnect diff --git a/tests/gateway/test_telegram_guest_message_allowed_updates.py b/tests/gateway/test_telegram_guest_message_allowed_updates.py new file mode 100644 index 000000000000..3c27202688d2 --- /dev/null +++ b/tests/gateway/test_telegram_guest_message_allowed_updates.py @@ -0,0 +1,40 @@ +"""Regression test: guest_message must be requested via allowed_updates. + +PTB's ``Update.ALL_TYPES`` is a hardcoded enumeration of update kinds PTB has +a typed field for. Bot API 10.0's ``guest_message`` isn't one of them, so +passing ``Update.ALL_TYPES`` verbatim as ``allowed_updates`` tells Telegram to +send every OTHER update type and silently omit guest_message -- Telegram +drops it server-side, before it ever reaches this process. The +``_handle_guest_message_update`` TypeHandler registers fine and no exception +is ever raised; the bot just never receives a guest @mention, from any chat, +indistinguishable from an outage. There was no test coverage for +``allowed_updates`` at all before this, which is how the bug shipped. +""" +from __future__ import annotations + +import inspect +import re + +import plugins.platforms.telegram.adapter as tg_adapter + + +def test_allowed_updates_constant_includes_guest_message(): + assert "guest_message" in tg_adapter._ALLOWED_UPDATES_WITH_GUEST + # And still covers everything PTB itself knows about, so this isn't a + # narrower request that drops some other update type instead. + for update_type in tg_adapter.Update.ALL_TYPES: + assert update_type in tg_adapter._ALLOWED_UPDATES_WITH_GUEST + + +def test_no_call_site_uses_bare_update_all_types_for_allowed_updates(): + """Bug-class contract: every ``allowed_updates=`` argument in the adapter + must route through ``_ALLOWED_UPDATES_WITH_GUEST``, not + ``Update.ALL_TYPES`` directly -- a new call site written the naive way + silently drops guest_message again, with no error anywhere.""" + src = inspect.getsource(tg_adapter) + bare = [ + (i + 1, line.strip()) + for i, line in enumerate(src.splitlines()) + if re.search(r"allowed_updates\s*=\s*Update\.ALL_TYPES\b", line) + ] + assert not bare, f"allowed_updates call sites bypassing the guest_message fix: {bare}"