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:
1 parent
fc879c09ea
commit
2e833f2af3
2 files changed
+191
-31
No files matched your search
+74
-2
@@ -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)}"
|
return False, f"set_birth_date HTTP {res.status_code}: {response_preview(res)}"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
# TLS 库损坏类错误上抛,供 enable_nsfw_for_token 切到 std requests
|
||||||
|
if _is_curl_tls_error(e):
|
||||||
|
raise
|
||||||
if log_callback:
|
if log_callback:
|
||||||
log_callback(f"[set_birth_date] 异常: {e}")
|
log_callback(f"[set_birth_date] 异常: {e}")
|
||||||
return False, 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)}"
|
return False, f"set_tos_accepted HTTP {res.status_code}: {response_preview(res)}"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
if _is_curl_tls_error(e):
|
||||||
|
raise
|
||||||
if log_callback:
|
if log_callback:
|
||||||
log_callback(f"[set_tos_accepted] 异常: {e}")
|
log_callback(f"[set_tos_accepted] 异常: {e}")
|
||||||
return False, f"set_tos_accepted 异常: {e}"
|
return False, f"set_tos_accepted 异常: {e}"
|
||||||
@@ -2082,16 +2087,58 @@ def update_nsfw_settings(session, log_callback=None):
|
|||||||
)
|
)
|
||||||
return False, f"update_nsfw_settings HTTP {res.status_code}: {response_preview(res)}"
|
return False, f"update_nsfw_settings HTTP {res.status_code}: {response_preview(res)}"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
if _is_curl_tls_error(e):
|
||||||
|
raise
|
||||||
if log_callback:
|
if log_callback:
|
||||||
log_callback(f"[update_nsfw] 异常: {e}")
|
log_callback(f"[update_nsfw] 异常: {e}")
|
||||||
return False, f"update_nsfw_settings 异常: {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):
|
def enable_nsfw_for_token(token, cf_clearance="", log_callback=None):
|
||||||
proxies = get_proxies()
|
proxies = get_proxies()
|
||||||
user_agent = get_user_agent()
|
user_agent = get_user_agent()
|
||||||
try:
|
log = log_callback or (lambda *_: None)
|
||||||
with requests.Session(impersonate="chrome120", proxies=proxies) as session:
|
|
||||||
|
def _run(session):
|
||||||
cookie_parts = [f"sso={token}", f"sso-rw={token}"]
|
cookie_parts = [f"sso={token}", f"sso-rw={token}"]
|
||||||
if cf_clearance:
|
if cf_clearance:
|
||||||
cookie_parts.append(f"cf_clearance={cf_clearance}")
|
cookie_parts.append(f"cf_clearance={cf_clearance}")
|
||||||
@@ -2111,6 +2158,31 @@ def enable_nsfw_for_token(token, cf_clearance="", log_callback=None):
|
|||||||
if not ok:
|
if not ok:
|
||||||
return False, message
|
return False, message
|
||||||
return True, "成功开启 NSFW"
|
return True, "成功开启 NSFW"
|
||||||
|
|
||||||
|
def _close(session):
|
||||||
|
try:
|
||||||
|
session.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
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
|
||||||
|
finally:
|
||||||
|
if session is not None:
|
||||||
|
_close(session)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return False, f"异常: {str(e)}"
|
return False, f"异常: {str(e)}"
|
||||||
|
|
||||||
|
|||||||
+117
-29
@@ -111,35 +111,85 @@ def _short(value: str, limit: int = 240) -> str:
|
|||||||
return value if len(value) <= limit else value[:limit]
|
return value if len(value) <= limit else value[:limit]
|
||||||
|
|
||||||
|
|
||||||
def _make_session(proxy: str | None = None):
|
def _is_curl_tls_broken(exc: BaseException | str) -> bool:
|
||||||
"""优先 curl_cffi(Chrome TLS),否则回退标准 requests。"""
|
"""识别 curl_cffi / libcurl OpenSSL 损坏类错误(常见于部分 Windows 环境)。"""
|
||||||
resolved = resolve_proxy(proxy)
|
text = str(exc or "").lower()
|
||||||
proxies = {"http": resolved, "https": resolved} if resolved else None
|
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:
|
try:
|
||||||
import requests as std_requests
|
import requests as std_requests
|
||||||
except ImportError as e: # pragma: no cover
|
except ImportError as e: # pragma: no cover
|
||||||
raise OAuthCodeError(
|
raise OAuthCodeError(
|
||||||
"需要 curl_cffi 或 requests 才能执行 SSO→OAuth 转换"
|
"需要 curl_cffi 或 requests 才能执行 SSO→OAuth 转换"
|
||||||
) from e
|
) from e
|
||||||
|
resolved = resolve_proxy(proxy)
|
||||||
|
proxies = {"http": resolved, "https": resolved} if resolved else None
|
||||||
sess = std_requests.Session()
|
sess = std_requests.Session()
|
||||||
if proxies:
|
if proxies:
|
||||||
sess.proxies.update(proxies)
|
sess.proxies.update(proxies)
|
||||||
|
try:
|
||||||
|
sess._cpa_http_backend = "requests" # type: ignore[attr-defined]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
return sess
|
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:
|
def _set_sso_cookies(session: Any, sso: str) -> None:
|
||||||
sso = normalize_sso_cookie(sso)
|
sso = normalize_sso_cookie(sso)
|
||||||
if not sso:
|
if not sso:
|
||||||
@@ -370,24 +420,17 @@ def exchange_auth_code(session: Any, code: str, flow: AuthCodeFlow) -> TokenResu
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def sso_to_token(
|
def _run_sso_flow(
|
||||||
sso_cookie: str,
|
sso: str,
|
||||||
*,
|
*,
|
||||||
proxy: str | None = None,
|
session: Any,
|
||||||
log: LogFn | None = None,
|
log: LogFn,
|
||||||
require_referrer: bool = True,
|
require_referrer: bool,
|
||||||
) -> TokenResult:
|
) -> TokenResult:
|
||||||
"""SSO cookie → 带 referrer=grok-build 的 OAuth token。"""
|
|
||||||
log = log or _noop_log
|
|
||||||
sso = normalize_sso_cookie(sso_cookie)
|
|
||||||
if not sso:
|
|
||||||
raise OAuthCodeError("sso cookie 为空")
|
|
||||||
|
|
||||||
flow = new_auth_code_flow()
|
flow = new_auth_code_flow()
|
||||||
session = _make_session(proxy)
|
backend = getattr(session, "_cpa_http_backend", "unknown")
|
||||||
try:
|
log(f"Authorization Code Flow referrer={GROK_REFERRER} http={backend}")
|
||||||
_set_sso_cookies(session, sso)
|
_set_sso_cookies(session, sso)
|
||||||
log(f"Authorization Code Flow referrer={GROK_REFERRER}")
|
|
||||||
consent_url = open_authorize_page(session, flow)
|
consent_url = open_authorize_page(session, flow)
|
||||||
log(f"authorize -> consent: {_short(consent_url, 120)}")
|
log(f"authorize -> consent: {_short(consent_url, 120)}")
|
||||||
code = approve_authorization(session, consent_url, flow)
|
code = approve_authorization(session, consent_url, flow)
|
||||||
@@ -402,11 +445,56 @@ def sso_to_token(
|
|||||||
log("access_token referrer=grok-build ok")
|
log("access_token referrer=grok-build ok")
|
||||||
log(f"token ok expires_in={token.expires_in} refresh=yes")
|
log(f"token ok expires_in={token.expires_in} refresh=yes")
|
||||||
return token
|
return token
|
||||||
|
|
||||||
|
|
||||||
|
def sso_to_token(
|
||||||
|
sso_cookie: str,
|
||||||
|
*,
|
||||||
|
proxy: str | None = None,
|
||||||
|
log: LogFn | None = None,
|
||||||
|
require_referrer: bool = True,
|
||||||
|
) -> TokenResult:
|
||||||
|
"""SSO cookie → 带 referrer=grok-build 的 OAuth token。"""
|
||||||
|
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)}")
|
||||||
|
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:
|
finally:
|
||||||
try:
|
try:
|
||||||
session.close()
|
session.close()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
session = None # type: ignore[assignment]
|
||||||
|
finally:
|
||||||
|
if session is not None:
|
||||||
|
try:
|
||||||
|
session.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def mint_from_sso(
|
def mint_from_sso(
|
||||||
|
|||||||
Reference in new issue
Block a user