Detect Cloudflare 403 on mint and retry alternate proxies.
- Clear CF block errors with egress label and config hint - Log mint exit (direct/proxy); probe local proxy ports on 403 - Retry mint_proxy/proxy/pool candidates without browser mint
This commit is contained in:
1 parent
cc0aa6a433
commit
7d6d52ac5d
2 files changed
+186
-17
No files matched your search
+138
-17
@@ -62,6 +62,72 @@ def _resolve_proxy(cfg: dict) -> str | None:
|
||||
return resolved or None
|
||||
|
||||
|
||||
def _proxy_label(proxy: str | None) -> str:
|
||||
try:
|
||||
from oidc_mint.proxyutil import proxy_log_label
|
||||
|
||||
return proxy_log_label(proxy or "") or "(direct)"
|
||||
except Exception:
|
||||
return proxy or "(direct)"
|
||||
|
||||
|
||||
def _port_open(host: str, port: int, timeout: float = 0.25) -> bool:
|
||||
import socket
|
||||
|
||||
try:
|
||||
with socket.create_connection((host, port), timeout=timeout):
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def _local_proxy_candidates(cfg: dict) -> list[str]:
|
||||
"""直连被 CF 拦时尝试的本机/配置代理列表(去重)。"""
|
||||
cands: list[str] = []
|
||||
seen: set[str] = set()
|
||||
|
||||
def _add(raw: str | None) -> None:
|
||||
p = (raw or "").strip()
|
||||
if not p or p in seen:
|
||||
return
|
||||
seen.add(p)
|
||||
cands.append(p)
|
||||
|
||||
for key in ("mint_proxy", "proxy"):
|
||||
_add(str(cfg.get(key) or ""))
|
||||
raw_list = cfg.get("proxy_pool") or []
|
||||
if isinstance(raw_list, str):
|
||||
for line in raw_list.replace(",", "\n").splitlines():
|
||||
_add(line)
|
||||
elif isinstance(raw_list, (list, tuple)):
|
||||
for x in raw_list:
|
||||
_add(str(x))
|
||||
try:
|
||||
import proxy_pool
|
||||
|
||||
for p in proxy_pool.pool_snapshot()[:8]:
|
||||
_add(p)
|
||||
except Exception:
|
||||
pass
|
||||
# 仅探测本机已监听的常见代理端口,避免空转超时
|
||||
for port in (7890, 7897, 10809, 10808, 7891, 20171, 6152, 1080):
|
||||
if _port_open("127.0.0.1", port):
|
||||
_add(f"http://127.0.0.1:{port}")
|
||||
return cands
|
||||
|
||||
|
||||
def _is_cf_mint_error(exc: BaseException | str) -> bool:
|
||||
try:
|
||||
from oidc_mint.oauth_code import is_cloudflare_block
|
||||
|
||||
return is_cloudflare_block(exc=exc)
|
||||
except Exception:
|
||||
text = str(exc or "").lower()
|
||||
return "403" in text and (
|
||||
"cloudflare" in text or "<!doctype" in text or "oldie" in text
|
||||
)
|
||||
|
||||
|
||||
def _mint_tokens(
|
||||
*,
|
||||
email: str,
|
||||
@@ -73,32 +139,87 @@ def _mint_tokens(
|
||||
proxy: str | None,
|
||||
) -> dict[str, Any]:
|
||||
"""优先 SSO 授权码;可选回退设备码。"""
|
||||
from oidc_mint import set_runtime_proxy
|
||||
from oidc_mint.oauth_code import OAuthCodeError, mint_from_sso, normalize_sso_cookie
|
||||
|
||||
sso_token = normalize_sso_cookie(sso or "")
|
||||
prefer_sso = bool(cfg.get("cpa_prefer_sso_oauth", True))
|
||||
allow_device = bool(cfg.get("cpa_allow_device_fallback", False))
|
||||
timeout = float(cfg.get("mint_timeout_sec", 300) or 300)
|
||||
max_proxy_tries = int(cfg.get("mint_proxy_retries", 4) or 4)
|
||||
|
||||
if prefer_sso and sso_token:
|
||||
log("[cpa] 使用 SSO→OAuth(PKCE, referrer=grok-build)")
|
||||
try:
|
||||
return mint_from_sso(
|
||||
sso_token,
|
||||
proxy=proxy,
|
||||
log=lambda m: log(f"[Debug] {m}"),
|
||||
require_referrer=bool(cfg.get("cpa_require_referrer", True)),
|
||||
)
|
||||
except OAuthCodeError as exc:
|
||||
log(f"[!] SSO→OAuth 失败: {exc}")
|
||||
if not allow_device:
|
||||
raise
|
||||
log("[cpa] 回退设备码铸造(可能缺 referrer)")
|
||||
except Exception as exc: # noqa: BLE001
|
||||
log(f"[!] SSO→OAuth 异常: {exc}")
|
||||
if not allow_device:
|
||||
raise
|
||||
log("[cpa] 回退设备码铸造(可能缺 referrer)")
|
||||
tried: set[str] = set()
|
||||
proxies_to_try: list[str | None] = [proxy]
|
||||
last_exc: Exception | None = None
|
||||
|
||||
def _expand_proxy_candidates() -> None:
|
||||
for p in _local_proxy_candidates(cfg):
|
||||
if p and p not in tried and p not in {
|
||||
x for x in proxies_to_try if x
|
||||
}:
|
||||
proxies_to_try.append(p)
|
||||
|
||||
# 直连时先挂上本机已开端口,减少首次 403 后的空等
|
||||
if not proxy:
|
||||
_expand_proxy_candidates()
|
||||
|
||||
idx = 0
|
||||
while idx < len(proxies_to_try) and idx < max(1, max_proxy_tries):
|
||||
use_proxy = proxies_to_try[idx]
|
||||
label = _proxy_label(use_proxy)
|
||||
if idx == 0:
|
||||
log(f"[cpa] mint 出口={label}")
|
||||
else:
|
||||
log(f"[cpa] CF/403 换出口重试 ({idx + 1}/{max_proxy_tries}): {label}")
|
||||
set_runtime_proxy(use_proxy)
|
||||
tried.add(use_proxy or "")
|
||||
try:
|
||||
return mint_from_sso(
|
||||
sso_token,
|
||||
proxy=use_proxy,
|
||||
log=lambda m: log(f"[Debug] {m}"),
|
||||
require_referrer=bool(cfg.get("cpa_require_referrer", True)),
|
||||
)
|
||||
except OAuthCodeError as exc:
|
||||
last_exc = exc
|
||||
log(f"[!] SSO→OAuth 失败: {exc}")
|
||||
if _is_cf_mint_error(exc):
|
||||
if use_proxy:
|
||||
try:
|
||||
import proxy_pool
|
||||
|
||||
proxy_pool.report_failure(
|
||||
use_proxy, reason=f"mint CF: {str(exc)[:120]}"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
_expand_proxy_candidates()
|
||||
idx += 1
|
||||
continue
|
||||
if not allow_device:
|
||||
raise
|
||||
log("[cpa] 回退设备码铸造(可能缺 referrer)")
|
||||
break
|
||||
except Exception as exc: # noqa: BLE001
|
||||
last_exc = exc
|
||||
log(f"[!] SSO→OAuth 异常: {exc}")
|
||||
if _is_cf_mint_error(exc):
|
||||
_expand_proxy_candidates()
|
||||
idx += 1
|
||||
continue
|
||||
if not allow_device:
|
||||
raise
|
||||
log("[cpa] 回退设备码铸造(可能缺 referrer)")
|
||||
break
|
||||
# 成功已 return;失败已 continue/break
|
||||
idx += 1 # pragma: no cover
|
||||
else:
|
||||
if last_exc is not None and not allow_device:
|
||||
raise last_exc
|
||||
if last_exc is not None and not allow_device:
|
||||
raise last_exc
|
||||
|
||||
if not allow_device and not sso_token:
|
||||
raise RuntimeError("无 sso cookie,且已禁用设备码回退;无法铸造带 referrer 的 token")
|
||||
|
||||
Reference in new issue
Block a user