gpt2api / services /cpa_export_service.py
jiayi.xie
Include password in CPA export payload
2a025c0
Raw
History Blame Contribute Delete
3.68 kB
from __future__ import annotations
import base64
import json
import re
import zipfile
from datetime import datetime, timedelta, timezone
from io import BytesIO
from typing import Any
_CPA_TZ = timezone(timedelta(hours=8))
_UNSAFE_FILENAME_CHARS = re.compile(r'[<>:"/\\|?*\x00-\x1f]+')
def _clean_text(value: Any) -> str:
return str(value or "").strip()
def decode_jwt_payload(token: str) -> dict[str, Any] | None:
parts = _clean_text(token).split(".")
if len(parts) < 2:
return None
payload = parts[1]
padding = "=" * (-len(payload) % 4)
try:
raw = base64.urlsafe_b64decode((payload + padding).encode("ascii"))
decoded = json.loads(raw.decode("utf-8"))
except Exception:
return None
return decoded if isinstance(decoded, dict) else None
def format_cpa_time(timestamp: int | float | str | None) -> str:
if timestamp is None or timestamp == "":
return ""
try:
value = float(timestamp)
except (TypeError, ValueError):
return _clean_text(timestamp)
try:
return datetime.fromtimestamp(value, _CPA_TZ).isoformat(timespec="seconds")
except (OverflowError, OSError, ValueError):
return ""
def safe_cpa_filename(email: str, index: int) -> str:
name = _clean_text(email) or f"account-{index + 1}"
name = _UNSAFE_FILENAME_CHARS.sub("_", name).strip(" .")
if not name:
name = f"account-{index + 1}"
return f"{name}.json"
def _is_disabled(account: dict[str, Any]) -> bool:
if bool(account.get("disabled")):
return True
status = _clean_text(account.get("status"))
if not status:
return False
return status not in {"正常", "active", "enabled", "ok"}
def build_cpa_payload(account: dict[str, Any]) -> dict[str, Any]:
access_token = _clean_text(account.get("access_token"))
jwt_payload = decode_jwt_payload(access_token) or {}
expired = account.get("expired")
if not expired:
expired = account.get("expires_at") or jwt_payload.get("exp")
last_refresh = account.get("last_refresh")
if not last_refresh:
last_refresh = account.get("refreshed_at") or jwt_payload.get("iat")
return {
"access_token": access_token,
"account_id": _clean_text(account.get("account_id") or account.get("user_id")),
"disabled": _is_disabled(account),
"email": _clean_text(account.get("email")),
"expired": format_cpa_time(expired),
"id_token": _clean_text(account.get("id_token")),
"last_refresh": format_cpa_time(last_refresh),
"password": _clean_text(account.get("password")),
"refresh_token": _clean_text(account.get("refresh_token")),
"type": _clean_text(account.get("cpa_type")) or "codex",
}
def build_cpa_zip(accounts: list[dict[str, Any]]) -> bytes:
buffer = BytesIO()
used_names: dict[str, int] = {}
with zipfile.ZipFile(buffer, "w", compression=zipfile.ZIP_DEFLATED) as archive:
for index, account in enumerate(accounts):
payload = build_cpa_payload(account)
base_name = safe_cpa_filename(payload.get("email") or payload.get("account_id"), index)
name = base_name
if name in used_names:
used_names[base_name] += 1
stem = base_name[:-5] if base_name.endswith(".json") else base_name
name = f"{stem}-{used_names[base_name]}.json"
else:
used_names[base_name] = 1
archive.writestr(
name,
json.dumps(payload, ensure_ascii=False, indent=2).encode("utf-8"),
)
return buffer.getvalue()