creditdaddy 1.3.2 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -13
- package/bin/creditdaddy.js +33 -12
- package/package.json +5 -2
- package/src/accounts.js +10 -0
- package/src/authDevice.js +5 -4
- package/src/bgdaemon.js +14 -5
- package/src/checkin.js +69 -39
- package/src/daemon.js +1266 -1059
- package/src/i18n/en.json +1490 -0
- package/src/i18n/ja.json +1490 -0
- package/src/i18n/ko.json +1490 -0
- package/src/i18n/zh-TW.json +1490 -0
- package/src/i18n.js +47 -0
- package/src/logger.js +114 -17
- package/src/minimaxClient.js +18 -21
- package/src/minimaxGateway.js +63 -45
- package/src/minimaxLocal.js +61 -16
- package/src/mirasimClient.js +6 -1
- package/src/panel.html +1139 -626
- package/src/qoderClient.js +24 -4
- package/src/qoderUmid.js +4 -3
- package/src/refreshGuard.js +69 -0
- package/src/store.js +11 -0
- package/src/tenrouter.js +14 -7
- package/src/traeGateway.js +43 -24
- package/src/transfer.js +44 -6
- package/src/workbuddyClient.js +14 -7
- package/src/zcodeAutoClaim.js +47 -7
- package/src/zcodeClient.js +8 -3
- package/src/zcodeGateway.js +22 -15
package/src/zcodeAutoClaim.js
CHANGED
|
@@ -12,7 +12,20 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { logger } from './logger.js';
|
|
15
|
+
import { t } from './i18n.js';
|
|
15
16
|
import { fetchClaimPlans, claimPlan, fetchCaptchaConfig } from './zcodeClient.js';
|
|
17
|
+
import { withAccounts } from './store.js';
|
|
18
|
+
|
|
19
|
+
// 额满(1005)时消费服务端 nextAt(plan.ends_at):
|
|
20
|
+
// 下轮自动轮询(claimIntervalMin 分钟一触)在 nextAt 之前跳过该号,不做无谓抢。
|
|
21
|
+
// 进程内缓存 + 落盘 account.meta.zcodeClaimNextAt 双轨(重启保护)。
|
|
22
|
+
const snoozeUntil = new Map(); // accountId → ms 时间戳
|
|
23
|
+
|
|
24
|
+
function snoozeUntilMs(account) {
|
|
25
|
+
const meta = Number(new Date(account?.meta?.zcodeClaimNextAt || 0)) || 0;
|
|
26
|
+
const proc = snoozeUntil.get(account.id) || 0;
|
|
27
|
+
return Math.max(meta, proc);
|
|
28
|
+
}
|
|
16
29
|
|
|
17
30
|
// 桌面版(Electron 主进程)注册的隐藏窗口验证码实现;NAS / CLI 为 null(走「需手动领取」分支)
|
|
18
31
|
let captchaProvider = null;
|
|
@@ -28,11 +41,18 @@ const grantsText = (plan) => (plan.grants && plan.grants.length ? plan.grants.jo
|
|
|
28
41
|
*/
|
|
29
42
|
export async function zcodeAutoClaim(account) {
|
|
30
43
|
const label = account.name || account.uid || account.id;
|
|
44
|
+
{
|
|
45
|
+
const until = snoozeUntilMs(account);
|
|
46
|
+
if (until && until > Date.now()) {
|
|
47
|
+
logger.debug('ZCODE-CLAIM', '{label} 名额已领完,跳过直到 {until}(服务端计划结束点)', { label, until: new Date(until).toISOString() });
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
31
51
|
let plans;
|
|
32
52
|
try {
|
|
33
53
|
plans = (await fetchClaimPlans(account)).plans;
|
|
34
54
|
} catch (e) {
|
|
35
|
-
|
|
55
|
+
logger.debug('ZCODE-CLAIM', '{label} 活动查询失败:{err}', { label, err: e.message });
|
|
36
56
|
return null;
|
|
37
57
|
}
|
|
38
58
|
if (!plans.length) return null;
|
|
@@ -41,6 +61,7 @@ export async function zcodeAutoClaim(account) {
|
|
|
41
61
|
let anySuccess = false;
|
|
42
62
|
let needManual = false;
|
|
43
63
|
|
|
64
|
+
let snoozeAt = 0;
|
|
44
65
|
for (const plan of plans) {
|
|
45
66
|
const planName = plan.name || plan.planId;
|
|
46
67
|
// 第 2 步:先试不带验证码
|
|
@@ -48,17 +69,20 @@ export async function zcodeAutoClaim(account) {
|
|
|
48
69
|
const r = await claimPlan(account, plan.planId, {});
|
|
49
70
|
claims.push({ plan: planName, via: 'direct', ok: true });
|
|
50
71
|
anySuccess = true;
|
|
51
|
-
|
|
72
|
+
logger.info('ZCODE-CLAIM', '{label} 已领取「{plan}」(免验证码){grants}', { label, plan: planName, grants: grantsText(plan) ? ':' + grantsText(plan) : '' });
|
|
52
73
|
continue;
|
|
53
74
|
} catch (e) {
|
|
54
75
|
if (e.code === 1003) {
|
|
55
76
|
claims.push({ plan: planName, ok: true, already: true });
|
|
56
|
-
|
|
77
|
+
logger.info('ZCODE-CLAIM', '{label}「{plan}」已领取过', { label, plan: planName });
|
|
57
78
|
continue;
|
|
58
79
|
}
|
|
80
|
+
if (e.code === 1005 && e.nextAt) {
|
|
81
|
+
snoozeAt = Math.max(snoozeAt, Number(new Date(e.nextAt)) || 0);
|
|
82
|
+
}
|
|
59
83
|
if (e.code !== 3007 && e.code !== 3001) {
|
|
60
84
|
claims.push({ plan: planName, ok: false, message: e.message });
|
|
61
|
-
|
|
85
|
+
logger.warn('ZCODE-CLAIM', '{label} 领取「{plan}」失败:{err}', { label, plan: planName, err: e.message });
|
|
62
86
|
continue;
|
|
63
87
|
}
|
|
64
88
|
}
|
|
@@ -66,7 +90,7 @@ export async function zcodeAutoClaim(account) {
|
|
|
66
90
|
if (!captchaProvider) {
|
|
67
91
|
needManual = true;
|
|
68
92
|
claims.push({ plan: planName, ok: false, needManual: true });
|
|
69
|
-
|
|
93
|
+
logger.info('ZCODE-CLAIM', '{label}「{plan}」需要验证码,本环境无法自动通过,请在面板手动领取', { label, plan: planName });
|
|
70
94
|
continue;
|
|
71
95
|
}
|
|
72
96
|
try {
|
|
@@ -76,13 +100,29 @@ export async function zcodeAutoClaim(account) {
|
|
|
76
100
|
await claimPlan(account, plan.planId, { captchaParam, region });
|
|
77
101
|
claims.push({ plan: planName, via: 'captcha', ok: true });
|
|
78
102
|
anySuccess = true;
|
|
79
|
-
|
|
103
|
+
logger.info('ZCODE-CLAIM', '{label} 已领取「{plan}」(静默验证码){grants}', { label, plan: planName, grants: grantsText(plan) ? ':' + grantsText(plan) : '' });
|
|
80
104
|
} catch (e2) {
|
|
105
|
+
if (e2.code === 1005 && e2.nextAt) {
|
|
106
|
+
snoozeAt = Math.max(snoozeAt, Number(new Date(e2.nextAt)) || 0);
|
|
107
|
+
}
|
|
81
108
|
needManual = true;
|
|
82
109
|
claims.push({ plan: planName, ok: false, needManual: true, message: e2.message });
|
|
83
|
-
|
|
110
|
+
logger.warn('ZCODE-CLAIM', '{label}「{plan}」验证码未完成:{err}(可在面板手动领取)', { label, plan: planName, err: e2.message });
|
|
84
111
|
}
|
|
85
112
|
}
|
|
113
|
+
// 1005 名额已满 → 按服务端 ends_at 将本账号挂进跳过窗口,下一轮自动轮询在该时间点前不再空打
|
|
114
|
+
if (snoozeAt > 0) {
|
|
115
|
+
snoozeUntil.set(account.id, snoozeAt);
|
|
116
|
+
try {
|
|
117
|
+
await withAccounts((list) => {
|
|
118
|
+
const cur = list.find((a) => a.id === account.id);
|
|
119
|
+
if (cur) {
|
|
120
|
+
cur.meta = cur.meta && typeof cur.meta === 'object' && !Array.isArray(cur.meta) ? cur.meta : {};
|
|
121
|
+
cur.meta.zcodeClaimNextAt = new Date(snoozeAt).toISOString();
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
} catch {}
|
|
125
|
+
}
|
|
86
126
|
|
|
87
127
|
const names = claims.filter((c) => c.ok && !c.already).map((c) => c.plan);
|
|
88
128
|
const status = names.length ? 'checked-in'
|
package/src/zcodeClient.js
CHANGED
|
@@ -293,7 +293,7 @@ function combineSignals({ wallSignal, connectSignal, external }) {
|
|
|
293
293
|
* 跑几分钟,不能因为「连接快、生成慢」被误杀。
|
|
294
294
|
* 不传 connectMs 时退回单段 timeoutMs(旧行为,所有既有调用方不受影响)。
|
|
295
295
|
*/
|
|
296
|
-
export async function fetchJsonRace(url, { method = 'GET', headers = {}, body = null, timeoutMs = 15000, connectMs = null, signal = null } = {}) {
|
|
296
|
+
export async function fetchJsonRace(url, { method = 'GET', headers = {}, body = null, timeoutMs = 15000, connectMs = null, signal = null, preferProxy = false, redirect = 'manual' } = {}) {
|
|
297
297
|
const useProxy = Boolean(effectiveProxy()) && !proxyBypass(url);
|
|
298
298
|
const twoPhase = Number.isFinite(connectMs) && connectMs > 0 && connectMs < timeoutMs;
|
|
299
299
|
// 超时信号必须每次尝试各建一个:共用一个的话,代理优先模式下第一条路吃完 timeoutMs,
|
|
@@ -302,7 +302,8 @@ export async function fetchJsonRace(url, { method = 'GET', headers = {}, body =
|
|
|
302
302
|
const wallSignal = AbortSignal.timeout(timeoutMs);
|
|
303
303
|
const connectSignal = twoPhase ? AbortSignal.timeout(connectMs) : null;
|
|
304
304
|
const { signal: abortSignal, releaseConnect } = combineSignals({ wallSignal, connectSignal, external: signal });
|
|
305
|
-
|
|
305
|
+
// redirect 默认 manual:follow 的 301/302 会把 POST 转 GET 丢 body、307 会把大 body 重放。
|
|
306
|
+
const base = { method, headers: { ...headers }, ...(body != null ? { body: typeof body === 'string' ? body : JSON.stringify(body) } : {}), signal: abortSignal, redirect };
|
|
306
307
|
return {
|
|
307
308
|
// 响应头到达 = 连接段成功:摘掉 connectSignal 转发,此后只有墙钟/外部取消能中断流
|
|
308
309
|
direct: async () => { const r = await fetch(url, base); releaseConnect(); return r; },
|
|
@@ -315,7 +316,11 @@ export async function fetchJsonRace(url, { method = 'GET', headers = {}, body =
|
|
|
315
316
|
},
|
|
316
317
|
};
|
|
317
318
|
};
|
|
318
|
-
|
|
319
|
+
// preferProxy:该域名按 IP 识别地区(如 qoder.com 国际版),配置了代理就该优先走代理、
|
|
320
|
+
// 直连只作兜底——直连「成功返回 403 风控页」也算成功响应,走默认顺序永远不会轮到代理
|
|
321
|
+
const order = useProxy
|
|
322
|
+
? (preferProxy || proxyFirst() ? ['viaProxy', 'direct'] : ['direct', 'viaProxy'])
|
|
323
|
+
: ['direct'];
|
|
319
324
|
let lastErr = null;
|
|
320
325
|
for (const which of order) {
|
|
321
326
|
const attempts = makeAttempt();
|
package/src/zcodeGateway.js
CHANGED
|
@@ -39,7 +39,8 @@ export function getZcodeCompletionProvider() { return completionProvider; }
|
|
|
39
39
|
import { loadAccounts, loadSettings, saveSettings, loadState, withState } from './store.js';
|
|
40
40
|
import crypto from 'node:crypto';
|
|
41
41
|
import os from 'node:os';
|
|
42
|
-
import { logger, summarizeAttempts } from './logger.js';
|
|
42
|
+
import { logger, summarizeAttempts } from './logger.js';
|
|
43
|
+
import { t } from './i18n.js';
|
|
43
44
|
|
|
44
45
|
const PLAN_MESSAGES_URL = 'https://zcode.z.ai/api/v1/zcode-plan/anthropic/v1/messages';
|
|
45
46
|
const CAPTCHA_CACHE_TTL_MS = 30_000;
|
|
@@ -108,7 +109,7 @@ function isDead(account) {
|
|
|
108
109
|
if (fp === credFingerprint(account)) return true;
|
|
109
110
|
dead.delete(account.id);
|
|
110
111
|
cooling.delete(account.id);
|
|
111
|
-
logger.info('ZCODE-GW',
|
|
112
|
+
logger.info('ZCODE-GW', '{label} 凭据已更新,解除拉黑', { label: account.name || account.uid || account.id });
|
|
112
113
|
return false;
|
|
113
114
|
}
|
|
114
115
|
|
|
@@ -368,7 +369,13 @@ export function remoteAllowed(remote, allowList) {
|
|
|
368
369
|
return (Array.isArray(allowList) ? allowList : []).some((entry) => {
|
|
369
370
|
const e = String(entry || '').trim();
|
|
370
371
|
if (!e) return false;
|
|
371
|
-
if (e.endsWith('*'))
|
|
372
|
+
if (e.endsWith('*')) {
|
|
373
|
+
// 通配要求带点段尾:防止 192.168.31.1* 这类把 .100-.199 整段一起放行的怪手。
|
|
374
|
+
// 要扩整段子网,把写法统一为 192.168.31.*(带尾句点)。
|
|
375
|
+
const prefix = e.slice(0, -1);
|
|
376
|
+
if (!prefix.endsWith('.')) return false;
|
|
377
|
+
return host.toLowerCase().startsWith(prefix.toLowerCase());
|
|
378
|
+
}
|
|
372
379
|
return host.toLowerCase() === e.toLowerCase();
|
|
373
380
|
});
|
|
374
381
|
}
|
|
@@ -487,7 +494,7 @@ export async function handleGateway(req, res) {
|
|
|
487
494
|
if (isAuthError(200, text)) {
|
|
488
495
|
markDead(account);
|
|
489
496
|
attempted.push({ account: label, ok: false, error: 'JWT 已失效,账号拉黑' });
|
|
490
|
-
logger.warn('ZCODE-GW',
|
|
497
|
+
logger.warn('ZCODE-GW', '{label} JWT 失效(200 业务码)', { label });
|
|
491
498
|
continue;
|
|
492
499
|
}
|
|
493
500
|
if (isExhausted(200, text)) {
|
|
@@ -509,20 +516,20 @@ export async function handleGateway(req, res) {
|
|
|
509
516
|
}
|
|
510
517
|
continue;
|
|
511
518
|
}
|
|
512
|
-
logger.warn('ZCODE-GW',
|
|
519
|
+
logger.warn('ZCODE-GW', '{label} 上游 200 业务错误:{err} ({snippet})', { label, err: formatUpstreamError(200, text), snippet: text.slice(0, 80) });
|
|
513
520
|
res.writeHead(502, { 'Content-Type': 'application/json' });
|
|
514
521
|
res.end(text);
|
|
515
522
|
return true;
|
|
516
523
|
}
|
|
517
524
|
// 真 Message(非流式)——原样转发
|
|
518
|
-
logger.debug('ZCODE-GW',
|
|
525
|
+
logger.debug('ZCODE-GW', '{label} 补全成功 ({status})', { label, status: upstream.status });
|
|
519
526
|
stats.lastAccount = label;
|
|
520
527
|
cooling.delete(account.id);
|
|
521
528
|
res.writeHead(upstream.status, { 'Content-Type': ct || 'application/json', 'Cache-Control': 'no-cache' });
|
|
522
529
|
res.end(text);
|
|
523
530
|
return true;
|
|
524
531
|
}
|
|
525
|
-
logger.debug('ZCODE-GW',
|
|
532
|
+
logger.debug('ZCODE-GW', '{label} 补全成功 ({status})', { label, status: upstream.status });
|
|
526
533
|
stats.lastAccount = label;
|
|
527
534
|
cooling.delete(account.id);
|
|
528
535
|
// 注意:undici 已自动解压上游 gzip——body 是明文,绝不能再带 content-encoding 头
|
|
@@ -543,7 +550,7 @@ export async function handleGateway(req, res) {
|
|
|
543
550
|
}
|
|
544
551
|
} catch (e) {
|
|
545
552
|
// 客户端主动断开(点停止/换一句)就会在这里结束——属正常收尾,不是故障,降为 debug
|
|
546
|
-
logger.debug('ZCODE-GW',
|
|
553
|
+
logger.debug('ZCODE-GW', '流式转发中断:{err}', { err: e.message });
|
|
547
554
|
}
|
|
548
555
|
res.end();
|
|
549
556
|
return true;
|
|
@@ -554,9 +561,9 @@ export async function handleGateway(req, res) {
|
|
|
554
561
|
if (isCaptchaError(upstream.status, text)) {
|
|
555
562
|
invalidateCaptcha();
|
|
556
563
|
attempted.push({ account: label, ok: false, error: `验证码被拒(${upstream.status})`, captcha: true });
|
|
557
|
-
logger.warn('ZCODE-GW',
|
|
564
|
+
logger.warn('ZCODE-GW', '{label} 验证码被拒 ({err})', { label, err: formatUpstreamError(upstream.status, text) });
|
|
558
565
|
try { captcha = await ensureCaptcha(true); } catch (e) {
|
|
559
|
-
logger.warn('ZCODE-GW',
|
|
566
|
+
logger.warn('ZCODE-GW', '验证码重解失败:{err}', { err: e.message });
|
|
560
567
|
captcha = null;
|
|
561
568
|
}
|
|
562
569
|
continue;
|
|
@@ -564,31 +571,31 @@ export async function handleGateway(req, res) {
|
|
|
564
571
|
if (isAuthError(upstream.status, text)) {
|
|
565
572
|
markDead(account);
|
|
566
573
|
attempted.push({ account: label, ok: false, error: 'JWT 已失效,账号拉黑(重新登录后再导入)' });
|
|
567
|
-
logger.debug('ZCODE-GW',
|
|
574
|
+
logger.debug('ZCODE-GW', '{label} JWT 失效,拉黑', { label });
|
|
568
575
|
continue;
|
|
569
576
|
}
|
|
570
577
|
if (isExhausted(upstream.status, text)) {
|
|
571
578
|
markCooling(account.id, 30 * 60_000);
|
|
572
579
|
attempted.push({ account: label, ok: false, error: '额度不足/无资源包' });
|
|
573
|
-
logger.debug('ZCODE-GW',
|
|
580
|
+
logger.debug('ZCODE-GW', '{label} 额度不足 ({status})', { label, status: upstream.status });
|
|
574
581
|
continue;
|
|
575
582
|
}
|
|
576
583
|
if (upstream.status === 429) {
|
|
577
584
|
markCooling(account.id);
|
|
578
585
|
attempted.push({ account: label, ok: false, error: '429 限流,冷却 5 分钟' });
|
|
579
|
-
logger.debug('ZCODE-GW',
|
|
586
|
+
logger.debug('ZCODE-GW', '{label} 429 限流,冷却 5 分钟', { label });
|
|
580
587
|
continue;
|
|
581
588
|
}
|
|
582
589
|
// 其他错误:友好化展示 + 原样透传给客户端
|
|
583
590
|
const errorSummary = formatUpstreamError(upstream.status, text);
|
|
584
|
-
logger.warn('ZCODE-GW',
|
|
591
|
+
logger.warn('ZCODE-GW', '{label} 上游错误 {status} — {summary}', { label, status: upstream.status, summary: errorSummary });
|
|
585
592
|
res.writeHead(upstream.status, { 'Content-Type': 'application/json' });
|
|
586
593
|
res.end(text);
|
|
587
594
|
return true;
|
|
588
595
|
}
|
|
589
596
|
|
|
590
597
|
const failedDetail = summarizeAttempts(attempted);
|
|
591
|
-
logger.warn('ZCODE-GW',
|
|
598
|
+
logger.warn('ZCODE-GW', '全部 {n} 次尝试失败{detail}(下一请求自动重试)', { n: attempted.length, detail: failedDetail ? ':' + failedDetail : '' });
|
|
592
599
|
res.writeHead(502, { 'Content-Type': 'application/json' });
|
|
593
600
|
res.end(JSON.stringify({ error: 'ZCode 网关:所有账号尝试均失败', attempts: attempted }));
|
|
594
601
|
return true;
|