from __future__ import annotations import json import time from pathlib import Path from typing import Any from yuxi.utils.logging_config import logger class SentMessageCache: def __init__(self, storage_dir: str | None = None, max_entries: int = 1000): self._storage_dir = Path(storage_dir or str(Path.home() / ".yuxi" / "imessage")) self._max_entries = max_entries self._entries: dict[str, dict[str, Any]] = {} self._storage_dir.mkdir(parents=True, exist_ok=True) self._load() def record(self, message_id: str, content: str, chat_guid: str, metadata: dict[str, Any] | None = None) -> None: entry = { "message_id": message_id, "content": content[:500], "chat_guid": chat_guid, "sent_at": time.time(), "metadata": metadata or {}, } self._entries[message_id] = entry if len(self._entries) > self._max_entries: oldest = min(self._entries.values(), key=lambda e: e["sent_at"]) self._entries.pop(oldest["message_id"], None) def get(self, message_id: str) -> dict[str, Any] | None: return self._entries.get(message_id) def get_content(self, message_id: str) -> str | None: entry = self._entries.get(message_id) return entry["content"] if entry else None def list_recent(self, limit: int = 50) -> list[dict[str, Any]]: entries = sorted(self._entries.values(), key=lambda e: e["sent_at"], reverse=True) return entries[:limit] async def persist(self) -> None: file_path = self._storage_dir / "sent_messages.json" file_path.write_text(json.dumps(self._entries, ensure_ascii=False, indent=2), encoding="utf-8") logger.debug(f"[iMessage/SentCache] Persisted {len(self._entries)} entries") def _load(self) -> None: file_path = self._storage_dir / "sent_messages.json" if not file_path.exists(): return try: self._entries = json.loads(file_path.read_text(encoding="utf-8")) now = time.time() self._entries = { mid: entry for mid, entry in self._entries.items() if now - entry.get("sent_at", 0) < 86400 } except (json.JSONDecodeError, OSError): self._entries = {}