Fallback mint/TOS HTTP from broken curl_cffi TLS to std requests.

Windows hosts hitting OPENSSL_internal:invalid library (curl 35) now retry SSO OAuth and post-reg TOS/NSFW via standard requests so CPA export can proceed.
This commit is contained in:
chaos committed 2026-07-14 09:56:11 +08:00
1 parent fc879c09ea
commit 2e833f2af3
2 files changed
+214 -54

No files matched your search

+122 -34
View File
@@ -111,35 +111,85 @@ def _short(value: str, limit: int = 240) -> str:
return value if len(value) <= limit else value[:limit]
def _make_session(proxy: str | None = None):
"""优先 curl_cffi(Chrome TLS),否则回退标准 requests。"""
resolved = resolve_proxy(proxy)
proxies = {"http": resolved, "https": resolved} if resolved else None
def _is_curl_tls_broken(exc: BaseException | str) -> bool:
"""识别 curl_cffi / libcurl OpenSSL 损坏类错误(常见于部分 Windows 环境)。"""
text = str(exc or "").lower()
needles = (
"openssl_internal:invalid library",
"tls connect error",
"curl: (35)",
"failed to perform, curl: (35)",
"ssl_error_syscall",
"ssl connect error",
"wrong version number",
)
return any(n in text for n in needles)
try:
from curl_cffi import requests as crequests
for impersonate in ("chrome131", "chrome124", "chrome120"):
try:
return crequests.Session(impersonate=impersonate, proxies=proxies)
except Exception:
continue
return crequests.Session(proxies=proxies)
except ImportError:
pass
def _make_std_session(proxy: str | None = None):
try:
import requests as std_requests
except ImportError as e: # pragma: no cover
raise OAuthCodeError(
"需要 curl_cffi 或 requests 才能执行 SSO→OAuth 转换"
) from e
resolved = resolve_proxy(proxy)
proxies = {"http": resolved, "https": resolved} if resolved else None
sess = std_requests.Session()
if proxies:
sess.proxies.update(proxies)
try:
sess._cpa_http_backend = "requests" # type: ignore[attr-defined]
except Exception:
pass
return sess
def _make_curl_session(proxy: str | None = None):
from curl_cffi import requests as crequests
resolved = resolve_proxy(proxy)
proxies = {"http": resolved, "https": resolved} if resolved else None
last_err: Exception | None = None
for impersonate in ("chrome131", "chrome124", "chrome120", "chrome110", None):
try:
if impersonate:
sess = crequests.Session(impersonate=impersonate, proxies=proxies)
else:
sess = crequests.Session(proxies=proxies)
try:
sess._cpa_http_backend = f"curl_cffi:{impersonate or 'default'}" # type: ignore[attr-defined]
except Exception:
pass
return sess
except Exception as exc: # noqa: BLE001
last_err = exc
continue
if last_err:
raise last_err
raise OAuthCodeError("curl_cffi Session 创建失败")
def _make_session(proxy: str | None = None, *, prefer: str = "curl"):
"""优先 curl_cffi(Chrome TLS);prefer=requests 时直接标准库。"""
prefer = (prefer or "curl").strip().lower()
errors: list[str] = []
if prefer != "requests":
try:
return _make_curl_session(proxy)
except ImportError:
errors.append("curl_cffi 未安装")
except Exception as exc: # noqa: BLE001
errors.append(f"curl_cffi: {exc}")
try:
return _make_std_session(proxy)
except Exception as exc: # noqa: BLE001
errors.append(f"requests: {exc}")
raise OAuthCodeError(
"无法创建 HTTP Session: " + " | ".join(errors)
) from exc
def _set_sso_cookies(session: Any, sso: str) -> None:
sso = normalize_sso_cookie(sso)
if not sso:
@@ -370,6 +420,33 @@ def exchange_auth_code(session: Any, code: str, flow: AuthCodeFlow) -> TokenResu
)
def _run_sso_flow(
sso: str,
*,
session: Any,
log: LogFn,
require_referrer: bool,
) -> TokenResult:
flow = new_auth_code_flow()
backend = getattr(session, "_cpa_http_backend", "unknown")
log(f"Authorization Code Flow referrer={GROK_REFERRER} http={backend}")
_set_sso_cookies(session, sso)
consent_url = open_authorize_page(session, flow)
log(f"authorize -> consent: {_short(consent_url, 120)}")
code = approve_authorization(session, consent_url, flow)
log("consent allow ok")
token = exchange_auth_code(session, code, flow)
if token.referrer != GROK_REFERRER:
msg = f"access_token 未包含预期 referrer(got={token.referrer!r})"
if require_referrer:
raise OAuthCodeError(msg)
log(f"WARN {msg}")
else:
log("access_token referrer=grok-build ok")
log(f"token ok expires_in={token.expires_in} refresh=yes")
return token
def sso_to_token(
sso_cookie: str,
*,
@@ -383,30 +460,41 @@ def sso_to_token(
if not sso:
raise OAuthCodeError("sso cookie 为空")
flow = new_auth_code_flow()
session = _make_session(proxy)
# 先 curl_cffi;若遇到 OpenSSL invalid library / curl(35),自动回退 std requests
session = _make_session(proxy, prefer="curl")
try:
_set_sso_cookies(session, sso)
log(f"Authorization Code Flow referrer={GROK_REFERRER}")
consent_url = open_authorize_page(session, flow)
log(f"authorize -> consent: {_short(consent_url, 120)}")
code = approve_authorization(session, consent_url, flow)
log("consent allow ok")
token = exchange_auth_code(session, code, flow)
if token.referrer != GROK_REFERRER:
msg = f"access_token 未包含预期 referrer(got={token.referrer!r})"
if require_referrer:
raise OAuthCodeError(msg)
log(f"WARN {msg}")
else:
log("access_token referrer=grok-build ok")
log(f"token ok expires_in={token.expires_in} refresh=yes")
return token
finally:
return _run_sso_flow(
sso, session=session, log=log, require_referrer=require_referrer
)
except Exception as exc: # noqa: BLE001
backend = str(getattr(session, "_cpa_http_backend", "") or "")
can_fallback = _is_curl_tls_broken(exc) or (
backend.startswith("curl_cffi") and "curl: (35)" in str(exc).lower()
)
if not can_fallback:
raise
log(f"curl TLS 异常,回退标准 requests: {_short(str(exc), 160)}")
try:
session.close()
except Exception:
pass
session = _make_session(proxy, prefer="requests")
try:
return _run_sso_flow(
sso, session=session, log=log, require_referrer=require_referrer
)
finally:
try:
session.close()
except Exception:
pass
session = None # type: ignore[assignment]
finally:
if session is not None:
try:
session.close()
except Exception:
pass
def mint_from_sso(