creditdaddy 1.5.0 → 1.5.2
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 +38 -3
- package/build.json +3 -3
- package/package.json +1 -1
- package/src/daemon.js +106 -7
- package/src/externalQuota.js +37 -7
- package/src/i18n/en.json +1755 -1743
- package/src/i18n/ja.json +1755 -1743
- package/src/i18n/ko.json +1755 -1743
- package/src/i18n/zh-TW.json +1755 -1743
- package/src/logger.js +56 -52
- package/src/panel.html +33 -4
- package/src/traeClient.js +294 -266
- package/src/workbuddyClient.js +87 -0
- package/src/zcodeClient.js +13 -3
package/src/logger.js
CHANGED
|
@@ -51,6 +51,10 @@ function archiveLine(line) {
|
|
|
51
51
|
archiveStream = fsSync.createWriteStream(path.join(archiveDir, `daemon-${day}.log`), { flags: 'a' });
|
|
52
52
|
jsonlFile = path.join(archiveDir, `daemon-${day}.jsonl`);
|
|
53
53
|
jsonlStream = fsSync.createWriteStream(jsonlFile, { flags: 'a' });
|
|
54
|
+
// 新流也必须挂 error 兜底:Windows 上写盘可能异步报 EBUSY/EPERM,WriteStream
|
|
55
|
+
// 无 error 监听时抛未捕获异常直接掀翻进程(跨轮换的那两份挂了,新建的漏了)。
|
|
56
|
+
archiveStream.on('error', () => {});
|
|
57
|
+
jsonlStream.on('error', () => {});
|
|
54
58
|
archiveDay = day;
|
|
55
59
|
}
|
|
56
60
|
archiveStream.write('[' + line.at + '] ' + line.level.toUpperCase() + ' [' + line.tag + '] ' + line.msg + '\n');
|
|
@@ -150,53 +154,53 @@ export function summarizeAttempts(attempted) {
|
|
|
150
154
|
return [...buckets.entries()].map(([k, n]) => `${k}×${n}`).join('、');
|
|
151
155
|
}
|
|
152
156
|
|
|
153
|
-
/**
|
|
154
|
-
* 运行期拼出的复合参数(如「[WorkBuddy 国内版] 账号名」)不可能整串进字典(账号名千变万化),
|
|
155
|
-
* 其中已知的产品 / 区域标签按当前语言做子串替换,其余字段原样保留。
|
|
156
|
-
*/
|
|
157
|
-
// 除产品线标签外,失败摘要的桶标签(summarizeAttempts 产出,记进 {detail} 参数,形如「账号拉黑×2」)
|
|
158
|
-
// 同样是运行期复合串,整串无法进词典,按子串替换。
|
|
159
|
-
const ATTEMPT_BUCKETS = ['验证码被拒', '账号拉黑', '额度耗尽', '额度瞬时拒绝', '429 限流', '网络错误', '上游错误'];
|
|
160
|
-
|
|
161
|
-
function localizeEmbedded(text) {
|
|
162
|
-
let out = text;
|
|
163
|
-
for (const zh of [...Object.values(PROVIDER_LABEL), ...ATTEMPT_BUCKETS]) {
|
|
164
|
-
if (!out.includes(zh)) continue;
|
|
165
|
-
const tv = t(zh);
|
|
166
|
-
if (tv !== zh) out = out.split(zh).join(tv);
|
|
167
|
-
}
|
|
168
|
-
return out;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/** 带运行时数字的既有消息(连签 11 天 / 第 1 天 +400 积分):按模式还原成模板再按当前语言渲染 */
|
|
172
|
-
const ARG_PATTERNS = [
|
|
173
|
-
[/^今日已签(连签 (\d+) 天)$/, '今日已签(连签 {n} 天)', (m) => ({ n: m[1] })],
|
|
174
|
-
[/^今日第 (\d+) 天已签到(([\d.]+) \+ ([\d.]+) 积分)$/, '今日第 {d} 天已签到({a} + {b} 积分)', (m) => ({ d: m[1], a: m[2], b: m[3] })],
|
|
175
|
-
[/^签到成功第 (\d+) 天(\+(\d+) 积分)$/, '签到成功第 {d} 天(+{p} 积分)', (m) => ({ d: m[1], p: m[2] })],
|
|
176
|
-
[/^今日已领取(\+(\d+) 积分)$/, '今日已领取(+{p} 积分)', (m) => ({ p: m[1] })],
|
|
177
|
-
[/^本机今日国际版额度已由「(.+)」领取(Qoder 每台设备每天限领一次)$/, '本机今日国际版额度已由「{name}」领取(Qoder 每台设备每天限领一次)', (m) => ({ name: localizeEmbedded(m[1]) })],
|
|
178
|
-
];
|
|
179
|
-
|
|
180
|
-
/** 字符串参数的三级翻译:整串精确 → 模式还原模板 → 子串标签替换 */
|
|
181
|
-
function localizeArgString(v, ctx) {
|
|
182
|
-
const tv = t(v, ctx);
|
|
183
|
-
if (tv !== v) return tv;
|
|
184
|
-
for (const [re, tpl, vars] of ARG_PATTERNS) {
|
|
185
|
-
const m = re.exec(v);
|
|
186
|
-
if (m) return t(tpl, vars(m));
|
|
187
|
-
}
|
|
188
|
-
return localizeEmbedded(v);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function localizeArgs(args) {
|
|
192
|
-
if (!args) return args;
|
|
193
|
-
const loc = {};
|
|
194
|
-
for (const [k, v] of Object.entries(args)) loc[k] = typeof v === 'string' ? localizeArgString(v, args) : v;
|
|
195
|
-
const out = {};
|
|
196
|
-
for (const [k, v] of Object.entries(args)) out[k] = typeof v === 'string' ? localizeArgString(v, loc) : v;
|
|
197
|
-
return out;
|
|
198
|
-
}
|
|
199
|
-
|
|
157
|
+
/**
|
|
158
|
+
* 运行期拼出的复合参数(如「[WorkBuddy 国内版] 账号名」)不可能整串进字典(账号名千变万化),
|
|
159
|
+
* 其中已知的产品 / 区域标签按当前语言做子串替换,其余字段原样保留。
|
|
160
|
+
*/
|
|
161
|
+
// 除产品线标签外,失败摘要的桶标签(summarizeAttempts 产出,记进 {detail} 参数,形如「账号拉黑×2」)
|
|
162
|
+
// 同样是运行期复合串,整串无法进词典,按子串替换。
|
|
163
|
+
const ATTEMPT_BUCKETS = ['验证码被拒', '账号拉黑', '额度耗尽', '额度瞬时拒绝', '429 限流', '网络错误', '上游错误'];
|
|
164
|
+
|
|
165
|
+
function localizeEmbedded(text) {
|
|
166
|
+
let out = text;
|
|
167
|
+
for (const zh of [...Object.values(PROVIDER_LABEL), ...ATTEMPT_BUCKETS]) {
|
|
168
|
+
if (!out.includes(zh)) continue;
|
|
169
|
+
const tv = t(zh);
|
|
170
|
+
if (tv !== zh) out = out.split(zh).join(tv);
|
|
171
|
+
}
|
|
172
|
+
return out;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** 带运行时数字的既有消息(连签 11 天 / 第 1 天 +400 积分):按模式还原成模板再按当前语言渲染 */
|
|
176
|
+
const ARG_PATTERNS = [
|
|
177
|
+
[/^今日已签(连签 (\d+) 天)$/, '今日已签(连签 {n} 天)', (m) => ({ n: m[1] })],
|
|
178
|
+
[/^今日第 (\d+) 天已签到(([\d.]+) \+ ([\d.]+) 积分)$/, '今日第 {d} 天已签到({a} + {b} 积分)', (m) => ({ d: m[1], a: m[2], b: m[3] })],
|
|
179
|
+
[/^签到成功第 (\d+) 天(\+(\d+) 积分)$/, '签到成功第 {d} 天(+{p} 积分)', (m) => ({ d: m[1], p: m[2] })],
|
|
180
|
+
[/^今日已领取(\+(\d+) 积分)$/, '今日已领取(+{p} 积分)', (m) => ({ p: m[1] })],
|
|
181
|
+
[/^本机今日国际版额度已由「(.+)」领取(Qoder 每台设备每天限领一次)$/, '本机今日国际版额度已由「{name}」领取(Qoder 每台设备每天限领一次)', (m) => ({ name: localizeEmbedded(m[1]) })],
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
/** 字符串参数的三级翻译:整串精确 → 模式还原模板 → 子串标签替换 */
|
|
185
|
+
function localizeArgString(v, ctx) {
|
|
186
|
+
const tv = t(v, ctx);
|
|
187
|
+
if (tv !== v) return tv;
|
|
188
|
+
for (const [re, tpl, vars] of ARG_PATTERNS) {
|
|
189
|
+
const m = re.exec(v);
|
|
190
|
+
if (m) return t(tpl, vars(m));
|
|
191
|
+
}
|
|
192
|
+
return localizeEmbedded(v);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function localizeArgs(args) {
|
|
196
|
+
if (!args) return args;
|
|
197
|
+
const loc = {};
|
|
198
|
+
for (const [k, v] of Object.entries(args)) loc[k] = typeof v === 'string' ? localizeArgString(v, args) : v;
|
|
199
|
+
const out = {};
|
|
200
|
+
for (const [k, v] of Object.entries(args)) out[k] = typeof v === 'string' ? localizeArgString(v, loc) : v;
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
|
|
200
204
|
export function getLogs(limit = 100, tag = null) {
|
|
201
205
|
// tag=null 是面板「运行日志」:网关调用日志(*-GW)刷屏快、口径不同,
|
|
202
206
|
// 只在自己的标签页里展示,不混进运行日志;显式传 tag 的查询不受影响。
|
|
@@ -204,11 +208,11 @@ export function getLogs(limit = 100, tag = null) {
|
|
|
204
208
|
return lines.slice(-limit).map((l) => (l.key ? { ...l, msg: t(l.key, localizeArgs(l.args)) } : l));
|
|
205
209
|
}
|
|
206
210
|
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* 读取结构化归档(.jsonl;缺省读当日、最多 limit 条)并按当前语言重渲染。
|
|
210
|
-
* 旧版本没有 .jsonl 时返回 [],调用方静默回退到环形缓冲。
|
|
211
|
-
* 同时兼容已 gzip 的当日归档(.jsonl.gz)。
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* 读取结构化归档(.jsonl;缺省读当日、最多 limit 条)并按当前语言重渲染。
|
|
214
|
+
* 旧版本没有 .jsonl 时返回 [],调用方静默回退到环形缓冲。
|
|
215
|
+
* 同时兼容已 gzip 的当日归档(.jsonl.gz)。
|
|
212
216
|
*/
|
|
213
217
|
export function readArchiveJsonl({ day = null, limit = 2000 } = {}) {
|
|
214
218
|
if (!archiveDir) return [];
|
package/src/panel.html
CHANGED
|
@@ -615,6 +615,9 @@
|
|
|
615
615
|
<button class="btn" id="mt-gw"></button>
|
|
616
616
|
<button class="btn" id="mt-open" style="display:none"></button>
|
|
617
617
|
</span>
|
|
618
|
+
<span id="w-actions" style="display:none">
|
|
619
|
+
<button class="btn" id="wt-act" title="领取临时活动(额外补偿 / 首月赠礼),用已存凭据批量领,免逐个网页点击"></button>
|
|
620
|
+
</span>
|
|
618
621
|
<button class="btn" id="btn-add"></button>
|
|
619
622
|
<button class="btn" id="btn-import"></button>
|
|
620
623
|
<button class="btn" id="btn-export"></button>
|
|
@@ -1717,6 +1720,7 @@ function render() {
|
|
|
1717
1720
|
$('z-actions').style.display = 'none'; // 兜底:切到仪表盘 / 10Router / 免费额度池 时 renderProd 不跑,先藏起来
|
|
1718
1721
|
$('t-actions').style.display = 'none';
|
|
1719
1722
|
$('m-actions').style.display = 'none';
|
|
1723
|
+
$('w-actions').style.display = 'none';
|
|
1720
1724
|
for (const p of PRODUCTS) $('cnt-' + p.id).textContent = ofProduct(p.id).length;
|
|
1721
1725
|
$('cnt-tenrouter').textContent = tr.quotas ? tr.quotas.connections.length : tr.config && tr.config.configured ? '…' : '–';
|
|
1722
1726
|
const freeAccounts = (state.cookieSites || []).reduce((n, s) => n + (s.accounts ? s.accounts.length : 0), 0);
|
|
@@ -1816,6 +1820,11 @@ function renderProd() {
|
|
|
1816
1820
|
$('mt-open').title = mc.running ? T('MiniMax Code 客户端运行中,点我唤到前台') : T('MiniMax Code 客户端未运行,点我启动');
|
|
1817
1821
|
}
|
|
1818
1822
|
}
|
|
1823
|
+
// WorkBuddy 临时活动(额外补偿 / 首月赠礼)批量领取入口——用已存凭据,免逐个网页点击
|
|
1824
|
+
$('w-actions').style.display = prod === 'workbuddy' ? 'contents' : 'none';
|
|
1825
|
+
if (prod === 'workbuddy') {
|
|
1826
|
+
$('wt-act').innerHTML = I.gift + T('领取活动');
|
|
1827
|
+
}
|
|
1819
1828
|
}
|
|
1820
1829
|
|
|
1821
1830
|
function kpi(label, value, sub, labelTitle) {
|
|
@@ -3057,7 +3066,8 @@ async function refreshAccountList() {
|
|
|
3057
3066
|
}
|
|
3058
3067
|
let quotaRun = 0;
|
|
3059
3068
|
let quotaLoadedAt = 0;
|
|
3060
|
-
async function loadQuotas(ids) {
|
|
3069
|
+
async function loadQuotas(ids, opts) {
|
|
3070
|
+
const live = !!(opts && opts.live);
|
|
3061
3071
|
const run = ++quotaRun;
|
|
3062
3072
|
quotaLoadedAt = Date.now();
|
|
3063
3073
|
const queue = (ids || state.accounts.map((a) => a.id)).slice();
|
|
@@ -3065,7 +3075,7 @@ async function loadQuotas(ids) {
|
|
|
3065
3075
|
const worker = async () => {
|
|
3066
3076
|
while (queue.length && run === quotaRun) {
|
|
3067
3077
|
const id = queue.shift();
|
|
3068
|
-
try { state.quotas[id] = (await api('/api/accounts/' + encodeURIComponent(id) + '/quota')).quota; }
|
|
3078
|
+
try { state.quotas[id] = (await api('/api/accounts/' + encodeURIComponent(id) + '/quota' + (live ? '?live=1' : ''))).quota; }
|
|
3069
3079
|
catch (e) { state.quotas[id] = { error: e.message }; }
|
|
3070
3080
|
render();
|
|
3071
3081
|
}
|
|
@@ -3166,6 +3176,22 @@ async function checkinAll(btn, product) {
|
|
|
3166
3176
|
btn.disabled = false; btn.innerHTML = label;
|
|
3167
3177
|
await loadStatus(); await loadAccounts(); loadLogs();
|
|
3168
3178
|
}
|
|
3179
|
+
// WorkBuddy 临时活动批量领取:用已存 Bearer 凭据领「额外补偿 / 首月赠礼」,免逐个网页点击
|
|
3180
|
+
async function claimWbActivities(btn) {
|
|
3181
|
+
const label = btn.innerHTML;
|
|
3182
|
+
btn.disabled = true; btn.innerHTML = I.refresh + T('领取中…');
|
|
3183
|
+
try {
|
|
3184
|
+
const d = await api('/api/workbuddy/claim-activities', { method: 'POST', json: {} });
|
|
3185
|
+
const s = d.summary || {};
|
|
3186
|
+
const claimed = (d.results || []).flatMap((r) => r.claimed || []).filter((x) => x.status === 'checked-in');
|
|
3187
|
+
const amount = claimed.reduce((sum, x) => sum + (Number(x.claimedAmount) || 0), 0);
|
|
3188
|
+
if (s.total === 0) toast(T('没有可领取的临时活动(都领过或未开启)'));
|
|
3189
|
+
else if (s.claimed > 0) toast(T('领取成功 {n} 项,+{amt} 积分', { n: s.claimed, amt: amount }) + (s.already ? T('({n} 项已领)', { n: s.already }) : '') + (s.failed ? T('({n} 项失败)', { n: s.failed }) : ''));
|
|
3190
|
+
else toast(T('已领 {n} 项,无可领活动', { n: s.already }) + (s.failed ? T('({n} 项失败)', { n: s.failed }) : ''), !s.failed);
|
|
3191
|
+
} catch (e) { toast(e.message, false); }
|
|
3192
|
+
btn.disabled = false; btn.innerHTML = label;
|
|
3193
|
+
await loadStatus(); await loadAccounts(); loadLogs();
|
|
3194
|
+
}
|
|
3169
3195
|
async function onCardClick(e) {
|
|
3170
3196
|
const btn = e.target.closest('[data-act]');
|
|
3171
3197
|
if (!btn) return;
|
|
@@ -3182,7 +3208,7 @@ async function onCardClick(e) {
|
|
|
3182
3208
|
if ([...sel.options].some((o) => o.value === a.provider && !o.hidden)) sel.value = a.provider;
|
|
3183
3209
|
return deviceLogin();
|
|
3184
3210
|
}
|
|
3185
|
-
if (act === 'quota') { loadQuotas([id]); return; }
|
|
3211
|
+
if (act === 'quota') { loadQuotas([id], { live: true }); return; }
|
|
3186
3212
|
if (act === 'claim') return openClaim(id);
|
|
3187
3213
|
if (act === 'switch') {
|
|
3188
3214
|
const a = state.accounts.find((x) => x.id === id);
|
|
@@ -3620,6 +3646,9 @@ function openFullChangelog() {
|
|
|
3620
3646
|
function openChangelogModal() {
|
|
3621
3647
|
const openReleases = openFullChangelog;
|
|
3622
3648
|
if (!(window.creditdaddy && typeof window.creditdaddy.changelogBrief === 'function')) { openReleases(); return; }
|
|
3649
|
+
// 先开窗给加载态:在线拉 GitHub API 最长 10s,无反馈就是「点了没反应」(用户反馈截图)
|
|
3650
|
+
$('changelog-body').innerHTML = T('<div class="hint">加载中…</div>');
|
|
3651
|
+
openModal('m-changelog');
|
|
3623
3652
|
window.creditdaddy.changelogBrief().then((r) => {
|
|
3624
3653
|
const box = $('changelog-body');
|
|
3625
3654
|
const strip = (s) => esc(String(s || '').replace(/\*\*/g, ''));
|
|
@@ -3631,7 +3660,6 @@ function openChangelogModal() {
|
|
|
3631
3660
|
+ (s.topics && s.topics.length ? '<div class="hint" style="margin-top:4px">' + s.topics.map(strip).join(' · ') + '</div>' : '')
|
|
3632
3661
|
+ '</div>').join('')
|
|
3633
3662
|
: T('<div class="hint">暂无本地更新日志,请到 GitHub Releases 查看。</div>');
|
|
3634
|
-
openModal('m-changelog');
|
|
3635
3663
|
}).catch(openReleases);
|
|
3636
3664
|
}
|
|
3637
3665
|
$('btn-changelog-full').onclick = openFullChangelog;
|
|
@@ -4391,6 +4419,7 @@ for (const id of ['btn-import', 'btn-import-dash']) { $(id).innerHTML = I.upload
|
|
|
4391
4419
|
for (const id of ['btn-export', 'btn-export-dash']) $(id).innerHTML = I.download + T('导出');
|
|
4392
4420
|
$('btn-checkin-all').onclick = () => checkinAll($('btn-checkin-all'));
|
|
4393
4421
|
$('btn-checkin').onclick = () => checkinAll($('btn-checkin'), state.view);
|
|
4422
|
+
$('wt-act').onclick = () => claimWbActivities($('wt-act'));
|
|
4394
4423
|
$('btn-add-dash').onclick = () => openAdd('');
|
|
4395
4424
|
$('btn-add').onclick = () => openAdd(state.view);
|
|
4396
4425
|
$('btn-export-dash').onclick = () => { $('ex-provider').value = ''; refreshExportNote(); openModal('m-export'); };
|