creditdaddy 0.9.3 → 0.9.5
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 +228 -204
- package/bin/creditdaddy.js +7 -3
- package/package.json +28 -28
- package/src/checkin.js +2 -1
- package/src/constants.js +4 -2
- package/src/daemon.js +74 -12
- package/src/localDetect.js +2 -2
- package/src/logger.js +4 -1
- package/src/mirasimClient.js +189 -0
- package/src/mirasimLocal.js +307 -0
- package/src/panel.html +566 -68
- package/src/providers.js +25 -1
- package/src/qoderApp.js +6 -4
- package/src/qoderUmid.js +25 -4
- package/src/store.js +10 -0
- package/src/zcodeClient.js +50 -36
- package/src/zcodeLocal.js +103 -1
package/src/providers.js
CHANGED
|
@@ -12,7 +12,7 @@ import { productOf } from './constants.js';
|
|
|
12
12
|
import { checkinOne as checkinQoder, fetchQuotaUsage, fetchUserinfo } from './qoderClient.js';
|
|
13
13
|
import { checkinWorkbuddy, checkinWorkbuddyIntl, fetchWorkbuddyQuota, inspectToken } from './workbuddyClient.js';
|
|
14
14
|
import { fetchZcodeQuota } from './zcodeClient.js';
|
|
15
|
-
|
|
15
|
+
import { fetchMirasimQuota, fetchMirasimProfile } from './mirasimClient.js';
|
|
16
16
|
/** 从 userinfo 响应中挑一个可读的显示名 */
|
|
17
17
|
export function displayNameFrom(ui) {
|
|
18
18
|
const pick = [ui?.nickname, ui?.name, ui?.username, ui?.email]
|
|
@@ -69,6 +69,30 @@ const PRODUCTS = {
|
|
|
69
69
|
throw new Error('ZCode 账号请通过「本机导入」添加(需要客户端的完整登录快照才能查询额度与切换)');
|
|
70
70
|
},
|
|
71
71
|
},
|
|
72
|
+
mirasim: {
|
|
73
|
+
label: 'mirasim',
|
|
74
|
+
// 无 checkin:mirasim 额度按 5h/7d 滚动窗口分配,不设签到。
|
|
75
|
+
// token 约 1 小时过期:quota 内部 401 时自动用 refreshToken 续期,
|
|
76
|
+
// ctx.onRefresh(refreshContext)回写账号库后,这里再把新凭据同步回 setting.json(若该账号是客户端当前登录)。
|
|
77
|
+
quota: async (account, ctx = {}) => {
|
|
78
|
+
const mirasimLocal = await import('./mirasimLocal.js');
|
|
79
|
+
const wrapped = {
|
|
80
|
+
...ctx,
|
|
81
|
+
onRefresh: async (creds) => {
|
|
82
|
+
await ctx.onRefresh?.(creds);
|
|
83
|
+
try {
|
|
84
|
+
const synced = await mirasimLocal.writeMirasimAuth({ uid: account.uid, token: creds.token, refreshToken: creds.refreshToken, expiresAt: creds.expiresAt });
|
|
85
|
+
if (synced) ctx.log?.('已同步新 token 到 mirasim 客户端');
|
|
86
|
+
} catch (e) { ctx.log?.('同步到 mirasim 客户端失败:' + e.message); }
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
return fetchMirasimQuota(account, wrapped);
|
|
90
|
+
},
|
|
91
|
+
verify: async (account) => {
|
|
92
|
+
const p = await fetchMirasimProfile(account.token);
|
|
93
|
+
return { name: p.name || p.email, uid: p.id, email: p.email };
|
|
94
|
+
},
|
|
95
|
+
},
|
|
72
96
|
};
|
|
73
97
|
|
|
74
98
|
export function productImpl(provider) {
|
package/src/qoderApp.js
CHANGED
|
@@ -136,8 +136,8 @@ export function machineId(provider) {
|
|
|
136
136
|
|
|
137
137
|
/** 客户端版本号(用于 Cosy-Version) */
|
|
138
138
|
export function clientVersion(provider) {
|
|
139
|
-
const
|
|
140
|
-
|
|
139
|
+
const apps = detectQoderApps();
|
|
140
|
+
const app = apps.find((a) => a.provider === provider && a.version) || apps.find((a) => a.version);
|
|
141
141
|
return app?.version || DEFAULT_CLIENT_VERSION;
|
|
142
142
|
}
|
|
143
143
|
|
|
@@ -234,11 +234,13 @@ export function riskIdentitySource() {
|
|
|
234
234
|
// ─── Electron safeStorage 解密 ───
|
|
235
235
|
|
|
236
236
|
function dpapiUnprotect(buf) {
|
|
237
|
-
const
|
|
237
|
+
const script = 'Add-Type -AssemblyName System.Security;'
|
|
238
238
|
+ '$b=[Convert]::FromBase64String([Console]::In.ReadToEnd());'
|
|
239
239
|
+ "[Convert]::ToBase64String([System.Security.Cryptography.ProtectedData]::Unprotect($b,$null,'CurrentUser'))";
|
|
240
|
+
// 使用 -EncodedCommand 传入 UTF-16LE Base64,避免命令行参数中包含敏感 API 关键字被杀毒软件误报
|
|
241
|
+
const encoded = Buffer.from(script, 'utf16le').toString('base64');
|
|
240
242
|
return new Promise((resolve, reject) => {
|
|
241
|
-
const child = execFile('powershell.exe', ['-NoProfile', '-NonInteractive', '-
|
|
243
|
+
const child = execFile('powershell.exe', ['-NoProfile', '-NonInteractive', '-EncodedCommand', encoded],
|
|
242
244
|
{ windowsHide: true, timeout: 20_000 }, (err, stdout) => {
|
|
243
245
|
if (err) return reject(new Error('DPAPI 解密失败:' + err.message));
|
|
244
246
|
resolve(Buffer.from(String(stdout).trim(), 'base64'));
|
package/src/qoderUmid.js
CHANGED
|
@@ -21,9 +21,11 @@ import { logger } from './logger.js';
|
|
|
21
21
|
|
|
22
22
|
const PKG = '@qoder-ai/qodercli';
|
|
23
23
|
const REGISTRIES = ['https://registry.npmmirror.com', 'https://registry.npmjs.org'];
|
|
24
|
+
const AUTH_REGISTRY = 'https://registry.npmjs.org'; // integrity 的权威来源(镜像只用于加速下载)
|
|
24
25
|
const ELF_MACHINE = { x64: 62, arm64: 183 };
|
|
25
26
|
const BUNDLE_ENTRY = 'package/bundle/qodercli.js';
|
|
26
27
|
const FETCH_TIMEOUT_MS = 180_000;
|
|
28
|
+
const META_TIMEOUT_MS = 15_000;
|
|
27
29
|
export const CLI_RISK_ENV = { qoder: 4, 'qoder-cn': 0 };
|
|
28
30
|
|
|
29
31
|
export function umidSupported(platform = process.platform, arch = process.arch) {
|
|
@@ -98,12 +100,25 @@ export function checkIntegrity(buf, integrity) {
|
|
|
98
100
|
|
|
99
101
|
// ── 安装 ──
|
|
100
102
|
|
|
101
|
-
async function fetchBuf(url) {
|
|
102
|
-
const res = await fetch(url, { signal: AbortSignal.timeout(
|
|
103
|
+
async function fetchBuf(url, timeoutMs = FETCH_TIMEOUT_MS) {
|
|
104
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
|
|
103
105
|
if (!res.ok) throw new Error(`HTTP ${res.status}:${url}`);
|
|
104
106
|
return Buffer.from(await res.arrayBuffer());
|
|
105
107
|
}
|
|
106
108
|
|
|
109
|
+
/**
|
|
110
|
+
* 从 npmjs 拉权威 integrity。镜像(npmmirror)的元数据与 tarball 同源,
|
|
111
|
+
* 镜像被污染时它自己的 integrity 校验形同虚设,所以校验值以 npmjs 为准;
|
|
112
|
+
* npmjs 不可达时返回 null,退回镜像自身 integrity(与旧行为一致)。
|
|
113
|
+
*/
|
|
114
|
+
async function authoritativeIntegrity() {
|
|
115
|
+
try {
|
|
116
|
+
const meta = JSON.parse((await fetchBuf(`${AUTH_REGISTRY}/${PKG.replace('/', '%2f')}/latest`, META_TIMEOUT_MS)).toString('utf8'));
|
|
117
|
+
if (meta?.dist?.integrity && meta?.version) return { integrity: meta.dist.integrity, version: meta.version };
|
|
118
|
+
} catch {}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
107
122
|
function testRun(file) {
|
|
108
123
|
return new Promise((resolve, reject) => {
|
|
109
124
|
execFile(file, [String(CLI_RISK_ENV.qoder)], { timeout: 30_000, maxBuffer: 1 << 20 }, (err, stdout) => {
|
|
@@ -127,15 +142,21 @@ export const umidInstalling = () => Boolean(installing);
|
|
|
127
142
|
|
|
128
143
|
async function doInstall() {
|
|
129
144
|
if (!umidSupported()) throw new Error(`当前平台(${process.platform}/${process.arch})不需要或不支持该组件`);
|
|
145
|
+
const authority = await authoritativeIntegrity();
|
|
130
146
|
const errors = [];
|
|
131
147
|
for (const registry of REGISTRIES) {
|
|
132
148
|
try {
|
|
133
|
-
const meta = JSON.parse((await fetchBuf(`${registry}/${PKG.replace('/', '%2f')}/latest
|
|
149
|
+
const meta = JSON.parse((await fetchBuf(`${registry}/${PKG.replace('/', '%2f')}/latest`, META_TIMEOUT_MS)).toString('utf8'));
|
|
134
150
|
const { tarball, integrity } = meta.dist || {};
|
|
135
151
|
if (!tarball || !integrity) throw new Error('包元数据缺少 tarball / integrity');
|
|
152
|
+
// 镜像版本与 npmjs 不一致时没有权威校验值可比,跳过镜像直接用官方源
|
|
153
|
+
if (authority && registry !== AUTH_REGISTRY && meta.version !== authority.version) {
|
|
154
|
+
throw new Error(`镜像版本(${meta.version})与 npmjs(${authority.version})不一致`);
|
|
155
|
+
}
|
|
156
|
+
const expected = authority && authority.version === meta.version ? authority.integrity : integrity;
|
|
136
157
|
logger.info('UMID', `下载 ${PKG}@${meta.version}(${registry})`);
|
|
137
158
|
const tgz = await fetchBuf(tarball);
|
|
138
|
-
if (!checkIntegrity(tgz,
|
|
159
|
+
if (!checkIntegrity(tgz, expected)) throw new Error('下载内容与 npm integrity 不一致');
|
|
139
160
|
const bundle = tarEntry(zlib.gunzipSync(tgz), BUNDLE_ENTRY);
|
|
140
161
|
if (!bundle) throw new Error(`包里没有 ${BUNDLE_ENTRY}(qodercli 结构可能变化)`);
|
|
141
162
|
const elf = extractElf(bundle.toString('latin1'), ELF_MACHINE[process.arch]);
|
package/src/store.js
CHANGED
|
@@ -172,6 +172,15 @@ export function maskToken(token) {
|
|
|
172
172
|
|
|
173
173
|
const maskPhone = (p) => (typeof p === 'string' && p.length >= 7 ? p.slice(0, 3) + '****' + p.slice(-4) : null);
|
|
174
174
|
|
|
175
|
+
function zcodeFlavor(meta) {
|
|
176
|
+
if (!meta) return null;
|
|
177
|
+
const c = meta.credentials || {};
|
|
178
|
+
const keys = Object.keys(c);
|
|
179
|
+
const hasZai = Boolean(c['oauth:zai:access_token'] || meta.loginProvider === 'zai' || keys.some((k) => k.includes('zai')));
|
|
180
|
+
const hasBig = Boolean(c['oauth:bigmodel:access_token'] || meta.loginProvider === 'bigmodel' || keys.some((k) => k.includes('bigmodel')));
|
|
181
|
+
return hasZai && hasBig ? 'both' : hasZai ? 'zai' : hasBig ? 'bigmodel' : null;
|
|
182
|
+
}
|
|
183
|
+
|
|
175
184
|
/** 对外输出的脱敏账号视图 */
|
|
176
185
|
export function publicAccount(a) {
|
|
177
186
|
const meta = a.meta || {};
|
|
@@ -189,6 +198,7 @@ export function publicAccount(a) {
|
|
|
189
198
|
source: a.source || 'manual',
|
|
190
199
|
phone: maskPhone(meta.phone),
|
|
191
200
|
domain: meta.domain || null,
|
|
201
|
+
flavor: a.provider === 'zcode' ? zcodeFlavor(meta) : null,
|
|
192
202
|
canSwitch: Boolean(meta.session?.account || meta.credentials),
|
|
193
203
|
verified: a.verified ?? null,
|
|
194
204
|
createdAt: a.createdAt,
|
package/src/zcodeClient.js
CHANGED
|
@@ -21,7 +21,7 @@ import https from 'node:https';
|
|
|
21
21
|
import os from 'node:os';
|
|
22
22
|
import path from 'node:path';
|
|
23
23
|
import tls from 'node:tls';
|
|
24
|
-
import {
|
|
24
|
+
import { execFile } from 'node:child_process';
|
|
25
25
|
import { FETCH_TIMEOUT_MS } from './constants.js';
|
|
26
26
|
import { defaultSecret, safeDecrypt } from './zcrypto.js';
|
|
27
27
|
|
|
@@ -35,28 +35,48 @@ const CLIENT_CONFIGS_URL = 'https://zcode.z.ai/api/v1/client/configs';
|
|
|
35
35
|
// 所以优先上报本机已安装 ZCode 的版本(注册表读取),没有客户端时退回兜底值。
|
|
36
36
|
const APP_VERSION_FALLBACK = '3.11.2';
|
|
37
37
|
let appVersionCache = null;
|
|
38
|
+
let appVersionPromise = null;
|
|
39
|
+
|
|
40
|
+
/** 读注册表拿本机已安装 ZCode 的版本(单 hive,失败返回空串) */
|
|
41
|
+
function queryUninstallHive(hive) {
|
|
42
|
+
return new Promise((resolve) => {
|
|
43
|
+
execFile('reg', ['query', hive, '/s'], { encoding: 'utf8', windowsHide: true, timeout: 8000, maxBuffer: 4 * 1024 * 1024 },
|
|
44
|
+
(err, stdout) => resolve(err ? '' : String(stdout)));
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function detectAppVersion() {
|
|
49
|
+
if (process.platform !== 'win32') return null;
|
|
50
|
+
for (const hive of [
|
|
51
|
+
'HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall',
|
|
52
|
+
'HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall',
|
|
53
|
+
'HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall',
|
|
54
|
+
]) {
|
|
55
|
+
const out = await queryUninstallHive(hive);
|
|
56
|
+
const m = out.match(/ZCode[^\r\n]*\r?\n(?:.*\r?\n)*?\s*DisplayVersion\s+REG_SZ\s+([\d.]+)/)
|
|
57
|
+
|| out.match(/DisplayName\s+REG_SZ\s+ZCode[^\r\n]*[\s\S]{0,400}?DisplayVersion\s+REG_SZ\s+([\d.]+)/);
|
|
58
|
+
if (m) return m[1];
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** 客户端版本号(异步:注册表枚举较慢,不能在请求处理里同步执行阻塞事件循环) */
|
|
38
64
|
export function zcodeAppVersion() {
|
|
39
|
-
if (appVersionCache) return appVersionCache;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
'HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall',
|
|
45
|
-
'HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall',
|
|
46
|
-
]) {
|
|
47
|
-
try {
|
|
48
|
-
const out = execFileSync('reg', ['query', hive, '/s'], { encoding: 'utf8', windowsHide: true, timeout: 8000 });
|
|
49
|
-
const m = out.match(/ZCode[^\r\n]*\r?\n(?:.*\r?\n)*?\s*DisplayVersion\s+REG_SZ\s+([\d.]+)/)
|
|
50
|
-
|| out.match(/DisplayName\s+REG_SZ\s+ZCode[^\r\n]*[\s\S]{0,400}?DisplayVersion\s+REG_SZ\s+([\d.]+)/);
|
|
51
|
-
if (m) { v = m[1]; break; }
|
|
52
|
-
} catch { /* 继续下一个 hive */ }
|
|
53
|
-
}
|
|
65
|
+
if (appVersionCache) return Promise.resolve(appVersionCache);
|
|
66
|
+
if (!appVersionPromise) {
|
|
67
|
+
appVersionPromise = detectAppVersion()
|
|
68
|
+
.catch(() => null)
|
|
69
|
+
.then((v) => { appVersionCache = v || APP_VERSION_FALLBACK; return appVersionCache; });
|
|
54
70
|
}
|
|
55
|
-
|
|
56
|
-
|
|
71
|
+
return appVersionPromise;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** 启动时预热版本探测(调度器调用),首个真实请求前缓存即可就绪 */
|
|
75
|
+
export function warmZcodeAppVersion() {
|
|
76
|
+
zcodeAppVersion().catch(() => {});
|
|
57
77
|
}
|
|
58
78
|
/** 测试用:重置版本探测缓存 */
|
|
59
|
-
export function _resetAppVersionCache(v) { appVersionCache = v || null; }
|
|
79
|
+
export function _resetAppVersionCache(v) { appVersionCache = v || null; appVersionPromise = null; }
|
|
60
80
|
|
|
61
81
|
// ── HTTP 出口:直连优先 / 代理优先(可切换),任一路成功即返回 ──
|
|
62
82
|
// 代理地址:优先用面板里保存的 proxyUrl(zcode-net.json,0600),没有则退回 HTTPS_PROXY 环境变量
|
|
@@ -196,12 +216,6 @@ export async function fetchJsonRace(url, { method = 'GET', headers = {}, body =
|
|
|
196
216
|
throw lastErr || new Error('fetch failed');
|
|
197
217
|
}
|
|
198
218
|
|
|
199
|
-
async function getJsonRace(url, headers) {
|
|
200
|
-
const res = await fetchJsonRace(url, { headers });
|
|
201
|
-
const text = await res.text();
|
|
202
|
-
try { return JSON.parse(text); } catch { return { code: res.status, msg: text.slice(0, 120) }; }
|
|
203
|
-
}
|
|
204
|
-
|
|
205
219
|
/** 领取失败码 → 中文提示(取自 zcode-switch i18n.rs) */
|
|
206
220
|
const CLAIM_FAIL = {
|
|
207
221
|
1001: '套餐不存在',
|
|
@@ -216,8 +230,8 @@ const CLAIM_FAIL = {
|
|
|
216
230
|
|
|
217
231
|
const platform = () => `${process.platform}-${process.arch === 'arm64' ? 'arm64' : process.arch === 'x64' ? 'x64' : process.arch}`;
|
|
218
232
|
|
|
219
|
-
function zaiHeaders(token, deviceMid) {
|
|
220
|
-
const ver = zcodeAppVersion();
|
|
233
|
+
async function zaiHeaders(token, deviceMid) {
|
|
234
|
+
const ver = await zcodeAppVersion();
|
|
221
235
|
return {
|
|
222
236
|
'User-Agent': `ZCode/${ver}`,
|
|
223
237
|
'HTTP-Referer': 'https://zcode.z.ai',
|
|
@@ -234,8 +248,8 @@ function zaiHeaders(token, deviceMid) {
|
|
|
234
248
|
};
|
|
235
249
|
}
|
|
236
250
|
|
|
237
|
-
function bigmodelHeaders(token) {
|
|
238
|
-
return { Authorization: `Bearer ${token}`, 'User-Agent': `ZCode/${zcodeAppVersion()}`, 'x-request-id': crypto.randomUUID() };
|
|
251
|
+
async function bigmodelHeaders(token) {
|
|
252
|
+
return { Authorization: `Bearer ${token}`, 'User-Agent': `ZCode/${await zcodeAppVersion()}`, 'x-request-id': crypto.randomUUID() };
|
|
239
253
|
}
|
|
240
254
|
|
|
241
255
|
async function getJson(url, headers) {
|
|
@@ -427,9 +441,9 @@ export async function fetchZcodeQuota(account) {
|
|
|
427
441
|
let authFails = 0;
|
|
428
442
|
for (const t of tokens) {
|
|
429
443
|
try {
|
|
430
|
-
const limit = await getJson(QUOTA_LIMIT_URL, bigmodelHeaders(t));
|
|
444
|
+
const limit = await getJson(QUOTA_LIMIT_URL, await bigmodelHeaders(t));
|
|
431
445
|
if (businessOk(limit)) {
|
|
432
|
-
const sub = await getJson(SUBSCRIPTION_URL, bigmodelHeaders(t)).catch(() => null);
|
|
446
|
+
const sub = await getJson(SUBSCRIPTION_URL, await bigmodelHeaders(t)).catch(() => null);
|
|
433
447
|
return { ...normalizeQuotaLimit(limit, sub), source: 'bigmodel' };
|
|
434
448
|
}
|
|
435
449
|
if (limit?.code === 401) authFails++;
|
|
@@ -438,7 +452,7 @@ export async function fetchZcodeQuota(account) {
|
|
|
438
452
|
}
|
|
439
453
|
for (const t of billingTokens(account)) {
|
|
440
454
|
try {
|
|
441
|
-
const bal = await getJson(`${BILLING_BALANCE_URL}?app_version=${zcodeAppVersion()}`, zaiHeaders(t, account.meta?.deviceMid));
|
|
455
|
+
const bal = await getJson(`${BILLING_BALANCE_URL}?app_version=${await zcodeAppVersion()}`, await zaiHeaders(t, account.meta?.deviceMid));
|
|
442
456
|
if (businessOk(bal)) return { ...normalizeBalance(bal), source: 'zcode.z.ai' };
|
|
443
457
|
if (bal?.code === 401) authFails++;
|
|
444
458
|
} catch (e) { lastErr = e.message; }
|
|
@@ -457,8 +471,8 @@ export async function fetchZcodeQuota(account) {
|
|
|
457
471
|
export async function fetchClaimPlans(account) {
|
|
458
472
|
const token = claimToken(account);
|
|
459
473
|
const v = await getJson(
|
|
460
|
-
`${BILLING_PREVIEW_URL}?app_version=${zcodeAppVersion()}&platform=${platform()}`,
|
|
461
|
-
zaiHeaders(token, account.meta?.deviceMid),
|
|
474
|
+
`${BILLING_PREVIEW_URL}?app_version=${await zcodeAppVersion()}&platform=${platform()}`,
|
|
475
|
+
await zaiHeaders(token, account.meta?.deviceMid),
|
|
462
476
|
);
|
|
463
477
|
if (v?.code !== 0) throw new Error(v?.msg || v?.message || `查询活动列表失败(code ${v?.code})`);
|
|
464
478
|
return { plans: normalizePlans(v), serverTime: v?.data?.server_time ? new Date(v.data.server_time * 1000).toISOString() : null };
|
|
@@ -466,7 +480,7 @@ export async function fetchClaimPlans(account) {
|
|
|
466
480
|
|
|
467
481
|
/** 验证码配置(无需登录)。当前服务端未下发时返回 { enabled: false }。 */
|
|
468
482
|
export async function fetchCaptchaConfig() {
|
|
469
|
-
const v = await getJson(CLIENT_CONFIGS_URL, zaiHeaders(''));
|
|
483
|
+
const v = await getJson(CLIENT_CONFIGS_URL, await zaiHeaders(''));
|
|
470
484
|
if (v?.code !== 0) throw new Error('获取验证码配置失败');
|
|
471
485
|
const c = v?.data?.configs?.captcha || {};
|
|
472
486
|
return {
|
|
@@ -483,7 +497,7 @@ export async function fetchCaptchaConfig() {
|
|
|
483
497
|
*/
|
|
484
498
|
export async function claimPlan(account, planId, { captchaParam = '', region = '' } = {}) {
|
|
485
499
|
const token = claimToken(account);
|
|
486
|
-
const headers = zaiHeaders(token, account.meta?.deviceMid);
|
|
500
|
+
const headers = await zaiHeaders(token, account.meta?.deviceMid);
|
|
487
501
|
if (captchaParam && captchaParam.trim()) headers['X-Aliyun-Captcha-Verify-Param'] = captchaParam.trim();
|
|
488
502
|
if (region && region.trim()) headers['X-Aliyun-Captcha-Verify-Region'] = region.trim();
|
|
489
503
|
const res = await fetchJsonRace(BILLING_CLAIM_URL, {
|
package/src/zcodeLocal.js
CHANGED
|
@@ -35,6 +35,8 @@ export function zcodePaths() {
|
|
|
35
35
|
home: h,
|
|
36
36
|
credentials: path.join(v2, 'credentials.json'),
|
|
37
37
|
config: path.join(v2, 'config.json'),
|
|
38
|
+
setting: path.join(v2, 'setting.json'),
|
|
39
|
+
providerConfig: path.join(v2, 'provider_config.json'),
|
|
38
40
|
telemetry: path.join(v2, 'telemetry-state.json'),
|
|
39
41
|
};
|
|
40
42
|
}
|
|
@@ -79,6 +81,8 @@ export function liveToAccount(secretOpts) {
|
|
|
79
81
|
meta: {
|
|
80
82
|
credentials: creds,
|
|
81
83
|
config: readJson(p.config),
|
|
84
|
+
setting: readJson(p.setting),
|
|
85
|
+
providerConfig: readJson(p.providerConfig),
|
|
82
86
|
canonicalHash: zc.canonicalHash(creds),
|
|
83
87
|
// 沿用本机当前的设备 ID:切回这个账号时还原成它原本的设备身份
|
|
84
88
|
deviceMid: readJson(p.telemetry)?.deviceMid || null,
|
|
@@ -195,11 +199,109 @@ export function switchTo(account, { force = false } = {}) {
|
|
|
195
199
|
}
|
|
196
200
|
|
|
197
201
|
atomicWriteJson(p.credentials, account.meta.credentials);
|
|
198
|
-
|
|
202
|
+
ensureCodingPlanIdentity(account, p);
|
|
203
|
+
|
|
204
|
+
// 深度对齐 config.json:保证目标账号对应渠道(zai / bigmodel)处于启用状态,避免出现套餐「未登录」
|
|
205
|
+
const creds = account.meta.credentials || {};
|
|
206
|
+
const isZai = Boolean(creds['oauth:zai:access_token'] || account.meta.loginProvider === 'zai' || Object.keys(creds).some((k) => k.includes('zai')));
|
|
207
|
+
const isBig = Boolean(creds['oauth:bigmodel:access_token'] || account.meta.loginProvider === 'bigmodel' || Object.keys(creds).some((k) => k.includes('bigmodel')));
|
|
208
|
+
|
|
209
|
+
const cfg = account.meta.config || readJson(p.config) || { provider: {} };
|
|
210
|
+
cfg.provider = cfg.provider || {};
|
|
211
|
+
if (isZai) {
|
|
212
|
+
if (cfg.provider['builtin:zai']) cfg.provider['builtin:zai'].enabled = true;
|
|
213
|
+
if (cfg.provider['builtin:zai-start-plan']) cfg.provider['builtin:zai-start-plan'].enabled = true;
|
|
214
|
+
if (cfg.provider['builtin:bigmodel']) cfg.provider['builtin:bigmodel'].enabled = false;
|
|
215
|
+
} else if (isBig) {
|
|
216
|
+
if (cfg.provider['builtin:bigmodel']) cfg.provider['builtin:bigmodel'].enabled = true;
|
|
217
|
+
if (cfg.provider['builtin:bigmodel-coding-plan']) cfg.provider['builtin:bigmodel-coding-plan'].enabled = true;
|
|
218
|
+
if (cfg.provider['builtin:zai']) cfg.provider['builtin:zai'].enabled = false;
|
|
219
|
+
if (cfg.provider['builtin:zai-start-plan']) cfg.provider['builtin:zai-start-plan'].enabled = false;
|
|
220
|
+
}
|
|
221
|
+
atomicWriteJson(p.config, cfg);
|
|
222
|
+
|
|
223
|
+
// 深度对齐 setting.json:即使账号未保存过 setting 快照,也自动修复选择器 key
|
|
224
|
+
const set = account.meta.setting || readJson(p.setting) || {};
|
|
225
|
+
set.modelProviderFamilyModes = set.modelProviderFamilyModes || {};
|
|
226
|
+
set.modelProviderFamilySelectedKeys = set.modelProviderFamilySelectedKeys || {};
|
|
227
|
+
// providerFamilyDomain 是客户端模型页的「家族显示开关」:
|
|
228
|
+
// 设为 zai 时整个 BigModel 家族被隐藏(反之亦然),切号必须与 active_provider 同步对齐,
|
|
229
|
+
// 否则客户端界面上目标渠道一栏直接消失 / 显示未登录(zcode-switch 同样写这个字段)。
|
|
230
|
+
if (isZai) {
|
|
231
|
+
set.modelProviderFamilyModes.zai = 'oauth';
|
|
232
|
+
set.modelProviderFamilySelectedKeys.zai = 'coding-plan:builtin:zai-start-plan';
|
|
233
|
+
set.providerFamilyDomain = 'zai';
|
|
234
|
+
} else if (isBig) {
|
|
235
|
+
set.modelProviderFamilyModes.bigmodel = 'oauth';
|
|
236
|
+
set.modelProviderFamilySelectedKeys.bigmodel = 'coding-plan:builtin:bigmodel-coding-plan';
|
|
237
|
+
set.providerFamilyDomain = 'bigmodel';
|
|
238
|
+
}
|
|
239
|
+
if (set.providerFamilyDomain) set.providerFamilyDomainUpdatedAt = Date.now();
|
|
240
|
+
atomicWriteJson(p.setting, set);
|
|
241
|
+
|
|
242
|
+
// 深度对齐 provider_config.json:默认模型选择指向目标账号的 Coding Plan,否则客户端模型页会判定「未登录」
|
|
243
|
+
if (account.meta.providerConfig) atomicWriteJson(p.providerConfig, account.meta.providerConfig);
|
|
244
|
+
alignDefaultModelSelection(account, isZai ? 'zai' : isBig ? 'bigmodel' : null);
|
|
245
|
+
|
|
199
246
|
writeVirtualDeviceMid(account);
|
|
200
247
|
return { switched: true, alreadyActive: false };
|
|
201
248
|
}
|
|
202
249
|
|
|
250
|
+
/** ZCode 客户端判定套餐已登录的必要键:account-provider:<providerId>:identity(值为该账号身份 ID)。
|
|
251
|
+
* 缺失时客户端 nPn/oPn 读不到 accountIdentity,直接跳过该套餐 → 界面显示「未登录」。 */
|
|
252
|
+
export function ensureCodingPlanIdentity(account, paths = zcodePaths()) {
|
|
253
|
+
const creds = account?.meta?.credentials;
|
|
254
|
+
if (!creds) return;
|
|
255
|
+
const secret = zc.defaultSecret(paths.home);
|
|
256
|
+
let changed = false;
|
|
257
|
+
|
|
258
|
+
const putIdentity = (providerId, identity) => {
|
|
259
|
+
if (!identity) return;
|
|
260
|
+
const key = `account-provider:${providerId}:identity`;
|
|
261
|
+
if (creds[key]) return; // 已有则不覆盖
|
|
262
|
+
creds[key] = zc.encryptWithSecret(String(identity), secret);
|
|
263
|
+
changed = true;
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
// BigModel:身份 ID 取自 oauth:bigmodel:user_info.id
|
|
267
|
+
const bigInfo = zc.decryptJsonOpt(creds['oauth:bigmodel:user_info'], secret);
|
|
268
|
+
const bigUid = bigInfo?.id ?? bigInfo?.user_id ?? null;
|
|
269
|
+
if (bigUid) {
|
|
270
|
+
putIdentity('account:bigmodel-individual-coding-plan', bigUid);
|
|
271
|
+
putIdentity('account:bigmodel-team-coding-plan', bigUid);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Z.ai:身份 ID 取自 oauth:zai:user_info.user_id
|
|
275
|
+
const zaiInfo = zc.decryptJsonOpt(creds['oauth:zai:user_info'], secret);
|
|
276
|
+
const zaiUid = zaiInfo?.user_id ?? zaiInfo?.id ?? null;
|
|
277
|
+
if (zaiUid) {
|
|
278
|
+
putIdentity('account:zai-individual-coding-plan', zaiUid);
|
|
279
|
+
putIdentity('account:zai-team-coding-plan', zaiUid);
|
|
280
|
+
putIdentity('account:zai-start-plan', zaiUid);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (changed) atomicWriteJson(paths.credentials, creds);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** 把 provider_config.json 的 defaultModelSelection 指向目标账号对应的 Coding Plan */
|
|
287
|
+
export function alignDefaultModelSelection(account, family) {
|
|
288
|
+
if (!family) return;
|
|
289
|
+
const p = zcodePaths();
|
|
290
|
+
const file = p.providerConfig;
|
|
291
|
+
if (!fs.existsSync(file)) return;
|
|
292
|
+
const cfg = readJson(file);
|
|
293
|
+
if (!cfg || typeof cfg !== 'object') return;
|
|
294
|
+
cfg.config = cfg.config || {};
|
|
295
|
+
|
|
296
|
+
const providerId = family === 'zai'
|
|
297
|
+
? 'account:zai-individual-coding-plan'
|
|
298
|
+
: 'account:bigmodel-individual-coding-plan';
|
|
299
|
+
const modelId = family === 'zai' ? 'glm-5.3-flash' : 'glm-5.3';
|
|
300
|
+
cfg.config.defaultModelSelection = { providerId, modelId };
|
|
301
|
+
atomicWriteJson(file, cfg);
|
|
302
|
+
return providerId;
|
|
303
|
+
}
|
|
304
|
+
|
|
203
305
|
/** 每个账号的虚拟设备 ID(随机生成一次,落在 account.meta.deviceMid),切换时写回 telemetry-state.json */
|
|
204
306
|
export function ensureVirtualDeviceMid(account) {
|
|
205
307
|
if (account.meta?.deviceMid) return account.meta.deviceMid;
|