creditdaddy 1.5.1 → 1.6.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/build.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
- "version": "1.5.1",
3
- "commit": "71c3321",
2
+ "version": "1.6.0",
3
+ "commit": "975f62f",
4
4
  "branch": "main",
5
- "builtAt": "2026-10-18T06:54:01.084Z",
5
+ "builtAt": "2026-10-21T14:12:42.335Z",
6
6
  "channel": "local",
7
7
  "dirty": false
8
8
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "creditdaddy",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "CreditDaddy — Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Trae / MiniMax / ModelScope 多账号本地管理与每日积分自动签到助手,及 10Router 供应商 Cookie 会话捕获与外部配额(Token Harbor / 小米 MiMo / River / ModelScope 魔搭 / LongCat)",
5
5
  "homepage": "https://github.com/techysy/CreditDaddy",
6
6
  "repository": {
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 45 45" shape-rendering="crispEdges"><path stroke="#d4b83a" d="M2 2.5h7m1 0h2m2 0h2m1 0h2m2 0h2m1 0h6m2 0h2m2 0h7M2 3.5h1m5 0h1m1 0h1m2 0h1m1 0h2m1 0h3m1 0h1m3 0h4m1 0h1m2 0h1m1 0h1m5 0h1M2 4.5h1m1 0h3m1 0h1m2 0h1m3 0h4m1 0h3m4 0h1m2 0h2m1 0h1m2 0h1m1 0h3m1 0h1M2 5.5h1m1 0h3m1 0h1m1 0h1m1 0h2m1 0h1m4 0h1m1 0h3m2 0h5m1 0h1m2 0h1m1 0h3m1 0h1M2 6.5h1m1 0h3m1 0h1m1 0h2m1 0h2m1 0h1m5 0h5m1 0h2m6 0h1m1 0h3m1 0h1M2 7.5h1m5 0h1m1 0h1m5 0h1m3 0h1m1 0h1m1 0h1m1 0h1m1 0h3m2 0h2m1 0h1m5 0h1M2 8.5h7m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h7M11 9.5h2m2 0h1m1 0h1m1 0h1m4 0h2m2 0h7M5 10.5h1m2 0h1m4 0h3m1 0h2m1 0h2m1 0h2m1 0h3m1 0h1m3 0h1m2 0h3m1 0h2M5 11.5h1m1 0h1m1 0h2m1 0h4m2 0h2m4 0h2m2 0h1m1 0h1m1 0h1m1 0h3m2 0h3M2 12.5h1m1 0h3m1 0h2m1 0h1m3 0h1m2 0h1m1 0h1m1 0h1m1 0h1m2 0h2m2 0h3m3 0h4M4 13.5h4m1 0h2m1 0h2m1 0h1m2 0h1m1 0h3m2 0h2m2 0h2m1 0h1m4 0h1m2 0h1M5 14.5h1m2 0h3m5 0h1m2 0h1m1 0h3m2 0h2m1 0h1m1 0h1m1 0h2m1 0h1m2 0h1m2 0h1M2 15.5h2m5 0h1m2 0h2m1 0h2m4 0h1m1 0h1m1 0h1m3 0h2m1 0h1m1 0h1m1 0h1m4 0h1M3 16.5h2m1 0h8m2 0h1m1 0h1m1 0h3m1 0h2m5 0h3m2 0h1m2 0h1m1 0h2M3 17.5h2m1 0h2m2 0h3m2 0h1m3 0h2m1 0h2m1 0h3m1 0h1m1 0h2m1 0h1m1 0h2m3 0h1M4 18.5h1m2 0h4m1 0h1m1 0h2m2 0h1m1 0h2m4 0h2m2 0h2m5 0h1M2 19.5h1m1 0h1m1 0h1m2 0h2m1 0h1m2 0h1m1 0h5m2 0h5m1 0h2m4 0h2m1 0h1m1 0h2M3 20.5h2m1 0h1m1 0h1m1 0h4m1 0h3m2 0h4m1 0h1m4 0h2m2 0h2m2 0h5M3 21.5h3m1 0h1m1 0h2m2 0h1m1 0h1m1 0h3m1 0h2m1 0h4m1 0h1m4 0h1m2 0h1m1 0h1m1 0h2M2 22.5h3m1 0h4m1 0h2m1 0h1m1 0h4m1 0h1m2 0h2m1 0h2m2 0h1m1 0h1m1 0h4m1 0h1m1 0h1M4 23.5h2m3 0h1m1 0h1m4 0h2m4 0h2m7 0h1m2 0h3m1 0h1m1 0h3M2 24.5h3m1 0h1m1 0h3m1 0h1m1 0h1m1 0h3m1 0h3m2 0h3m2 0h4m1 0h1m3 0h1m1 0h1M2 25.5h2m3 0h1m4 0h1m3 0h2m3 0h1m1 0h1m2 0h1m2 0h1m6 0h3M2 26.5h1m1 0h1m2 0h2m5 0h2m1 0h1m1 0h4m1 0h2m1 0h1m2 0h8m2 0h2M2 27.5h4m1 0h1m1 0h1m1 0h1m2 0h1m1 0h3m2 0h4m2 0h2m4 0h1m5 0h1M3 28.5h3m2 0h3m1 0h2m2 0h2m3 0h1m4 0h2m3 0h2m1 0h2m1 0h2m3 0h1M4 29.5h2m4 0h2m1 0h2m2 0h4m1 0h3m1 0h1m1 0h2m1 0h1m2 0h5m2 0h1M3 30.5h1m1 0h1m2 0h2m2 0h1m1 0h1m7 0h2m3 0h1m3 0h2m2 0h2m4 0h1M3 31.5h2m1 0h1m5 0h3m1 0h1m1 0h2m7 0h1m1 0h7m1 0h1m1 0h1m2 0h1M2 32.5h1m1 0h1m2 0h4m3 0h1m4 0h3m5 0h1m1 0h4m6 0h2m1 0h1M4 33.5h1m4 0h2m1 0h2m1 0h3m2 0h1m2 0h1m1 0h3m3 0h2m2 0h1m2 0h1m3 0h1M2 34.5h1m2 0h5m2 0h1m3 0h2m3 0h1m1 0h1m4 0h3m3 0h5m1 0h1m1 0h1M10 35.5h2m4 0h1m3 0h1m1 0h1m3 0h1m1 0h1m1 0h5m3 0h1m1 0h1m1 0h1M2 36.5h7m3 0h2m1 0h2m3 0h3m3 0h1m1 0h1m3 0h1m1 0h1m1 0h1m1 0h4M2 37.5h1m5 0h1m3 0h4m6 0h3m2 0h4m1 0h1m1 0h1m3 0h1m1 0h1M2 38.5h1m1 0h3m1 0h1m2 0h1m4 0h3m1 0h2m1 0h1m2 0h1m5 0h1m1 0h5M2 39.5h1m1 0h3m1 0h1m1 0h2m2 0h1m4 0h5m2 0h2m2 0h1m1 0h1m2 0h2m1 0h1m2 0h1M2 40.5h1m1 0h3m1 0h1m2 0h2m4 0h2m3 0h1m3 0h1m1 0h1m9 0h2m2 0h1M2 41.5h1m5 0h1m2 0h2m1 0h1m3 0h7m1 0h2m1 0h1m1 0h1m1 0h2m1 0h2m1 0h1m1 0h1M2 42.5h7m2 0h1m3 0h4m2 0h2m1 0h2m2 0h1m2 0h1m1 0h3m1 0h2m1 0h2"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 37 37" shape-rendering="crispEdges"><path stroke="#d4b83a" d="M2 2.5h7m2 0h1m1 0h1m2 0h6m2 0h1m1 0h1m1 0h7M2 3.5h1m5 0h1m1 0h1m1 0h2m1 0h3m2 0h1m2 0h2m3 0h1m5 0h1M2 4.5h1m1 0h3m1 0h1m1 0h2m1 0h1m3 0h1m1 0h1m1 0h4m1 0h1m1 0h1m1 0h3m1 0h1M2 5.5h1m1 0h3m1 0h1m1 0h1m1 0h1m1 0h5m3 0h4m2 0h1m1 0h3m1 0h1M2 6.5h1m1 0h3m1 0h1m1 0h1m1 0h2m1 0h1m2 0h1m3 0h1m1 0h2m2 0h1m1 0h3m1 0h1M2 7.5h1m5 0h1m1 0h1m2 0h1m1 0h1m2 0h2m1 0h5m2 0h1m5 0h1M2 8.5h7m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h7M11 9.5h2m2 0h3m1 0h1m1 0h1m2 0h3M4 10.5h1m2 0h5m1 0h1m13 0h1m1 0h5M2 11.5h1m2 0h1m1 0h1m3 0h2m2 0h4m2 0h1m2 0h1m1 0h4m1 0h2m1 0h1M3 12.5h1m4 0h5m7 0h2m1 0h3m1 0h1m1 0h2m1 0h1m1 0h1M2 13.5h1m1 0h2m3 0h2m1 0h1m6 0h1m1 0h2m1 0h1m1 0h2m1 0h1m3 0h1M4 14.5h1m2 0h4m3 0h1m1 0h1m1 0h3m2 0h4m1 0h2m1 0h1m1 0h2M2 15.5h4m1 0h1m2 0h1m4 0h2m1 0h1m3 0h4m2 0h2m2 0h1m1 0h1M7 16.5h3m2 0h3m4 0h2m4 0h2m3 0h1m1 0h1m1 0h1M3 17.5h2m2 0h1m2 0h1m1 0h5m1 0h3m3 0h5m2 0h1m1 0h2M2 18.5h1m2 0h2m1 0h2m2 0h1m1 0h3m1 0h2m2 0h1m1 0h1m3 0h2M2 19.5h6m1 0h1m5 0h8m2 0h1m1 0h2m3 0h1m1 0h1M2 20.5h1m1 0h2m1 0h2m1 0h2m2 0h1m2 0h1m3 0h1m2 0h2m4 0h3m1 0h1M2 21.5h6m1 0h1m2 0h2m1 0h1m4 0h3m1 0h2m2 0h1m2 0h1M3 22.5h1m1 0h1m1 0h7m2 0h4m2 0h1m2 0h2m1 0h2m3 0h1M3 23.5h1m1 0h1m1 0h1m1 0h2m2 0h2m3 0h2m2 0h1m5 0h2m1 0h1m2 0h1M2 24.5h3m1 0h1m1 0h1m1 0h3m1 0h1m2 0h3m2 0h1m1 0h2m1 0h5m2 0h1M6 25.5h2m3 0h2m1 0h1m2 0h2m1 0h1m1 0h1m1 0h3m2 0h1m1 0h1m1 0h2M2 26.5h2m2 0h3m1 0h1m2 0h4m3 0h2m3 0h7M10 27.5h1m3 0h2m1 0h3m1 0h3m1 0h2m3 0h3m1 0h1M2 28.5h7m1 0h2m1 0h1m1 0h1m1 0h1m2 0h1m1 0h1m3 0h1m1 0h1m1 0h1m3 0h1M2 29.5h1m5 0h1m1 0h2m1 0h1m1 0h1m6 0h2m1 0h2m3 0h2m1 0h2M2 30.5h1m1 0h3m1 0h1m3 0h2m1 0h1m2 0h2m2 0h2m1 0h7M2 31.5h1m1 0h3m1 0h1m2 0h2m2 0h2m6 0h1m1 0h1m1 0h1m1 0h2m1 0h2M2 32.5h1m1 0h3m1 0h1m1 0h1m4 0h1m2 0h1m1 0h6m4 0h1m1 0h3M2 33.5h1m5 0h1m6 0h3m1 0h1m3 0h2m1 0h1m4 0h1M2 34.5h7m5 0h2m1 0h2m2 0h1m2 0h1m1 0h1m1 0h1m1 0h2m2 0h1"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 45 45" shape-rendering="crispEdges"><path stroke="#d4b83a" d="M2 2.5h7m7 0h1m1 0h2m1 0h3m1 0h2m3 0h3m3 0h7M2 3.5h1m5 0h1m3 0h4m4 0h1m1 0h2m1 0h1m5 0h3m2 0h1m5 0h1M2 4.5h1m1 0h3m1 0h1m1 0h1m1 0h1m1 0h2m1 0h1m3 0h4m1 0h1m2 0h1m4 0h1m1 0h1m1 0h3m1 0h1M2 5.5h1m1 0h3m1 0h1m1 0h1m1 0h1m5 0h2m1 0h6m3 0h1m1 0h3m1 0h1m1 0h3m1 0h1M2 6.5h1m1 0h3m1 0h1m2 0h1m1 0h2m1 0h1m1 0h1m2 0h3m1 0h1m2 0h1m1 0h3m3 0h1m1 0h3m1 0h1M2 7.5h1m5 0h1m2 0h2m3 0h5m5 0h2m6 0h1m1 0h1m5 0h1M2 8.5h7m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h7M17 9.5h2m3 0h1m2 0h1m3 0h1m2 0h1M5 10.5h2m1 0h2m1 0h2m2 0h1m1 0h2m1 0h4m1 0h1m3 0h1m1 0h3m5 0h2M6 11.5h2m3 0h1m4 0h1m3 0h1m2 0h1m4 0h4m1 0h1m1 0h1m1 0h5M2 12.5h4m1 0h2m1 0h1m3 0h1m3 0h2m1 0h2m2 0h1m3 0h4m1 0h3m1 0h2m1 0h1M2 13.5h3m2 0h1m2 0h1m1 0h1m1 0h1m1 0h1m6 0h1m1 0h1m5 0h3m3 0h3M2 14.5h2m4 0h2m3 0h2m1 0h1m1 0h2m4 0h1m1 0h1m1 0h1m4 0h2m1 0h1M4 15.5h2m3 0h3m1 0h5m2 0h1m1 0h2m2 0h3m2 0h1m3 0h2m1 0h2m2 0h1M2 16.5h3m3 0h3m1 0h3m1 0h2m2 0h2m1 0h3m3 0h1m2 0h2m4 0h1m3 0h1M2 17.5h1m1 0h3m4 0h1m3 0h2m1 0h1m2 0h2m1 0h4m2 0h1m1 0h1m2 0h1m2 0h3M2 18.5h3m2 0h2m1 0h1m4 0h3m1 0h1m1 0h1m4 0h2m1 0h1m4 0h2m1 0h1m1 0h4M2 19.5h1m2 0h1m1 0h1m2 0h1m1 0h3m1 0h3m1 0h1m4 0h2m2 0h1m2 0h1m1 0h1m2 0h4M2 20.5h3m1 0h1m1 0h2m1 0h2m1 0h1m4 0h2m2 0h3m1 0h3m3 0h2m1 0h2m1 0h1m2 0h1M3 21.5h3m1 0h1m3 0h2m1 0h2m4 0h3m4 0h1m1 0h7m4 0h2M2 22.5h3m2 0h2m4 0h1m1 0h1m4 0h4m1 0h1m2 0h3m4 0h3m1 0h4M2 23.5h3m6 0h1m1 0h1m2 0h2m1 0h2m2 0h2m1 0h3m3 0h1m2 0h1m1 0h5M4 24.5h3m1 0h1m1 0h1m1 0h1m1 0h2m2 0h1m1 0h1m6 0h3m1 0h2m1 0h5M6 25.5h1m4 0h1m1 0h1m4 0h5m2 0h3m1 0h1m1 0h2m1 0h1m3 0h2m1 0h2M4 26.5h1m2 0h3m2 0h2m1 0h1m2 0h10m1 0h3m4 0h2m4 0h1M3 27.5h2m2 0h1m1 0h1m1 0h1m1 0h1m4 0h2m2 0h1m1 0h1m1 0h3m1 0h1m3 0h1m1 0h4M2 28.5h1m2 0h1m1 0h3m1 0h2m2 0h1m2 0h1m1 0h4m2 0h1m2 0h5m2 0h1m2 0h1m2 0h1M2 29.5h1m1 0h1m4 0h1m1 0h1m1 0h1m2 0h5m1 0h3m2 0h4m2 0h1m4 0h3m1 0h1M8 30.5h1m3 0h2m2 0h1m1 0h1m1 0h2m5 0h1m1 0h1m1 0h1m6 0h2M2 31.5h1m2 0h1m1 0h1m2 0h1m3 0h1m1 0h1m3 0h1m3 0h2m6 0h3m1 0h1m1 0h2m1 0h2M2 32.5h5m1 0h1m1 0h1m1 0h1m2 0h1m3 0h3m2 0h1m3 0h1m1 0h1m1 0h3m2 0h2m1 0h1m1 0h1M2 33.5h4m4 0h1m3 0h2m1 0h1m1 0h1m2 0h2m2 0h1m2 0h2m1 0h1m2 0h4m1 0h2M2 34.5h3m1 0h1m1 0h2m3 0h2m2 0h2m2 0h1m2 0h2m2 0h1m1 0h1m2 0h7M10 35.5h1m2 0h4m4 0h2m1 0h1m3 0h1m3 0h3m3 0h1m1 0h1M2 36.5h7m1 0h1m3 0h1m2 0h1m1 0h3m2 0h1m1 0h2m1 0h1m1 0h4m1 0h1m1 0h3M2 37.5h1m5 0h1m3 0h1m1 0h1m2 0h4m2 0h6m2 0h1m2 0h1m3 0h2m1 0h2M2 38.5h1m1 0h3m1 0h1m1 0h2m5 0h1m2 0h3m1 0h1m1 0h2m3 0h10m1 0h1M2 39.5h1m1 0h3m1 0h1m1 0h3m1 0h3m1 0h2m5 0h1m1 0h3m5 0h1m1 0h1m1 0h4M2 40.5h1m1 0h3m1 0h1m2 0h1m1 0h2m2 0h1m3 0h1m1 0h1m2 0h2m1 0h5m1 0h1m2 0h1m1 0h3M2 41.5h1m5 0h1m2 0h2m1 0h6m1 0h1m2 0h4m1 0h1m2 0h1m1 0h1m1 0h1m1 0h3m1 0h1M2 42.5h7m3 0h1m2 0h1m2 0h2m1 0h1m1 0h1m2 0h5m1 0h4m1 0h2"/></svg>
package/src/checkin.js CHANGED
@@ -224,13 +224,12 @@ async function runTickNow(opts) {
224
224
 
225
225
  async function runTickInner(opts) {
226
226
  const allAccounts = await loadAccounts();
227
- // WB 国际版活跃请求全局开关(面板「设置 → 通用」):关闭时该轮与手动领取都不再打
228
- const wbActivity = (await loadSettings()).workbuddyActivity !== false;
227
+ // workbuddy-intl 无 checkin 动作(providers.js 的 intl 分支已删),providers 过滤天然跳过;
228
+ // 「活跃请求」全局开关随该机制一并删除(设置项不再读写)
229
229
  const accounts = allAccounts.filter(
230
230
  (a) => (!opts.provider || a.provider === opts.provider)
231
231
  && (!opts.product || productOf(a.provider) === opts.product)
232
232
  && (!opts.onlyAccountId || a.id === opts.onlyAccountId)
233
- && (wbActivity || a.provider !== 'workbuddy-intl')
234
233
  && typeof productImpl(a.provider)?.checkin === 'function'
235
234
  );
236
235
  const zAccounts = (opts.includeZcode !== false && autoClaimEnabled()
@@ -333,16 +332,18 @@ async function runTickInner(opts) {
333
332
  const limited = results.filter((r) => r.status === 'limited');
334
333
  const already = results.length - claimed.length - failed.length - none.length - limited.length;
335
334
  const totalCredits = claimed.reduce((sum, r) => sum + (r.claimedAmount || 0), 0);
335
+ const summaryKey = limited.length
336
+ ? '签到汇总:成功 {ok}(+{credits} Credits)、已领 {al}、本机限领 {limited}、无活动 {none}、失败 {fail}'
337
+ : '签到汇总:成功 {ok}(+{credits} Credits)、已领 {al}、无活动 {none}、失败 {fail}';
338
+ const summaryArgs = { ok: claimed.length, credits: totalCredits, al: already, limited: limited.length, none: none.length, fail: failed.length };
339
+ // 摘要走 i18n 模板(key+args,面板按界面语言 T() 渲染,与日志同源同语);
340
+ // summary 保留简中拼接兜底旧面板/CLI 直读(不再被面板展示路径使用)
336
341
  const summary = `签到汇总:成功 ${claimed.length}(+${totalCredits} Credits)、已领 ${already}`
337
342
  + (limited.length ? `、本机限领 ${limited.length}` : '')
338
343
  + `、无活动 ${none.length}、失败 ${failed.length}`;
339
- // 日志走 key+args(按界面语言实时重渲染);summary 原文继续供面板/状态展示
340
- logger.info('CHECKIN', limited.length
341
- ? '签到汇总:成功 {ok}(+{credits} Credits)、已领 {al}、本机限领 {limited}、无活动 {none}、失败 {fail}'
342
- : '签到汇总:成功 {ok}(+{credits} Credits)、已领 {al}、无活动 {none}、失败 {fail}',
343
- { ok: claimed.length, credits: totalCredits, al: already, limited: limited.length, none: none.length, fail: failed.length });
344
- lastTick = { at: new Date().toISOString(), summary };
345
- return { results, summary };
344
+ logger.info('CHECKIN', summaryKey, summaryArgs);
345
+ lastTick = { at: new Date().toISOString(), summary, summaryKey, summaryArgs };
346
+ return { results, summary, summaryKey, summaryArgs };
346
347
  }
347
348
 
348
349
  async function scheduleTick() {
package/src/constants.js CHANGED
@@ -35,7 +35,7 @@ export const WEB_USAGE_PATH = '/api/v2/me/usages/big_model_credits';
35
35
  export const JOB_TOKEN_EXCHANGE_PATH = '/api/v1/jobToken/exchange';
36
36
 
37
37
  // 账号的 provider = 产品 + 区域。新增产品时在此登记,并在 providers.js 挂上对应实现。
38
- export const PROVIDERS = ['qoder', 'qoder-cn', 'workbuddy', 'workbuddy-intl', 'zcode', 'mirasim', 'catpaw', 'trae', 'minimax', 'tokenharbor', 'xiaomi-mimo', 'river', 'modelscope', 'longcat'];
38
+ export const PROVIDERS = ['qoder', 'qoder-cn', 'workbuddy', 'workbuddy-intl', 'zcode', 'mirasim', 'catpaw', 'trae', 'minimax', 'tokenharbor', 'xiaomi-mimo', 'river', 'modelscope', 'longcat', 'doubao'];
39
39
  export const PROVIDER_LABEL = {
40
40
  qoder: 'Qoder 国际版',
41
41
  'qoder-cn': 'Qoder 国内版',
@@ -51,6 +51,7 @@ export const PROVIDER_LABEL = {
51
51
  river: 'River AI',
52
52
  modelscope: 'ModelScope 魔搭',
53
53
  longcat: 'LongCat',
54
+ doubao: '豆包',
54
55
  };
55
56
  /** provider → 产品线(qoder / workbuddy / zcode / mirasim / catpaw / trae / minimax / tokenharbor / xiaomi-mimo / river) */
56
57
  export const productOf = (provider) => {
@@ -66,6 +67,7 @@ export const productOf = (provider) => {
66
67
  if (p === 'river') return 'river';
67
68
  if (p === 'modelscope') return 'modelscope';
68
69
  if (p === 'longcat') return 'longcat';
70
+ if (p === 'doubao') return 'doubao';
69
71
  return 'qoder';
70
72
  };
71
73
  /** provider 是否国内版 */
@@ -65,7 +65,7 @@ export const COOKIE_SITES = [
65
65
  id: 'tokenharbor',
66
66
  name: 'Token Harbor',
67
67
  provider: 'tokenharbor',
68
- label: 'Token Harbor 免费额度',
68
+ label: 'Token Harbor',
69
69
  loginUrl: 'https://tokenharbor.ai/login',
70
70
  homeUrl: 'https://tokenharbor.ai',
71
71
  // 探测标记:由于 Token Harbor 的 dashboard 页面将 Free allowance 移至客户端动态组件中渲染,
@@ -86,14 +86,14 @@ export const COOKIE_SITES = [
86
86
  },
87
87
  // issue #24:捕获时顺手解析网页读数,存 meta.quotaSnapshot 供额度推送消费
88
88
  quota: { fromHtml: parseTokenHarborQuota },
89
- accountName: 'Token Harbor 网页会话',
89
+ accountName: 'Token Harbor',
90
90
  hint: '登录后 dashboard 才有 Free allowance 配额;凭据只存本机(账号库加密),同步到 10Router 时按 creditDaddyWebSession 形状传输。',
91
91
  },
92
92
  {
93
93
  id: 'xiaomi-mimo',
94
94
  name: 'Xiaomi MiMo',
95
95
  provider: 'xiaomi-mimo',
96
- label: '小米 MiMo 控制台余额',
96
+ label: '小米 MiMo',
97
97
  loginUrl: 'https://platform.xiaomimimo.com/console/balance',
98
98
  homeUrl: 'https://platform.xiaomimimo.com',
99
99
  probe: {
@@ -101,21 +101,21 @@ export const COOKIE_SITES = [
101
101
  match: 'userId',
102
102
  accept: 'application/json, text/plain, */*',
103
103
  },
104
- accountName: '小米 MiMo 控制台会话',
104
+ accountName: '小米 MiMo',
105
105
  hint: '登录后自动抓取 UID 与控制台充值余额;通过外部配额(issue #24/26)推送到 10Router 配额追踪器显示余额卡片。',
106
106
  },
107
107
  {
108
108
  id: 'modelscope',
109
109
  name: 'ModelScope 魔搭',
110
110
  provider: 'modelscope',
111
- label: 'ModelScope 魔方余额',
111
+ label: 'ModelScope',
112
112
  loginUrl: 'https://modelscope.cn',
113
113
  homeUrl: 'https://modelscope.cn',
114
114
  // 魔方余额 API 本身就是探测(GET /openapi/v1/magicubes/balance 带会话 cookie);
115
115
  // 每日魔方额度为登录自动发放(前端无 claim 端点),故无签到自动化,只读余额出卡。
116
116
  cookieUrls: ['https://modelscope.cn', 'https://www.modelscope.cn'],
117
117
  verify: 'api',
118
- accountName: 'ModelScope 网页会话',
118
+ accountName: 'ModelScope',
119
119
  identity: { fromHtml: /[\w.+-]+@[\w-]+\.[\w.]+/ },
120
120
  hint: '登录魔搭后自动抓取魔方(Magicube)余额;每日额度自动发放无需签到,token 只存本机,余额数字推 10Router 出卡(同名认领可挂连接卡)。',
121
121
  },
@@ -123,7 +123,7 @@ export const COOKIE_SITES = [
123
123
  id: 'river',
124
124
  name: 'River AI',
125
125
  provider: 'river',
126
- label: 'River AI 免费额度',
126
+ label: 'River AI',
127
127
  loginUrl: 'https://console.river.ai',
128
128
  homeUrl: 'https://console.river.ai',
129
129
  // 凭据主体是 WorkOS 会话——实测(2026-10-14)SPA 的 token 只在内存 Map,localStorage
@@ -134,14 +134,14 @@ export const COOKIE_SITES = [
134
134
  storagePrefixes: ['workos:'],
135
135
  cookieUrls: ['https://console.river.ai', 'https://auth.console.river.ai', 'https://login.console.river.ai'],
136
136
  verify: 'api',
137
- accountName: 'River AI 网页会话',
137
+ accountName: 'River AI',
138
138
  hint: '登录 console.river.ai 后自动抓取 WorkOS 会话与 Billing 余额($25 免费额度);token 只存本机,推送 10Router 只带余额数字,会话过期自动刷新(单次轮换即时落盘)。',
139
139
  },
140
140
  {
141
141
  id: 'longcat',
142
142
  name: 'LongCat',
143
143
  provider: 'longcat',
144
- label: 'LongCat Token 资源包',
144
+ label: 'LongCat',
145
145
  loginUrl: 'https://longcat.chat/pc/login',
146
146
  homeUrl: 'https://longcat.chat',
147
147
  // 资源包汇总 API 即探测:带对 cookie 返 200 {code:0},未登录 401「用户未登录」(2026-10-15 实测)
@@ -151,9 +151,25 @@ export const COOKIE_SITES = [
151
151
  accept: 'application/json, text/plain, */*',
152
152
  },
153
153
  verify: 'api',
154
- accountName: 'LongCat 网页会话',
154
+ accountName: 'LongCat',
155
155
  hint: '登录 longcat.chat 后自动抓取 Token 资源包余量与逐包明细(当前包 + 其他包,按官方消耗顺序排列);推送 10Router 只带 token 数字,会话 cookie 只存本机。',
156
156
  },
157
+ {
158
+ id: 'doubao',
159
+ name: '豆包',
160
+ provider: 'doubao',
161
+ label: '豆包',
162
+ loginUrl: 'https://www.doubao.com',
163
+ homeUrl: 'https://www.doubao.com',
164
+ // 探测 = 任务面板数据接口本身(issue #32,2026-10-19/20 实测):POST detail 带会话 cookie
165
+ // 返 200 {code:0},未登录返 200 {code:710012001,"login invalid"}——用 match 判业务码而非 HTTP 状态。
166
+ // 活动页 UI 有「仅支持电脑版」前端墙,但接口不吃这套(纯 cookie 鉴权,无 a_bogus 签名)。
167
+ // 最终定位(用户拍板):豆包 = 纯「快速打开客户端」入口。网页会话 cookie 下 detail
168
+ // 返回残缺数据(额度恒 0、签到状态不可信),额度可见也做不实——不加会话捕获探测,
169
+ // 只在面板出示快捷按钮。
170
+ entryOnly: true, // 只出「打开豆包」按钮,无会话捕获 / 无探测 / 无账号行
171
+ hint: '在豆包客户端(或网页)登录后捕获会话,CD 只读展示体验额度与每日签到状态(+200/天);签到请在「打开豆包」里手动完成——活动在客户端内,CD 不代签;仅个人账号可参与(飞书企业号被排除),凭据只存本机。'
172
+ }
157
173
  ];
158
174
 
159
175
  export const getSite = (id) => COOKIE_SITES.find((s) => s.id === id) || null;
@@ -187,13 +203,16 @@ function testProbeMatch(site, text) {
187
203
  /**
188
204
  * 探测一份会话 cookie 是否仍然有效。
189
205
  * 探测走 fetchJsonRace(跟随全局代理配置——Token Harbor 对大陆/港澳有封锁,出口代理是必须项)。
206
+ * probe 支持 method / body(JSON API 探测,如豆包的 POST detail 接口——GET 打不到任务数据)。
190
207
  * @returns {Promise<{ok: boolean, reason?: 'invalid'|'no-match'|'http'|'network', status?: number, html?: string}>}
191
208
  */
192
209
  export async function verifySiteCookie(site, cookie, { fetchImpl } = {}) {
193
210
  const url = probeUrl(site);
194
- const doFetch = fetchImpl || ((u, init) => fetchJsonRace(u, { headers: init.headers, timeoutMs: 12000 }));
211
+ const method = site.probe?.method || 'GET';
212
+ const doFetch = fetchImpl || ((u, init) => fetchJsonRace(u, { headers: init.headers, method, body: site.probe?.body || undefined, timeoutMs: 12000, preferProxy: site.probe?.preferProxy === true }));
195
213
  try {
196
214
  const headers = { ...dashboardHeaders(site), Cookie: String(cookie || '') };
215
+ if (site.probe?.body) headers['Content-Type'] = headers['Content-Type'] || 'application/json';
197
216
  const r = await doFetch(url, { headers });
198
217
  if (r.status === 401 || r.status === 403) return { ok: false, reason: 'invalid', status: r.status };
199
218
  if (r.status !== 200) return { ok: false, reason: 'http', status: r.status };
package/src/daemon.js CHANGED
@@ -333,6 +333,25 @@ async function reprobeCookieAccount(site, a) {
333
333
  logger.info('COOKIE-SITE', '{name} 就地重探通过({balance} USD)', { name: site.name, balance: bal.balance });
334
334
  return finish({ id: a.id, name: a.name, ok: true, identity: null, balance: bal.balance, quotaSnapshot: { used: 0, total: bal.balance, isCreditBalance: true, at: now } });
335
335
  }
336
+ if (site.id === 'doubao') {
337
+ // 豆包(issue #32):detail 接口一次拿全(会话有效性 + 签到状态 + 累计点数),
338
+ // 重探 = 刷新额度快照;无 10R 同步(豆包不推外部配额卡,错误文案不再误导)
339
+ const { fetchDoubaoStatus } = await import('./doubaoClient.js');
340
+ const st = await fetchDoubaoStatus(cookie, a.uid || '').catch((e) => ({ auth: false, code: e?.message || 'network' }));
341
+ if (!st.auth) return finish({ id: a.id, name: a.name, ok: false, error: '豆包会话已失效,请重新登录捕获' });
342
+ await withAccounts((list) => {
343
+ const hit = list.find((x) => x.id === a.id);
344
+ if (!hit) return;
345
+ hit.meta = {
346
+ ...(hit.meta || {}),
347
+ cookieSiteSession: { cookie, capturedAt: hit.meta?.cookieSiteSession?.capturedAt || now },
348
+ quotaSnapshot: { used: 0, total: st.totalAmount, isCreditBalance: true, at: now },
349
+ };
350
+ hit.updatedAt = now;
351
+ });
352
+ logger.info('COOKIE-SITE', '{name} 就地重探通过(累计 {amount} 点,签到{signed})', { name: site.name, amount: st.totalAmount, signed: st.checkin?.checkedIn ? '已签' : '未签' });
353
+ return finish({ id: a.id, name: a.name, ok: true, identity: null, balance: st.totalAmount, quotaSnapshot: { used: 0, total: st.totalAmount, isCreditBalance: true, at: now } });
354
+ }
336
355
  if (site.verify === 'api' || site.probe) {
337
356
  // tokenharbor 等 HTML 探针站点:探测 + 身份 + 读数一并刷新
338
357
  const v = await verifySiteCookie(site, cookie);
@@ -659,6 +678,7 @@ async function handleApi(req, res, url) {
659
678
  if (account.meta?.cookieSiteId) {
660
679
  const site = getSite(account.meta.cookieSiteId);
661
680
  if (!site) return json(res, 404, { error: `站点 ${account.meta.cookieSiteId} 未配置` });
681
+ if (site.disabled === true) return json(res, 410, { error: '该站点已停用(issue #35),不再探测上游' });
662
682
  // 频率治理(面板 2 分钟自动轮询曾把五家上游全轰一遍):默认读缓存快照零网络,
663
683
  // 仅 ?live=1(手动「刷新积分」按钮 / 单卡回调)才真探上游并回填快照。缓存保鲜交给
664
684
  // 后台推送循环(default 60 分钟)——它 live 拉到的余额本就该落库,顺手把这条链补上。
@@ -1324,7 +1344,7 @@ async function handleApi(req, res, url) {
1324
1344
  }
1325
1345
  });
1326
1346
  if (ih) logger.info('COOKIE-SITE', '{name} 身份归户({identity},账号轮换键生效)', { name: site.name, identity });
1327
- logger.info('COOKIE-SITE', '{name} cookie 捕获成功(探测通过,同步到 10Router 后卡片点亮)', { name: site.name });
1347
+ logger.info('COOKIE-SITE', site.no10rSync ? '{name} cookie 捕获成功(探测通过,额度卡片已就绪)' : '{name} cookie 捕获成功(探测通过,同步到 10Router 后卡片点亮)', { name: site.name });
1328
1348
  // 捕获成功后只推当前站点的额度卡(单号精准推送,绝不全量刷屏)
1329
1349
  (async () => {
1330
1350
  try {
@@ -1368,6 +1388,7 @@ async function handleApi(req, res, url) {
1368
1388
  const accounts = await loadAccounts();
1369
1389
  const targets = accounts.filter((a) => a.provider === site.provider && a.meta?.cookieSiteId === site.id
1370
1390
  && (!onlyId || a.id === onlyId));
1391
+ if (site.disabled === true) return json(res, 410, { error: '该站点已停用(issue #35),不再探测上游' });
1371
1392
  if (!targets.length) return json(res, 404, { error: '没有可重探的会话(先登录捕获一次)' });
1372
1393
  // 重探只推涉及的那几路 source(其余卡不打上游、不重推):
1373
1394
  // tokenharbor 是单例卡(source 固定);mimo/river/ms 按账号算 source;失败行也进名单
@@ -1458,8 +1479,10 @@ async function handleApi(req, res, url) {
1458
1479
  // 6) Trae 客户端:解密 <userData>\User\globalStorage\storage.json 的 tc 信封当前登录
1459
1480
  try {
1460
1481
  const tr = await traeLiveAccount();
1461
- if (tr) addRecord(tr, { source: 'Trae 当前登录', current: true });
1462
- } catch (e) { errors.push({ file: 'TRAE SOLO CN/User/globalStorage/storage.json', error: e.message }); }
1482
+ // 国际版客户端(信封 host 为 *.trae.ai):CD 的 Trae 线只打 api.trae.cn,导入只会有
1483
+ // 一张空额度卡——照常列出让用户可见,但带 intlWarning 由前端明确提示,不静默。
1484
+ if (tr) addRecord(tr, { source: 'Trae 当前登录', current: true, ...(tr.meta?.isIntl ? { intlWarning: '检测到 Trae 国际版客户端(trae.ai)——CreditDaddy 的 Trae 线仅支持国内版(api.trae.cn),导入后额度与签到不可用' } : {}) });
1485
+ } catch (e) { errors.push({ file: 'TRAE/User/globalStorage/storage.json', error: e.message }); }
1463
1486
  // 7) MiniMax 客户端:读取 ~/.minimax 凭据当前登录
1464
1487
  try {
1465
1488
  const mm = await minimaxLiveAccount();
@@ -1677,7 +1700,7 @@ async function handleApi(req, res, url) {
1677
1700
 
1678
1701
  // Panel 设置:GET 读取(只回状态不回显密码),PUT 设置 / 修改 / 关闭访问密码
1679
1702
  if (p === '/api/settings' && method === 'GET') {
1680
- return json(res, 200, { panelKeyEnabled: panelKeyRequired(), locale: getLocale(), workbuddyActivity: (await loadSettings()).workbuddyActivity !== false });
1703
+ return json(res, 200, { panelKeyEnabled: panelKeyRequired(), locale: getLocale() });
1681
1704
  }
1682
1705
  if (p === '/api/settings' && method === 'PUT') {
1683
1706
  const body = await readBody(req).catch(() => ({}));
@@ -1696,11 +1719,6 @@ async function handleApi(req, res, url) {
1696
1719
  await resyncAutoCheckinScheduler();
1697
1720
  return json(res, 200, { ok: true });
1698
1721
  }
1699
- // WB 国际版活跃请求总开关(面板「设置 → 通用」)
1700
- if (typeof body?.workbuddyActivity === 'boolean') {
1701
- await saveSettings({ workbuddyActivity: body.workbuddyActivity });
1702
- return json(res, 200, { ok: true });
1703
- }
1704
1722
  // 只有携带密码字段才走密码分支:纯语言/其它设置的请求此前会掉进
1705
1723
  // 「请提供新密码」400,面板 api() 抛错导致语言切换点了不刷新(i18n 切换失效根因)
1706
1724
  if (body?.panelKey !== undefined || body?.disable === true) return await handleSettingsPut(res, body);
@@ -1825,6 +1843,16 @@ export async function startDaemon(port = DEFAULT_PORT, host = '127.0.0.1') {
1825
1843
  logger.info('DAEMON', '已移除 {count} 个 MiniMax 国际版账号(该渠道已下线,额度接口对国内网络不可用)', { count: dead.length });
1826
1844
  }
1827
1845
  }
1846
+ // 一次性清理(issue #35):Inception Labs 线已整体删除(Cloudflare 对代理出口 /api/*
1847
+ // 持续 429),存量账号留着只会被签到/额度轮询反复打上游(401/429 报错刷屏)。命中才落盘。
1848
+ {
1849
+ const list = await loadAccounts();
1850
+ const dead = list.filter((a) => a.provider === 'inception');
1851
+ if (dead.length) {
1852
+ await withAccounts((l) => { l.splice(0, l.length, ...l.filter((a) => a.provider !== 'inception')); });
1853
+ logger.info('DAEMON', '已移除 {count} 个 Inception Labs 会话(该渠道已删除,网页会话线不可用)', { count: dead.length });
1854
+ }
1855
+ }
1828
1856
  let panelHtml = '';
1829
1857
  fs.readFile(PANEL_FILE, 'utf8').then((h) => { panelHtml = h; }).catch(() => {});
1830
1858
 
@@ -1901,9 +1929,25 @@ export async function startDaemon(port = DEFAULT_PORT, host = '127.0.0.1') {
1901
1929
  }
1902
1930
  }
1903
1931
  if (!panelHtml) { try { panelHtml = await fs.readFile(PANEL_FILE, 'utf8'); } catch {} }
1904
- res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
1932
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache' });
1905
1933
  return res.end(panelHtml || '<h1>CreditDaddy</h1><p>panel.html 缺失</p>');
1906
1934
  }
1935
+ // 静态资产(仅捐赠二维码三张 SVG;白名单精确匹配,面板密码门与面板一致)
1936
+ if (url.pathname.startsWith('/assets/donate-')) {
1937
+ const file = {
1938
+ '/assets/donate-alipay.svg': 'donate-alipay.svg', '/assets/donate-ifdian.svg': 'donate-ifdian.svg', '/assets/donate-wechat.svg': 'donate-wechat.svg',
1939
+ }[url.pathname];
1940
+ if (!file) { res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' }); return res.end('Not Found'); }
1941
+ if (!panelKeyRequired()) {
1942
+ const remote = (req.socket && req.socket.remoteAddress) || '';
1943
+ if (!isLoopbackSocketAddr(remote)) { res.writeHead(403, { 'Content-Type': 'text/plain; charset=utf-8' }); return res.end('Forbidden'); }
1944
+ }
1945
+ try {
1946
+ const svg = await fs.readFile(path.join(__dirname, 'assets', file));
1947
+ res.writeHead(200, { 'Content-Type': 'image/svg+xml', 'Cache-Control': 'no-cache' });
1948
+ return res.end(svg);
1949
+ } catch { res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' }); return res.end('Not Found'); }
1950
+ }
1907
1951
  res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
1908
1952
  res.end('Not Found');
1909
1953
  } catch (err) {
@@ -0,0 +1,168 @@
1
+ /**
2
+ * 豆包(doubao.com)网页会话 — 每日签到领体验额度(issue #32)。
3
+ *
4
+ * POST https://www.doubao.com/alice/ug/campaign/v3/detail/ 任务面板数据(含签到状态)
5
+ * POST https://www.doubao.com/alice/ug/campaign/landing/checkin 领取(+200 额度/天)
6
+ *
7
+ * 鉴权:纯 doubao.com 会话 cookie(网页捕获线),无 a_bogus / msToken 请求签名;
8
+ * device_id 空可过;服务端自带幂等(重复领取返回 idempotent:true 不重复发放)。
9
+ * 接口形状 2026-10-19/20 带调试端口实测(CDP 挂监听抓已签 + 未签两个场景)。
10
+ *
11
+ * 参与门槛:仅个人账号(飞书企业号被排除,活动页会提示「请切换至个人账号」)。
12
+ * 活动页 UI 有「仅支持豆包电脑版」前端墙,但接口不吃这套——cookie 直打即通。
13
+ */
14
+
15
+ import crypto from 'node:crypto';
16
+ import { fetchJsonRace } from './zcodeClient.js';
17
+ import { FETCH_TIMEOUT_MS } from './constants.js';
18
+
19
+ const DETAIL_URL = 'https://www.doubao.com/alice/ug/campaign/v3/detail/';
20
+ const CHECKIN_URL = 'https://www.doubao.com/alice/ug/campaign/landing/checkin';
21
+ /** 任务面板所属活动组(pc 端激励任务 v3;签到是其中的 daily_signin 任务) */
22
+ const CAMPAIGN_GROUP = 'pc_incentive_task_mvp_v3';
23
+
24
+ /**
25
+ * 客户端指纹伪装(issue #32 用户实测反馈:纯网页 cookie 裸 UA 打活动接口吃「请求非法」,
26
+ * 活动在服务端按客户端环境放行)。指纹 = 豆包桌面版 webview 的完整请求形状(CDP 抓包):
27
+ * - UA 与 webview 同款(Chrome/147 + DoubaoWebView 标识)
28
+ * - URL 必带 version_code / device_platform=web / web_platform=desktop / aid 等平台参数
29
+ * - web_id / tea_uuid 按账号 uid 确定性派生(每号一台稳定「设备」,同 attempt 语义)
30
+ * - device_id 空(实测可过)
31
+ */
32
+ const CLIENT_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) DoubaoDesktop/2.32.6 Chrome/147.0.7727.149 Safari/537.36';
33
+ const COMMON_PARAMS = {
34
+ version_code: '20800',
35
+ language: 'zh',
36
+ device_platform: 'web',
37
+ doubao_device_platform: 'web',
38
+ aid: '497858',
39
+ real_aid: '497858',
40
+ pkg_type: 'release_version',
41
+ pc_version: '2.32.6',
42
+ doubao_pc_version: '5.72.66',
43
+ samantha_web: '1',
44
+ web_platform: 'desktop',
45
+ 'use-olympus-account': '1',
46
+ web_tab_id: '0',
47
+ };
48
+
49
+ /** 按 uid 派生稳定设备标识(每号恒定,账号间互异;纯数字 web_id 风格) */
50
+ function deviceIdsFor(uid) {
51
+ const seed = crypto.createHash('sha256').update(`doubao-dev:${uid || 'anonymous'}`).digest();
52
+ const webId = '76' + [...seed.subarray(0, 8)].map((b) => b % 10).join(''); // 18 位数字
53
+ const teaUuid = [...seed.subarray(8, 16)].map((b) => (b % 10)).join('').padEnd(16, '7');
54
+ return { web_id: webId, tea_uuid: teaUuid };
55
+ }
56
+
57
+ /** 组装带客户端指纹的接口 URL */
58
+ function apiUrl(base, uid) {
59
+ const params = new URLSearchParams({ ...COMMON_PARAMS, ...deviceIdsFor(uid), device_id: '', region: '', sys_region: '' });
60
+ return base + '?' + params.toString();
61
+ }
62
+
63
+ const postJson = (url, cookie, body, uid) => fetchJsonRace(url, {
64
+ method: 'POST',
65
+ headers: {
66
+ Cookie: String(cookie || ''),
67
+ 'Content-Type': 'application/json',
68
+ Accept: 'application/json, text/plain, */*',
69
+ Referer: 'https://www.doubao.com/pc/agent-explore',
70
+ Origin: 'https://www.doubao.com',
71
+ 'User-Agent': CLIENT_UA,
72
+ },
73
+ body: JSON.stringify(body),
74
+ timeoutMs: FETCH_TIMEOUT_MS,
75
+ });
76
+
77
+ /** 会话失效的业务码(实测:匿名 / 过期 cookie 返 code 710012001「登录已过期」) */
78
+ const LOGIN_INVALID = 710012001;
79
+
80
+ /** detail 响应里挖签到任务(tasks[].task_kind === 'daily_signin') */
81
+ function findCheckinTask(body) {
82
+ const tasks = body?.data?.tasks;
83
+ if (!Array.isArray(tasks)) return null;
84
+ return tasks.find((t) => t && t.task_kind === 'daily_signin') || null;
85
+ }
86
+
87
+ /**
88
+ * 签到状态 + 体验额度读数(detail 接口一次拿全)。
89
+ * @returns {{ auth: boolean, code: number|string|null, checkin: { checkedIn: boolean, continuousDays: number, lastDate: string, rewardAmount: number }|null, totalAmount: number|null }}
90
+ */
91
+ /**
92
+ * 签到状态 + 体验额度读数(detail 接口一次拿全)。uid 用于派生稳定设备指纹。
93
+ * @returns {{ auth: boolean, code: number|string|null, checkin: object|null, totalAmount: number|null }}
94
+ */
95
+ export async function fetchDoubaoStatus(cookie, uid = '') {
96
+ const res = await postJson(apiUrl(DETAIL_URL, uid), cookie, { campaign_group_code: CAMPAIGN_GROUP }, uid);
97
+ let body = null;
98
+ try { body = JSON.parse(await res.text()); } catch { /* 非 JSON */ }
99
+ if (!body || typeof body !== 'object') return { auth: false, code: `HTTP ${res.status}`, checkin: null, totalAmount: null };
100
+ if (!body || typeof body !== 'object') return { auth: res.status !== 401, code: `HTTP ${res.status}`, checkin: null, totalAmount: null };
101
+ const code = Number(body.code);
102
+ if (code === LOGIN_INVALID) return { auth: false, code, checkin: null, totalAmount: null };
103
+ const task = findCheckinTask(body);
104
+ const c = task?.daily_checkin || {};
105
+ return {
106
+ auth: true,
107
+ code,
108
+ checkin: task ? {
109
+ checkedIn: c.checked_in_today === true,
110
+ continuousDays: Number(c.continuous_days) || 0,
111
+ lastDate: typeof c.last_checkin_date === 'string' ? c.last_checkin_date : null,
112
+ rewardAmount: Number(c.reward?.reward_amount) || 0,
113
+ } : null,
114
+ totalAmount: Number(body.data?.progress?.total_amount) || 0,
115
+ };
116
+ }
117
+
118
+ /**
119
+ * 每日签到:先查状态,未签才 claim。返回 CreditDaddy 签到契约。
120
+ * 服务端幂等:重复领取 code 0 + idempotent:true——归 already 不算错误。
121
+ */
122
+ export async function checkinDoubao(account) {
123
+ const base = { accountId: account.id, account: account.name || account.id, provider: 'doubao' };
124
+ const cookie = account.meta?.cookieSiteSession?.cookie || account.cookie;
125
+ if (!cookie) return { ...base, status: 'failed', message: '缺少豆包会话 cookie(请重新登录捕获)' };
126
+
127
+ const uid = account.uid || '';
128
+ let st;
129
+ try {
130
+ st = await fetchDoubaoStatus(cookie, uid);
131
+ } catch (e) {
132
+ return { ...base, status: 'failed', message: e?.message || '网络错误' };
133
+ }
134
+ if (!st.auth) {
135
+ const e = new Error('豆包会话已失效,请重新登录捕获');
136
+ e.auth = true;
137
+ throw e;
138
+ }
139
+ if (st.checkin?.checkedIn) {
140
+ return { ...base, status: 'already', message: '今天已签到', uid: null };
141
+ }
142
+ if (!st.checkin) {
143
+ // 活动下线 / 飞书企业号被排除等场景:detail 里没有签到任务
144
+ return { ...base, status: 'no-activity', message: '当前账号无签到任务(活动可能未开放,或飞书企业账号不支持参与)' };
145
+ }
146
+
147
+ let body = null;
148
+ try {
149
+ const res = await postJson(apiUrl(CHECKIN_URL, uid), cookie, { scene: CAMPAIGN_GROUP }, uid);
150
+ const text = await res.text();
151
+ body = JSON.parse(text);
152
+ } catch (e) {
153
+ return { ...base, status: 'failed', message: e?.message || '网络错误' };
154
+ }
155
+ if (!body || typeof body !== 'object') return { ...base, status: 'failed', message: '签到接口返回无法解析' };
156
+ const code = Number(body.code);
157
+ if (code === LOGIN_INVALID) {
158
+ const e = new Error('豆包会话已失效,请重新登录捕获');
159
+ e.auth = true;
160
+ throw e;
161
+ }
162
+ if (code !== 0) return { ...base, status: 'failed', message: `${body.msg || body.message || '签到失败'}(code ${code})` };
163
+ const d = body.data || {};
164
+ if (d.idempotent === true) return { ...base, status: 'already', message: '今天已签到(服务端幂等去重)' };
165
+ const amount = Number(d.reward_amount) || st.checkin.rewardAmount || 200;
166
+ const days = Number(d.continuous_days) || st.checkin.continuousDays + 1;
167
+ return { ...base, status: 'checked-in', claimedAmount: amount, message: `签到成功,+${amount} 额度(连续 ${days} 天)` };
168
+ }