Skip to content

Commit 3166710

Browse files
committed
feat: add method to list normal tokens and update Cloudflare clearance handling
1 parent 574b0da commit 3166710

5 files changed

Lines changed: 54 additions & 22 deletions

File tree

api/support.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,17 @@ def worker() -> None:
8686
while not stop_event.is_set():
8787
try:
8888
limited_tokens = account_service.list_limited_tokens()
89+
normal_tokens = account_service.list_normal_tokens()
8990
expiring_tokens = account_service.list_expiring_access_tokens()
9091
keepalive_tokens = account_service.list_refresh_token_keepalive_tokens()
91-
tokens = list(dict.fromkeys([*limited_tokens, *expiring_tokens]))
92+
tokens = list(dict.fromkeys([*limited_tokens, *normal_tokens, *expiring_tokens]))
9293
expiring_token_set = set(expiring_tokens)
9394
keepalive_tokens = [token for token in keepalive_tokens if token not in expiring_token_set]
9495
if tokens:
9596
print(
9697
"[account-watcher] checking "
9798
f"{len(limited_tokens)} limited accounts, "
99+
f"{len(normal_tokens)} normal accounts, "
98100
f"{len(expiring_tokens)} expiring access tokens"
99101
)
100102
account_service.refresh_accounts(tokens)

config.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"auth-key": "chatgpt2api",
3-
"refresh_account_interval_minute": 60,
3+
"refresh_account_interval_minute": 5,
44
"image_retention_days": 15,
55
"image_poll_timeout_secs": 120,
66
"auto_remove_rate_limited_accounts": false,
@@ -13,7 +13,7 @@
1313
],
1414
"proxy": "",
1515
"proxy_runtime": {
16-
"enabled": true,
16+
"enabled": false,
1717
"egress_mode": "single_proxy",
1818
"proxy_url": "http://privoxy:8118",
1919
"resource_proxy_url": "",

services/account_service.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,6 +1076,15 @@ def list_limited_tokens(self) -> list[str]:
10761076
and (token := item.get("access_token") or "")
10771077
]
10781078

1079+
def list_normal_tokens(self) -> list[str]:
1080+
with self._lock:
1081+
return [
1082+
token
1083+
for item in self._accounts.values()
1084+
if item.get("status") == "正常"
1085+
and (token := item.get("access_token") or "")
1086+
]
1087+
10791088
@staticmethod
10801089
def _account_payload_token(item: dict) -> str:
10811090
return str(item.get("access_token") or item.get("accessToken") or "").strip()

