from __future__ import annotations import time from collections import defaultdict from threading import Lock class MessageDebouncer: def __init__(self, window_seconds: float = 2.0, max_calls: int = 3): self._window = window_seconds self._max_calls = max_calls self._last_send: defaultdict[str, list[float]] = defaultdict(list) self._lock = Lock() def should_throttle(self, remote_jid: str) -> bool: now = time.monotonic() with self._lock: times = self._last_send.get(remote_jid, []) times = [t for t in times if now - t < self._window] if len(times) >= self._max_calls: return True times.append(now) self._last_send[remote_jid] = times return False def record_send(self, remote_jid: str) -> None: now = time.monotonic() with self._lock: self._last_send[remote_jid].append(now) def window_remaining(self, remote_jid: str) -> float: now = time.monotonic() with self._lock: times = [t for t in self._last_send.get(remote_jid, []) if now - t < self._window] if len(times) >= self._max_calls and times: return max(0.0, self._window - (now - times[0])) return 0.0 def clear(self, remote_jid: str | None = None) -> None: with self._lock: if remote_jid: self._last_send.pop(remote_jid, None) else: self._last_send.clear()