creditdaddy 1.1.1 → 1.2.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/LICENSE +21 -21
- package/README.md +306 -289
- package/bin/creditdaddy.js +8 -4
- package/package.json +29 -28
- package/src/accounts.js +161 -161
- package/src/authDevice.js +205 -205
- package/src/catpawClient.js +103 -103
- package/src/catpawLocal.js +229 -229
- package/src/checkin.js +357 -340
- package/src/constants.js +97 -95
- package/src/daemon.js +130 -14
- package/src/localDetect.js +147 -147
- package/src/mirasimClient.js +189 -189
- package/src/panel.html +465 -142
- package/src/providers.js +190 -181
- package/src/qoderApp.js +93 -7
- package/src/qoderClient.js +237 -237
- package/src/qoderUmid.js +198 -198
- package/src/store.js +238 -238
- package/src/tenrouter.js +18 -8
- package/src/tenrouterAccounts.js +163 -163
- package/src/traeClient.js +258 -0
- package/src/traeLocal.js +337 -0
- package/src/transfer.js +160 -160
- package/src/usageSync.js +505 -505
- package/src/workbuddyLocal.js +133 -133
- package/src/zcodeAuth.js +131 -131
- package/src/zcodeAutoClaim.js +97 -97
- package/src/zcodeClient.js +33 -2
- package/src/zcodeGateway.js +26 -30
- package/src/zcodeLocal.js +334 -319
- package/src/zcrypto.js +160 -160
package/src/checkin.js
CHANGED
|
@@ -1,340 +1,357 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 签到调度:普通账号每 ~2h 扫描一次;ZCode 自动活动领取使用独立的短周期调度。
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { loadAccounts, loadState, saveState, withAccounts } from './store.js';
|
|
6
|
-
import { productImpl } from './providers.js';
|
|
7
|
-
import { productOf } from './constants.js';
|
|
8
|
-
import { startUsageSyncScheduler } from './tenrouter.js';
|
|
9
|
-
import { refreshContext } from './accounts.js';
|
|
10
|
-
import { zcodeAutoClaim } from './zcodeAutoClaim.js';
|
|
11
|
-
import { clearQuotaMark } from './zcodeGateway.js';
|
|
12
|
-
import {
|
|
13
|
-
autoClaimEnabled, autoClaimUntil, enableAutoClaimFor, setAutoClaimEnabled,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const ZCODE_JITTER_MS = 15 * 1000;
|
|
22
|
-
const ZCODE_FIRST_DELAY_MS = 2 * 60 * 1000;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
//
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
let timerHandle = null;
|
|
29
|
-
let zcodeTimerHandle = null;
|
|
30
|
-
let zcodeExpiryTimerHandle = null;
|
|
31
|
-
let zcodeExpiryAt = null;
|
|
32
|
-
let zcodeNextTickAt = null;
|
|
33
|
-
let zcodeLastTick = null;
|
|
34
|
-
let zcodeTicking = false;
|
|
35
|
-
let zcodeQueue = Promise.resolve();
|
|
36
|
-
let tickQueue = Promise.resolve();
|
|
37
|
-
let nextTickAt = null;
|
|
38
|
-
let lastTick = null;
|
|
39
|
-
let ticking = false;
|
|
40
|
-
|
|
41
|
-
/** 调度器状态(面板展示用) */
|
|
42
|
-
export function getSchedulerInfo() {
|
|
43
|
-
const enabled = autoClaimEnabled();
|
|
44
|
-
return {
|
|
45
|
-
running: Boolean(timerHandle), nextTickAt, lastTick, ticking,
|
|
46
|
-
zcode: {
|
|
47
|
-
enabled,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function
|
|
65
|
-
return Math.max(
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
zcodeTimerHandle
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
zcodeExpiryTimerHandle
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
export function
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
account.
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
if (
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* 签到调度:普通账号每 ~2h 扫描一次;ZCode 自动活动领取使用独立的短周期调度。
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { loadAccounts, loadState, saveState, withAccounts } from './store.js';
|
|
6
|
+
import { productImpl } from './providers.js';
|
|
7
|
+
import { productOf } from './constants.js';
|
|
8
|
+
import { startUsageSyncScheduler } from './tenrouter.js';
|
|
9
|
+
import { refreshContext } from './accounts.js';
|
|
10
|
+
import { zcodeAutoClaim } from './zcodeAutoClaim.js';
|
|
11
|
+
import { clearQuotaMark } from './zcodeGateway.js';
|
|
12
|
+
import {
|
|
13
|
+
autoClaimEnabled, autoClaimUntil, enableAutoClaimFor, setAutoClaimEnabled,
|
|
14
|
+
claimIntervalMin, claimWindowMin,
|
|
15
|
+
warmZcodeAppVersion,
|
|
16
|
+
} from './zcodeClient.js';
|
|
17
|
+
import { logger } from './logger.js';
|
|
18
|
+
|
|
19
|
+
const TICK_MS = 2 * 60 * 60 * 1000;
|
|
20
|
+
const TICK_JITTER_MS = 10 * 60 * 1000;
|
|
21
|
+
const ZCODE_JITTER_MS = 15 * 1000;
|
|
22
|
+
const ZCODE_FIRST_DELAY_MS = 2 * 60 * 1000;
|
|
23
|
+
|
|
24
|
+
// 各产品「业务日」刷新时点(UTC+8):Qoder 每日 10:00 放出/重置领取资格,其余产品按 00:00。
|
|
25
|
+
// 签到日 = 对应时点起的日历日:dayKey(now, provider) = (now + (8 - 刷新小时)h) 的 UTC 日期
|
|
26
|
+
const DAY_REFRESH_HOUR_UTC8 = { qoder: 10, 'qoder-cn': 10 };
|
|
27
|
+
|
|
28
|
+
let timerHandle = null;
|
|
29
|
+
let zcodeTimerHandle = null;
|
|
30
|
+
let zcodeExpiryTimerHandle = null;
|
|
31
|
+
let zcodeExpiryAt = null;
|
|
32
|
+
let zcodeNextTickAt = null;
|
|
33
|
+
let zcodeLastTick = null;
|
|
34
|
+
let zcodeTicking = false;
|
|
35
|
+
let zcodeQueue = Promise.resolve();
|
|
36
|
+
let tickQueue = Promise.resolve();
|
|
37
|
+
let nextTickAt = null;
|
|
38
|
+
let lastTick = null;
|
|
39
|
+
let ticking = false;
|
|
40
|
+
|
|
41
|
+
/** 调度器状态(面板展示用) */
|
|
42
|
+
export function getSchedulerInfo() {
|
|
43
|
+
const enabled = autoClaimEnabled();
|
|
44
|
+
return {
|
|
45
|
+
running: Boolean(timerHandle), nextTickAt, lastTick, ticking,
|
|
46
|
+
zcode: {
|
|
47
|
+
enabled,
|
|
48
|
+
intervalMin: claimIntervalMin(),
|
|
49
|
+
windowMin: claimWindowMin(),
|
|
50
|
+
autoOffAt: enabled ? autoClaimUntil() : null,
|
|
51
|
+
nextTickAt: enabled ? zcodeNextTickAt : null,
|
|
52
|
+
lastTick: zcodeLastTick,
|
|
53
|
+
ticking: zcodeTicking,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** 签到业务日。Qoder 以 10:00 (UTC+8) 为界,其余产品以 00:00 (UTC+8) 为界;默认 Qoder 口径(/api/status 展示用)。 */
|
|
59
|
+
export function dayKey(nowMs = Date.now(), provider = 'qoder') {
|
|
60
|
+
const h = DAY_REFRESH_HOUR_UTC8[provider] ?? 0;
|
|
61
|
+
return new Date(nowMs + (8 - h) * 3600 * 1000).toISOString().slice(0, 10);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function msUntilNextTick(nowMs = Date.now(), rand = Math.random) {
|
|
65
|
+
return Math.max(TICK_MS + Math.floor(rand() * TICK_JITTER_MS), 1000);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function zcodeMsUntilNextTick(rand = Math.random) {
|
|
69
|
+
const base = claimIntervalMin() * 60 * 1000;
|
|
70
|
+
return Math.max(base + Math.floor(rand() * ZCODE_JITTER_MS), 1000);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** 按用户配置的「自动领取计划」开启 ZCode 自动领取;首次资格检查两分钟后开始。
|
|
74
|
+
* 运行时长在面板弹窗里配置(默认 60 分钟;0 = 一直运行直到手动关闭)。 */
|
|
75
|
+
export function enableZcodeAutoClaimWindow() {
|
|
76
|
+
const w = claimWindowMin();
|
|
77
|
+
if (w > 0) enableAutoClaimFor(w * 60 * 1000);
|
|
78
|
+
else setAutoClaimEnabled(true);
|
|
79
|
+
syncZcodeScheduler(true);
|
|
80
|
+
return autoClaimUntil();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** 更新 ZCode 定时器,autoClaimUntil 是持久化的到期时间,重启后仍会生效。 */
|
|
84
|
+
export function refreshZcodeScheduler() {
|
|
85
|
+
syncZcodeScheduler();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function clearZcodeTimers() {
|
|
89
|
+
if (zcodeTimerHandle) clearTimeout(zcodeTimerHandle);
|
|
90
|
+
if (zcodeExpiryTimerHandle) clearTimeout(zcodeExpiryTimerHandle);
|
|
91
|
+
zcodeTimerHandle = null;
|
|
92
|
+
zcodeExpiryTimerHandle = null;
|
|
93
|
+
zcodeExpiryAt = null;
|
|
94
|
+
zcodeNextTickAt = null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function scheduleZcodeNextTick(delay = zcodeMsUntilNextTick()) {
|
|
98
|
+
if (!autoClaimEnabled()) {
|
|
99
|
+
syncZcodeScheduler();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (zcodeTimerHandle) clearTimeout(zcodeTimerHandle);
|
|
103
|
+
zcodeNextTickAt = new Date(Date.now() + delay).toISOString();
|
|
104
|
+
zcodeTimerHandle = setTimeout(() => {
|
|
105
|
+
zcodeTimerHandle = null;
|
|
106
|
+
zcodeNextTickAt = null;
|
|
107
|
+
runZcodeTick().catch((err) => logger.error('ZCODE-CLAIM', `资格轮询失败:${err?.message || err}`))
|
|
108
|
+
.finally(() => scheduleZcodeNextTick());
|
|
109
|
+
}, delay);
|
|
110
|
+
zcodeTimerHandle.unref?.();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function syncZcodeScheduler(startFresh = false) {
|
|
114
|
+
if (!autoClaimEnabled()) {
|
|
115
|
+
clearZcodeTimers();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const expiresAt = autoClaimUntil();
|
|
120
|
+
if (startFresh && zcodeTimerHandle) {
|
|
121
|
+
clearTimeout(zcodeTimerHandle);
|
|
122
|
+
zcodeTimerHandle = null;
|
|
123
|
+
zcodeNextTickAt = null;
|
|
124
|
+
}
|
|
125
|
+
if (!zcodeTimerHandle && !zcodeTicking) scheduleZcodeNextTick(ZCODE_FIRST_DELAY_MS);
|
|
126
|
+
|
|
127
|
+
if (expiresAt && zcodeExpiryAt !== expiresAt) {
|
|
128
|
+
if (zcodeExpiryTimerHandle) clearTimeout(zcodeExpiryTimerHandle);
|
|
129
|
+
zcodeExpiryAt = expiresAt;
|
|
130
|
+
zcodeExpiryTimerHandle = setTimeout(() => {
|
|
131
|
+
zcodeExpiryTimerHandle = null;
|
|
132
|
+
setAutoClaimEnabled(false);
|
|
133
|
+
clearZcodeTimers();
|
|
134
|
+
logger.info('ZCODE-CLAIM', '自动领取时段已结束,自动领取已关闭');
|
|
135
|
+
}, Math.max(0, expiresAt - Date.now()));
|
|
136
|
+
zcodeExpiryTimerHandle.unref?.();
|
|
137
|
+
} else if (!expiresAt && zcodeExpiryTimerHandle) {
|
|
138
|
+
// 运行时长为 0(不自动关闭):清掉可能残留的到期定时器
|
|
139
|
+
clearTimeout(zcodeExpiryTimerHandle);
|
|
140
|
+
zcodeExpiryTimerHandle = null;
|
|
141
|
+
zcodeExpiryAt = null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function runZcodeTick(accountIds, { force = false } = {}) {
|
|
146
|
+
const run = zcodeQueue.then(async () => {
|
|
147
|
+
if (!force && !autoClaimEnabled()) return { results: [], summary: 'ZCode 自动领取已关闭' };
|
|
148
|
+
zcodeTicking = true;
|
|
149
|
+
const results = [];
|
|
150
|
+
const claimedIds = [];
|
|
151
|
+
try {
|
|
152
|
+
const accounts = (await loadAccounts()).filter((a) => a.provider === 'zcode'
|
|
153
|
+
&& (!accountIds || accountIds.has(a.id)));
|
|
154
|
+
for (const account of accounts) {
|
|
155
|
+
if (!force && !autoClaimEnabled()) break;
|
|
156
|
+
const label = account.name || account.uid || account.id;
|
|
157
|
+
try {
|
|
158
|
+
const outcome = await zcodeAutoClaim(account);
|
|
159
|
+
if (!outcome) continue;
|
|
160
|
+
if (outcome.status === 'checked-in') {
|
|
161
|
+
claimedIds.push(account.id);
|
|
162
|
+
await clearQuotaMark(account.id);
|
|
163
|
+
}
|
|
164
|
+
results.push({ accountId: account.id, account: label, provider: 'zcode', status: outcome.status, message: outcome.message });
|
|
165
|
+
const lastResult = { status: outcome.status, message: outcome.message, amount: 0, at: new Date().toISOString() };
|
|
166
|
+
await withAccounts((list) => {
|
|
167
|
+
const current = list.find((a) => a.id === account.id);
|
|
168
|
+
if (current) current.lastResult = lastResult;
|
|
169
|
+
});
|
|
170
|
+
} catch (err) {
|
|
171
|
+
logger.warn('ZCODE-CLAIM', `${label} 自动领取异常:${err?.message || err}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
zcodeLastTick = {
|
|
175
|
+
at: new Date().toISOString(),
|
|
176
|
+
summary: `ZCode 资格轮询完成:${results.length} 个账号返回活动结果`,
|
|
177
|
+
claimedIds, // 面板据此即时刷新这些账号的额度
|
|
178
|
+
};
|
|
179
|
+
return { results, summary: zcodeLastTick.summary };
|
|
180
|
+
} finally {
|
|
181
|
+
zcodeTicking = false;
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
zcodeQueue = run.catch(() => {});
|
|
185
|
+
return run;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** 手动触发一次 ZCode 资格轮询(面板单账号 / 全部领取走这里),与定时轮共用同一队列;手动轮不受开关限制。 */
|
|
189
|
+
export function pollZcodeNow(accountIds) {
|
|
190
|
+
return runZcodeTick(accountIds && accountIds.length ? new Set(accountIds) : null, { force: true });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function getDoneMap(state) {
|
|
194
|
+
// 任一口径下仍属于「今天」的记录都保留(0–10 点间 Qoder 记录的业务日还是昨天,不能误删)
|
|
195
|
+
const keepDays = new Set([dayKey(Date.now(), 'qoder'), dayKey(Date.now(), 'workbuddy')]);
|
|
196
|
+
const raw = state?.qoderDailyDone;
|
|
197
|
+
const map = raw && typeof raw === 'object' && !Array.isArray(raw) ? { ...raw } : {};
|
|
198
|
+
for (const key of Object.keys(map)) if (!keepDays.has(map[key])) delete map[key];
|
|
199
|
+
return map;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** 多次调用排队串行执行,避免并发签到和 state.json 覆盖。 */
|
|
203
|
+
export function runCheckinTick(opts = {}) {
|
|
204
|
+
const run = tickQueue.then(() => runTickNow(opts));
|
|
205
|
+
tickQueue = run.catch(() => {});
|
|
206
|
+
return run;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function runTickNow(opts) {
|
|
210
|
+
ticking = true;
|
|
211
|
+
try { return await runTickInner(opts); } finally { ticking = false; }
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function runTickInner(opts) {
|
|
215
|
+
const allAccounts = await loadAccounts();
|
|
216
|
+
const accounts = allAccounts.filter(
|
|
217
|
+
(a) => (!opts.provider || a.provider === opts.provider)
|
|
218
|
+
&& (!opts.product || productOf(a.provider) === opts.product)
|
|
219
|
+
&& (!opts.onlyAccountId || a.id === opts.onlyAccountId)
|
|
220
|
+
&& typeof productImpl(a.provider)?.checkin === 'function'
|
|
221
|
+
);
|
|
222
|
+
const zAccounts = (opts.includeZcode !== false && autoClaimEnabled()
|
|
223
|
+
&& !opts.onlyAccountId
|
|
224
|
+
&& (!opts.provider || opts.provider === 'zcode')
|
|
225
|
+
&& (!opts.product || opts.product === 'zcode'))
|
|
226
|
+
? allAccounts.filter((a) => a.provider === 'zcode')
|
|
227
|
+
: [];
|
|
228
|
+
|
|
229
|
+
if (accounts.length === 0 && zAccounts.length === 0) {
|
|
230
|
+
return { results: [], summary: '没有匹配的账号' };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const state = await loadState();
|
|
234
|
+
const memo = await getDoneMap(state);
|
|
235
|
+
const today = dayKey();
|
|
236
|
+
const results = [];
|
|
237
|
+
let deviceClaim = state?.deviceClaim?.day === today ? state.deviceClaim : null;
|
|
238
|
+
|
|
239
|
+
for (const account of accounts) {
|
|
240
|
+
const label = account.name || account.id;
|
|
241
|
+
// 「今日已领」按各产品自己的业务日界比对:Qoder 10 点翻日,其余 0 点翻日
|
|
242
|
+
const todayFor = dayKey(Date.now(), account.provider);
|
|
243
|
+
try {
|
|
244
|
+
if (opts.skipIfCheckedToday && memo[account.id] === todayFor) {
|
|
245
|
+
results.push({ accountId: account.id, account: label, provider: account.provider, status: 'already', memoized: true });
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const ctx = refreshContext(account, (message) => logger.info('CHECKIN', `${label}:${message}`));
|
|
250
|
+
let outcome = await productImpl(account.provider).checkin(account, ctx);
|
|
251
|
+
outcome = { accountId: account.id, account: label, provider: account.provider, ...outcome };
|
|
252
|
+
if (account.provider === 'qoder' && outcome.risk) {
|
|
253
|
+
if (outcome.status === 'checked-in') {
|
|
254
|
+
deviceClaim = { day: today, accountId: account.id, name: label };
|
|
255
|
+
} else if (outcome.status === 'no-activity' && deviceClaim && deviceClaim.accountId !== account.id) {
|
|
256
|
+
outcome = { ...outcome, status: 'limited', message: `本机今日国际版额度已由「${deviceClaim.name}」领取(Qoder 每台设备每天限领一次)` };
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
results.push(outcome);
|
|
260
|
+
if (outcome.uid && !account.uid) account.uid = outcome.uid;
|
|
261
|
+
account.lastResult = {
|
|
262
|
+
status: outcome.status,
|
|
263
|
+
message: outcome.error || outcome.message || null,
|
|
264
|
+
amount: outcome.claimedAmount || 0,
|
|
265
|
+
...(outcome.streakDays !== undefined ? { streakDays: outcome.streakDays + (outcome.status === 'checked-in' ? 1 : 0) } : {}),
|
|
266
|
+
at: new Date().toISOString(),
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
if (outcome.status === 'checked-in') {
|
|
270
|
+
memo[account.id] = todayFor;
|
|
271
|
+
account.lastCheckin = new Date().toISOString();
|
|
272
|
+
logger.info('CHECKIN', `${label} 领取成功 +${outcome.claimedAmount} Credits`);
|
|
273
|
+
} else if (outcome.status === 'already') {
|
|
274
|
+
memo[account.id] = todayFor;
|
|
275
|
+
account.lastCheckin = account.lastCheckin || new Date().toISOString();
|
|
276
|
+
logger.info('CHECKIN', `${label}:${outcome.message || '今日已领'}`);
|
|
277
|
+
} else if (outcome.status === 'limited') {
|
|
278
|
+
memo[account.id] = todayFor;
|
|
279
|
+
logger.info('CHECKIN', `${label}:${outcome.message}`);
|
|
280
|
+
} else if (outcome.status === 'no-activity') {
|
|
281
|
+
logger.debug('CHECKIN', `${label}:${outcome.message || '当前无可领取的活动'}`);
|
|
282
|
+
} else {
|
|
283
|
+
logger.warn('CHECKIN', `${label} 领取失败:${outcome.error || outcome.status}`);
|
|
284
|
+
}
|
|
285
|
+
} catch (err) {
|
|
286
|
+
results.push({ accountId: account.id, account: label, provider: account.provider, status: 'failed', error: err?.message || String(err) });
|
|
287
|
+
account.lastResult = { status: 'failed', message: err?.message || String(err), amount: 0, at: new Date().toISOString() };
|
|
288
|
+
logger.error('CHECKIN', `${label} 异常:${err?.message || err}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (accounts.length) {
|
|
293
|
+
await withAccounts((all) => {
|
|
294
|
+
for (const updated of accounts) {
|
|
295
|
+
const current = all.find((a) => a.id === updated.id);
|
|
296
|
+
if (!current) continue;
|
|
297
|
+
current.lastCheckin = updated.lastCheckin;
|
|
298
|
+
if (updated.lastResult) current.lastResult = updated.lastResult;
|
|
299
|
+
if (updated.uid && !current.uid) current.uid = updated.uid;
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
await saveState({ ...state, qoderDailyDone: memo, deviceClaim });
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (zAccounts.length) {
|
|
306
|
+
const zResult = await runZcodeTick(new Set(zAccounts.map((a) => a.id)));
|
|
307
|
+
results.push(...zResult.results);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const claimed = results.filter((r) => r.status === 'checked-in');
|
|
311
|
+
const failed = results.filter((r) => r.status === 'failed');
|
|
312
|
+
const none = results.filter((r) => r.status === 'no-activity');
|
|
313
|
+
const limited = results.filter((r) => r.status === 'limited');
|
|
314
|
+
const already = results.length - claimed.length - failed.length - none.length - limited.length;
|
|
315
|
+
const totalCredits = claimed.reduce((sum, r) => sum + (r.claimedAmount || 0), 0);
|
|
316
|
+
const summary = `签到汇总:成功 ${claimed.length}(+${totalCredits} Credits)、已领 ${already}`
|
|
317
|
+
+ (limited.length ? `、本机限领 ${limited.length}` : '')
|
|
318
|
+
+ `、无活动 ${none.length}、失败 ${failed.length}`;
|
|
319
|
+
logger.info('CHECKIN', summary);
|
|
320
|
+
lastTick = { at: new Date().toISOString(), summary };
|
|
321
|
+
return { results, summary };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** 启动普通签到调度与 ZCode 独立轮询调度。 */
|
|
325
|
+
export function startScheduler() {
|
|
326
|
+
if (timerHandle) return;
|
|
327
|
+
startUsageSyncScheduler();
|
|
328
|
+
warmZcodeAppVersion();
|
|
329
|
+
const scheduleNext = () => {
|
|
330
|
+
const delay = msUntilNextTick();
|
|
331
|
+
nextTickAt = new Date(Date.now() + delay).toISOString();
|
|
332
|
+
timerHandle = setTimeout(async () => {
|
|
333
|
+
try {
|
|
334
|
+
await runCheckinTick({ skipIfCheckedToday: true, includeZcode: false });
|
|
335
|
+
} catch (err) {
|
|
336
|
+
logger.error('CHECKIN', `定时轮失败:${err?.message || err}`);
|
|
337
|
+
} finally {
|
|
338
|
+
scheduleNext();
|
|
339
|
+
}
|
|
340
|
+
}, delay);
|
|
341
|
+
timerHandle.unref?.();
|
|
342
|
+
logger.info('CHECKIN', `定时签到已启动,下次执行约 ${Math.round(delay / 60000)} 分钟后`);
|
|
343
|
+
};
|
|
344
|
+
scheduleNext();
|
|
345
|
+
syncZcodeScheduler();
|
|
346
|
+
setTimeout(() => {
|
|
347
|
+
runCheckinTick({ skipIfCheckedToday: true, includeZcode: false }).catch((err) =>
|
|
348
|
+
logger.error('CHECKIN', `引导轮失败:${err?.message || err}`));
|
|
349
|
+
}, 15000).unref?.();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export function stopScheduler() {
|
|
353
|
+
if (timerHandle) clearTimeout(timerHandle);
|
|
354
|
+
timerHandle = null;
|
|
355
|
+
nextTickAt = null;
|
|
356
|
+
clearZcodeTimers();
|
|
357
|
+
}
|