# -*- coding: utf-8 -*- """IPC-Datei: aktiver Chat-Peer der Empfang-WebView (keine Nachrichteninhalte).""" from __future__ import annotations import json import time from pathlib import Path from typing import Any, Optional _SURFACE_TTL_SEC = 45.0 def shell_surface_state_path() -> Path: try: from aza_persistence import get_writable_data_dir root = Path(get_writable_data_dir()) except Exception: root = Path.home() / ".aza" return root / "empfang_shell_surface.json" def write_shell_surface_state(payload: dict[str, Any]) -> None: data = { "ts": time.time(), "mode": str(payload.get("mode") or "").strip()[:32], "peer_user_id": str(payload.get("peer_user_id") or "").strip()[:64], "external_peer_user_id": str(payload.get("external_peer_user_id") or "").strip()[:64], "document_visible": bool(payload.get("document_visible")), "has_focus": bool(payload.get("has_focus")), "shell_minimized": bool(payload.get("shell_minimized")), } path = shell_surface_state_path() try: path.parent.mkdir(parents=True, exist_ok=True) tmp = path.with_suffix(".tmp") tmp.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8") tmp.replace(path) except Exception: pass def read_shell_surface_state(*, max_age_sec: float = _SURFACE_TTL_SEC) -> Optional[dict[str, Any]]: path = shell_surface_state_path() try: if not path.is_file(): return None raw = path.read_text(encoding="utf-8") data = json.loads(raw) if not isinstance(data, dict): return None age = time.time() - float(data.get("ts") or 0) if age > float(max_age_sec): return None return data except Exception: return None def peer_matches_surface(peer_user_id: str, surface: Optional[dict[str, Any]]) -> bool: puid = (peer_user_id or "").strip() if not puid or not isinstance(surface, dict): return False for k in ("peer_user_id", "external_peer_user_id"): if str(surface.get(k) or "").strip() == puid: return True return False def surface_indicates_active_direct_chat( peer_user_id: str, *, require_visible: bool = True, require_not_minimized: bool = True, ) -> bool: st = read_shell_surface_state() if not st: return False if str(st.get("mode") or "") != "direct": return False if require_visible and not bool(st.get("document_visible")): return False if require_not_minimized and bool(st.get("shell_minimized")): return False return peer_matches_surface(peer_user_id, st) def shell_peer_refresh_signal_path() -> Path: try: from aza_persistence import get_writable_data_dir root = Path(get_writable_data_dir()) except Exception: root = Path.home() / ".aza" return root / "empfang_shell_peer_refresh.json" def touch_shell_peer_refresh_signal(*, source: str = "") -> None: """Kontakt-Panel / Fokus-Pfad: Huelle soll Shell-Context erneut lesen (keine Peer-IDs).""" path = shell_peer_refresh_signal_path() try: path.parent.mkdir(parents=True, exist_ok=True) tmp = path.with_suffix(".tmp") tmp.write_text( json.dumps( {"ts": time.time(), "source": str(source or "").strip()[:32]}, ensure_ascii=False, ), encoding="utf-8", ) tmp.replace(path) except Exception: pass def consume_shell_peer_refresh_signal(last_seen_ts: float = 0.0) -> float: """Liefert neuen Signal-Zeitstempel oder 0.0 — ohne Inhalte zu loggen.""" path = shell_peer_refresh_signal_path() try: if not path.is_file(): return 0.0 data = json.loads(path.read_text(encoding="utf-8")) if not isinstance(data, dict): return 0.0 ts = float(data.get("ts") or 0.0) if ts <= float(last_seen_ts or 0.0): return 0.0 return ts except Exception: return 0.0 def empfang_shell_debug_log_path() -> Path: try: from aza_persistence import get_writable_data_dir root = Path(get_writable_data_dir()) except Exception: root = Path.home() / ".aza" return root / "empfang_shell_debug.log" def append_empfang_shell_debug_log(line: str) -> None: """Technische Kurzzeile ohne Tokens, Peer-IDs oder URLs mit Query.""" msg = str(line or "").strip() if not msg: return path = empfang_shell_debug_log_path() try: path.parent.mkdir(parents=True, exist_ok=True) stamp = time.strftime("%Y-%m-%dT%H:%M:%S") with path.open("a", encoding="utf-8", newline="\n") as f: f.write(f"{stamp} {msg}\n") except Exception: pass _OFFICE_EMPFANG_CHAT_SHELL_OPEN_REQUEST = "office_empfang_chat_shell_open_request.flag" def _office_shell_ipc_path(name: str) -> Path: try: from aza_persistence import get_writable_data_dir root = Path(get_writable_data_dir()) except Exception: root = Path.home() / ".aza" return root / name def request_office_open_empfang_chat_shell_ipc() -> None: """Kontakt-Panel -> Office: Chat-Huelle per Office-Starter oeffnen/fokussieren.""" try: path = _office_shell_ipc_path(_OFFICE_EMPFANG_CHAT_SHELL_OPEN_REQUEST) path.parent.mkdir(parents=True, exist_ok=True) path.write_text("1", encoding="utf-8") except Exception: pass def consume_office_open_empfang_chat_shell_request_flag() -> bool: try: path = _office_shell_ipc_path(_OFFICE_EMPFANG_CHAT_SHELL_OPEN_REQUEST) if not path.is_file(): return False path.unlink(missing_ok=True) return True except Exception: return False