From 2e833f2af3690ddfaea8f43c9feac045acd73868 Mon Sep 17 00:00:00 2001 From: Chaos Date: Tue, 14 Jul 2026 09:56:11 +0800 Subject: [PATCH] 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. --- grok_register_ttk.py | 112 +++++++++++++++++++++++------ oidc_mint/oauth_code.py | 156 +++++++++++++++++++++++++++++++--------- 2 files changed, 214 insertions(+), 54 deletions(-) diff --git a/grok_register_ttk.py b/grok_register_ttk.py index 9e94809..ba3e984 100644 --- a/grok_register_ttk.py +++ b/grok_register_ttk.py @@ -2012,6 +2012,9 @@ def set_birth_date(session, log_callback=None): ) return False, f"set_birth_date HTTP {res.status_code}: {response_preview(res)}" except Exception as e: + # TLS 库损坏类错误上抛,供 enable_nsfw_for_token 切到 std requests + if _is_curl_tls_error(e): + raise if log_callback: log_callback(f"[set_birth_date] 异常: {e}") return False, f"set_birth_date 异常: {e}" @@ -2042,6 +2045,8 @@ def set_tos_accepted(session, log_callback=None): ) return False, f"set_tos_accepted HTTP {res.status_code}: {response_preview(res)}" except Exception as e: + if _is_curl_tls_error(e): + raise if log_callback: log_callback(f"[set_tos_accepted] 异常: {e}") return False, f"set_tos_accepted 异常: {e}" @@ -2082,35 +2087,102 @@ def update_nsfw_settings(session, log_callback=None): ) return False, f"update_nsfw_settings HTTP {res.status_code}: {response_preview(res)}" except Exception as e: + if _is_curl_tls_error(e): + raise if log_callback: log_callback(f"[update_nsfw] 异常: {e}") return False, f"update_nsfw_settings 异常: {e}" +def _is_curl_tls_error(exc) -> bool: + text = str(exc or "").lower() + return any( + n in text + for n in ( + "openssl_internal:invalid library", + "tls connect error", + "curl: (35)", + "failed to perform, curl: (35)", + "ssl_error_syscall", + "ssl connect error", + ) + ) + + +def _make_post_reg_session(proxies=None, *, prefer: str = "curl"): + """注册后 set_tos / nsfw 用的 session;curl TLS 坏了时回退 std requests。""" + prefer = (prefer or "curl").strip().lower() + if prefer != "requests": + try: + return requests.Session(impersonate="chrome120", proxies=proxies) + except TypeError: + # 极老 curl_cffi 无 impersonate + try: + return requests.Session(proxies=proxies) + except Exception: + pass + except Exception: + pass + try: + import requests as std_requests + except ImportError as exc: # pragma: no cover + raise RuntimeError("需要 curl_cffi 或 requests") from exc + sess = std_requests.Session() + if proxies: + sess.proxies.update(proxies) + return sess + + def enable_nsfw_for_token(token, cf_clearance="", log_callback=None): proxies = get_proxies() user_agent = get_user_agent() + log = log_callback or (lambda *_: None) + + def _run(session): + cookie_parts = [f"sso={token}", f"sso-rw={token}"] + if cf_clearance: + cookie_parts.append(f"cf_clearance={cf_clearance}") + session.headers.update( + { + "user-agent": user_agent, + "cookie": "; ".join(cookie_parts), + } + ) + ok, message = set_tos_accepted(session, log_callback) + if not ok: + return False, message + ok, message = set_birth_date(session, log_callback) + if not ok: + return False, message + ok, message = update_nsfw_settings(session, log_callback) + if not ok: + return False, message + return True, "成功开启 NSFW" + + def _close(session): + try: + session.close() + except Exception: + pass + try: - with requests.Session(impersonate="chrome120", proxies=proxies) as session: - cookie_parts = [f"sso={token}", f"sso-rw={token}"] - if cf_clearance: - cookie_parts.append(f"cf_clearance={cf_clearance}") - session.headers.update( - { - "user-agent": user_agent, - "cookie": "; ".join(cookie_parts), - } - ) - ok, message = set_tos_accepted(session, log_callback) - if not ok: - return False, message - ok, message = set_birth_date(session, log_callback) - if not ok: - return False, message - ok, message = update_nsfw_settings(session, log_callback) - if not ok: - return False, message - return True, "成功开启 NSFW" + session = _make_post_reg_session(proxies, prefer="curl") + try: + return _run(session) + except Exception as exc: + if not _is_curl_tls_error(exc): + return False, f"异常: {exc}" + log(f"[Debug] curl TLS 异常,set_tos/nsfw 回退 requests: {exc}") + _close(session) + session = _make_post_reg_session(proxies, prefer="requests") + try: + return _run(session) + finally: + _close(session) + session = None + finally: + if session is not None: + _close(session) except Exception as e: return False, f"异常: {str(e)}" diff --git a/oidc_mint/oauth_code.py b/oidc_mint/oauth_code.py index 8ecb020..2199eb5 100644 --- a/oidc_mint/oauth_code.py +++ b/oidc_mint/oauth_code.py @@ -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(