ForcePilot/backend/package/yuxi/channel/extensions/qqbot/session.py

76 lines
2.7 KiB
Python
Raw Normal View History

from __future__ import annotations
import json
import logging
import time
from pathlib import Path
from typing import Any
from yuxi.channel.extensions.qqbot.types import SessionState
logger = logging.getLogger(__name__)
DEFAULT_SESSION_DIR = Path.home() / ".forcepilot" / "qqbot" / "sessions"
SESSION_EXPIRE_SECONDS = 300
class SessionStore:
def __init__(self, session_dir: Path | None = None):
self._session_dir = session_dir or DEFAULT_SESSION_DIR
self._session_dir.mkdir(parents=True, exist_ok=True)
def _session_path(self, account_id: str) -> Path:
safe_id = account_id.replace("/", "_").replace("\\", "_")
return self._session_dir / f"{safe_id}.json"
def save(self, account_id: str, state: SessionState) -> None:
path = self._session_path(account_id)
data = {
"session_id": state.session_id,
"last_seq": state.last_seq,
"last_connected_at": state.last_connected_at,
"account_id": state.account_id,
"saved_at": time.time(),
"app_id": state.app_id,
}
try:
path.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")
logger.debug("Session saved for account '%s': session_id=%s", account_id, state.session_id)
except OSError as e:
logger.warning("Failed to save session for '%s': %s", account_id, e)
def load(self, account_id: str) -> SessionState | None:
path = self._session_path(account_id)
if not path.exists():
return None
try:
data = json.loads(path.read_text(encoding="utf-8"))
saved_at = data.get("saved_at", 0)
if time.time() - saved_at > SESSION_EXPIRE_SECONDS:
logger.debug("Session expired for account '%s'", account_id)
return None
return SessionState(
session_id=data.get("session_id"),
last_seq=data.get("last_seq"),
last_connected_at=data.get("last_connected_at", 0),
account_id=data.get("account_id", account_id),
saved_at=saved_at,
app_id=data.get("app_id"),
)
except (json.JSONDecodeError, OSError) as e:
logger.warning("Failed to load session for '%s': %s", account_id, e)
return None
def clear(self, account_id: str) -> None:
path = self._session_path(account_id)
try:
path.unlink(missing_ok=True)
except OSError:
pass
def clear_all(self) -> None:
for path in self._session_dir.glob("*.json"):
try:
path.unlink()
except OSError:
pass