import asyncio import logging import time from dataclasses import dataclass, field logger = logging.getLogger(__name__) MAX_RETRY = 3 WAIT_WINDOW_SEC = 4.0 POLL_INTERVAL_SEC = 0.1 _passive_state: "PassiveReplyState | None" = None def _get_passive_state() -> "PassiveReplyState": global _passive_state if _passive_state is None: _passive_state = PassiveReplyState() return _passive_state def _reset_passive_state() -> None: global _passive_state _passive_state = None @dataclass class PassiveReplyState: cache: dict[str, list[tuple[str, str]]] = field(default_factory=dict) running: set[str] = field(default_factory=set) request_cnt: dict[str, int] = field(default_factory=dict) def is_new_request(self, from_user: str, message_id: str, content: str) -> bool: has_cache = from_user in self.cache and bool(self.cache[from_user]) is_running = from_user in self.running is_command = content.startswith("#") is_new_msg = message_id not in self.request_cnt return (not has_cache and not is_running) or (is_command and is_new_msg) def mark_running(self, from_user: str): self.running.add(from_user) def mark_done(self, from_user: str): self.running.discard(from_user) def incr_request(self, message_id: str): self.request_cnt[message_id] = self.request_cnt.get(message_id, 0) + 1 def is_done(self, from_user: str) -> bool: return from_user not in self.running def reap(self, from_user: str) -> tuple[str, str] | None: entries = self.cache.get(from_user) if not entries: return None result = entries.pop(0) if not entries: del self.cache[from_user] return result def add_cache(self, from_user: str, reply_type: str, content: str): if from_user not in self.cache: self.cache[from_user] = [] self.cache[from_user].append((reply_type, content)) def cleanup_user(self, from_user: str): self.cache.pop(from_user, None) self.running.discard(from_user) def reset(self): self.cache.clear() self.running.clear() self.request_cnt.clear()