services/register/openai_register.py

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -194,25 +194,20 @@ def _response_debug_detail(resp, limit: int = 800) -> str:
194194
def _is_cloudflare_challenge(resp) -> bool:
195195
if resp is None:
196196
return False
197-
text = str(getattr(resp, "text", "") or "").lower()
198-
headers = getattr(resp, "headers", {}) or {}
199-
server = str(headers.get("server") or "").lower()
200197
try:
201198
status_code = int(getattr(resp, "status_code", 0) or 0)
202199
except (TypeError, ValueError):
203200
status_code = 0
204-
205-
explicit_challenge_markers = (
206-
"challenges.cloudflare.com" in text
207-
or "<title>just a moment" in text
208-
or "/cdn-cgi/challenge-platform/" in text
201+
if status_code not in (403, 503):
202+
return False
203+
text = str(getattr(resp, "text", "") or "").lower()
204+
return (
205+
"<title>just a moment" in text
206+
or "<title>attention required! | cloudflare" in text
209207
or "cf-chl-" in text
210208
or "__cf_chl_" in text
209+
or "cf-browser-verification" in text
211210
)
212-
if explicit_challenge_markers:
213-
return True
214-
215-
return "cloudflare" in server and status_code in (403, 503)
216211

217212

218213
def _mail_config() -> dict:
@@ -300,10 +295,11 @@ def _headers_with_clearance(
300295
return normalized
301296

302297

303-
def _cloudflare_block_message(resp, prefix: str = "被 Cloudflare 拦截") -> str:
298+
def _cloudflare_block_message(resp, prefix: str = "被 Cloudflare 拦截", reason: str = "") -> str:
304299
status = getattr(resp, "status_code", "unknown")
305300
debug = _response_debug_detail(resp)
306-
return f"{prefix},clearance 刷新失败或重试后仍失败,请更换 IP/代理重试: status={status}, {debug}"
301+
reason = reason or "clearance 刷新失败或重试后仍失败,请更换 IP/代理重试"
302+
return f"{prefix}{reason}: status={status}, {debug}"
307303

308304

309305
def request_with_local_retry(session: requests.Session, method: str, url: str, retry_attempts: int = 3, **kwargs):
@@ -386,6 +382,7 @@ def __init__(self, proxy: str = "") -> None:
386382
self.proxy = str(proxy or "").strip()
387383
self.session = create_session(self.proxy)
388384
self.clearance_user_agent = ""
385+
self.clearance_failure_reason = ""
389386
self.device_id = str(uuid.uuid4())
390387
self.code_verifier = ""
391388
self.platform_auth_code = ""
@@ -407,6 +404,14 @@ def _json_headers(self, referer: str) -> dict[str, str]:
407404
return headers
408405

409406
def _refresh_cloudflare_clearance(self, target_url: str, index: int) -> ClearanceBundle | None:
407+
self.clearance_failure_reason = ""
408+
profile = proxy_settings.get_profile(proxy=self.proxy, upstream=True)
409+
if not profile.clearance_enabled:
410+
self.clearance_failure_reason = (
411+
"可尝试使用 FlareSolverr 清障方式,注意需要 Docker 部署 flaresolverr、privoxy、warp-proxy 等相关容器"
412+
)
413+
step(index, f"检测到 Cloudflare 拦截,{self.clearance_failure_reason}", "yellow")
414+
return None
410415
step(index, "检测到 Cloudflare 拦截,尝试刷新 clearance", "yellow")
411416
bundle = proxy_settings.refresh_clearance(
412417
target_url=target_url,
@@ -418,6 +423,9 @@ def _refresh_cloudflare_clearance(self, target_url: str, index: int) -> Clearanc
418423
_apply_clearance_to_session(self.session, bundle)
419424
self.clearance_user_agent = bundle.user_agent or self.clearance_user_agent
420425
step(index, "Cloudflare clearance 刷新完成,重试当前请求", "yellow")
426+
else:
427+
self.clearance_failure_reason = "clearance 刷新未返回可用 Cookie,请检查 FlareSolverr URL、代理和出口 IP"
428+
step(index, f"Cloudflare clearance 刷新失败:{self.clearance_failure_reason}", "yellow")
421429
return bundle
422430

423431
def _platform_authorize(self, email: str, index: int) -> None:
@@ -453,7 +461,7 @@ def _platform_authorize(self, email: str, index: int) -> None:
453461
if _is_cloudflare_challenge(resp):
454462
bundle = self._refresh_cloudflare_clearance(auth_base, index)
455463
if bundle is None:
456-
raise RuntimeError(_cloudflare_block_message(resp))
464+
raise RuntimeError(_cloudflare_block_message(resp, reason=self.clearance_failure_reason))
457465
retry_headers = _headers_with_clearance(self._navigate_headers(f"{platform_base}/"), target_url, self.proxy, self.clearance_user_agent)
458466
resp, error = request_with_local_retry(self.session, "get", target_url, headers=retry_headers, allow_redirects=True, verify=False)
459467
if _is_cloudflare_challenge(resp):
@@ -479,7 +487,7 @@ def _register_user(self, email: str, password: str, index: int) -> None:
479487
if _is_cloudflare_challenge(resp):
480488
bundle = self._refresh_cloudflare_clearance(auth_base, index)
481489
if bundle is None:
482-
raise RuntimeError(_cloudflare_block_message(resp))
490+
raise RuntimeError(_cloudflare_block_message(resp, reason=self.clearance_failure_reason))
483491
headers = self._json_headers(f"{auth_base}/create-account/password")
484492
headers["openai-sentinel-token"] = build_sentinel_token(self.session, self.device_id, "username_password_create")
485493
headers = _headers_with_clearance(headers, url, self.proxy, self.clearance_user_agent)
@@ -502,7 +510,7 @@ def _send_otp(self, index: int) -> None:
502510
if _is_cloudflare_challenge(resp):
503511
bundle = self._refresh_cloudflare_clearance(auth_base, index)
504512
if bundle is None:
505-
raise RuntimeError(_cloudflare_block_message(resp))
513+
raise RuntimeError(_cloudflare_block_message(resp, reason=self.clearance_failure_reason))
506514
headers = _headers_with_clearance(self._navigate_headers(f"{auth_base}/create-account/password"), url, self.proxy, self.clearance_user_agent)
507515
resp, error = request_with_local_retry(self.session, "get", url, headers=headers, allow_redirects=True, verify=False)
508516
if _is_cloudflare_challenge(resp):
@@ -533,7 +541,7 @@ def _create_account(self, name: str, birthdate: str, index: int) -> None:
533541
if _is_cloudflare_challenge(resp):
534542
bundle = self._refresh_cloudflare_clearance(auth_base, index)
535543
if bundle is None:
536-
raise RuntimeError(_cloudflare_block_message(resp))
544+
raise RuntimeError(_cloudflare_block_message(resp, reason=self.clearance_failure_reason))
537545
headers = self._json_headers(f"{auth_base}/about-you")
538546
headers["openai-sentinel-token"] = build_sentinel_token(self.session, self.device_id, "oauth_create_account")
539547
headers = _headers_with_clearance(headers, url, self.proxy, self.clearance_user_agent)

web/src/app/settings/components/proxy-runtime-card.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { AlertTriangle, Cookie, LoaderCircle, PlugZap, ShieldCheck } from "lucide-react";
3+
import { AlertTriangle, Cookie, LoaderCircle, PlugZap, Save, ShieldCheck } from "lucide-react";
44
import { useState } from "react";
55
import { toast } from "sonner";
66

@@ -29,6 +29,7 @@ export function ProxyRuntimeCard() {
2929
const [targetUrl, setTargetUrl] = useState("https://chatgpt.com");
3030
const config = useSettingsStore((state) => state.config);
3131
const isLoadingConfig = useSettingsStore((state) => state.isLoadingConfig);
32+
const isSavingConfig = useSettingsStore((state) => state.isSavingConfig);
3233
const saveConfig = useSettingsStore((state) => state.saveConfig);
3334
const setProxyRuntimeField = useSettingsStore((state) => state.setProxyRuntimeField);
3435
const setProxyRuntimeClearanceField = useSettingsStore((state) => state.setProxyRuntimeClearanceField);
@@ -360,6 +361,18 @@ export function ProxyRuntimeCard() {
360361
) : null}
361362
</div>
362363
</div>
364+
365+
<div className="flex justify-end">
366+
<Button
367+
type="button"
368+
className="h-10 rounded-xl bg-stone-950 px-5 text-white hover:bg-stone-800"
369+
onClick={() => void saveConfig()}
370+
disabled={isSavingConfig}
371+
>
372+
{isSavingConfig ? <LoaderCircle className="size-4 animate-spin" /> : <Save className="size-4" />}
373+
保存配置
374+
</Button>
375+
</div>
363376
</CardContent>
364377
</Card>
365378
);

0 commit comments

Comments
 (0)