cpa_upload.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. """CPA upload helpers for the zhuce6 ChatGPT platform."""
  2. from __future__ import annotations
  3. import json
  4. from typing import Any
  5. from curl_cffi import CurlMime
  6. from curl_cffi import requests as cffi_requests
  7. from .constants import (
  8. OPENAI_IMPERSONATE,
  9. OPENAI_SEC_CH_UA,
  10. OPENAI_SEC_CH_UA_MOBILE,
  11. OPENAI_SEC_CH_UA_PLATFORM,
  12. OPENAI_USER_AGENT,
  13. )
  14. def _upload_url(api_url: str) -> str:
  15. return f"{api_url.rstrip('/')}/v0/management/auth-files"
  16. def _headers(api_key: str | None) -> dict[str, str]:
  17. return {
  18. "Authorization": f"Bearer {api_key or ''}",
  19. "User-Agent": OPENAI_USER_AGENT,
  20. "sec-ch-ua": OPENAI_SEC_CH_UA,
  21. "sec-ch-ua-mobile": OPENAI_SEC_CH_UA_MOBILE,
  22. "sec-ch-ua-platform": OPENAI_SEC_CH_UA_PLATFORM,
  23. }
  24. def _error_message(response: Any) -> str:
  25. base = f"upload failed: HTTP {response.status_code}"
  26. try:
  27. payload = response.json()
  28. except Exception:
  29. payload = None
  30. if isinstance(payload, dict):
  31. message = str(payload.get("message") or payload.get("error") or "").strip()
  32. if message:
  33. return message
  34. text = str(getattr(response, "text", "") or "").strip()
  35. if text:
  36. return f"{base} - {text[:200]}"
  37. return base
  38. def generate_token_json(account: Any) -> dict[str, str]:
  39. expires_at = getattr(account, "expires_at", None)
  40. last_refresh = getattr(account, "last_refresh", None)
  41. return {
  42. "type": "codex",
  43. "email": str(getattr(account, "email", "") or "").strip(),
  44. "expired": expires_at.strftime("%Y-%m-%dT%H:%M:%S+08:00") if expires_at else "",
  45. "id_token": str(getattr(account, "id_token", "") or "").strip(),
  46. "account_id": str(getattr(account, "account_id", "") or "").strip(),
  47. "access_token": str(getattr(account, "access_token", "") or "").strip(),
  48. "last_refresh": last_refresh.strftime("%Y-%m-%dT%H:%M:%S+08:00") if last_refresh else "",
  49. "refresh_token": str(getattr(account, "refresh_token", "") or "").strip(),
  50. }
  51. def upload_to_cpa(
  52. token_data: dict[str, str],
  53. api_url: str | None = None,
  54. api_key: str | None = None,
  55. proxy: str | None = None,
  56. ) -> tuple[bool, str]:
  57. del proxy # CPA is direct-connect by default in zhuce6.
  58. if not api_url:
  59. return False, "CPA API URL is required"
  60. upload_url = _upload_url(api_url)
  61. payload = json.dumps(token_data, ensure_ascii=False, indent=2).encode("utf-8")
  62. mime = CurlMime()
  63. mime.addpart(
  64. name="file",
  65. data=payload,
  66. filename=f"{token_data.get('email', 'account')}.json",
  67. content_type="application/json",
  68. )
  69. try:
  70. response = cffi_requests.post(
  71. upload_url,
  72. multipart=mime,
  73. headers=_headers(api_key),
  74. timeout=30,
  75. impersonate=OPENAI_IMPERSONATE,
  76. )
  77. except Exception as exc:
  78. return False, f"upload exception: {exc}"
  79. if response.status_code in {200, 201}:
  80. return True, "upload success"
  81. return False, _error_message(response)
  82. def upload_to_team_manager(account: Any, api_url: str | None = None, api_key: str | None = None) -> tuple[bool, str]:
  83. ok, message = upload_to_cpa(
  84. generate_token_json(account),
  85. api_url=api_url,
  86. api_key=api_key,
  87. proxy=None,
  88. )
  89. if ok:
  90. return True, "team manager upload success"
  91. return False, message
  92. def test_cpa_connection(api_url: str | None = None, api_key: str | None = None) -> tuple[bool, str]:
  93. if not api_url:
  94. return False, "CPA API URL is required"
  95. try:
  96. response = cffi_requests.options(
  97. _upload_url(api_url),
  98. headers=_headers(api_key),
  99. timeout=10,
  100. impersonate=OPENAI_IMPERSONATE,
  101. )
  102. except Exception as exc:
  103. return False, f"connection failed: {exc}"
  104. if response.status_code in {200, 204, 401, 403, 405}:
  105. if response.status_code == 401:
  106. return False, "connection reached server but API key is invalid"
  107. return True, "connection ok"
  108. return False, f"connection failed: HTTP {response.status_code}"