From 96061cbd788434a623f9f43759f00eb2fd3d0536 Mon Sep 17 00:00:00 2001 From: Chaos Date: Tue, 14 Jul 2026 10:19:25 +0800 Subject: [PATCH] Prefer browser PKCE mint over flaky direct HTTP to auth.x.ai. Logs show curl(28)/ReadTimeout on direct mint while registration browser still works; skip slow requests fallback, shorten HTTP step timeout, and mint via page first when available. --- cpa_export.py | 42 ++++++++++++++++++++++++--------- grok_register_ttk.py | 30 +++++++++++++----------- oidc_mint/oauth_code.py | 51 ++++++++++++++++------------------------- 3 files changed, 68 insertions(+), 55 deletions(-) diff --git a/cpa_export.py b/cpa_export.py index 357d215..a7524dd 100644 --- a/cpa_export.py +++ b/cpa_export.py @@ -72,7 +72,13 @@ def _mint_tokens( log: Callable[[str], None], proxy: str | None, ) -> dict[str, Any]: - """优先 HTTP SSO 授权码;TLS 失败时用注册浏览器 PKCE;可选设备码。""" + """铸造策略(有 page 时优先浏览器,避免直连 auth.x.ai 卡超时): + + 1. page+sso 且 cpa_prefer_browser_mint:直接浏览器 PKCE + 2. 否则 HTTP SSO→OAuth(curl_cffi,短超时) + 3. HTTP 失败且有 page:浏览器 PKCE + 4. 可选设备码(通常无 referrer,默认关) + """ from oidc_mint.oauth_code import ( OAuthCodeError, _is_http_tls_failure, @@ -84,10 +90,31 @@ def _mint_tokens( sso_token = normalize_sso_cookie(sso or "") prefer_sso = bool(cfg.get("cpa_prefer_sso_oauth", True)) allow_browser = bool(cfg.get("cpa_allow_browser_fallback", True)) + # 有注册浏览器时默认先浏览器:直连/跨境对 auth.x.ai 经常 curl(28)/ReadTimeout + prefer_browser = bool(cfg.get("cpa_prefer_browser_mint", True)) allow_device = bool(cfg.get("cpa_allow_device_fallback", False)) timeout = float(cfg.get("mint_timeout_sec", 300) or 300) require_ref = bool(cfg.get("cpa_require_referrer", True)) http_err: Exception | None = None + browser_timeout = min(timeout, float(cfg.get("cpa_browser_mint_timeout_sec", 120) or 120)) + + def _browser_mint(reason: str) -> dict[str, Any]: + log(f"[cpa] 浏览器 PKCE 铸造({reason})") + return mint_from_sso_browser( + sso_token, + page, + log=lambda m: log(f"[Debug] {m}"), + require_referrer=require_ref, + timeout_sec=browser_timeout, + ) + + # 路径 A:有 page 时优先浏览器(最稳,不依赖 Python→auth.x.ai 直连) + if prefer_sso and sso_token and allow_browser and page is not None and prefer_browser: + try: + return _browser_mint("优先浏览器,绕开 Python 直连 auth.x.ai") + except Exception as exc: # noqa: BLE001 + log(f"[!] 浏览器 PKCE 优先路径失败: {exc}") + log("[cpa] 继续尝试 HTTP SSO→OAuth") if prefer_sso and sso_token: log("[cpa] 使用 SSO→OAuth(PKCE, referrer=grok-build)") @@ -105,18 +132,11 @@ def _mint_tokens( http_err = exc log(f"[!] SSO→OAuth 异常: {exc}") - # HTTP 失败后:有 page+sso 就优先浏览器 PKCE(Chrome TLS 通常正常,且保留 referrer) + # HTTP 失败后:有 page+sso 再试浏览器(若优先路径没走过或当时失败) if allow_browser and page is not None and sso_token and http_err is not None: - why = "TLS/连接" if _is_http_tls_failure(http_err) else "HTTP" - log(f"[cpa] 回退浏览器 PKCE 铸造({why}失败,绕开 Python TLS)") + why = "TLS/连接/超时" if _is_http_tls_failure(http_err) else "HTTP" try: - return mint_from_sso_browser( - sso_token, - page, - log=lambda m: log(f"[Debug] {m}"), - require_referrer=require_ref, - timeout_sec=min(timeout, 120.0), - ) + return _browser_mint(f"{why}失败后回退") except Exception as exc: # noqa: BLE001 log(f"[!] 浏览器 PKCE 失败: {exc}") if not allow_device: diff --git a/grok_register_ttk.py b/grok_register_ttk.py index bed610a..9614eea 100644 --- a/grok_register_ttk.py +++ b/grok_register_ttk.py @@ -85,7 +85,9 @@ DEFAULT_CONFIG = { # OIDC:优先 SSO→Authorization Code + referrer=grok-build "cpa_prefer_sso_oauth": True, # True=用 sso cookie 走 PKCE(必须带 referrer) "cpa_require_referrer": True, # True=access_token 无 referrer=grok-build 则失败 - "cpa_allow_browser_fallback": True, # True=HTTP 铸造失败时用注册浏览器走 PKCE(绕 Python TLS) + "cpa_prefer_browser_mint": True, # True=有注册浏览器时优先浏览器 PKCE(避开 Python 直连超时) + "cpa_allow_browser_fallback": True, # True=HTTP 铸造失败时用注册浏览器走 PKCE + "cpa_browser_mint_timeout_sec": 120, # 浏览器 PKCE 最长等待 "cpa_allow_device_fallback": False, # True=SSO 失败时回退设备码(通常不可用) # OIDC 铸造代理/超时 "mint_proxy": "", # 铸造专用代理;空=复用 proxy @@ -2096,6 +2098,7 @@ def update_nsfw_settings(session, log_callback=None): def _is_curl_tls_error(exc) -> bool: + """curl_cffi 库损坏 / TLS / 连接超时:可尝试换后端或记失败,勿当业务错误。""" text = str(exc or "").lower() return any( n in text @@ -2103,9 +2106,16 @@ def _is_curl_tls_error(exc) -> bool: "openssl_internal:invalid library", "tls connect error", "curl: (35)", + "curl: (28)", "failed to perform, curl: (35)", + "failed to perform, curl: (28)", + "connection timed out", "ssl_error_syscall", "ssl connect error", + "readtimeout", + "connecttimeout", + "timed out", + "timeout", ) ) @@ -2166,24 +2176,18 @@ def enable_nsfw_for_token(token, cf_clearance="", log_callback=None): except Exception: pass + # 仅用 curl_cffi:本机实测 std requests 对 accounts.x.ai/auth.x.ai 更易 ReadTimeout try: 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 + if _is_curl_tls_error(exc): + log(f"[Debug] set_tos/nsfw 网络/TLS 失败(不回退 requests): {exc}") + return False, f"网络/TLS: {exc}" + return False, f"异常: {exc}" finally: - if session is not None: - _close(session) + _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 7aa83db..35183db 100644 --- a/oidc_mint/oauth_code.py +++ b/oidc_mint/oauth_code.py @@ -260,6 +260,10 @@ def _final_url(resp: Any) -> str: return "" +# HTTP 铸造单步超时:直连/跨境链路差时不要卡 30s,尽快让上层走浏览器 +HTTP_STEP_TIMEOUT = 12 + + def open_authorize_page(session: Any, flow: AuthCodeFlow) -> str: params = { "response_type": "code", @@ -277,7 +281,7 @@ def open_authorize_page(session: Any, flow: AuthCodeFlow) -> str: url, headers=_browser_headers("GET", url), allow_redirects=True, - timeout=30, + timeout=HTTP_STEP_TIMEOUT, ) body = resp.text or "" final = _final_url(resp) @@ -355,7 +359,7 @@ def approve_authorization(session: Any, consent_url: str, flow: AuthCodeFlow) -> data=body.encode("utf-8"), headers=_browser_headers("POST", consent_url, NEXT_ACTION_ID), allow_redirects=True, - timeout=30, + timeout=HTTP_STEP_TIMEOUT, ) text = resp.text or "" if resp.status_code < 200 or resp.status_code >= 300: @@ -384,7 +388,7 @@ def exchange_auth_code(session: Any, code: str, flow: AuthCodeFlow) -> TokenResu TOKEN_URL, data=urlencode(form), headers=_token_headers(), - timeout=30, + timeout=HTTP_STEP_TIMEOUT, ) text = resp.text or "" if resp.status_code < 200 or resp.status_code >= 300: @@ -454,47 +458,27 @@ def sso_to_token( log: LogFn | None = None, require_referrer: bool = True, ) -> TokenResult: - """SSO cookie → 带 referrer=grok-build 的 OAuth token。""" + """SSO cookie → 带 referrer=grok-build 的 OAuth token。 + + 仅用 curl_cffi(Chrome TLS)。不再回退 std requests: + 实测 requests 访问 auth.x.ai / accounts.x.ai 常 ReadTimeout,比 curl 更差。 + 连接/TLS/超时由上层切到浏览器 PKCE。 + """ log = log or _noop_log sso = normalize_sso_cookie(sso_cookie) if not sso: raise OAuthCodeError("sso cookie 为空") - # 先 curl_cffi;若遇到 OpenSSL invalid library / curl(35),自动回退 std requests session = _make_session(proxy, prefer="curl") try: 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)}") + finally: 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( @@ -523,7 +507,7 @@ def mint_from_sso( def _is_http_tls_failure(exc: BaseException | str) -> bool: - """HTTP 层 TLS/连接失败:适合改走浏览器铸造。""" + """HTTP 层 TLS/连接/超时失败:适合改走浏览器铸造。""" text = str(exc or "").lower() needles = ( "unexpected_eof_while_reading", @@ -533,11 +517,16 @@ def _is_http_tls_failure(exc: BaseException | str) -> bool: "openssl_internal:invalid library", "tls connect error", "curl: (35)", + "curl: (28)", "failed to perform, curl: (35)", + "failed to perform, curl: (28)", + "connection timed out", "ssl_error_syscall", "connection reset", "connection aborted", "name resolution", + "readtimeout", + "connecttimeout", "timed out", "timeout", )