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 消息、分片、长轮询与回调授权测试全部通过")