bg5 commited on
Commit
e263ccb
·
verified ·
1 Parent(s): ca50bff

添加 captcha fallback

Browse files
.local/state/gh/device-id ADDED
@@ -0,0 +1 @@
 
 
1
+ a11513e8-7e98-4930-b381-793073bbab61
README.md CHANGED
@@ -24,6 +24,7 @@ Required runtime secrets:
24
  - `BACKUP_TOKEN`: Z.AI JWT token copied from `chat.z.ai` localStorage
25
 
26
  The container sets `CAPTCHA_PROVIDER_URL=http://127.0.0.1:9876` so the proxy can fetch captcha tokens from the internal provider.
 
27
 
28
  > 将 [Z.AI](https://chat.z.ai) 转换为 OpenAI 兼容 API 的代理服务。
29
  >
 
24
  - `BACKUP_TOKEN`: Z.AI JWT token copied from `chat.z.ai` localStorage
25
 
26
  The container sets `CAPTCHA_PROVIDER_URL=http://127.0.0.1:9876` so the proxy can fetch captcha tokens from the internal provider.
27
+ If the internal provider cannot get an Aliyun token from the current Space egress IP, set `FALLBACK_CAPTCHA_PROVIDER_URL` to another compatible captcha token service.
28
 
29
  > 将 [Z.AI](https://chat.z.ai) 转换为 OpenAI 兼容 API 的代理服务。
30
  >
captcha-provider/server.js CHANGED
@@ -17,7 +17,9 @@ const BROWSER_BACKEND = process.env.BROWSER_BACKEND || 'cloak';
17
  // Token 预取池配置
18
  const POOL_SIZE = Number(process.env.POOL_SIZE || 5); // 池中保持的 token 数量
19
  const TOKEN_TTL = Number(process.env.TOKEN_TTL || 240000); // token 有效期 4 分钟
20
- const REFILL_INTERVAL = Number(process.env.REFILL_INTERVAL || 3000); // 补充间隔 3s
 
 
21
 
22
  // ─── State ───
23
  let browser = null;
@@ -104,7 +106,7 @@ async function launchBrowser() {
104
  //
105
  // BROWSER_BACKEND=cloak: 使用 CloakBrowser(C++ 源码级 stealth patch chromium,更难被反爬识别)
106
  // BROWSER_BACKEND=playwright: 使用普通 chromium + JS stealth 补丁(fallback)
107
- async function acquireToken() {
108
  let localBrowser;
109
 
110
  if (BROWSER_BACKEND === 'cloak') {
@@ -208,7 +210,41 @@ async function acquireToken() {
208
  } finally {
209
  try { await localBrowser.close(); } catch {}
210
  }
211
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
 
213
  // ─── Token pool management ───
214
 
@@ -284,15 +320,32 @@ const server = http.createServer(async (req, res) => {
284
  }
285
 
286
  // 先从池里取
287
- const cached = getValidToken();
288
- if (cached) {
289
- stats.served++;
290
- console.log(`[provider] Served cached token (pool: ${tokenPool.length})`);
291
- return sendJson(res, 200, { ok: true, token: cached, cached: true });
292
- }
293
-
294
- // 池空了,实时获取
295
- try {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296
  const started = Date.now();
297
  const token = await acquireToken();
298
  const elapsed = Date.now() - started;
 
17
  // Token 预取池配置
18
  const POOL_SIZE = Number(process.env.POOL_SIZE || 5); // 池中保持的 token 数量
19
  const TOKEN_TTL = Number(process.env.TOKEN_TTL || 240000); // token 有效期 4 分钟
20
+ const REFILL_INTERVAL = Number(process.env.REFILL_INTERVAL || 3000); // 补充间隔 3s
21
+ const FALLBACK_CAPTCHA_PROVIDER_URL = process.env.FALLBACK_CAPTCHA_PROVIDER_URL || '';
22
+ const FALLBACK_CAPTCHA_TIMEOUT = Number(process.env.FALLBACK_CAPTCHA_TIMEOUT || 60000);
23
 
24
  // ─── State ───
25
  let browser = null;
 
106
  //
107
  // BROWSER_BACKEND=cloak: 使用 CloakBrowser(C++ 源码级 stealth patch chromium,更难被反爬识别)
108
  // BROWSER_BACKEND=playwright: 使用普通 chromium + JS stealth 补丁(fallback)
109
+ async function acquireToken() {
110
  let localBrowser;
111
 
112
  if (BROWSER_BACKEND === 'cloak') {
 
210
  } finally {
211
  try { await localBrowser.close(); } catch {}
212
  }
213
+ }
214
+
215
+ async function acquireFallbackToken() {
216
+ if (!FALLBACK_CAPTCHA_PROVIDER_URL) return null;
217
+ if (!process.env.BACKUP_TOKEN) {
218
+ throw new Error('fallback captcha requires BACKUP_TOKEN');
219
+ }
220
+
221
+ const controller = new AbortController();
222
+ const timeout = setTimeout(() => controller.abort(), FALLBACK_CAPTCHA_TIMEOUT);
223
+ try {
224
+ const url = FALLBACK_CAPTCHA_PROVIDER_URL.replace(/\/+$/, '') + '/token';
225
+ const resp = await fetch(url, {
226
+ method: 'POST',
227
+ headers: {
228
+ 'Accept': 'application/json',
229
+ 'Content-Type': 'application/json',
230
+ },
231
+ body: JSON.stringify({ jwt_token: process.env.BACKUP_TOKEN }),
232
+ signal: controller.signal,
233
+ });
234
+ const text = await resp.text();
235
+ if (!resp.ok) {
236
+ throw new Error(`fallback status ${resp.status}: ${text.slice(0, 200)}`);
237
+ }
238
+ const data = JSON.parse(text);
239
+ const token = data.captcha_verify_param || data.captchaVerifyParam || data.token;
240
+ if (!token) {
241
+ throw new Error('fallback response missing captcha token');
242
+ }
243
+ return token;
244
+ } finally {
245
+ clearTimeout(timeout);
246
+ }
247
+ }
248
 
249
  // ─── Token pool management ───
250
 
 
320
  }
321
 
322
  // 先从池里取
323
+ const cached = getValidToken();
324
+ if (cached) {
325
+ stats.served++;
326
+ console.log(`[provider] Served cached token (pool: ${tokenPool.length})`);
327
+ return sendJson(res, 200, { ok: true, token: cached, cached: true });
328
+ }
329
+
330
+ if (FALLBACK_CAPTCHA_PROVIDER_URL) {
331
+ try {
332
+ const started = Date.now();
333
+ const token = await acquireFallbackToken();
334
+ if (token) {
335
+ const elapsed = Date.now() - started;
336
+ stats.served++;
337
+ console.log(`[provider] Served fallback token in ${elapsed}ms`);
338
+ return sendJson(res, 200, { ok: true, token, cached: false, fallback: true, elapsed_ms: elapsed });
339
+ }
340
+ } catch (err) {
341
+ lastError = err.message;
342
+ stats.errors++;
343
+ console.error(`[provider] Fallback token error: ${err.message}`);
344
+ }
345
+ }
346
+
347
+ // 池空了,实时获取
348
+ try {
349
  const started = Date.now();
350
  const token = await acquireToken();
351
  const elapsed = Date.now() - started;