dsh-account-pool 0.1.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/README.md +362 -0
- package/cordis.patch.yml +26 -0
- package/lib/accounts.js +576 -0
- package/lib/client.js +2049 -0
- package/lib/headers.js +76 -0
- package/lib/index.js +1142 -0
- package/lib/selector.js +343 -0
- package/lib/shim.js +370 -0
- package/lib/tasks.js +429 -0
- package/lib/trae-accounts.js +453 -0
- package/lib/trae-bridge.js +266 -0
- package/lib/trae-storage.js +215 -0
- package/lib/trae-upstream.js +485 -0
- package/lib/upstream.js +610 -0
- package/lib/usage.js +420 -0
- package/package.json +68 -0
package/lib/tasks.js
ADDED
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 每日任务:签到、猫猫旅行、连登查询。
|
|
3
|
+
*
|
|
4
|
+
* 接口分两域:
|
|
5
|
+
* - billing 域(www.codebuddy.cn/v2/billing/meter/*):签到、余额
|
|
6
|
+
* - growth 域(copilot.tencent.com/activity/growth/*):Buddy、旅行、连登
|
|
7
|
+
*
|
|
8
|
+
* 与账号池的关系:任务**只做余额与状态维护**,不参与对话选号。
|
|
9
|
+
* 签到会让冷却中的账号恢复可用——这是它对网关的主要价值。
|
|
10
|
+
*
|
|
11
|
+
* 设计要点:
|
|
12
|
+
* - 签到成功与「今天已签到」都算成功:上游对重复签到返回 code=10001,
|
|
13
|
+
* 那是幂等成功,当失败会让日志天天报红。
|
|
14
|
+
* - 国际版账号没有这套任务体系(签到/旅行都是国内版活动),直接跳过,
|
|
15
|
+
* 不发起任何上游调用——避免无谓请求触发风控。
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { chatBaseOf } from './accounts.js'
|
|
19
|
+
import { commonHeaders } from './headers.js'
|
|
20
|
+
|
|
21
|
+
/** 单次任务请求超时。 */
|
|
22
|
+
const TASK_TIMEOUT_MS = 30_000
|
|
23
|
+
|
|
24
|
+
/** 国内版计费域(签到、余额都在这里)。 */
|
|
25
|
+
const CN_BILLING_BASE = 'https://www.codebuddy.cn'
|
|
26
|
+
|
|
27
|
+
/** 签到端点。 */
|
|
28
|
+
const CHECKIN_PATH = '/v2/billing/meter/daily-checkin'
|
|
29
|
+
|
|
30
|
+
/** 余额端点(与 upstream.js 的 fetchCredits 同一个,这里只用于签到后回报)。 */
|
|
31
|
+
const RESOURCE_PATH = '/v2/billing/meter/get-user-resource'
|
|
32
|
+
|
|
33
|
+
/** 猫猫旅行:状态 / 派出 / 领奖。 */
|
|
34
|
+
const TRAVEL_STATUS_PATH = '/activity/growth/buddy/travel/status'
|
|
35
|
+
const TRAVEL_DEPART_PATH = '/activity/growth/buddy/travel/depart'
|
|
36
|
+
const TRAVEL_CLAIM_PATH = '/activity/growth/buddy/travel/claim'
|
|
37
|
+
|
|
38
|
+
/** Buddy 档案与连登信息。 */
|
|
39
|
+
const BUDDY_INFO_PATH = '/activity/growth/buddy/info'
|
|
40
|
+
const STREAK_PATH = '/activity/growth/streak'
|
|
41
|
+
|
|
42
|
+
/** 连登兑换与抽奖。 */
|
|
43
|
+
const REDEEM_PATH = '/activity/growth/redeem'
|
|
44
|
+
const LOTTERY_SUMMARY_PATH = '/activity/growth/lottery/summary'
|
|
45
|
+
const LOTTERY_DRAW_PATH = '/activity/growth/lottery/draw'
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 旅行地点固定用 4(古镇客栈)。
|
|
49
|
+
* 上游 4 个地点的收益与时长区间完全相同,没有最优解,所以固定一个即可。
|
|
50
|
+
*/
|
|
51
|
+
const TRAVEL_LOCATION_ID = 4
|
|
52
|
+
|
|
53
|
+
/** 「今天已签到」的标记:命中说明是幂等成功,不是失败。 */
|
|
54
|
+
const ALREADY_CHECKIN_MARKERS = ['已签到', 'already']
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 发一个任务请求。
|
|
58
|
+
*
|
|
59
|
+
* 与普通 API 的区别:任务域要带 X-CodeBuddy-Request(风控闸门头),
|
|
60
|
+
* 计费域还要带 X-User-Id / X-Domain / X-Enterprise-Id。
|
|
61
|
+
*
|
|
62
|
+
* @returns {Promise<{ok:boolean, code:number, msg:string, data:any}>}
|
|
63
|
+
*/
|
|
64
|
+
async function taskRequest(credential, base, path, { method = 'GET', body } = {}) {
|
|
65
|
+
const headers = {
|
|
66
|
+
...commonHeaders(credential),
|
|
67
|
+
authorization: `Bearer ${credential.accessToken}`,
|
|
68
|
+
}
|
|
69
|
+
if (credential.uid) headers['x-user-id'] = credential.uid
|
|
70
|
+
if (credential.enterpriseId) {
|
|
71
|
+
headers['x-enterprise-id'] = credential.enterpriseId
|
|
72
|
+
headers['x-tenant-id'] = credential.enterpriseId
|
|
73
|
+
}
|
|
74
|
+
if (credential.domain) headers['x-domain'] = credential.domain
|
|
75
|
+
|
|
76
|
+
const response = await fetch(`${base}${path}`, {
|
|
77
|
+
method,
|
|
78
|
+
headers,
|
|
79
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
80
|
+
signal: AbortSignal.timeout(TASK_TIMEOUT_MS),
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
const text = await response.text()
|
|
84
|
+
let envelope
|
|
85
|
+
try {
|
|
86
|
+
envelope = text === '' ? {} : JSON.parse(text)
|
|
87
|
+
} catch {
|
|
88
|
+
return { ok: false, code: -1, msg: `上游返回非 JSON(HTTP ${response.status})`, data: undefined }
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
ok: response.ok && (envelope.code === 0 || envelope.code === undefined),
|
|
92
|
+
code: Number(envelope.code ?? 0),
|
|
93
|
+
msg: String(envelope.msg ?? ''),
|
|
94
|
+
data: envelope.data,
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** 是否「今天已签到」这类幂等成功。 */
|
|
99
|
+
function isAlreadyCheckin(msg) {
|
|
100
|
+
const lower = String(msg ?? '').toLowerCase()
|
|
101
|
+
return ALREADY_CHECKIN_MARKERS.some(marker => msg.includes(marker) || lower.includes(marker.toLowerCase()))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 生成幂等令牌。上游要求兑换/抽奖带 client_token,
|
|
106
|
+
* 同一 token 重复提交不会重复发奖——这是防重复扣费的机制。
|
|
107
|
+
*/
|
|
108
|
+
function clientToken() {
|
|
109
|
+
const bytes = new Uint8Array(16)
|
|
110
|
+
crypto.getRandomValues(bytes)
|
|
111
|
+
const hex = [...bytes].map(b => b.toString(16).padStart(2, '0')).join('')
|
|
112
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** 国内版账号才能做这些任务;国际版没有这套活动体系。 */
|
|
116
|
+
export function tasksSupported(credential) {
|
|
117
|
+
return credential.region !== 'global'
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 签到。
|
|
122
|
+
*
|
|
123
|
+
* @returns {Promise<{status:'done'|'already'|'failed', message:string}>}
|
|
124
|
+
*/
|
|
125
|
+
export async function checkin(credential) {
|
|
126
|
+
if (!tasksSupported(credential)) {
|
|
127
|
+
return { status: 'skipped', message: '国际版账号无签到体系' }
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
const result = await taskRequest(credential, CN_BILLING_BASE, CHECKIN_PATH, { method: 'POST', body: {} })
|
|
131
|
+
if (result.ok) return { status: 'done', message: '签到成功' }
|
|
132
|
+
// 重复签到是幂等成功,不能算失败。
|
|
133
|
+
if (isAlreadyCheckin(result.msg)) return { status: 'already', message: '今天已签到' }
|
|
134
|
+
return { status: 'failed', message: `code=${result.code} ${result.msg}` }
|
|
135
|
+
} catch (error) {
|
|
136
|
+
return { status: 'failed', message: `请求失败:${error?.message ?? error}` }
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** 签到后查一次余额,用于回报「签到带来多少积分」。 */
|
|
141
|
+
export async function fetchResource(credential) {
|
|
142
|
+
try {
|
|
143
|
+
const result = await taskRequest(credential, CN_BILLING_BASE, RESOURCE_PATH, {
|
|
144
|
+
method: 'POST',
|
|
145
|
+
body: {
|
|
146
|
+
PageNumber: 1,
|
|
147
|
+
PageSize: 100,
|
|
148
|
+
ProductCode: 'p_tcaca',
|
|
149
|
+
Status: [0, 3],
|
|
150
|
+
},
|
|
151
|
+
})
|
|
152
|
+
if (!result.ok) return undefined
|
|
153
|
+
const accounts = result.data?.Response?.Data?.Accounts
|
|
154
|
+
if (!Array.isArray(accounts)) return undefined
|
|
155
|
+
let total = 0
|
|
156
|
+
for (const account of accounts) {
|
|
157
|
+
const remain = typeof account?.CapacityRemain === 'number' ? account.CapacityRemain : 0
|
|
158
|
+
if (remain > 0) total += remain
|
|
159
|
+
}
|
|
160
|
+
return total
|
|
161
|
+
} catch {
|
|
162
|
+
return undefined
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** 读连登信息:连续登录天数、下一档位还差几天、可兑换档位。 */
|
|
167
|
+
export async function fetchStreak(credential) {
|
|
168
|
+
if (!tasksSupported(credential)) return undefined
|
|
169
|
+
try {
|
|
170
|
+
const result = await taskRequest(credential, chatBaseOf(credential.domain), STREAK_PATH)
|
|
171
|
+
if (!result.ok) return undefined
|
|
172
|
+
const streak = result.data?.streak
|
|
173
|
+
if (streak === undefined || streak === null) return undefined
|
|
174
|
+
// 兑换状态的权威判定:tier_*_status 由上游给(locked / 已解锁 / 已兑换)。
|
|
175
|
+
// 不用「天数 >= 门槛」自己算——上游还会考虑补签卡、月份重置等因素。
|
|
176
|
+
const redemption = result.data?.redemption_status ?? {}
|
|
177
|
+
const tiers = Array.isArray(redemption.tiers) ? redemption.tiers : []
|
|
178
|
+
const redeemable = tiers
|
|
179
|
+
.map(tier => ({
|
|
180
|
+
tier: String(tier?.tier ?? ''),
|
|
181
|
+
days: Number(tier?.days ?? 0),
|
|
182
|
+
status: String(redemption[`tier_${String(tier?.tier ?? '')}_status`] ?? 'unknown'),
|
|
183
|
+
}))
|
|
184
|
+
.filter(tier => tier.tier !== '')
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
days: Number(streak.days ?? 0),
|
|
188
|
+
monthTotalDays: Number(streak.month_total_days ?? 0),
|
|
189
|
+
nextTier: streak.next_tier ?? null,
|
|
190
|
+
nextTierRemaining: Number(streak.next_tier_remaining ?? 0),
|
|
191
|
+
makeupCards: Number(result.data?.makeup_cards?.balance ?? 0),
|
|
192
|
+
// 可兑换的档位(status 不是 locked、也不是已兑换)
|
|
193
|
+
redeemable: redeemable.filter(t => t.status === 'unlocked' || t.status === 'available' || t.status === 'redeemable'),
|
|
194
|
+
tiers: redeemable,
|
|
195
|
+
}
|
|
196
|
+
} catch {
|
|
197
|
+
return undefined
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** 读 Buddy(猫)档案;没有猫返回 undefined。 */
|
|
202
|
+
export async function fetchBuddy(credential) {
|
|
203
|
+
if (!tasksSupported(credential)) return undefined
|
|
204
|
+
try {
|
|
205
|
+
const result = await taskRequest(credential, chatBaseOf(credential.domain), BUDDY_INFO_PATH)
|
|
206
|
+
if (!result.ok) return undefined
|
|
207
|
+
const buddy = result.data?.buddy
|
|
208
|
+
if (buddy === undefined || buddy === null) return undefined
|
|
209
|
+
return {
|
|
210
|
+
id: Number(buddy.instance_id ?? 0),
|
|
211
|
+
name: String(buddy.name ?? ''),
|
|
212
|
+
rarity: String(buddy.rarity ?? ''),
|
|
213
|
+
}
|
|
214
|
+
} catch {
|
|
215
|
+
return undefined
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* 只读:读猫猫旅行状态(不改动任何东西,供界面展示)。
|
|
221
|
+
* 与 {@link travel} 的区别是它只查不派、不领。
|
|
222
|
+
*/
|
|
223
|
+
export async function travelStatus(credential) {
|
|
224
|
+
if (!tasksSupported(credential)) return undefined
|
|
225
|
+
try {
|
|
226
|
+
const result = await taskRequest(credential, chatBaseOf(credential.domain), TRAVEL_STATUS_PATH)
|
|
227
|
+
if (!result.ok) return undefined
|
|
228
|
+
return {
|
|
229
|
+
state: String(result.data?.state ?? 'unknown'),
|
|
230
|
+
recordId: Number(result.data?.record_id ?? 0),
|
|
231
|
+
dailyLimitReached: result.data?.daily_limit_reached === true,
|
|
232
|
+
rewardCredit: Number(result.data?.reward_credit ?? 0) || undefined,
|
|
233
|
+
}
|
|
234
|
+
} catch {
|
|
235
|
+
return undefined
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** 读抽奖次数(只读)。 */
|
|
240
|
+
export async function fetchLottery(credential) {
|
|
241
|
+
if (!tasksSupported(credential)) return undefined
|
|
242
|
+
try {
|
|
243
|
+
const result = await taskRequest(credential, chatBaseOf(credential.domain), LOTTERY_SUMMARY_PATH)
|
|
244
|
+
if (!result.ok) return undefined
|
|
245
|
+
return { chances: Number(result.data?.chances ?? 0) }
|
|
246
|
+
} catch {
|
|
247
|
+
return undefined
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* 连登兑换:把已解锁的档位兑换掉(发积分/能量/补签卡/抽奖次数)。
|
|
253
|
+
*
|
|
254
|
+
* 档位按连续登录天数解锁(7/14/28 天)。未解锁的档位上游返回 403,
|
|
255
|
+
* 属于正常情况,不当作失败。
|
|
256
|
+
*
|
|
257
|
+
* @returns {Promise<{status:string, message:string, redeemed:string[]}>}
|
|
258
|
+
*/
|
|
259
|
+
export async function redeemTiers(credential) {
|
|
260
|
+
if (!tasksSupported(credential)) {
|
|
261
|
+
return { status: 'skipped', message: '国际版账号无连登体系', redeemed: [] }
|
|
262
|
+
}
|
|
263
|
+
const base = chatBaseOf(credential.domain)
|
|
264
|
+
const redeemed = []
|
|
265
|
+
|
|
266
|
+
// 先读状态,拿到各档位的解锁情况与已兑换次数。
|
|
267
|
+
const streakResult = await taskRequest(credential, base, STREAK_PATH)
|
|
268
|
+
if (!streakResult.ok) {
|
|
269
|
+
return { status: 'failed', message: `读连登失败:code=${streakResult.code}`, redeemed: [] }
|
|
270
|
+
}
|
|
271
|
+
const status = streakResult.data?.redemption_status
|
|
272
|
+
|
|
273
|
+
// 用上游给的 tier_<n>d_status 判定能否兑换,不自己比天数——
|
|
274
|
+
// 上游还会考虑补签卡、月份重置等因素,自己算会漏。
|
|
275
|
+
const tiers = Array.isArray(status?.tiers) ? status.tiers : []
|
|
276
|
+
for (const tier of tiers) {
|
|
277
|
+
const name = String(tier?.tier ?? '')
|
|
278
|
+
if (name === '') continue
|
|
279
|
+
const tierStatus = String(status?.[`tier_${name}_status`] ?? 'locked')
|
|
280
|
+
// 只兑换上游明确说可领的档位;locked / 已兑换都跳过
|
|
281
|
+
const redeemable = tierStatus === 'unlocked' || tierStatus === 'available' || tierStatus === 'redeemable'
|
|
282
|
+
if (!redeemable) continue
|
|
283
|
+
|
|
284
|
+
const result = await taskRequest(credential, base, REDEEM_PATH, {
|
|
285
|
+
method: 'POST',
|
|
286
|
+
body: { tier: name, client_token: clientToken() },
|
|
287
|
+
})
|
|
288
|
+
if (result.ok) redeemed.push(name)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const message = redeemed.length > 0 ? `已兑换 ${redeemed.join('、')}` : '暂无可兑换档位'
|
|
292
|
+
return { status: 'done', message, redeemed }
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* 把当前的抽奖次数全部抽掉。
|
|
297
|
+
*
|
|
298
|
+
* 抽奖次数只能从连登兑换获得,所以顺序上必须「先兑换、后抽奖」。
|
|
299
|
+
*
|
|
300
|
+
* @returns {Promise<{status:string, message:string, drawn:number}>}
|
|
301
|
+
*/
|
|
302
|
+
export async function drawLottery(credential) {
|
|
303
|
+
if (!tasksSupported(credential)) {
|
|
304
|
+
return { status: 'skipped', message: '国际版账号无抽奖体系', drawn: 0 }
|
|
305
|
+
}
|
|
306
|
+
const base = chatBaseOf(credential.domain)
|
|
307
|
+
|
|
308
|
+
const before = await fetchLottery(credential)
|
|
309
|
+
const remaining = before?.chances ?? 0
|
|
310
|
+
if (remaining <= 0) return { status: 'done', message: '无抽奖次数', drawn: 0 }
|
|
311
|
+
|
|
312
|
+
let drawn = 0
|
|
313
|
+
// 上限保护:避免上游计数异常时无限循环。
|
|
314
|
+
const maxDraws = Math.min(remaining, 50)
|
|
315
|
+
for (let i = 0; i < maxDraws; i += 1) {
|
|
316
|
+
const result = await taskRequest(credential, base, LOTTERY_DRAW_PATH, {
|
|
317
|
+
method: 'POST',
|
|
318
|
+
body: { client_token: clientToken() },
|
|
319
|
+
})
|
|
320
|
+
if (!result.ok) break
|
|
321
|
+
drawn += 1
|
|
322
|
+
}
|
|
323
|
+
return { status: 'done', message: `抽了 ${drawn} 次`, drawn }
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* 猫猫旅行:一次调用完成完整的「领奖 → 派出」状态机。
|
|
328
|
+
*
|
|
329
|
+
* 上游把旅行做成了三态:
|
|
330
|
+
* - arrived → 先领奖,再尝试派出(同一天可以领完再派)
|
|
331
|
+
* - idle → 直接派出
|
|
332
|
+
* - traveling → 什么都不做(在途,等下次调用)
|
|
333
|
+
*
|
|
334
|
+
* @returns {Promise<{status:string, message:string, reward?:number}>}
|
|
335
|
+
*/
|
|
336
|
+
export async function travel(credential) {
|
|
337
|
+
if (!tasksSupported(credential)) {
|
|
338
|
+
return { status: 'skipped', message: '国际版账号无旅行体系' }
|
|
339
|
+
}
|
|
340
|
+
const base = chatBaseOf(credential.domain)
|
|
341
|
+
|
|
342
|
+
try {
|
|
343
|
+
// 先看状态。没猫的账号直接跳过(领养是另一个流程,不在每日任务范围)。
|
|
344
|
+
const status = await taskRequest(credential, base, TRAVEL_STATUS_PATH)
|
|
345
|
+
if (!status.ok) {
|
|
346
|
+
return { status: 'failed', message: `查状态失败:code=${status.code} ${status.msg}` }
|
|
347
|
+
}
|
|
348
|
+
const state = String(status.data?.state ?? 'unknown')
|
|
349
|
+
const recordId = Number(status.data?.record_id ?? 0)
|
|
350
|
+
const dailyLimitReached = status.data?.daily_limit_reached === true
|
|
351
|
+
|
|
352
|
+
const steps = []
|
|
353
|
+
|
|
354
|
+
// 1) 到站 → 领奖
|
|
355
|
+
let reward
|
|
356
|
+
if (state === 'arrived' && recordId > 0) {
|
|
357
|
+
const claim = await taskRequest(credential, base, TRAVEL_CLAIM_PATH, {
|
|
358
|
+
method: 'POST',
|
|
359
|
+
body: { record_id: recordId },
|
|
360
|
+
})
|
|
361
|
+
if (claim.ok) {
|
|
362
|
+
reward = Number(claim.data?.reward_credit ?? 0) || undefined
|
|
363
|
+
steps.push(`领奖 +${reward ?? 0}`)
|
|
364
|
+
} else if (isAlreadyCheckin(claim.msg) || /already|已领/.test(claim.msg)) {
|
|
365
|
+
steps.push('奖励已领')
|
|
366
|
+
} else {
|
|
367
|
+
steps.push(`领奖失败(code=${claim.code})`)
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// 2) 空闲(或刚领完)→ 派出
|
|
372
|
+
if ((state === 'idle' || state === 'arrived') && !dailyLimitReached) {
|
|
373
|
+
const depart = await taskRequest(credential, base, TRAVEL_DEPART_PATH, {
|
|
374
|
+
method: 'POST',
|
|
375
|
+
body: { location_id: TRAVEL_LOCATION_ID },
|
|
376
|
+
})
|
|
377
|
+
if (depart.ok) steps.push('已派出')
|
|
378
|
+
else if (/limit|已派/.test(depart.msg)) steps.push('今日已派')
|
|
379
|
+
else steps.push(`派出失败(code=${depart.code})`)
|
|
380
|
+
} else if (dailyLimitReached) {
|
|
381
|
+
steps.push('今日已派')
|
|
382
|
+
} else if (state === 'traveling') {
|
|
383
|
+
steps.push('在途中')
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (steps.length === 0) steps.push(`状态 ${state},无需操作`)
|
|
387
|
+
return { status: 'done', message: steps.join(','), reward }
|
|
388
|
+
} catch (error) {
|
|
389
|
+
return { status: 'failed', message: `请求失败:${error?.message ?? error}` }
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* 对一个账号执行全部每日任务。
|
|
395
|
+
*
|
|
396
|
+
* 顺序有意义:签到会让冷却账号恢复,所以排在前面。
|
|
397
|
+
*
|
|
398
|
+
* @param {object} credential
|
|
399
|
+
* @returns {Promise<object>} 各任务的结果
|
|
400
|
+
*/
|
|
401
|
+
export async function runDailyTasks(credential) {
|
|
402
|
+
const out = { accountId: credential.id, nickname: credential.nickname ?? credential.uid }
|
|
403
|
+
|
|
404
|
+
// 签到
|
|
405
|
+
out.checkin = await checkin(credential)
|
|
406
|
+
|
|
407
|
+
// 签到后立刻看余额变化
|
|
408
|
+
const credits = await fetchResource(credential)
|
|
409
|
+
if (credits !== undefined) out.credits = credits
|
|
410
|
+
|
|
411
|
+
// 连登与猫(只读,用于展示)
|
|
412
|
+
const [streak, buddy] = await Promise.all([
|
|
413
|
+
fetchStreak(credential),
|
|
414
|
+
fetchBuddy(credential),
|
|
415
|
+
])
|
|
416
|
+
if (streak !== undefined) out.streak = streak
|
|
417
|
+
if (buddy !== undefined) out.buddy = buddy
|
|
418
|
+
|
|
419
|
+
// 连登兑换(必须在抽奖之前:抽奖次数只能从兑换获得)
|
|
420
|
+
out.redeem = await redeemTiers(credential)
|
|
421
|
+
|
|
422
|
+
// 抽奖
|
|
423
|
+
out.lottery = await drawLottery(credential)
|
|
424
|
+
|
|
425
|
+
// 旅行
|
|
426
|
+
out.travel = await travel(credential)
|
|
427
|
+
|
|
428
|
+
return out
|
|
429
|
+
}
|