http_client.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. """OpenAI-specific HTTP client helpers for zhuce6."""
  2. from __future__ import annotations
  3. import json
  4. import logging
  5. from typing import Any
  6. from curl_cffi import requests as cffi_requests
  7. from core.http_client import HTTPClient, HTTPClientError, RequestConfig
  8. from .constants import (
  9. OPENAI_API_ENDPOINTS,
  10. OPENAI_IMPERSONATE,
  11. OPENAI_SEC_CH_UA,
  12. OPENAI_SEC_CH_UA_MOBILE,
  13. OPENAI_SEC_CH_UA_PLATFORM,
  14. OPENAI_USER_AGENT,
  15. )
  16. from .sentinel_pow import SentinelTokenGenerator
  17. logger = logging.getLogger(__name__)
  18. class OpenAIHTTPClient(HTTPClient):
  19. def __init__(self, proxy_url: str | None = None, config: RequestConfig | None = None) -> None:
  20. resolved_config = config or RequestConfig(impersonate=OPENAI_IMPERSONATE)
  21. super().__init__(proxy_url=proxy_url, config=resolved_config)
  22. self._sentinel_payloads: dict[tuple[str, str], dict[str, str]] = {}
  23. self.default_headers = {
  24. "User-Agent": OPENAI_USER_AGENT,
  25. "Accept": "application/json",
  26. "Accept-Language": "en-US,en;q=0.9",
  27. "sec-ch-ua": OPENAI_SEC_CH_UA,
  28. "sec-ch-ua-mobile": OPENAI_SEC_CH_UA_MOBILE,
  29. "sec-ch-ua-platform": OPENAI_SEC_CH_UA_PLATFORM,
  30. }
  31. def check_ip_location(self) -> tuple[bool, str | None]:
  32. try:
  33. response = self.get("https://cloudflare.com/cdn-cgi/trace", timeout=10)
  34. for line in response.text.splitlines():
  35. if line.startswith("loc="):
  36. loc = line.split("=", 1)[1].strip()
  37. if loc == "CN":
  38. return False, loc
  39. return True, loc
  40. except Exception as exc:
  41. logger.warning("IP location check failed, proceeding anyway: %s", exc)
  42. return True, None
  43. def send_openai_request(
  44. self,
  45. endpoint: str,
  46. method: str = "POST",
  47. data: Any = None,
  48. json_data: Any = None,
  49. headers: dict[str, str] | None = None,
  50. **kwargs: Any,
  51. ) -> dict[str, Any]:
  52. request_headers = self.default_headers.copy()
  53. if headers:
  54. request_headers.update(headers)
  55. try:
  56. response = self.request(
  57. method,
  58. endpoint,
  59. data=data,
  60. json=json_data,
  61. headers=request_headers,
  62. **kwargs,
  63. )
  64. response.raise_for_status()
  65. try:
  66. return response.json()
  67. except json.JSONDecodeError:
  68. return {"raw_response": response.text}
  69. except cffi_requests.RequestsError as exc:
  70. raise HTTPClientError(f"OpenAI request failed: {endpoint} - {exc}") from exc
  71. def build_sentinel_header(self, *, device_id: str, flow: str, token: str = "") -> str:
  72. payload = self._sentinel_payloads.get((str(device_id or "").strip(), str(flow or "").strip()))
  73. if payload:
  74. return json.dumps(payload, separators=(",", ":"))
  75. return json.dumps(
  76. {
  77. "p": "",
  78. "t": "",
  79. "c": str(token or "").strip(),
  80. "id": str(device_id or "").strip(),
  81. "flow": str(flow or "").strip(),
  82. },
  83. separators=(",", ":"),
  84. )
  85. def check_sentinel(self, did: str, *, flow: str = "authorize_continue") -> str | None:
  86. try:
  87. device_id = str(did or "").strip()
  88. resolved_flow = str(flow or "authorize_continue").strip() or "authorize_continue"
  89. generator = SentinelTokenGenerator(
  90. device_id=device_id,
  91. user_agent=self.default_headers.get("User-Agent"),
  92. )
  93. sen_req_body = json.dumps(
  94. {
  95. "p": generator.generate_requirements_token(),
  96. "id": device_id,
  97. "flow": resolved_flow,
  98. },
  99. separators=(",", ":"),
  100. )
  101. response = self.post(
  102. OPENAI_API_ENDPOINTS["sentinel"],
  103. headers={
  104. "origin": "https://sentinel.openai.com",
  105. "referer": (
  106. "https://sentinel.openai.com/backend-api/"
  107. "sentinel/frame.html?sv=20260219f9f6"
  108. ),
  109. "content-type": "text/plain;charset=UTF-8",
  110. "sec-ch-ua": OPENAI_SEC_CH_UA,
  111. "sec-ch-ua-mobile": OPENAI_SEC_CH_UA_MOBILE,
  112. "sec-ch-ua-platform": OPENAI_SEC_CH_UA_PLATFORM,
  113. },
  114. data=sen_req_body,
  115. )
  116. if response.status_code == 200:
  117. payload = response.json()
  118. token = str(payload.get("token") or "").strip()
  119. if not token:
  120. return None
  121. pow_data = payload.get("proofofwork") or {}
  122. if isinstance(pow_data, dict) and pow_data.get("required") and pow_data.get("seed"):
  123. p_value = generator.generate_token(
  124. seed=str(pow_data.get("seed") or ""),
  125. difficulty=str(pow_data.get("difficulty") or "0"),
  126. )
  127. else:
  128. p_value = generator.generate_requirements_token()
  129. self._sentinel_payloads[(device_id, resolved_flow)] = {
  130. "p": p_value,
  131. "t": "0",
  132. "c": token,
  133. "id": device_id,
  134. "flow": resolved_flow,
  135. }
  136. return token
  137. except Exception as exc:
  138. logger.warning("Sentinel request failed: %s", exc)
  139. return None