creditdaddy 0.9.3 → 1.1.1
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 +289 -234
- package/bin/creditdaddy.js +205 -187
- package/package.json +2 -2
- package/src/accounts.js +25 -7
- package/src/catpawClient.js +103 -0
- package/src/catpawLocal.js +229 -0
- package/src/checkin.js +178 -67
- package/src/constants.js +18 -2
- package/src/daemon.js +819 -596
- package/src/localDetect.js +2 -2
- package/src/logger.js +43 -2
- package/src/mirasimClient.js +189 -0
- package/src/mirasimLocal.js +386 -0
- package/src/panel.html +881 -155
- package/src/providers.js +113 -8
- package/src/qoderApp.js +438 -330
- package/src/qoderClient.js +40 -2
- package/src/qoderUmid.js +25 -4
- package/src/store.js +27 -1
- package/src/tenrouter.js +253 -221
- package/src/tenrouterAccounts.js +163 -0
- package/src/transfer.js +24 -5
- package/src/usageSync.js +214 -28
- package/src/workbuddyAuth.js +127 -126
- package/src/workbuddyClient.js +302 -302
- package/src/zcodeAutoClaim.js +1 -0
- package/src/zcodeClient.js +549 -515
- package/src/zcodeGateway.js +574 -0
- package/src/zcodeLocal.js +103 -1
- package/src/zcodePlanShape.json +29 -0
package/bin/creditdaddy.js
CHANGED
|
@@ -1,187 +1,205 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* CreditDaddy CLI
|
|
4
|
-
* creditdaddy daemon 启动守护进程(默认)
|
|
5
|
-
* creditdaddy add <token> 添加账号(--cn 国内版 --name 名字)
|
|
6
|
-
* creditdaddy list 查看账号
|
|
7
|
-
* creditdaddy checkin 立即领取全部账号
|
|
8
|
-
* creditdaddy remove <id> 删除账号
|
|
9
|
-
* creditdaddy export/import 导出/导入账号
|
|
10
|
-
* creditdaddy logs 查看领取状态(state.json)
|
|
11
|
-
* creditdaddy help 显示帮助
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { startDaemon } from '../src/daemon.js';
|
|
15
|
-
import { logger } from '../src/logger.js';
|
|
16
|
-
|
|
17
|
-
const args = process.argv.slice(2);
|
|
18
|
-
const cmd = args[0] || 'daemon';
|
|
19
|
-
|
|
20
|
-
const HELP = `CreditDaddy — Qoder / WorkBuddy / ZCode 多账号管理 + 每日积分自动领取
|
|
21
|
-
|
|
22
|
-
用法:
|
|
23
|
-
creditdaddy daemon [--port 47860] [--host 127.0.0.1] 启动守护进程 + Web 面板
|
|
24
|
-
creditdaddy add <token> [--cn] [--name 名字] 添加 Qoder 账号(--cn 为国内版)
|
|
25
|
-
creditdaddy add <token> --workbuddy [--intl] 添加 WorkBuddy 账号(登录 token)
|
|
26
|
-
creditdaddy list 查看账号
|
|
27
|
-
creditdaddy checkin [--cn | --intl] 立即领取
|
|
28
|
-
creditdaddy remove <id前缀> 删除账号
|
|
29
|
-
creditdaddy export [file.json]
|
|
30
|
-
|
|
31
|
-
creditdaddy import <file.json> [--password 口令] 导入 CreditDaddy / 10router 导出文件
|
|
32
|
-
creditdaddy scan 导入本机 Qoder / WorkBuddy / ZCode 客户端已登录的账号
|
|
33
|
-
creditdaddy umid [install|remove] Qoder 设备身份组件(Linux / fnOS 国际版领取用)
|
|
34
|
-
creditdaddy logs 查看领取状态
|
|
35
|
-
creditdaddy help 显示本帮助`;
|
|
36
|
-
|
|
37
|
-
function flag(name) {
|
|
38
|
-
const i = args.indexOf(name);
|
|
39
|
-
return i >= 0 ? args[i + 1] : undefined;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function main() {
|
|
43
|
-
switch (cmd) {
|
|
44
|
-
case 'daemon': {
|
|
45
|
-
const port = Number(flag('--port')) || Number(process.env.PORT) || undefined;
|
|
46
|
-
const host = flag('--host') || process.env.HOST || undefined;
|
|
47
|
-
await startDaemon(port, host);
|
|
48
|
-
const { startScheduler } = await import('../src/checkin.js');
|
|
49
|
-
startScheduler();
|
|
50
|
-
logger.info('CLI', '按 Ctrl+C 停止');
|
|
51
|
-
break;
|
|
52
|
-
}
|
|
53
|
-
case 'add': {
|
|
54
|
-
const token = args[1];
|
|
55
|
-
if (!token) { console.error('用法: creditdaddy add <token> [--cn] [--name 名字]'); process.exit(1); }
|
|
56
|
-
const { publicAccount } = await import('../src/store.js');
|
|
57
|
-
const { addAccount } = await import('../src/accounts.js');
|
|
58
|
-
const wbFlag = args.includes('--workbuddy');
|
|
59
|
-
const provider = wbFlag
|
|
60
|
-
? (args.includes('--intl') ? 'workbuddy-intl' : 'workbuddy')
|
|
61
|
-
: (args.includes('--cn') ? 'qoder-cn' : 'qoder');
|
|
62
|
-
const { account, duplicate } = await addAccount({ token, provider, name: flag('--name') });
|
|
63
|
-
if (duplicate) { console.error('该账号已存在'); process.exit(1); }
|
|
64
|
-
console.log(account.verified ? '✓ token 校验通过' : '⚠ token 校验失败(仍会保存):' + account.verifyError);
|
|
65
|
-
console.log('✓ 已添加:', JSON.stringify(publicAccount(account), null, 2));
|
|
66
|
-
break;
|
|
67
|
-
}
|
|
68
|
-
case 'list': {
|
|
69
|
-
const { loadAccounts, publicAccount } = await import('../src/store.js');
|
|
70
|
-
const accounts = await loadAccounts();
|
|
71
|
-
if (accounts.length === 0) return console.log('(还没有账号,用 creditdaddy add <token> 添加)');
|
|
72
|
-
for (const a of accounts) {
|
|
73
|
-
const p = publicAccount(a);
|
|
74
|
-
console.log(`[${p.id.slice(0, 8)}] ${(p.name || '(未命名)').padEnd(16)} ${p.provider.padEnd(14)} ${p.tokenMasked} ${p.lastCheckin ? '上次领取 ' + p.lastCheckin.slice(0, 16) : '未领取'}`);
|
|
75
|
-
}
|
|
76
|
-
break;
|
|
77
|
-
}
|
|
78
|
-
case 'checkin': {
|
|
79
|
-
const { runCheckinTick } = await import('../src/checkin.js');
|
|
80
|
-
const provider = args.includes('--workbuddy')
|
|
81
|
-
? (args.includes('--intl') ? 'workbuddy-intl' : 'workbuddy')
|
|
82
|
-
: args.includes('--cn') ? 'qoder-cn' : args.includes('--intl') ? 'qoder' : undefined;
|
|
83
|
-
const { summary, results } = await runCheckinTick({ provider, skipIfCheckedToday: false });
|
|
84
|
-
console.log(summary);
|
|
85
|
-
for (const r of results) {
|
|
86
|
-
console.log(` ${r.status === 'checked-in' ? '✓' : r.status === 'failed' ? '✗' : '·'} ${r.account}(${r.provider})${r.status === 'checked-in' ? ` +${r.claimedAmount} Credits` : r.error ? ' ' + r.error : r.message ? ' ' + r.message : ''}`);
|
|
87
|
-
}
|
|
88
|
-
break;
|
|
89
|
-
}
|
|
90
|
-
case 'remove': {
|
|
91
|
-
const id = args[1];
|
|
92
|
-
if (!id) { console.error('用法: creditdaddy remove <id前缀>(id 见 creditdaddy list)'); process.exit(1); }
|
|
93
|
-
const { withAccounts } = await import('../src/store.js');
|
|
94
|
-
const outcome = await withAccounts((accounts) => {
|
|
95
|
-
const matches = accounts.filter((a) => a.id.startsWith(id));
|
|
96
|
-
if (matches.length !== 1) return { count: matches.length };
|
|
97
|
-
accounts.splice(accounts.indexOf(matches[0]), 1);
|
|
98
|
-
return { count: 1, removed: matches[0] };
|
|
99
|
-
});
|
|
100
|
-
if (outcome.count === 0) { console.error('账号不存在'); process.exit(1); }
|
|
101
|
-
if (outcome.count > 1) { console.error(`id 前缀 "${id}" 匹配到 ${outcome.count} 个账号,请输入更长的前缀`); process.exit(1); }
|
|
102
|
-
console.log('✓ 已删除', outcome.removed.name || outcome.removed.id);
|
|
103
|
-
break;
|
|
104
|
-
}
|
|
105
|
-
case 'export': {
|
|
106
|
-
const { loadAccounts } = await import('../src/store.js');
|
|
107
|
-
const { exportAccounts } = await import('../src/transfer.js');
|
|
108
|
-
const out = args[1] && !args[1].startsWith('--') ? args[1] : 'creditdaddy-backup.json';
|
|
109
|
-
const password = flag('--password');
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const {
|
|
129
|
-
const {
|
|
130
|
-
const
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
];
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
break;
|
|
170
|
-
}
|
|
171
|
-
case '
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CreditDaddy CLI
|
|
4
|
+
* creditdaddy daemon 启动守护进程(默认)
|
|
5
|
+
* creditdaddy add <token> 添加账号(--cn 国内版 --name 名字)
|
|
6
|
+
* creditdaddy list 查看账号
|
|
7
|
+
* creditdaddy checkin 立即领取全部账号
|
|
8
|
+
* creditdaddy remove <id> 删除账号
|
|
9
|
+
* creditdaddy export/import 导出/导入账号
|
|
10
|
+
* creditdaddy logs 查看领取状态(state.json)
|
|
11
|
+
* creditdaddy help 显示帮助
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { startDaemon } from '../src/daemon.js';
|
|
15
|
+
import { logger } from '../src/logger.js';
|
|
16
|
+
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
const cmd = args[0] || 'daemon';
|
|
19
|
+
|
|
20
|
+
const HELP = `CreditDaddy — Qoder / WorkBuddy / ZCode / mirasim / 妙手 多账号管理 + 每日积分自动领取
|
|
21
|
+
|
|
22
|
+
用法:
|
|
23
|
+
creditdaddy daemon [--port 47860] [--host 127.0.0.1] 启动守护进程 + Web 面板
|
|
24
|
+
creditdaddy add <token> [--cn] [--name 名字] 添加 Qoder 账号(--cn 为国内版)
|
|
25
|
+
creditdaddy add <token> --workbuddy [--intl] 添加 WorkBuddy 账号(登录 token)
|
|
26
|
+
creditdaddy list 查看账号
|
|
27
|
+
creditdaddy checkin [--cn | --intl] 立即领取
|
|
28
|
+
creditdaddy remove <id前缀> 删除账号
|
|
29
|
+
creditdaddy export [file.json] --password 口令 [--cn|--intl]
|
|
30
|
+
导出账号(口令必填,至少 4 位;与 10router 迁移文件互通)
|
|
31
|
+
creditdaddy import <file.json> [--password 口令] 导入 CreditDaddy / 10router 导出文件
|
|
32
|
+
creditdaddy scan 导入本机 Qoder / WorkBuddy / ZCode / mirasim / 妙手 客户端已登录的账号
|
|
33
|
+
creditdaddy umid [install|remove] Qoder 设备身份组件(Linux / fnOS 国际版领取用)
|
|
34
|
+
creditdaddy logs 查看领取状态
|
|
35
|
+
creditdaddy help 显示本帮助`;
|
|
36
|
+
|
|
37
|
+
function flag(name) {
|
|
38
|
+
const i = args.indexOf(name);
|
|
39
|
+
return i >= 0 ? args[i + 1] : undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function main() {
|
|
43
|
+
switch (cmd) {
|
|
44
|
+
case 'daemon': {
|
|
45
|
+
const port = Number(flag('--port')) || Number(process.env.PORT) || undefined;
|
|
46
|
+
const host = flag('--host') || process.env.HOST || undefined;
|
|
47
|
+
await startDaemon(port, host);
|
|
48
|
+
const { startScheduler } = await import('../src/checkin.js');
|
|
49
|
+
startScheduler();
|
|
50
|
+
logger.info('CLI', '按 Ctrl+C 停止');
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
case 'add': {
|
|
54
|
+
const token = args[1];
|
|
55
|
+
if (!token) { console.error('用法: creditdaddy add <token> [--cn] [--name 名字]'); process.exit(1); }
|
|
56
|
+
const { publicAccount } = await import('../src/store.js');
|
|
57
|
+
const { addAccount } = await import('../src/accounts.js');
|
|
58
|
+
const wbFlag = args.includes('--workbuddy');
|
|
59
|
+
const provider = wbFlag
|
|
60
|
+
? (args.includes('--intl') ? 'workbuddy-intl' : 'workbuddy')
|
|
61
|
+
: (args.includes('--cn') ? 'qoder-cn' : 'qoder');
|
|
62
|
+
const { account, duplicate } = await addAccount({ token, provider, name: flag('--name') });
|
|
63
|
+
if (duplicate) { console.error('该账号已存在'); process.exit(1); }
|
|
64
|
+
console.log(account.verified ? '✓ token 校验通过' : '⚠ token 校验失败(仍会保存):' + account.verifyError);
|
|
65
|
+
console.log('✓ 已添加:', JSON.stringify(publicAccount(account), null, 2));
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
case 'list': {
|
|
69
|
+
const { loadAccounts, publicAccount } = await import('../src/store.js');
|
|
70
|
+
const accounts = await loadAccounts();
|
|
71
|
+
if (accounts.length === 0) return console.log('(还没有账号,用 creditdaddy add <token> 添加)');
|
|
72
|
+
for (const a of accounts) {
|
|
73
|
+
const p = publicAccount(a);
|
|
74
|
+
console.log(`[${p.id.slice(0, 8)}] ${(p.name || '(未命名)').padEnd(16)} ${p.provider.padEnd(14)} ${p.tokenMasked} ${p.lastCheckin ? '上次领取 ' + p.lastCheckin.slice(0, 16) : '未领取'}`);
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case 'checkin': {
|
|
79
|
+
const { runCheckinTick } = await import('../src/checkin.js');
|
|
80
|
+
const provider = args.includes('--workbuddy')
|
|
81
|
+
? (args.includes('--intl') ? 'workbuddy-intl' : 'workbuddy')
|
|
82
|
+
: args.includes('--cn') ? 'qoder-cn' : args.includes('--intl') ? 'qoder' : undefined;
|
|
83
|
+
const { summary, results } = await runCheckinTick({ provider, skipIfCheckedToday: false });
|
|
84
|
+
console.log(summary);
|
|
85
|
+
for (const r of results) {
|
|
86
|
+
console.log(` ${r.status === 'checked-in' ? '✓' : r.status === 'failed' ? '✗' : '·'} ${r.account}(${r.provider})${r.status === 'checked-in' ? ` +${r.claimedAmount} Credits` : r.error ? ' ' + r.error : r.message ? ' ' + r.message : ''}`);
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case 'remove': {
|
|
91
|
+
const id = args[1];
|
|
92
|
+
if (!id) { console.error('用法: creditdaddy remove <id前缀>(id 见 creditdaddy list)'); process.exit(1); }
|
|
93
|
+
const { withAccounts } = await import('../src/store.js');
|
|
94
|
+
const outcome = await withAccounts((accounts) => {
|
|
95
|
+
const matches = accounts.filter((a) => a.id.startsWith(id));
|
|
96
|
+
if (matches.length !== 1) return { count: matches.length };
|
|
97
|
+
accounts.splice(accounts.indexOf(matches[0]), 1);
|
|
98
|
+
return { count: 1, removed: matches[0] };
|
|
99
|
+
});
|
|
100
|
+
if (outcome.count === 0) { console.error('账号不存在'); process.exit(1); }
|
|
101
|
+
if (outcome.count > 1) { console.error(`id 前缀 "${id}" 匹配到 ${outcome.count} 个账号,请输入更长的前缀`); process.exit(1); }
|
|
102
|
+
console.log('✓ 已删除', outcome.removed.name || outcome.removed.id);
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
case 'export': {
|
|
106
|
+
const { loadAccounts } = await import('../src/store.js');
|
|
107
|
+
const { exportAccounts, TransferError } = await import('../src/transfer.js');
|
|
108
|
+
const out = args[1] && !args[1].startsWith('--') ? args[1] : 'creditdaddy-secure-backup.json';
|
|
109
|
+
const password = flag('--password');
|
|
110
|
+
if (!password) { console.error('导出账号必须设置加密口令:creditdaddy export [file.json] --password 口令(至少 4 位)'); process.exit(1); }
|
|
111
|
+
const provider = args.includes('--cn') ? 'qoder-cn' : args.includes('--intl') ? 'qoder' : undefined;
|
|
112
|
+
const { writeFileSync } = await import('node:fs');
|
|
113
|
+
let blob;
|
|
114
|
+
try {
|
|
115
|
+
blob = exportAccounts(await loadAccounts(), { password, provider });
|
|
116
|
+
} catch (e) {
|
|
117
|
+
console.error(e instanceof TransferError ? `导出失败:${e.message}` : e.message);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
writeFileSync(out, JSON.stringify(blob, null, 2), { mode: 0o600 });
|
|
121
|
+
console.log('✓ 已导出加密文件(10router 可直接导入):', out);
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
case 'import': {
|
|
125
|
+
const file = args[1];
|
|
126
|
+
if (!file) { console.error('用法: creditdaddy import <file.json>'); process.exit(1); }
|
|
127
|
+
const { readFileSync } = await import('node:fs');
|
|
128
|
+
const { importAccounts } = await import('../src/accounts.js');
|
|
129
|
+
const { parseImport } = await import('../src/transfer.js');
|
|
130
|
+
const parsed = parseImport(JSON.parse(readFileSync(file, 'utf8')), { password: flag('--password') });
|
|
131
|
+
const r = await importAccounts(parsed.accounts);
|
|
132
|
+
console.log(`✓ 导入完成(来源 ${parsed.source}):新增 ${r.added},续期 ${r.updated},跳过 ${r.skipped + parsed.skipped}`);
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
case 'scan': {
|
|
136
|
+
const { readQoderAppAccounts } = await import('../src/qoderApp.js');
|
|
137
|
+
const { readWorkbuddySessions } = await import('../src/workbuddyLocal.js');
|
|
138
|
+
const { liveToAccount: zcodeLive } = await import('../src/zcodeLocal.js');
|
|
139
|
+
const { liveToAccount: mirasimLive } = await import('../src/mirasimLocal.js');
|
|
140
|
+
const { liveToAccount: catpawLive } = await import('../src/catpawLocal.js');
|
|
141
|
+
const { addAccount } = await import('../src/accounts.js');
|
|
142
|
+
const qa = await readQoderAppAccounts();
|
|
143
|
+
const wb = readWorkbuddySessions();
|
|
144
|
+
const skipped = [];
|
|
145
|
+
let zAccount = null;
|
|
146
|
+
try { zAccount = zcodeLive(); } catch (e) { skipped.push({ file: '~/.zcode/v2/credentials.json', error: e.message }); }
|
|
147
|
+
let miraAccount = null;
|
|
148
|
+
try { miraAccount = await mirasimLive(); } catch (e) { skipped.push({ file: '~/.mirasim/setting.json', error: e.message }); }
|
|
149
|
+
let cpAccount = null;
|
|
150
|
+
try { cpAccount = await catpawLive(); } catch (e) { skipped.push({ file: 'catpaw-moon/catx-credential.json', error: e.message }); }
|
|
151
|
+
const records = [
|
|
152
|
+
...qa.accounts.map((c) => ({
|
|
153
|
+
label: c.source,
|
|
154
|
+
rec: { provider: c.provider, token: c.token, name: c.user.name || c.user.email, uid: c.user.id, email: c.user.email, refreshToken: c.refreshToken, expiresAt: c.expiresAt, source: 'local-app' },
|
|
155
|
+
})),
|
|
156
|
+
...wb.accounts.map(({ file: _f, fileTime: _t, current, source, ...rec }) => ({
|
|
157
|
+
label: source, rec: { ...rec, source: current ? 'workbuddy-current' : 'workbuddy-history' },
|
|
158
|
+
})),
|
|
159
|
+
...(zAccount ? [{ label: 'ZCode 当前登录', rec: zAccount }] : []),
|
|
160
|
+
...(miraAccount ? [{ label: 'mirasim 当前登录', rec: miraAccount }] : []),
|
|
161
|
+
...(cpAccount ? [{ label: '妙手当前登录', rec: cpAccount }] : []),
|
|
162
|
+
];
|
|
163
|
+
for (const e of [...qa.errors, ...wb.errors, ...skipped]) console.log('⚠', e.file, e.error);
|
|
164
|
+
if (!records.length) { console.log('(本机 Qoder / WorkBuddy / ZCode / mirasim / 妙手 客户端未登录或未安装)'); break; }
|
|
165
|
+
for (const { label, rec } of records) {
|
|
166
|
+
const r = await addAccount(rec, { trusted: true });
|
|
167
|
+
console.log(r.duplicate ? (r.updated ? '↻ 已更新' : '· 已存在') : '✓ 已导入', r.account.name || r.account.id, `(${label})`);
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
case 'umid': {
|
|
172
|
+
const umid = await import('../src/qoderUmid.js');
|
|
173
|
+
const sub = args[1] || 'status';
|
|
174
|
+
if (sub === 'install') {
|
|
175
|
+
console.log('正在从 npm 下载官方 @qoder-ai/qodercli 并提取设备身份组件(约 30MB)…');
|
|
176
|
+
const m = await umid.installUmid();
|
|
177
|
+
console.log(`✓ 已安装:qodercli ${m.version} / ${m.arch},Qoder 国际版领取可用`);
|
|
178
|
+
} else if (sub === 'remove') {
|
|
179
|
+
umid.removeUmid();
|
|
180
|
+
console.log('✓ 已移除设备身份组件');
|
|
181
|
+
} else {
|
|
182
|
+
const info = umid.umidInfo();
|
|
183
|
+
if (!info.supported) console.log('当前平台不需要该组件(Windows / macOS 使用 Qoder 客户端自带的 runtime-info)');
|
|
184
|
+
else if (info.installed) console.log(`已安装:qodercli ${info.installed.version} / ${info.installed.arch}(${info.installed.installedAt.slice(0, 10)})`);
|
|
185
|
+
else console.log('未安装。运行 creditdaddy umid install 安装后即可领取 Qoder 国际版');
|
|
186
|
+
}
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
case 'logs': {
|
|
190
|
+
const { loadState } = await import('../src/store.js');
|
|
191
|
+
console.log('数据目录状态:', JSON.stringify(await loadState(), null, 2));
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
case 'help':
|
|
195
|
+
case '--help':
|
|
196
|
+
case '-h':
|
|
197
|
+
console.log(HELP);
|
|
198
|
+
break;
|
|
199
|
+
default:
|
|
200
|
+
console.error(`未知命令: ${cmd}\n\n${HELP}`);
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
main().catch((e) => { console.error('✗', e.message); process.exit(1); });
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "creditdaddy",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "CreditDaddy — Qoder / WorkBuddy / ZCode 多账号本地管理与每日积分自动签到助手",
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "CreditDaddy — Qoder / WorkBuddy / ZCode / mirasim 多账号本地管理与每日积分自动签到助手",
|
|
5
5
|
"homepage": "https://github.com/techysy/CreditDaddy",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
package/src/accounts.js
CHANGED
|
@@ -6,11 +6,27 @@
|
|
|
6
6
|
* 同一 provider 下 uid 相同但 token 不同 → 视为 token 续期,原地更新而不是新增。
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { normalizeAccountInput, findDuplicate, loadAccounts, withAccounts } from './store.js';
|
|
9
|
+
import { normalizeAccountInput, findDuplicate, loadAccounts, withAccounts, loadSettings, saveSettings } from './store.js';
|
|
10
10
|
import { productImpl, displayNameFrom } from './providers.js';
|
|
11
11
|
|
|
12
12
|
export { displayNameFrom };
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* 若该 Qoder 账号有 uid 但还没有网页会话(逐资源包明细用),从 settings 的暂存区取货挂上。
|
|
16
|
+
* 暂存区由桌面版登录窗口写入(会话可能先于账号入库出现)。返回是否有变化。
|
|
17
|
+
*/
|
|
18
|
+
async function attachPendingWebSession(target) {
|
|
19
|
+
if (!String(target.provider || '').startsWith('qoder') || !target.uid) return false;
|
|
20
|
+
if (target.meta?.qoderWebSession) return false;
|
|
21
|
+
const pending = (await loadSettings()).pendingQoderWebSessions || {};
|
|
22
|
+
const item = pending[target.uid];
|
|
23
|
+
if (!item || item.kind !== target.provider || !item.cookie) return false;
|
|
24
|
+
const { [target.uid]: _used, ...rest } = pending;
|
|
25
|
+
await saveSettings({ pendingQoderWebSessions: rest });
|
|
26
|
+
target.meta = { ...(target.meta || {}), qoderWebSession: { cookie: item.cookie, capturedAt: item.capturedAt || new Date().toISOString() } };
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
|
|
14
30
|
/**
|
|
15
31
|
* 用新记录刷新已存在账号(保留 id / 名字 / 签到记录):
|
|
16
32
|
* token 不同 → 续期(换 token + 凭据元数据);token 相同 → 只补全缺失的 uid / 邮箱 / 有效期 / refreshToken
|
|
@@ -44,15 +60,17 @@ function refreshExisting(existing, incoming) {
|
|
|
44
60
|
return changed;
|
|
45
61
|
}
|
|
46
62
|
|
|
47
|
-
/** 在锁内合并一条记录:新增 / 续期 /
|
|
48
|
-
function mergeInto(accounts, account) {
|
|
63
|
+
/** 在锁内合并一条记录:新增 / 续期 / 重复;顺带挂上暂存的 Qoder 网页会话 */
|
|
64
|
+
async function mergeInto(accounts, account) {
|
|
49
65
|
const dup = findDuplicate(accounts, account.provider, account.token, account.uid);
|
|
50
66
|
if (!dup) {
|
|
67
|
+
await attachPendingWebSession(account);
|
|
51
68
|
accounts.push(account);
|
|
52
69
|
return { account, duplicate: false, updated: false };
|
|
53
70
|
}
|
|
54
|
-
const
|
|
55
|
-
|
|
71
|
+
const refreshed = refreshExisting(dup, account);
|
|
72
|
+
const attached = await attachPendingWebSession(dup);
|
|
73
|
+
return { account: dup, duplicate: true, updated: refreshed || attached };
|
|
56
74
|
}
|
|
57
75
|
|
|
58
76
|
/**
|
|
@@ -94,11 +112,11 @@ export async function addAccount(input, { verify = true, trusted = false } = {})
|
|
|
94
112
|
* @returns {Promise<{added, updated, skipped}>}
|
|
95
113
|
*/
|
|
96
114
|
export function importAccounts(list) {
|
|
97
|
-
return withAccounts((accounts) => {
|
|
115
|
+
return withAccounts(async (accounts) => {
|
|
98
116
|
let added = 0, updated = 0, skipped = 0;
|
|
99
117
|
for (const item of list) {
|
|
100
118
|
try {
|
|
101
|
-
const r = mergeInto(accounts, normalizeAccountInput(item));
|
|
119
|
+
const r = await mergeInto(accounts, normalizeAccountInput(item));
|
|
102
120
|
if (!r.duplicate) added++;
|
|
103
121
|
else if (r.updated) updated++;
|
|
104
122
|
else skipped++;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 妙手(美团 CatPaw)桌面网关 API 客户端 — 用户信息与额度/版本查询。
|
|
3
|
+
*
|
|
4
|
+
* GET https://catx.nocode.cn/api/gateway/auth/current-user 用户信息(userId, name, mobile, avatarUrl)
|
|
5
|
+
* GET https://catx.nocode.cn/api/gateway/credit/balance 可用额度 + 当前套餐(体验版/专业版…)
|
|
6
|
+
*
|
|
7
|
+
* 鉴权:本机妙手客户端解密出的 access_token,放在 X-Auth-Token 头
|
|
8
|
+
* (客户端内部 API 封装同款;网页端 credit.catpaw.meituan.com 的接口只认浏览器 Cookie,不适用)。
|
|
9
|
+
* 响应统一信封 { code: 0, message, data },code !== 0 视为业务错误。
|
|
10
|
+
*
|
|
11
|
+
* 凭据由妙手客户端登录管理(网页 OAuth 轮转),没有对外的 refresh 接口:
|
|
12
|
+
* token 失效(401)时提示在妙手客户端重新登录后重新本机导入。
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { fetchJsonRace } from './zcodeClient.js';
|
|
16
|
+
import { FETCH_TIMEOUT_MS } from './constants.js';
|
|
17
|
+
|
|
18
|
+
export const CATPAW_GATEWAY = 'https://catx.nocode.cn';
|
|
19
|
+
|
|
20
|
+
const gw = (p) => CATPAW_GATEWAY + '/api/gateway' + p;
|
|
21
|
+
const authHeaders = (token) => ({ 'X-Auth-Token': token, Accept: 'application/json' });
|
|
22
|
+
|
|
23
|
+
/** GET 网关接口并拆信封;401 → e.auth = true(凭据失效) */
|
|
24
|
+
async function gwGet(path, token) {
|
|
25
|
+
const res = await fetchJsonRace(gw(path), { headers: authHeaders(token), timeoutMs: FETCH_TIMEOUT_MS });
|
|
26
|
+
if (res.status === 401) {
|
|
27
|
+
const e = new Error('妙手登录凭据已过期,请在妙手客户端重新登录后到「添加账号 → 本机导入」同步');
|
|
28
|
+
e.auth = true;
|
|
29
|
+
throw e;
|
|
30
|
+
}
|
|
31
|
+
if (!res.ok) throw new Error(`妙手接口 ${path} 失败: HTTP ${res.status}`);
|
|
32
|
+
const body = await res.json().catch(() => null);
|
|
33
|
+
if (!body || typeof body !== 'object') throw new Error(`妙手接口 ${path} 返回无法解析`);
|
|
34
|
+
if (body.code !== 0) {
|
|
35
|
+
const msg = String(body.message || body.errorCode || '未知错误');
|
|
36
|
+
if (body.code === 401 || /未登录|登录失效/.test(msg)) {
|
|
37
|
+
const e = new Error('妙手登录凭据已过期,请在妙手客户端重新登录后重新本机导入');
|
|
38
|
+
e.auth = true;
|
|
39
|
+
throw e;
|
|
40
|
+
}
|
|
41
|
+
throw new Error(`妙手接口 ${path} 返回错误:${msg}`);
|
|
42
|
+
}
|
|
43
|
+
return body.data;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** 用户信息 → { userId, name, mobile, avatarUrl } */
|
|
47
|
+
export async function fetchCatpawProfile(token) {
|
|
48
|
+
if (!token) throw new Error('账号缺少 token');
|
|
49
|
+
const d = await gwGet('/auth/current-user', token);
|
|
50
|
+
if (!d || !d.userId) throw new Error('妙手用户信息响应缺少 userId(上游结构可能变化)');
|
|
51
|
+
return { userId: String(d.userId), name: d.name || null, mobile: d.mobile || null, avatarUrl: d.avatarUrl || null };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const iso = (ms) => (Number.isFinite(ms) && ms > 0 ? new Date(ms).toISOString() : null);
|
|
55
|
+
|
|
56
|
+
/** credit/balance → 统一配额结构(可用妙手 Credits + 当前版本;无总量口径,不计入面板「剩余积分」合计) */
|
|
57
|
+
export function normalizeCatpawBalance(d) {
|
|
58
|
+
// 注意 Number(null) === 0:字段缺失 / 为 null 都要判为空额度,而不是 0
|
|
59
|
+
const raw = d?.availableCredits;
|
|
60
|
+
const credits = raw === null || raw === undefined || raw === '' ? NaN : Number(raw);
|
|
61
|
+
const plan = d?.userPlan || {};
|
|
62
|
+
const planName = plan.planName || (plan.pro ? '专业版' : '体验版');
|
|
63
|
+
const remaining = Number.isFinite(credits) ? credits : null;
|
|
64
|
+
return {
|
|
65
|
+
total: remaining ?? 0,
|
|
66
|
+
used: 0,
|
|
67
|
+
remaining: remaining ?? 0,
|
|
68
|
+
unit: 'Credits',
|
|
69
|
+
plan: planName,
|
|
70
|
+
planExpiresAt: iso(Number(plan.expireTime)),
|
|
71
|
+
nextRefreshAt: iso(Number(plan.nextRefreshTime)),
|
|
72
|
+
parts: [{
|
|
73
|
+
name: '可用额度',
|
|
74
|
+
total: remaining ?? 0,
|
|
75
|
+
used: 0,
|
|
76
|
+
remaining: remaining ?? 0,
|
|
77
|
+
unit: 'Credits',
|
|
78
|
+
expiresAt: iso(Number(plan.nextRefreshTime)),
|
|
79
|
+
recurring: true,
|
|
80
|
+
}],
|
|
81
|
+
empty: remaining === null,
|
|
82
|
+
exceeded: plan.status ? plan.status !== 'active' : false,
|
|
83
|
+
pro: plan.pro === true,
|
|
84
|
+
autoRenew: plan.autoRenew === true,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** 查询额度与版本(一次 balance 请求即含 userPlan;401 抛 auth 错误) */
|
|
89
|
+
export async function fetchCatpawQuota(account) {
|
|
90
|
+
if (!account.token) throw new Error('账号缺少 token');
|
|
91
|
+
return normalizeCatpawBalance(await gwGet('/credit/balance', account.token));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 云端用量(按天 token 总量,无输入/输出/缓存拆分、无模型维度):
|
|
96
|
+
* GET /api/gateway/v1/usage/token/daily?startTime=&endTime= → { daily: [{ date: 'YYYY-MM-DD', totalTokens }] }
|
|
97
|
+
* 当天的桶随用随涨,调用方应只取已结束的日期,避免同一天反复快照产生重复行。
|
|
98
|
+
*/
|
|
99
|
+
export async function fetchCatpawTokenDaily(token, { startTime, endTime } = {}) {
|
|
100
|
+
if (!token) throw new Error('账号缺少 token');
|
|
101
|
+
const d = await gwGet(`/v1/usage/token/daily?startTime=${startTime}&endTime=${endTime}`, token);
|
|
102
|
+
return Array.isArray(d && d.daily) ? d.daily : [];
|
|
103
|
+
}
|