#!/usr/bin/env python3 """Group trusted Telegram API sessions under a system WebUI project.""" from __future__ import annotations from pathlib import Path ROOT = Path("/opt/hermes-webui") def replace_exact(path: Path, before: str, after: str, count: int = 1) -> None: """Replace a pinned upstream fragment and fail closed on source drift.""" source = path.read_text(encoding="utf-8") if source.count(before) != count: raise SystemExit(f"Telegram project patch context changed in {path}") path.write_text(source.replace(before, after, count), encoding="utf-8") models = ROOT / "api/models.py" replace_exact( models, """def _profile_has_user_projects() -> bool: """, """TELEGRAM_PROJECT_NAME = 'Telegram' _TELEGRAM_PROJECT_LOCK = threading.Lock() def ensure_telegram_project() -> str: \"\"\"Return the per-profile system project for trusted Telegram sessions.\"\"\" from api.profiles import get_active_profile_name, _is_root_profile active = get_active_profile_name() or 'default' with _TELEGRAM_PROJECT_LOCK: projects = load_projects() for project in projects: if project.get('name') != TELEGRAM_PROJECT_NAME: continue row_profile = project.get('profile') if row_profile == active: return project['project_id'] if _is_root_profile(row_profile or 'default') and _is_root_profile(active): return project['project_id'] project_id = uuid.uuid4().hex[:12] projects.append({ 'project_id': project_id, 'name': TELEGRAM_PROJECT_NAME, 'color': '#229ed9', 'profile': active, 'created_at': time.time(), }) save_projects(projects) return project_id def _profile_has_user_projects() -> bool: """, ) replace_exact( models, "reserved = {CRON_PROJECT_NAME, WEBHOOK_PROJECT_NAME}", "reserved = {CRON_PROJECT_NAME, WEBHOOK_PROJECT_NAME, TELEGRAM_PROJECT_NAME}", ) replace_exact( models, """ _webhook_pid_cache: list[str | None] = [None] def _webhook_pid(): if _webhook_pid_cache[0] is None: _webhook_pid_cache[0] = ensure_webhook_project() return _webhook_pid_cache[0] def _state_row_project_id(sid: str, source: str | None) -> str | None: if is_cron_session(sid, source): return _cron_pid() if is_webhook_session(sid, source): return _webhook_pid() return None """, """ _webhook_pid_cache: list[str | None] = [None] def _webhook_pid(): if _webhook_pid_cache[0] is None: _webhook_pid_cache[0] = ensure_webhook_project() return _webhook_pid_cache[0] _telegram_pid_cache: list[str | None] = [None] def _telegram_pid(): if _telegram_pid_cache[0] is None: _telegram_pid_cache[0] = ensure_telegram_project() return _telegram_pid_cache[0] def _state_row_project_id(row: dict) -> str | None: sid = str(row.get('id') or '') source = str(row.get('source') or '') if is_cron_session(sid, source): return _cron_pid() if is_webhook_session(sid, source): return _webhook_pid() session_key = str(row.get('session_key') or '') if source == 'api_server' and ( session_key == 'telegram' or session_key.startswith('telegram-topic-') ): return _telegram_pid() return None """, ) replace_exact( models, "'project_id': _state_row_project_id(sid, _source),", "'project_id': _state_row_project_id(row),", )