creditdaddy 1.1.1 → 1.2.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/LICENSE +21 -21
- package/README.md +306 -289
- package/bin/creditdaddy.js +8 -4
- package/package.json +29 -28
- package/src/accounts.js +161 -161
- package/src/authDevice.js +205 -205
- package/src/catpawClient.js +103 -103
- package/src/catpawLocal.js +229 -229
- package/src/checkin.js +357 -340
- package/src/constants.js +97 -95
- package/src/daemon.js +130 -14
- package/src/localDetect.js +147 -147
- package/src/mirasimClient.js +189 -189
- package/src/panel.html +465 -142
- package/src/providers.js +190 -181
- package/src/qoderApp.js +93 -7
- package/src/qoderClient.js +237 -237
- package/src/qoderUmid.js +198 -198
- package/src/store.js +238 -238
- package/src/tenrouter.js +18 -8
- package/src/tenrouterAccounts.js +163 -163
- package/src/traeClient.js +258 -0
- package/src/traeLocal.js +337 -0
- package/src/transfer.js +160 -160
- package/src/usageSync.js +505 -505
- package/src/workbuddyLocal.js +133 -133
- package/src/zcodeAuth.js +131 -131
- package/src/zcodeAutoClaim.js +97 -97
- package/src/zcodeClient.js +33 -2
- package/src/zcodeGateway.js +26 -30
- package/src/zcodeLocal.js +334 -319
- package/src/zcrypto.js +160 -160
package/src/zcodeAutoClaim.js
CHANGED
|
@@ -1,97 +1,97 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ZCode 活动自动领取 — 签到调度器每轮调用。
|
|
3
|
-
*
|
|
4
|
-
* 策略(对齐用户手动路径,逐级尝试):
|
|
5
|
-
* 1. billing/preview 查可领活动;为空 → 无事可做(返回 null,不留痕迹)
|
|
6
|
-
* 2. 先直接 claim(不带验证码:服务端未风控时可直接成功)
|
|
7
|
-
* 3. 返回 3007/3001(需要验证码 / 参数被拒)且有验证码提供者 → 拉 captcha 配置,
|
|
8
|
-
* 让提供者(桌面版的隐藏窗口跑阿里云验证码 SDK 静默验证)拿 captchaVerifyParam 后重试
|
|
9
|
-
* 4. 没有提供者(NAS / 纯 CLI)或验证码需要人工 → 标记「需手动领取」,卡片上提示
|
|
10
|
-
* - 1003(已领取过)按成功口径处理并记忆
|
|
11
|
-
* 结果写入账号 lastResult(签到日历记忆用的是 status checked-in/already,第二天照常重查 preview)
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { logger } from './logger.js';
|
|
15
|
-
import { fetchClaimPlans, claimPlan, fetchCaptchaConfig } from './zcodeClient.js';
|
|
16
|
-
|
|
17
|
-
// 桌面版(Electron 主进程)注册的隐藏窗口验证码实现;NAS / CLI 为 null(走「需手动领取」分支)
|
|
18
|
-
let captchaProvider = null;
|
|
19
|
-
export function setZcodeCaptchaProvider(fn) { captchaProvider = typeof fn === 'function' ? fn : null; }
|
|
20
|
-
export function hasZcodeCaptchaProvider() { return Boolean(captchaProvider); }
|
|
21
|
-
export function getZcodeCaptchaProvider() { return captchaProvider; }
|
|
22
|
-
|
|
23
|
-
const grantsText = (plan) => (plan.grants && plan.grants.length ? plan.grants.join(';') : '');
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* 为单个 ZCode 账号执行一次自动领取。
|
|
27
|
-
* @returns {null | { status, message, claims: Array }} null = preview 为空或查询失败(已记日志,不上卡片)
|
|
28
|
-
*/
|
|
29
|
-
export async function zcodeAutoClaim(account) {
|
|
30
|
-
const label = account.name || account.uid || account.id;
|
|
31
|
-
let plans;
|
|
32
|
-
try {
|
|
33
|
-
plans = (await fetchClaimPlans(account)).plans;
|
|
34
|
-
} catch (e) {
|
|
35
|
-
logger.debug('ZCODE-CLAIM', `${label} 活动查询失败:${e.message}`);
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
if (!plans.length) return null;
|
|
39
|
-
|
|
40
|
-
const claims = [];
|
|
41
|
-
let anySuccess = false;
|
|
42
|
-
let needManual = false;
|
|
43
|
-
|
|
44
|
-
for (const plan of plans) {
|
|
45
|
-
const planName = plan.name || plan.planId;
|
|
46
|
-
// 第 2 步:先试不带验证码
|
|
47
|
-
try {
|
|
48
|
-
const r = await claimPlan(account, plan.planId, {});
|
|
49
|
-
claims.push({ plan: planName, via: 'direct', ok: true });
|
|
50
|
-
anySuccess = true;
|
|
51
|
-
logger.info('ZCODE-CLAIM', `${label} 已领取「${planName}」(免验证码)${grantsText(plan) ? ':' + grantsText(plan) : ''}`);
|
|
52
|
-
continue;
|
|
53
|
-
} catch (e) {
|
|
54
|
-
if (e.code === 1003) {
|
|
55
|
-
claims.push({ plan: planName, ok: true, already: true });
|
|
56
|
-
logger.info('ZCODE-CLAIM', `${label}「${planName}」已领取过`);
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
if (e.code !== 3007 && e.code !== 3001) {
|
|
60
|
-
claims.push({ plan: planName, ok: false, message: e.message });
|
|
61
|
-
logger.warn('ZCODE-CLAIM', `${label} 领取「${planName}」失败:${e.message}`);
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
// 第 3 步:需要验证码
|
|
66
|
-
if (!captchaProvider) {
|
|
67
|
-
needManual = true;
|
|
68
|
-
claims.push({ plan: planName, ok: false, needManual: true });
|
|
69
|
-
logger.info('ZCODE-CLAIM', `${label}「${planName}」需要验证码,本环境无法自动通过,请在面板手动领取`);
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
try {
|
|
73
|
-
const cfg = await fetchCaptchaConfig();
|
|
74
|
-
if (!cfg.enabled || !cfg.sceneId) throw new Error('验证码配置不可用');
|
|
75
|
-
const { captchaParam, region } = await captchaProvider(cfg);
|
|
76
|
-
await claimPlan(account, plan.planId, { captchaParam, region });
|
|
77
|
-
claims.push({ plan: planName, via: 'captcha', ok: true });
|
|
78
|
-
anySuccess = true;
|
|
79
|
-
logger.info('ZCODE-CLAIM', `${label} 已领取「${planName}」(静默验证码)${grantsText(plan) ? ':' + grantsText(plan) : ''}`);
|
|
80
|
-
} catch (e2) {
|
|
81
|
-
needManual = true;
|
|
82
|
-
claims.push({ plan: planName, ok: false, needManual: true, message: e2.message });
|
|
83
|
-
logger.warn('ZCODE-CLAIM', `${label}「${planName}」验证码未完成:${e2.message}(可在面板手动领取)`);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const names = claims.filter((c) => c.ok && !c.already).map((c) => c.plan);
|
|
88
|
-
const status = names.length ? 'checked-in'
|
|
89
|
-
: claims.every((c) => c.already) ? 'already'
|
|
90
|
-
: 'failed';
|
|
91
|
-
const message = status === 'checked-in'
|
|
92
|
-
? `已领取:${names.join('、')}${claims.some((c) => c.via === 'captcha') ? '(静默验证码)' : ''}`
|
|
93
|
-
: status === 'already' ? '活动均已领取过'
|
|
94
|
-
: needManual ? `有 ${claims.filter((c) => c.needManual).length} 个活动需要验证码,请在面板手动领取`
|
|
95
|
-
: `领取失败:${claims.filter((c) => !c.ok).map((c) => c.message).filter(Boolean)[0] || '未知错误'}`;
|
|
96
|
-
return { status, message, claims };
|
|
97
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* ZCode 活动自动领取 — 签到调度器每轮调用。
|
|
3
|
+
*
|
|
4
|
+
* 策略(对齐用户手动路径,逐级尝试):
|
|
5
|
+
* 1. billing/preview 查可领活动;为空 → 无事可做(返回 null,不留痕迹)
|
|
6
|
+
* 2. 先直接 claim(不带验证码:服务端未风控时可直接成功)
|
|
7
|
+
* 3. 返回 3007/3001(需要验证码 / 参数被拒)且有验证码提供者 → 拉 captcha 配置,
|
|
8
|
+
* 让提供者(桌面版的隐藏窗口跑阿里云验证码 SDK 静默验证)拿 captchaVerifyParam 后重试
|
|
9
|
+
* 4. 没有提供者(NAS / 纯 CLI)或验证码需要人工 → 标记「需手动领取」,卡片上提示
|
|
10
|
+
* - 1003(已领取过)按成功口径处理并记忆
|
|
11
|
+
* 结果写入账号 lastResult(签到日历记忆用的是 status checked-in/already,第二天照常重查 preview)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { logger } from './logger.js';
|
|
15
|
+
import { fetchClaimPlans, claimPlan, fetchCaptchaConfig } from './zcodeClient.js';
|
|
16
|
+
|
|
17
|
+
// 桌面版(Electron 主进程)注册的隐藏窗口验证码实现;NAS / CLI 为 null(走「需手动领取」分支)
|
|
18
|
+
let captchaProvider = null;
|
|
19
|
+
export function setZcodeCaptchaProvider(fn) { captchaProvider = typeof fn === 'function' ? fn : null; }
|
|
20
|
+
export function hasZcodeCaptchaProvider() { return Boolean(captchaProvider); }
|
|
21
|
+
export function getZcodeCaptchaProvider() { return captchaProvider; }
|
|
22
|
+
|
|
23
|
+
const grantsText = (plan) => (plan.grants && plan.grants.length ? plan.grants.join(';') : '');
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 为单个 ZCode 账号执行一次自动领取。
|
|
27
|
+
* @returns {null | { status, message, claims: Array }} null = preview 为空或查询失败(已记日志,不上卡片)
|
|
28
|
+
*/
|
|
29
|
+
export async function zcodeAutoClaim(account) {
|
|
30
|
+
const label = account.name || account.uid || account.id;
|
|
31
|
+
let plans;
|
|
32
|
+
try {
|
|
33
|
+
plans = (await fetchClaimPlans(account)).plans;
|
|
34
|
+
} catch (e) {
|
|
35
|
+
logger.debug('ZCODE-CLAIM', `${label} 活动查询失败:${e.message}`);
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
if (!plans.length) return null;
|
|
39
|
+
|
|
40
|
+
const claims = [];
|
|
41
|
+
let anySuccess = false;
|
|
42
|
+
let needManual = false;
|
|
43
|
+
|
|
44
|
+
for (const plan of plans) {
|
|
45
|
+
const planName = plan.name || plan.planId;
|
|
46
|
+
// 第 2 步:先试不带验证码
|
|
47
|
+
try {
|
|
48
|
+
const r = await claimPlan(account, plan.planId, {});
|
|
49
|
+
claims.push({ plan: planName, via: 'direct', ok: true });
|
|
50
|
+
anySuccess = true;
|
|
51
|
+
logger.info('ZCODE-CLAIM', `${label} 已领取「${planName}」(免验证码)${grantsText(plan) ? ':' + grantsText(plan) : ''}`);
|
|
52
|
+
continue;
|
|
53
|
+
} catch (e) {
|
|
54
|
+
if (e.code === 1003) {
|
|
55
|
+
claims.push({ plan: planName, ok: true, already: true });
|
|
56
|
+
logger.info('ZCODE-CLAIM', `${label}「${planName}」已领取过`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (e.code !== 3007 && e.code !== 3001) {
|
|
60
|
+
claims.push({ plan: planName, ok: false, message: e.message });
|
|
61
|
+
logger.warn('ZCODE-CLAIM', `${label} 领取「${planName}」失败:${e.message}`);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// 第 3 步:需要验证码
|
|
66
|
+
if (!captchaProvider) {
|
|
67
|
+
needManual = true;
|
|
68
|
+
claims.push({ plan: planName, ok: false, needManual: true });
|
|
69
|
+
logger.info('ZCODE-CLAIM', `${label}「${planName}」需要验证码,本环境无法自动通过,请在面板手动领取`);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const cfg = await fetchCaptchaConfig();
|
|
74
|
+
if (!cfg.enabled || !cfg.sceneId) throw new Error('验证码配置不可用');
|
|
75
|
+
const { captchaParam, region } = await captchaProvider(cfg);
|
|
76
|
+
await claimPlan(account, plan.planId, { captchaParam, region });
|
|
77
|
+
claims.push({ plan: planName, via: 'captcha', ok: true });
|
|
78
|
+
anySuccess = true;
|
|
79
|
+
logger.info('ZCODE-CLAIM', `${label} 已领取「${planName}」(静默验证码)${grantsText(plan) ? ':' + grantsText(plan) : ''}`);
|
|
80
|
+
} catch (e2) {
|
|
81
|
+
needManual = true;
|
|
82
|
+
claims.push({ plan: planName, ok: false, needManual: true, message: e2.message });
|
|
83
|
+
logger.warn('ZCODE-CLAIM', `${label}「${planName}」验证码未完成:${e2.message}(可在面板手动领取)`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const names = claims.filter((c) => c.ok && !c.already).map((c) => c.plan);
|
|
88
|
+
const status = names.length ? 'checked-in'
|
|
89
|
+
: claims.every((c) => c.already) ? 'already'
|
|
90
|
+
: 'failed';
|
|
91
|
+
const message = status === 'checked-in'
|
|
92
|
+
? `已领取:${names.join('、')}${claims.some((c) => c.via === 'captcha') ? '(静默验证码)' : ''}`
|
|
93
|
+
: status === 'already' ? '活动均已领取过'
|
|
94
|
+
: needManual ? `有 ${claims.filter((c) => c.needManual).length} 个活动需要验证码,请在面板手动领取`
|
|
95
|
+
: `领取失败:${claims.filter((c) => !c.ok).map((c) => c.message).filter(Boolean)[0] || '未知错误'}`;
|
|
96
|
+
return { status, message, claims };
|
|
97
|
+
}
|
package/src/zcodeClient.js
CHANGED
|
@@ -97,14 +97,16 @@ function netPref() {
|
|
|
97
97
|
proxyUrl: typeof j.proxyUrl === 'string' && j.proxyUrl.trim() ? j.proxyUrl.trim() : null,
|
|
98
98
|
autoClaim: j.autoClaim === true,
|
|
99
99
|
autoClaimUntil: Number.isFinite(j.autoClaimUntil) ? j.autoClaimUntil : null,
|
|
100
|
+
claimIntervalMin: Number.isFinite(j.claimIntervalMin) ? Number(j.claimIntervalMin) : null,
|
|
101
|
+
claimWindowMin: Number.isFinite(j.claimWindowMin) ? Number(j.claimWindowMin) : null,
|
|
100
102
|
};
|
|
101
|
-
} catch { netPrefCache = { proxyFirst: false, proxyUrl: null, autoClaim: false, autoClaimUntil: null }; }
|
|
103
|
+
} catch { netPrefCache = { proxyFirst: false, proxyUrl: null, autoClaim: false, autoClaimUntil: null, claimIntervalMin: null, claimWindowMin: null }; }
|
|
102
104
|
return netPrefCache;
|
|
103
105
|
}
|
|
104
106
|
function writeNetPref(p) {
|
|
105
107
|
netPrefCache = p;
|
|
106
108
|
fs.mkdirSync(codeHome(), { recursive: true, mode: 0o700 });
|
|
107
|
-
fs.writeFileSync(NET_PREFS_FILE(), JSON.stringify({ proxyFirst: p.proxyFirst, proxyUrl: p.proxyUrl, autoClaim: p.autoClaim, autoClaimUntil: p.autoClaimUntil || null }, null, 2), { mode: 0o600 });
|
|
109
|
+
fs.writeFileSync(NET_PREFS_FILE(), JSON.stringify({ proxyFirst: p.proxyFirst, proxyUrl: p.proxyUrl, autoClaim: p.autoClaim, autoClaimUntil: p.autoClaimUntil || null, claimIntervalMin: p.claimIntervalMin ?? null, claimWindowMin: p.claimWindowMin ?? null }, null, 2), { mode: 0o600 });
|
|
108
110
|
}
|
|
109
111
|
export function proxyFirst() { return netPref().proxyFirst; }
|
|
110
112
|
export function proxyUrl() { return netPref().proxyUrl; }
|
|
@@ -126,6 +128,35 @@ export function enableAutoClaimFor(durationMs) {
|
|
|
126
128
|
if (!Number.isFinite(duration) || duration <= 0) throw new Error('自动领取时长无效');
|
|
127
129
|
writeNetPref({ ...netPref(), autoClaim: true, autoClaimUntil: Date.now() + duration });
|
|
128
130
|
}
|
|
131
|
+
|
|
132
|
+
// ── 自动领取计划:轮询周期 / 单次运行时长(用户可在面板弹窗里改,落 zcode-net.json) ──
|
|
133
|
+
|
|
134
|
+
/** 轮询周期(分钟)。默认 2,可配 1–720 */
|
|
135
|
+
export function claimIntervalMin() {
|
|
136
|
+
const raw = netPref().claimIntervalMin;
|
|
137
|
+
if (raw === null || raw === undefined) return 2;
|
|
138
|
+
const n = Math.round(Number(raw));
|
|
139
|
+
return Number.isFinite(n) && n >= 1 && n <= 720 ? n : 2;
|
|
140
|
+
}
|
|
141
|
+
export function setClaimIntervalMin(v) {
|
|
142
|
+
const n = Math.round(Number(v));
|
|
143
|
+
if (!Number.isFinite(n) || n < 1 || n > 720) throw new Error('轮询周期需在 1–720 分钟之间');
|
|
144
|
+
writeNetPref({ ...netPref(), claimIntervalMin: n });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** 单次运行时长(分钟)。默认 60;0 = 一直运行直到手动关闭 */
|
|
148
|
+
export function claimWindowMin() {
|
|
149
|
+
const raw = netPref().claimWindowMin;
|
|
150
|
+
if (raw === null || raw === undefined) return 60;
|
|
151
|
+
const n = Math.round(Number(raw));
|
|
152
|
+
if (n === 0) return 0;
|
|
153
|
+
return n >= 5 && n <= 1440 ? n : 60;
|
|
154
|
+
}
|
|
155
|
+
export function setClaimWindowMin(v) {
|
|
156
|
+
const n = Math.round(Number(v));
|
|
157
|
+
if (!Number.isFinite(n) || n < 0 || n > 1440 || (n > 0 && n < 5)) throw new Error('运行时长需为 0(一直运行)或 5–1440 分钟');
|
|
158
|
+
writeNetPref({ ...netPref(), claimWindowMin: n });
|
|
159
|
+
}
|
|
129
160
|
export function setProxyFirst(v) { writeNetPref({ ...netPref(), proxyFirst: v === true }); }
|
|
130
161
|
export function setProxyUrl(u) {
|
|
131
162
|
const t = typeof u === 'string' ? u.trim() : '';
|
package/src/zcodeGateway.js
CHANGED
|
@@ -28,7 +28,7 @@ let PLAN_SHAPE = null;
|
|
|
28
28
|
try { PLAN_SHAPE = JSON.parse(readFileSync(path.join(__dirname, 'zcodePlanShape.json'), 'utf8')); } catch {}
|
|
29
29
|
|
|
30
30
|
import { getZcodeCaptchaProvider } from './zcodeAutoClaim.js';
|
|
31
|
-
import {
|
|
31
|
+
import { gatewayHostSuggestion } from './tenrouter.js';
|
|
32
32
|
|
|
33
33
|
// 整链补全提供者(桌面版注册):隐藏窗口内「真 Chromium 求解验证码 + 同源发起补全」,
|
|
34
34
|
// 规避 Node fetch 的 TLS 指纹风控。签名 ({ captchaCfg, jwt, rawBody }) → { status, contentType, body }。
|
|
@@ -308,23 +308,29 @@ const LOOPBACK_RE = /^(::1|::ffff:127\.0\.0\.1|127\.0\.0\.1)$/;
|
|
|
308
308
|
function isLoopbackRemote(remote) {
|
|
309
309
|
return !remote || LOOPBACK_RE.test(String(remote));
|
|
310
310
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
311
|
+
|
|
312
|
+
/** 规范化远端地址(剥 IPv4-mapped IPv6 前缀),与白名单条目比对 */
|
|
313
|
+
export function remoteHost(remote) {
|
|
314
|
+
return String(remote || '').replace(/^::ffff:/i, '').trim();
|
|
314
315
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
316
|
+
|
|
317
|
+
/** 白名单匹配:精确 IP / 主机名,或 `*` 结尾的前缀通配(如 192.168.31.*) */
|
|
318
|
+
export function remoteAllowed(remote, allowList) {
|
|
319
|
+
const host = remoteHost(remote);
|
|
320
|
+
if (!host) return false;
|
|
321
|
+
return (Array.isArray(allowList) ? allowList : []).some((entry) => {
|
|
322
|
+
const e = String(entry || '').trim();
|
|
323
|
+
if (!e) return false;
|
|
324
|
+
if (e.endsWith('*')) return host.toLowerCase().startsWith(e.slice(0, -1).toLowerCase());
|
|
325
|
+
return host.toLowerCase() === e.toLowerCase();
|
|
326
|
+
});
|
|
324
327
|
}
|
|
325
328
|
|
|
326
329
|
/**
|
|
327
330
|
* 网关数据面入口。返回 true 表示响应已写出(含失败结论)。
|
|
331
|
+
*
|
|
332
|
+
* 访问控制:本机回环始终放行;开局域网后仅 IP 白名单内的机器可访问——
|
|
333
|
+
* zcode-free 本就是 10Router 侧免授权供应商,10Router 所在机器加白即可,不再保留虚拟 key 鉴权。
|
|
328
334
|
*/
|
|
329
335
|
export async function handleGateway(req, res) {
|
|
330
336
|
if (req.method !== 'POST') {
|
|
@@ -338,27 +344,17 @@ export async function handleGateway(req, res) {
|
|
|
338
344
|
// 首发不带验证码(客户端真实流量多数直过),所以纯 daemon / NAS 也能用;
|
|
339
345
|
// 只有当上游索要验证码且本环境没有求解提供者时才失败(见 attempts 汇总)。
|
|
340
346
|
|
|
341
|
-
//
|
|
342
|
-
// (推荐直接复用 10r 的 LLM key:在 10r 面板复制,填进网关密钥与节点连接各一次)
|
|
347
|
+
// 鉴权:本机回环免密;开局域网后仅白名单内 IP 免密直连(如 10Router 所在机器——zcode-free 本就是免授权供应商)
|
|
343
348
|
const remote = (req.socket && req.socket.remoteAddress) || '';
|
|
344
349
|
if (!isLoopbackRemote(remote)) {
|
|
345
350
|
const settings = await loadSettings();
|
|
346
351
|
if (settings.zcodeGatewayLan !== true) {
|
|
347
352
|
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
348
|
-
return res.end(JSON.stringify({ error: '网关未允许局域网访问(面板 → ZCode →
|
|
349
|
-
}
|
|
350
|
-
// 访问密钥 = 10Router 连接设置里保存的虚拟 key(sk-…)——用户从有鉴权的
|
|
351
|
-
// 10Router 面板复制,同一把 key 也配在 10Router 的 zcode-free 连接里
|
|
352
|
-
const expected = tenrouterGatewayKey();
|
|
353
|
-
if (expected.length === 0) {
|
|
354
|
-
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
355
|
-
return res.end(JSON.stringify({ error: '网关已对局域网开放但未配置 10Router 虚拟 key(10Router 连接设置)' }));
|
|
353
|
+
return res.end(JSON.stringify({ error: '网关未允许局域网访问(面板 → ZCode → 接口设置)' }));
|
|
356
354
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
361
|
-
return res.end(JSON.stringify({ error: '网关密钥不匹配' }));
|
|
355
|
+
if (!remoteAllowed(remote, settings.zcodeGatewayAllow)) {
|
|
356
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
357
|
+
return res.end(JSON.stringify({ error: `来源 ${remoteHost(remote) || '未知'} 不在网关 IP 白名单内(面板 → ZCode → 接口设置里添加)` }));
|
|
362
358
|
}
|
|
363
359
|
}
|
|
364
360
|
|
|
@@ -550,12 +546,12 @@ export async function gatewayStatus() {
|
|
|
550
546
|
for (const a of all) { try { claimToken(a); withJwt++; } catch { /* 无 plan JWT */ } }
|
|
551
547
|
const now = Date.now();
|
|
552
548
|
const settings = await loadSettings();
|
|
553
|
-
const key = tenrouterGatewayKey();
|
|
554
549
|
return {
|
|
555
550
|
enabled,
|
|
556
551
|
hasCaptcha,
|
|
557
552
|
lan: settings.zcodeGatewayLan === true,
|
|
558
|
-
|
|
553
|
+
allow: Array.isArray(settings.zcodeGatewayAllow) ? settings.zcodeGatewayAllow : [],
|
|
554
|
+
suggestAllow: gatewayHostSuggestion(),
|
|
559
555
|
stats: {
|
|
560
556
|
lastCallAt: stats.lastCallAt,
|
|
561
557
|
calls: stats.calls,
|