from __future__ import annotations import hashlib import time class InboundDebouncer: """入站消息防抖 — 按 channel + thread key 去重。 防止 Mattermost WebSocket 重复推送导致的多次触发。 """ def __init__(self, ttl_ms: int = 1000, max_entries: int = 2000): self._ttl_ms = ttl_ms self._max_entries = max_entries self._entries: dict[str, float] = {} def should_process(self, channel_id: str, thread_id: str, content: str) -> bool: key = self._build_key(channel_id, thread_id, content) now = time.monotonic() if key in self._entries: if now - self._entries[key] < self._ttl_ms / 1000: return False self._entries[key] = now self._prune(now) return True def _build_key(self, channel_id: str, thread_id: str, content: str) -> str: raw = f"{channel_id}:{thread_id}:{content[:100]}" return hashlib.md5(raw.encode()).hexdigest() def _prune(self, now: float) -> None: if len(self._entries) > self._max_entries: cutoff = now - self._ttl_ms / 1000 self._entries = {k: v for k, v in self._entries.items() if v > cutoff} if len(self._entries) > self._max_entries: overflow = len(self._entries) - self._max_entries keys = sorted(self._entries, key=lambda k: self._entries[k])[:overflow] for k in keys: del self._entries[k] def clear(self) -> None: self._entries.clear()