pool.py 3.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. """Pool file helpers for zhuce6 ChatGPT token records."""
  2. from __future__ import annotations
  3. from datetime import datetime
  4. import json
  5. from pathlib import Path
  6. import re
  7. import time
  8. from typing import Any
  9. def _safe_component(value: str) -> str:
  10. cleaned = re.sub(r"[^A-Za-z0-9@._+-]+", "_", str(value or "").strip())
  11. return cleaned.strip("._") or "chatgpt_account"
  12. def build_pool_filename(token_data: dict[str, Any]) -> str:
  13. email = str(token_data.get("email") or "").strip()
  14. if email:
  15. return f"{_safe_component(email)}.json"
  16. account_id = str(token_data.get("account_id") or "").strip()
  17. if account_id:
  18. return f"{_safe_component(account_id)}.json"
  19. return f"chatgpt_{int(time.time())}.json"
  20. def now_iso() -> str:
  21. return datetime.now().astimezone().isoformat(timespec="seconds")
  22. def _apply_pool_defaults(token_data: dict[str, Any], *, assign_created_at: bool) -> dict[str, Any]:
  23. payload = dict(token_data)
  24. post_create_gate = str(payload.get("registration_post_create_gate") or "").strip().lower()
  25. warmup_required = bool(payload.get("warmup_required")) or post_create_gate == "add_phone"
  26. payload.setdefault("health_status", "unknown")
  27. payload.setdefault("source", str(payload.get("source") or "register").strip() or "register")
  28. if assign_created_at:
  29. payload.setdefault("created_at", now_iso())
  30. else:
  31. payload.setdefault("created_at", "")
  32. payload.setdefault("backup_written", True)
  33. payload.setdefault("cpa_sync_status", "pending")
  34. payload.setdefault("last_cpa_sync_at", "")
  35. payload.setdefault("last_cpa_sync_error", "")
  36. payload.setdefault("last_probe_at", "")
  37. payload.setdefault("last_probe_status_code", None)
  38. payload.setdefault("last_probe_result", "")
  39. payload.setdefault("last_probe_detail", "")
  40. payload.setdefault("warmup_required", warmup_required)
  41. payload.setdefault("warmup_state", "pending" if warmup_required else "not_required")
  42. payload.setdefault("warmup_passed", False if warmup_required else True)
  43. payload.setdefault("warmup_completed_at", "")
  44. payload.setdefault("successful_probe_count", 0)
  45. payload.setdefault("first_use_proxy_key", "")
  46. payload.setdefault("first_use_proxy_region", "")
  47. payload.setdefault("first_invalid_proxy_key", "")
  48. payload.setdefault("first_invalid_proxy_region", "")
  49. payload.pop("in_main_pool", None)
  50. payload.pop("promoted_at", None)
  51. payload.pop("last_main_pool_attempted_at", None)
  52. return payload
  53. def is_warmup_pending_record(payload: dict[str, Any]) -> bool:
  54. return bool(payload.get("warmup_required")) and not bool(payload.get("warmup_passed"))
  55. def load_token_record(path: Path) -> dict[str, Any]:
  56. payload = json.loads(path.read_text(encoding="utf-8"))
  57. if not isinstance(payload, dict):
  58. raise ValueError(f"token record must be a JSON object: {path}")
  59. return _apply_pool_defaults(payload, assign_created_at=False)
  60. def update_token_record(path: Path, **updates: Any) -> dict[str, Any]:
  61. payload = load_token_record(path)
  62. payload.update(updates)
  63. tmp_path = path.with_name(f"{path.name}.tmp")
  64. tmp_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
  65. tmp_path.replace(path)
  66. return payload
  67. def write_token_record(token_data: dict[str, Any], pool_dir: Path, filename: str | None = None) -> Path:
  68. pool_dir.mkdir(parents=True, exist_ok=True)
  69. target_name = filename or build_pool_filename(token_data)
  70. target_path = pool_dir / target_name
  71. payload = _apply_pool_defaults(token_data, assign_created_at=True)
  72. target_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
  73. return target_path