diff --git a/bot/handler.py b/bot/handler.py index cc99990..60701a3 100644 --- a/bot/handler.py +++ b/bot/handler.py @@ -25,6 +25,26 @@ _ws_connected: bool = False _last_message_time: float = 0.0 _reconnect_count: int = 0 +# Deduplication: drop Feishu re-deliveries by (user_id, content) within a short window. +# Feishu retries on network hiccups within ~60s using the same payload. +# We use a 10s window: identical content from the same user within 10s is a re-delivery, +# not a deliberate repeat (user intentional repeats arrive after the bot has already replied). +_recent_messages: dict[tuple[str, str], float] = {} # key: (user_id, content) → timestamp +_DEDUP_WINDOW = 10.0 # seconds + + +def _is_duplicate(user_id: str, content: str) -> bool: + """Return True if this (user, content) pair arrived within the dedup window.""" + now = time.time() + expired = [k for k, ts in _recent_messages.items() if now - ts > _DEDUP_WINDOW] + for k in expired: + del _recent_messages[k] + key = (user_id, content) + if key in _recent_messages: + return True + _recent_messages[key] = now + return False + def get_ws_status() -> dict[str, Any]: """Return WebSocket connection status.""" @@ -79,10 +99,14 @@ def _handle_message(data: P2ImMessageReceiveV1) -> None: logger.info("Empty text after stripping, ignoring") return - logger.info("✉ ...%s → %r", open_id[-8:], text[:80]) - user_id = open_id or chat_id + if _is_duplicate(user_id, text): + logger.info("Dropping duplicate delivery: user=...%s text=%r", user_id[-8:], text[:60]) + return + + logger.info("✉ ...%s → %r", open_id[-8:], text[:80]) + if _main_loop is None: logger.error("Main event loop not set; cannot process message") return