from __future__ import annotations import time from collections import OrderedDict from typing import Any from yuxi.utils.logging_config import logger def _normalize_jid(jid: str) -> str: return jid.split("@")[0].replace("+", "").replace(" ", "").replace("-", "") def _is_group_jid(jid: str) -> bool: return "@g.us" in jid class InboundMessageCache: def __init__(self, max_size: int = 500, ttl_seconds: int = 600): self._cache: OrderedDict[str, dict[str, Any]] = OrderedDict() self._max_size = max_size self._ttl = ttl_seconds def put(self, msg_id: str, jid: str, content: str, metadata: dict[str, Any] | None = None) -> None: if not msg_id: return self._cache[msg_id] = { "jid": jid, "content": content[:500], "timestamp": time.time(), "metadata": metadata or {}, } self._cache.move_to_end(msg_id) self._evict() def get(self, msg_id: str) -> dict[str, Any] | None: entry = self._cache.get(msg_id) if entry is None: return None if time.time() - entry["timestamp"] > self._ttl: self._cache.pop(msg_id, None) return None return entry def lookup_inbound_meta(self, target_jid: str, target_msg_id: str | None = None) -> dict[str, Any] | None: if target_msg_id: exact = self.get(target_msg_id) if exact: return exact target_is_group = _is_group_jid(target_jid) candidates: list[tuple[str, dict[str, Any], float]] = [] now = time.time() for msg_id, entry in self._cache.items(): if now - entry["timestamp"] > self._ttl: continue entry_jid = entry.get("jid", "") if target_is_group != _is_group_jid(entry_jid): continue candidates.append((msg_id, entry, entry["timestamp"])) if len(candidates) >= 10: break if not candidates: return None candidates.sort(key=lambda x: x[2], reverse=True) best_msg_id, best_entry, _ = candidates[0] logger.debug( f"InboundMessageCache: fuzzy match for {target_jid} -> msg_id={best_msg_id}, candidates={len(candidates)}" ) return best_entry def resolve_quoted_message_key(self, target_jid: str, quoted_msg_id: str | None = None) -> dict[str, Any] | None: meta = None if quoted_msg_id: meta = self.get(quoted_msg_id) if meta is None: meta = self.lookup_inbound_meta(target_jid, None) if meta is None: return None msg_jid = meta.get("jid", target_jid) return { "remoteJid": msg_jid, "fromMe": False, "id": quoted_msg_id or "", } def _evict(self) -> None: now = time.time() expired = [k for k, v in self._cache.items() if now - v["timestamp"] > self._ttl] for k in expired: del self._cache[k] while len(self._cache) > self._max_size: oldest = next(iter(self._cache)) del self._cache[oldest] def clear(self) -> None: self._cache.clear() def __len__(self) -> int: return len(self._cache)