diff --git a/plugins/platforms/telegram/adapter.py b/plugins/platforms/telegram/adapter.py index a852b7b830..00dbba0b59 100644 --- a/plugins/platforms/telegram/adapter.py +++ b/plugins/platforms/telegram/adapter.py @@ -33,6 +33,23 @@ def _redact_telegram_error_text(error: object) -> str: return "" +def _decode_json_list_literal(raw): + """Decode a JSON-encoded allowlist written by ``hermes config set``. + + String-typed defaults keep list literals verbatim on write (``allowed_chats`` is + declared as ``""``), so the config can hold ``'["-100","-200"]'`` as a string. + Malformed JSON passes through unchanged and keeps the legacy comma-split path. + """ + if isinstance(raw, str) and raw.lstrip()[:1] == "[": + try: + loaded = json.loads(raw) + except ValueError: + return raw + if isinstance(loaded, list): + return loaded + return raw + + def _consume_abandoned_task(task: asyncio.Task) -> None: """Observe a detached task's terminal exception to avoid noisy loop logs.""" try: @@ -5058,6 +5075,7 @@ class TelegramAdapter(BasePlatformAdapter): raw = self.config.extra.get(key) if raw is None: raw = _scoped_gate_env(env_name) + raw = _decode_json_list_literal(raw) if isinstance(raw, list): return {str(part).strip() for part in raw if str(part).strip()} return {part.strip() for part in str(raw).split(",") if part.strip()} @@ -5131,6 +5149,7 @@ class TelegramAdapter(BasePlatformAdapter): raw = self.config.extra.get("ignored_threads") if raw is None: raw = _scoped_gate_env("TELEGRAM_IGNORED_THREADS") + raw = _decode_json_list_literal(raw) ignored: set[int] = set() for value in (raw if isinstance(raw, list) else str(raw).split(",")): text = str(value).strip() diff --git a/tests/gateway/test_telegram_allowlist_json.py b/tests/gateway/test_telegram_allowlist_json.py new file mode 100644 index 0000000000..a6cdc54fe3 --- /dev/null +++ b/tests/gateway/test_telegram_allowlist_json.py @@ -0,0 +1,83 @@ +import json +from types import SimpleNamespace + +from gateway.config import Platform, PlatformConfig + + +def _make_json_adapter(allowed_chats): + from plugins.platforms.telegram.adapter import TelegramAdapter + + extra = { + "allowed_chats": allowed_chats, + "allowed_topics": [], + "group_allowed_chats": [], + } + adapter = object.__new__(TelegramAdapter) + adapter.platform = Platform.TELEGRAM + adapter.config = PlatformConfig(enabled=True, token="***", extra=extra) + adapter._bot = SimpleNamespace(id=999, username="hermes_bot") + return adapter + + +def _group_msg(chat_id=-100): + return SimpleNamespace( + message_id=42, + text="hello", + caption=None, + entities=[], + caption_entities=[], + message_thread_id=None, + chat=SimpleNamespace(id=chat_id, type="group", title="G", is_forum=False), + from_user=SimpleNamespace(id=111, full_name="A B", first_name="A"), + reply_to_message=None, + date=None, + ) + + +def test_allowed_chats_json_string_parses_as_allowlist(): + adapter = _make_json_adapter('["-100","-200"]') + assert adapter._telegram_allowed_chats() == {"-100", "-200"} + + +def test_allowed_chats_json_string_end_to_end_gating(): + adapter = _make_json_adapter(json.dumps(["-100"])) + assert adapter._should_process_message(_group_msg(chat_id=-100)) is True + assert adapter._should_process_message(_group_msg(chat_id=-300)) is False + + +def test_allowed_chats_comma_string_still_works(): + adapter = _make_json_adapter("-100,-200") + assert adapter._telegram_allowed_chats() == {"-100", "-200"} + + +def test_allowed_chats_native_list_still_works(): + adapter = _make_json_adapter(["-100", "-200"]) + assert adapter._telegram_allowed_chats() == {"-100", "-200"} + + +def test_allowed_chats_malformed_json_falls_back_to_comma_split(): + adapter = _make_json_adapter('["-100", "-200') + assert adapter._telegram_allowed_chats() == {'["-100"', '"-200'} + + +def test_ignored_threads_json_string_parses(): + from plugins.platforms.telegram.adapter import TelegramAdapter + + extra = {"ignored_threads": '["7", "9"]'} + adapter = object.__new__(TelegramAdapter) + adapter.platform = Platform.TELEGRAM + adapter.config = PlatformConfig(enabled=True, token="***", extra=extra) + adapter._bot = SimpleNamespace(id=999, username="hermes_bot") + assert adapter._telegram_ignored_threads() == {7, 9} + + +def test_all_allowlist_keys_decode_json_string(): + adapter = _make_json_adapter('["-100"]') + adapter.config.extra["group_allowed_chats"] = '["-300"]' + adapter.config.extra["allowed_topics"] = '["5"]' + adapter.config.extra["free_response_chats"] = '["-400"]' + adapter.config.extra["free_response_topics"] = '["-100:3"]' + assert adapter._telegram_group_allowed_chats() == {"-300"} + assert adapter._telegram_allowed_topics() == {"5"} + assert adapter._telegram_free_response_chats() == {"-400"} + assert adapter._telegram_free_response_topics() == {"-100:3"}