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/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'); };