dsh-account-pool 0.2.2 → 0.2.3
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 +11 -6
- package/lib/client.js +13 -7
- package/lib/earnings.js +146 -0
- package/lib/index.js +33 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -117,13 +117,18 @@ dsh plugin --profile web add dsh-account-pool
|
|
|
117
117
|
|
|
118
118
|
全完成显示绿色 `3/3 已完成`,部分完成显示黄色 `2/3`,没做显示灰色 `0/3`。
|
|
119
119
|
|
|
120
|
-
|
|
121
|
-
算出这次实际到手的积分。
|
|
120
|
+
**账号明细**表里还有一列「今日获得」,按账号显示本次到手的积分:
|
|
122
121
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
122
|
+
| 情况 | 显示 |
|
|
123
|
+
|---|---|
|
|
124
|
+
| 还没执行过 | `未执行` |
|
|
125
|
+
| 算出收益 | `+150`(有收益时绿色) |
|
|
126
|
+
| 收益为 0 | `+0`——「确实没加」与「不知道」是两回事 |
|
|
127
|
+
| 算不出 | `—`,不编造数字 |
|
|
128
|
+
|
|
129
|
+
数值来自**执行前后的余额差**:签到接口只回成功/失败、不回奖励数量,
|
|
130
|
+
Trae 的任务流也没有计费字段,余额差是唯一可靠的真值。只把**正差值**
|
|
131
|
+
算作获得(负差值可能是期间用了额度)。
|
|
127
132
|
|
|
128
133
|
### 只读与执行分离
|
|
129
134
|
|
package/lib/client.js
CHANGED
|
@@ -617,18 +617,24 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
|
|
|
617
617
|
/**
|
|
618
618
|
* 账号明细里的「今日获得」单元格。
|
|
619
619
|
*
|
|
620
|
-
*
|
|
621
|
-
*
|
|
620
|
+
* 取值优先级:
|
|
621
|
+
* 1. account.earnedCredits —— 后端台账里的**今日累计**,打开页面就有
|
|
622
|
+
* 2. lastRun[id].earnedCredits —— 刚执行完的最新值(台账已含它,
|
|
623
|
+
* 这里是防状态还没刷新的兜底)
|
|
624
|
+
*
|
|
625
|
+
* 为什么不是"本次差值":同一天重复点执行时签到幂等、余额不变,
|
|
626
|
+
* 差值会是 0,但今天确实拿到过积分。台账按天累加才是用户要的口径。
|
|
622
627
|
*
|
|
623
628
|
* 三态刻意区分开:
|
|
624
|
-
*
|
|
625
|
-
*
|
|
626
|
-
*
|
|
629
|
+
* 有数字 → +N(有收益为绿);0 也显示 +0,因为「确实没加」与
|
|
630
|
+
* 「不知道」不同
|
|
631
|
+
* 没数字 → —;从没执行过也走这里(后端台账为空)
|
|
627
632
|
*/
|
|
628
633
|
function earnedCell(account, lastRun) {
|
|
629
634
|
const run = lastRun?.[account.accountId];
|
|
630
|
-
|
|
631
|
-
|
|
635
|
+
const earned = typeof account.earnedCredits === "number"
|
|
636
|
+
? account.earnedCredits
|
|
637
|
+
: run?.earnedCredits;
|
|
632
638
|
if (typeof earned !== "number") return h("span", { style: { opacity: 0.5 } }, "—");
|
|
633
639
|
return h("span", {
|
|
634
640
|
style: { color: earned > 0 ? "var(--dsw-alias-state-success-primary)" : "inherit" },
|
package/lib/earnings.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 每日积分收益记录器。
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么需要它
|
|
5
|
+
*
|
|
6
|
+
* 「今日获得」不能直接用「执行前后余额差」表示——那个差值只反映
|
|
7
|
+
* **本次执行**的变化:
|
|
8
|
+
*
|
|
9
|
+
* ```text
|
|
10
|
+
* 第 1 次点执行 未签到 → 领取 → 余额 +150 显示 +150 ✓
|
|
11
|
+
* 第 2 次点执行 已签到 → 无变化 → 余额 +0 显示 0 ✗
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* 同一天重复点执行(幂等),第二次就开始显示 0,看起来像"没获得积分",
|
|
15
|
+
* 但今天确实拿到过 150。用户真正想知道的是**今天累计获得了多少**。
|
|
16
|
+
*
|
|
17
|
+
* 所以这里把每次算出的**正收益累加**并按天保存;跨天自动归零,
|
|
18
|
+
* 不需要清理任务。
|
|
19
|
+
*
|
|
20
|
+
* ## 为什么只累加正差值
|
|
21
|
+
*
|
|
22
|
+
* 差值为负说明期间有别的消耗(对话),不是"获得了负数积分"。
|
|
23
|
+
* 只有变多才算收益。另外重复减去同一笔收益不可能发生——每次执行
|
|
24
|
+
* 的差值本身就是增量。
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { readFile, writeFile, rename, mkdir } from 'node:fs/promises'
|
|
28
|
+
import { dirname, join } from 'node:path'
|
|
29
|
+
|
|
30
|
+
/** 文件格式版本,字段不兼容时便于识别旧数据。 */
|
|
31
|
+
const FILE_VERSION = 1
|
|
32
|
+
|
|
33
|
+
/** 把时间戳转成本地日期键(YYYY-MM-DD),与用户看到的"今天"一致。 */
|
|
34
|
+
export function dayKeyOf(timestamp = Date.now()) {
|
|
35
|
+
const d = new Date(timestamp)
|
|
36
|
+
const pad = (n) => String(n).padStart(2, '0')
|
|
37
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 按天的积分收益台账。
|
|
42
|
+
*
|
|
43
|
+
* 结构:`{ version, days: { '2026-09-21': { [accountId]: 150 } } }`
|
|
44
|
+
* 只保留最近若干天,避免文件无限增长。
|
|
45
|
+
*/
|
|
46
|
+
export class DailyEarningsRecorder {
|
|
47
|
+
/**
|
|
48
|
+
* @param {object} options
|
|
49
|
+
* @param {string} options.file 落盘路径
|
|
50
|
+
* @param {number} [options.keepDays] 保留多少天(默认 30)
|
|
51
|
+
*/
|
|
52
|
+
constructor({ file, keepDays = 30 }) {
|
|
53
|
+
this.file = file
|
|
54
|
+
this.keepDays = keepDays
|
|
55
|
+
/** @type {Record<string, Record<string, number>>} */
|
|
56
|
+
this.days = {}
|
|
57
|
+
this.loaded = false
|
|
58
|
+
/** 写操作串行:read→改→write 不是原子的,并发会互相覆盖。 */
|
|
59
|
+
this.writeQueue = Promise.resolve()
|
|
60
|
+
/** 待写入标记:多次累加合并成一次写盘。 */
|
|
61
|
+
this.dirty = false
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** 读盘(幂等)。文件不存在或损坏时按空台账处理,不抛错。 */
|
|
65
|
+
async load() {
|
|
66
|
+
if (this.loaded) return
|
|
67
|
+
this.loaded = true
|
|
68
|
+
try {
|
|
69
|
+
const doc = JSON.parse(await readFile(this.file, 'utf8'))
|
|
70
|
+
if (doc?.version === FILE_VERSION && typeof doc.days === 'object' && doc.days !== null) {
|
|
71
|
+
this.days = doc.days
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
// 首次运行或文件损坏:保持空台账
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 累加一个账号今天的收益,返回它今天的累计值。
|
|
80
|
+
*
|
|
81
|
+
* @param {string} accountId 账号 id
|
|
82
|
+
* @param {number} delta 本次新增的收益(<=0 时直接返回当前累计,不记负数)
|
|
83
|
+
* @param {number} [now] 当前时间戳(便于测试)
|
|
84
|
+
* @returns {Promise<number>} 该账号今日累计收益
|
|
85
|
+
*/
|
|
86
|
+
async add(accountId, delta, now = Date.now()) {
|
|
87
|
+
await this.load()
|
|
88
|
+
const key = dayKeyOf(now)
|
|
89
|
+
const today = this.days[key] ?? (this.days[key] = {})
|
|
90
|
+
const current = typeof today[accountId] === 'number' ? today[accountId] : 0
|
|
91
|
+
if (typeof delta !== 'number' || !Number.isFinite(delta) || delta <= 0) {
|
|
92
|
+
return current
|
|
93
|
+
}
|
|
94
|
+
today[accountId] = Math.round((current + delta) * 100) / 100
|
|
95
|
+
this.dirty = true
|
|
96
|
+
await this.flush()
|
|
97
|
+
return today[accountId]
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 读某天各账号的累计收益。
|
|
102
|
+
*
|
|
103
|
+
* @param {number} [now] 当前时间戳(便于测试)
|
|
104
|
+
* @returns {Promise<Record<string, number>>}
|
|
105
|
+
*/
|
|
106
|
+
async today(now = Date.now()) {
|
|
107
|
+
await this.load()
|
|
108
|
+
return this.days[dayKeyOf(now)] ?? {}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** 落盘(先串行化,再清理过期天数)。 */
|
|
112
|
+
async flush() {
|
|
113
|
+
if (!this.dirty) return
|
|
114
|
+
this.dirty = false
|
|
115
|
+
this.prune()
|
|
116
|
+
const payload = JSON.stringify({ version: FILE_VERSION, days: this.days }, null, 0)
|
|
117
|
+
this.writeQueue = this.writeQueue.then(async () => {
|
|
118
|
+
try {
|
|
119
|
+
await mkdir(dirname(this.file), { recursive: true })
|
|
120
|
+
// 先写临时文件再改名:避免写一半崩溃留下损坏的 JSON
|
|
121
|
+
const tmp = `${this.file}.${process.pid}.tmp`
|
|
122
|
+
await writeFile(tmp, payload, { encoding: 'utf8', mode: 0o600 })
|
|
123
|
+
await rename(tmp, this.file)
|
|
124
|
+
} catch {
|
|
125
|
+
// 落盘失败不影响本次收益的正常返回;下次累加会再试
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
await this.writeQueue
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** 只保留最近 keepDays 天,防止文件无限增长。 */
|
|
132
|
+
prune() {
|
|
133
|
+
const keys = Object.keys(this.days).sort()
|
|
134
|
+
if (keys.length <= this.keepDays) return
|
|
135
|
+
for (const key of keys.slice(0, keys.length - this.keepDays)) {
|
|
136
|
+
delete this.days[key]
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** 按区域打开台账(每个区域一份,与凭证/用量文件命名一致)。 */
|
|
142
|
+
export function openDailyEarnings(dshHome, region) {
|
|
143
|
+
return new DailyEarningsRecorder({
|
|
144
|
+
file: join(dshHome, `.account-pool.earnings.${region}.json`),
|
|
145
|
+
})
|
|
146
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -35,6 +35,7 @@ import { fetchModels, fetchCredits } from './upstream.js'
|
|
|
35
35
|
import { AccountPicker } from './selector.js'
|
|
36
36
|
import { createShim } from './shim.js'
|
|
37
37
|
import { UsageRecorder } from './usage.js'
|
|
38
|
+
import { openDailyEarnings } from './earnings.js'
|
|
38
39
|
import { runDailyTasks, fetchStreak, fetchBuddy, fetchLottery, travelStatus as fetchTravelStatus } from './tasks.js'
|
|
39
40
|
import { openTraeStore, accountIdOf as accountIdOfTrae } from './trae-accounts.js'
|
|
40
41
|
import { parseTraeStorage } from './trae-storage.js'
|
|
@@ -220,6 +221,9 @@ async function createRegionStack({ region, dshHome, config, logger, ctx }) {
|
|
|
220
221
|
],
|
|
221
222
|
})
|
|
222
223
|
await usage.load()
|
|
224
|
+
// 每日积分收益台账:同一天多次执行要能累计,不能只看本次差值。
|
|
225
|
+
const earnings = openDailyEarnings(dshHome, region)
|
|
226
|
+
await earnings.load()
|
|
223
227
|
const picker = new AccountPicker({
|
|
224
228
|
softCooldownMs: (config.softCooldownSeconds ?? 600) * 1000,
|
|
225
229
|
breakerThreshold: config.breakerThreshold ?? 3,
|
|
@@ -373,6 +377,7 @@ async function createRegionStack({ region, dshHome, config, logger, ctx }) {
|
|
|
373
377
|
shim,
|
|
374
378
|
adapter,
|
|
375
379
|
usage,
|
|
380
|
+
earnings,
|
|
376
381
|
refreshCatalog,
|
|
377
382
|
loadCredits,
|
|
378
383
|
getCatalog: () => catalog,
|
|
@@ -981,24 +986,38 @@ export function apply(ctx, config = {}) {
|
|
|
981
986
|
const after = await stack.loadCredits(true).catch(() => ({}))
|
|
982
987
|
|
|
983
988
|
/**
|
|
984
|
-
*
|
|
989
|
+
* 算出每个账号本次的增量,并**累加进今天的台账**。
|
|
985
990
|
*
|
|
986
|
-
*
|
|
987
|
-
*
|
|
988
|
-
*
|
|
989
|
-
*
|
|
991
|
+
* 为什么要台账:差值只反映"本次执行"的变化。同一天再点一次
|
|
992
|
+
* 执行(签到幂等,余额不变)差值就是 0——但今天确实拿到过积分。
|
|
993
|
+
* 界面要显示的是"今天累计获得多少",所以按天累加并持久化,
|
|
994
|
+
* 跨天自动归零(按本地日期分键)。
|
|
995
|
+
*
|
|
996
|
+
* 只把**正差值**算作获得:差值为负说明期间有别的消耗(对话),
|
|
997
|
+
* 不是"获得负数"。
|
|
990
998
|
*/
|
|
991
999
|
const earnedOf = (accountId) => {
|
|
992
1000
|
const b = before[accountId]?.total
|
|
993
1001
|
const a = after[accountId]?.total
|
|
994
1002
|
if (typeof b !== 'number' || typeof a !== 'number') return undefined
|
|
995
1003
|
const delta = a - b
|
|
996
|
-
// 负差值可能是「期间用了额度」,不算获得;只取正收益
|
|
997
1004
|
return delta > 0 ? Math.round(delta * 100) / 100 : 0
|
|
998
1005
|
}
|
|
999
1006
|
for (const r of results) {
|
|
1000
|
-
const
|
|
1001
|
-
if (
|
|
1007
|
+
const delta = earnedOf(r.accountId)
|
|
1008
|
+
if (delta === undefined) continue
|
|
1009
|
+
// add() 返回该账号**今日累计**值;delta<=0 时原样返回已有累计,
|
|
1010
|
+
// 所以重复执行不会把数字冲掉
|
|
1011
|
+
r.earnedCredits = await stack.earnings.add(r.accountId, delta)
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// 补上今天已经累计、但本次没参与执行(或算不出差值)的账号,
|
|
1015
|
+
// 否则它们的「今日获得」会显示成"未执行"或"—"。
|
|
1016
|
+
const todayEarnings = await stack.earnings.today()
|
|
1017
|
+
for (const r of results) {
|
|
1018
|
+
if (typeof r.earnedCredits === 'number') continue
|
|
1019
|
+
const accumulated = todayEarnings[r.accountId]
|
|
1020
|
+
if (typeof accumulated === 'number') r.earnedCredits = accumulated
|
|
1002
1021
|
}
|
|
1003
1022
|
|
|
1004
1023
|
const totalEarned = results.reduce(
|
|
@@ -1007,8 +1026,6 @@ export function apply(ctx, config = {}) {
|
|
|
1007
1026
|
ok: true,
|
|
1008
1027
|
results,
|
|
1009
1028
|
totalEarned: Math.round(totalEarned * 100) / 100,
|
|
1010
|
-
// 有账号算不出差值时,界面要对「合计」加注说明,避免误以为是全部
|
|
1011
|
-
earnedPartial: results.some(r => typeof r.earnedCredits !== 'number'),
|
|
1012
1029
|
})
|
|
1013
1030
|
} catch (error) {
|
|
1014
1031
|
json(res, 500, { ok: false, error: safeMessage(error) })
|
|
@@ -1031,6 +1048,10 @@ export function apply(ctx, config = {}) {
|
|
|
1031
1048
|
const stack = list.find(s => s.region === region)
|
|
1032
1049
|
if (stack === undefined) { json(res, 503, { ok: false, error: '该区域尚未初始化' }); return }
|
|
1033
1050
|
|
|
1051
|
+
// 今日已累计的收益:让「今日获得」在打开页面时就有值,
|
|
1052
|
+
// 不必等到点过执行(台账里存的是按天累计值)。
|
|
1053
|
+
const todayEarnings = await stack.earnings.today()
|
|
1054
|
+
|
|
1034
1055
|
const out = []
|
|
1035
1056
|
for (const account of await stack.store.list()) {
|
|
1036
1057
|
try {
|
|
@@ -1048,6 +1069,7 @@ export function apply(ctx, config = {}) {
|
|
|
1048
1069
|
creditsSize: status.creditsSize,
|
|
1049
1070
|
...(status.checkinError === undefined ? {} : { checkinError: status.checkinError }),
|
|
1050
1071
|
...(status.creditsError === undefined ? {} : { creditsError: status.creditsError }),
|
|
1072
|
+
earnedCredits: todayEarnings[account.id],
|
|
1051
1073
|
})
|
|
1052
1074
|
continue
|
|
1053
1075
|
}
|
|
@@ -1067,6 +1089,7 @@ export function apply(ctx, config = {}) {
|
|
|
1067
1089
|
buddy,
|
|
1068
1090
|
travel: travelNow,
|
|
1069
1091
|
lottery,
|
|
1092
|
+
earnedCredits: todayEarnings[account.id],
|
|
1070
1093
|
...(credential.expiresAtMs === undefined ? {} : { expiresAtMs: credential.expiresAtMs }),
|
|
1071
1094
|
})
|
|
1072
1095
|
} catch (error) {
|