From e2e4dc3336081f4eba47ff1b041496c2f7932b4f Mon Sep 17 00:00:00 2001 From: kaireewu Date: Sat, 31 Oct 2026 21:55:10 +0800 Subject: [PATCH] fix(telegram): dedupe candidates before sending Deduplicate candidates by aid at the notifier boundary so duplicate queue entries cannot produce repeated message bodies and button rows. The discovery layer already deduped by aid; this adds defense in depth with a regression test. --- reaction_board/notify/telegram.py | 10 +++++++++- tests/test_telegram.py | 13 +++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/reaction_board/notify/telegram.py b/reaction_board/notify/telegram.py index 18f9bc1..301c566 100644 --- a/reaction_board/notify/telegram.py +++ b/reaction_board/notify/telegram.py @@ -98,7 +98,15 @@ class TelegramClient: return self._call("sendMessage", params) def send_candidates(self, candidates: Sequence[Mapping[str, Any]]) -> bool: - items = list(candidates) + # 通知器边界再做一次 aid 去重,避免调用方重复入队导致消息主体和按钮重复。 + items: list[Mapping[str, Any]] = [] + seen: set[int] = set() + for candidate in candidates: + aid = candidate.get("aid") + if aid in seen: + continue + seen.add(aid) + items.append(candidate) if not items: return True for index in range(0, len(items), MAX_CANDIDATES_PER_MESSAGE): diff --git a/tests/test_telegram.py b/tests/test_telegram.py index b6f6952..4e6ef8b 100644 --- a/tests/test_telegram.py +++ b/tests/test_telegram.py @@ -67,6 +67,18 @@ def test_send_candidates_and_chunking(): assert len(json.loads(transport.calls[1]["data"]["reply_markup"])["inline_keyboard"]) == 1 +def test_send_candidates_dedupes_by_aid(): + transport = FakeTransport() + bot = TelegramClient("token", 42, transport=transport) + bot.send_candidates([candidate(1), candidate(1), candidate(2)]) + assert len(transport.calls) == 1 + keyboard = json.loads(transport.calls[0]["data"]["reply_markup"]) + assert len(keyboard["inline_keyboard"]) == 2 + assert [row[0]["callback_data"] for row in keyboard["inline_keyboard"]] == [ + "fav:1", "fav:2", + ] + + def test_get_updates_and_answer(): transport = FakeTransport(result=[]) bot = TelegramClient("token", 42, transport=transport, poll_timeout=17) @@ -147,6 +159,7 @@ def test_poller_dispatch_and_authorization(): def main(): test_format_duration() test_send_candidates_and_chunking() + test_send_candidates_dedupes_by_aid() test_get_updates_and_answer() test_poller_dispatch_and_authorization() print("OK: Telegram 消息、分片、长轮询与回调授权测试全部通过")