ForcePilot/backend/package/yuxi/channels/adapters/matrix/credentials.py
Kris 1f78c44b03 refactor: 整理并清理项目中的冗余代码与格式问题
这是一个批量整理提交,包含以下主要改动:
1.  删除多处冗余的空行和未使用的导入
2.  修复文件末尾缺少换行符的问题
3.  调整部分模块的导入顺序与代码排版
4.  修复部分配置默认值与策略逻辑
5.  新增多个功能模块与辅助工具
6.  完善异常处理与日志记录
7.  修复速率限制、消息缓存、权限校验等逻辑bug
8.  废弃部分旧有API与配置项并添加警告提示
2026-05-12 14:51:53 +08:00

137 lines
4.5 KiB
Python

from __future__ import annotations
import base64
import hashlib
import json
import os
from dataclasses import dataclass
from typing import Any
from cryptography.fernet import Fernet
from yuxi.utils.logging_config import logger
_CREDENTIALS_DIR = ".yuxi"
_CREDENTIALS_SUBDIR = "credentials"
_CREDENTIALS_FILE = "matrix_credentials.json"
@dataclass
class MatrixCredentials:
user_id: str = ""
homeserver: str = ""
access_token: str = ""
device_id: str = ""
display_name: str = ""
def to_dict(self) -> dict[str, str]:
return {
"user_id": self.user_id,
"homeserver": self.homeserver,
"access_token": self.access_token,
"device_id": self.device_id,
"display_name": self.display_name,
}
@classmethod
def from_dict(cls, data: dict[str, Any]) -> MatrixCredentials:
return cls(
user_id=data.get("user_id", ""),
homeserver=data.get("homeserver", ""),
access_token=data.get("access_token", ""),
device_id=data.get("device_id", ""),
display_name=data.get("display_name", ""),
)
@property
def is_valid(self) -> bool:
return bool(self.user_id and self.homeserver and self.access_token)
class CredentialStore:
def __init__(self, base_dir: str | None = None):
if base_dir:
self._base_dir = base_dir
else:
home = os.path.expanduser("~")
self._base_dir = os.path.join(home, _CREDENTIALS_DIR)
self._store_dir = os.path.join(self._base_dir, _CREDENTIALS_SUBDIR, "matrix")
def _get_path(self, account_id: str) -> str:
safe_id = account_id.replace("@", "_").replace(":", "_").replace("/", "_")
return os.path.join(self._store_dir, f"{safe_id}.json")
@staticmethod
def _get_fernet() -> Fernet | None:
key = os.environ.get("MATRIX_CREDENTIAL_KEY")
if not key:
return None
digest = hashlib.sha256(key.encode()).digest()
return Fernet(base64.urlsafe_b64encode(digest))
def save(self, credentials: MatrixCredentials) -> bool:
os.makedirs(self._store_dir, exist_ok=True)
filepath = self._get_path(credentials.user_id or "default")
try:
data = credentials.to_dict()
fernet = self._get_fernet()
payload = json.dumps(data)
if fernet:
payload = fernet.encrypt(payload.encode()).decode()
with open(filepath, "w", encoding="utf-8") as f:
f.write(payload)
os.chmod(filepath, 0o600)
safe_data = {k: "***" if k == "access_token" else v for k, v in data.items()}
logger.info(f"Matrix credentials saved: {filepath} ({safe_data})")
return True
except OSError as e:
logger.error(f"Matrix credentials save failed: {e}")
return False
def load(self, account_id: str = "default") -> MatrixCredentials | None:
filepath = self._get_path(account_id)
if not os.path.exists(filepath):
return None
try:
with open(filepath, encoding="utf-8") as f:
raw = f.read()
fernet = self._get_fernet()
if fernet:
try:
raw = fernet.decrypt(raw.encode()).decode()
except Exception:
pass
data = json.loads(raw)
return MatrixCredentials.from_dict(data)
except (json.JSONDecodeError, OSError) as e:
logger.error(f"Matrix credentials load failed: {e}")
return None
def delete(self, account_id: str) -> bool:
filepath = self._get_path(account_id)
try:
if os.path.exists(filepath):
os.remove(filepath)
logger.info(f"Matrix credentials deleted: {filepath}")
return True
except OSError as e:
logger.error(f"Matrix credentials delete failed: {e}")
return False
def list_accounts(self) -> list[str]:
if not os.path.isdir(self._store_dir):
return []
accounts = []
for filename in os.listdir(self._store_dir):
if filename.endswith(".json"):
accounts.append(filename.rsplit(".", 1)[0])
return accounts
def load_all(self) -> list[MatrixCredentials]:
accounts = []
for account_id in self.list_accounts():
creds = self.load(account_id)
if creds and creds.is_valid:
accounts.append(creds)
return accounts