from __future__ import annotations import json import logging import os from pathlib import Path logger = logging.getLogger(__name__) CREDENTIAL_BACKUP_DIR = Path.home() / ".forcepilot" / "qqbot" / "credentials" class CredentialBackup: def __init__(self, backup_dir: Path | None = None): self._backup_dir = backup_dir or CREDENTIAL_BACKUP_DIR self._backup_dir.mkdir(parents=True, exist_ok=True) def _backup_path(self, account_id: str) -> Path: safe_id = account_id.replace("/", "_").replace("\\", "_") return self._backup_dir / f"{safe_id}.json" def save(self, account_id: str, app_id: str, client_secret: str) -> None: path = self._backup_path(account_id) data = { "account_id": account_id, "app_id": app_id, "client_secret": client_secret, } try: path.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8") logger.debug("Credential backup saved for account '%s'", account_id) except OSError as e: logger.warning("Failed to save credential backup for '%s': %s", account_id, e) def load(self, account_id: str) -> dict | None: path = self._backup_path(account_id) if not path.exists(): return None try: data = json.loads(path.read_text(encoding="utf-8")) if data.get("app_id") and data.get("client_secret"): logger.debug("Credential backup loaded for account '%s'", account_id) return data except (json.JSONDecodeError, OSError) as e: logger.warning("Failed to load credential backup for '%s': %s", account_id, e) return None def exists(self, account_id: str) -> bool: return self._backup_path(account_id).exists() def clear(self, account_id: str) -> None: path = self._backup_path(account_id) try: path.unlink(missing_ok=True) logger.debug("Credential backup cleared for account '%s'", account_id) except OSError as e: logger.warning("Failed to clear credential backup for '%s': %s", account_id, e) def clear_all(self) -> None: for path in self._backup_dir.glob("*.json"): try: path.unlink() except OSError: pass logger.debug("All credential backups cleared")