tt-help-cli-ycl 1.4.183 → 1.4.185
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/package.json +1 -1
- package/scripts/test-candidate-retry.mjs +496 -0
- package/scripts/test-crash-circuit-breaker.mjs +250 -0
- package/scripts/test-error-auto-reset.mjs +103 -0
- package/scripts/test-error-report.mjs +215 -0
- package/scripts/test-login-pool-acquire-order.mjs +92 -26
- package/scripts/test-login-pool-faceban-e2e.mjs +131 -0
- package/scripts/test-login-pool-faceban.mjs +235 -0
- package/scripts/test-login-pool-ui.mjs +2 -1
- package/scripts/test-login-pool.mjs +97 -54
- package/scripts/test-process-manager-label.mjs +145 -0
- package/src/cli/explore.js +171 -25
- package/src/cli/search.js +171 -18
- package/src/lib/browser/health-checker.js +19 -3
- package/src/lib/browser/page.js +23 -0
- package/src/lib/error-report.js +119 -0
- package/src/lib/login-pool-client.js +123 -0
- package/src/watch/data-store.js +25 -0
- package/src/watch/login-pool.js +118 -81
- package/src/watch/public/app.js +7 -2
- package/src/watch/public/index.html +2 -1
- package/src/watch/server.js +53 -8
- package/src/watchdog/process-manager.js +71 -6
- package/src/watchdog/reporter.js +2 -0
package/src/cli/explore.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getOrCreatePage,
|
|
3
3
|
isBrowserClosedError,
|
|
4
|
+
isRetryableRestoreFailure,
|
|
4
5
|
relaunchBrowser,
|
|
5
6
|
} from "../lib/browser/page.js";
|
|
6
7
|
import {
|
|
@@ -28,7 +29,6 @@ import {
|
|
|
28
29
|
import { showResourceUsage } from "../utils/index.js";
|
|
29
30
|
import {
|
|
30
31
|
HealthChecker,
|
|
31
|
-
MEMORY_THRESHOLD,
|
|
32
32
|
ROTATE_INTERVAL_MS,
|
|
33
33
|
} from "../lib/browser/health-checker.js";
|
|
34
34
|
import {
|
|
@@ -37,8 +37,16 @@ import {
|
|
|
37
37
|
gotoTiktok,
|
|
38
38
|
injectPoolAccount,
|
|
39
39
|
ensurePoolLogin,
|
|
40
|
+
crashBackoffMs,
|
|
41
|
+
retryRestoreOnNavFailure,
|
|
42
|
+
CANDIDATE_INJECT_MAX_RETRIES,
|
|
43
|
+
CANDIDATE_INJECT_DELAY_MS,
|
|
44
|
+
decideCandidateInjectAction,
|
|
45
|
+
isRetryableInjectFailure,
|
|
40
46
|
} from "../lib/login-pool-client.js";
|
|
47
|
+
import { getClientConfig } from "../watchdog/reporter.js";
|
|
41
48
|
import { createApiClient } from "../lib/api-client.js";
|
|
49
|
+
import { createErrorReporter } from "../lib/error-report.js";
|
|
42
50
|
import { createCDNHandler } from "../lib/cdn-handler.js";
|
|
43
51
|
import { proxyPortOf, triggerProxySwitch } from "../clash/proxy-switch.js";
|
|
44
52
|
import path from "path";
|
|
@@ -48,6 +56,7 @@ const MAX_RETRY_WAIT = 5 * 60 * 1000;
|
|
|
48
56
|
const MAX_REBUILD_ATTEMPTS = 5; // 账户切换后导航恢复:连续重启浏览器超过该次数仍失败则上报并退出
|
|
49
57
|
const STARTUP_TIKTOK_URL = "https://www.tiktok.com/@ycl5007";
|
|
50
58
|
const PAGE_GOTO_TIMEOUT = 60000; // 页面导航超时 60 秒(账户切换后需要更长时间)
|
|
59
|
+
const CRASH_BACKOFF_SLICE_MS = 1000; // 崩溃熔断/恢复重试等待的分片粒度(分片醒检 shuttingDown)
|
|
51
60
|
|
|
52
61
|
// 换号原因受控 code(2026-10-30 定稿:服务端 switchStats 按账号聚合展示;
|
|
53
62
|
// 只用这组固定值上报,不传自由文本;startup 首次取号不带 reason)
|
|
@@ -110,6 +119,21 @@ export async function handleExplore(options) {
|
|
|
110
119
|
let poolClient = null; // 账号中心客户端
|
|
111
120
|
let poolAccount = null; // 当前在手账号(含 cookies 与租约 id)
|
|
112
121
|
let poolAccountSince = Date.now(); // 当前账号开始使用时刻(48min 换号计时)
|
|
122
|
+
// 崩溃熔断状态(F16 实录):浏览器级注入失败轮数 + 退避窗口截止时刻(窗口内不取号)
|
|
123
|
+
let crashStreak = 0;
|
|
124
|
+
let crashBackoffUntil = 0;
|
|
125
|
+
|
|
126
|
+
/** 分片 sleep:每 CRASH_BACKOFF_SLICE_MS 醒检 shuttingDown,被中断返回 false */
|
|
127
|
+
const sleepSliced = async (totalMs) => {
|
|
128
|
+
let remaining = totalMs;
|
|
129
|
+
while (remaining > 0) {
|
|
130
|
+
if (shuttingDown) return false;
|
|
131
|
+
const slice = Math.min(CRASH_BACKOFF_SLICE_MS, remaining);
|
|
132
|
+
await new Promise((r) => setTimeout(r, slice));
|
|
133
|
+
remaining -= slice;
|
|
134
|
+
}
|
|
135
|
+
return !shuttingDown;
|
|
136
|
+
};
|
|
113
137
|
|
|
114
138
|
const shutdown = async (signal) => {
|
|
115
139
|
if (shuttingDown) return;
|
|
@@ -177,11 +201,14 @@ export async function handleExplore(options) {
|
|
|
177
201
|
|
|
178
202
|
const poolMode = !exploreNoPool;
|
|
179
203
|
const poolPort = explorePort || exploreBasePort || 9222;
|
|
204
|
+
// 内存阈值可配置覆盖(~/.tt-help.json memoryThreshold,默认 0.95):F16 类 7G 双浏览器机可调低提前重启
|
|
205
|
+
const memoryThresholdConfig = getClientConfig().memoryThreshold;
|
|
180
206
|
const healthChecker = poolMode
|
|
181
|
-
? new HealthChecker({ basePort: poolPort, totalAccounts: 1 })
|
|
207
|
+
? new HealthChecker({ basePort: poolPort, totalAccounts: 1, memoryThreshold: memoryThresholdConfig })
|
|
182
208
|
: new HealthChecker({
|
|
183
209
|
basePort: exploreBasePort,
|
|
184
210
|
totalAccounts: explorePortCount,
|
|
211
|
+
memoryThreshold: memoryThresholdConfig,
|
|
185
212
|
});
|
|
186
213
|
let currentAccount = healthChecker.getCurrentAccount();
|
|
187
214
|
|
|
@@ -246,6 +273,15 @@ export async function handleExplore(options) {
|
|
|
246
273
|
meta: { port: cdpOptions.port },
|
|
247
274
|
});
|
|
248
275
|
|
|
276
|
+
// 客户端错误上报封装:自动附带 poolUsername(报错时在手池号)与 proxyNode(当前 clash 节点)
|
|
277
|
+
const errorReporter = createErrorReporter({
|
|
278
|
+
apiPost,
|
|
279
|
+
serverUrl,
|
|
280
|
+
userId,
|
|
281
|
+
proxyUrl: resolvedProxy,
|
|
282
|
+
getState: () => ({ poolUsername: poolAccount?.username || "" }),
|
|
283
|
+
});
|
|
284
|
+
|
|
249
285
|
await apiGet(`${serverUrl}/api/stats`);
|
|
250
286
|
|
|
251
287
|
if (exploreUsernames && exploreUsernames.length > 0) {
|
|
@@ -358,13 +394,50 @@ export async function handleExplore(options) {
|
|
|
358
394
|
|
|
359
395
|
/**
|
|
360
396
|
* 换号(以号为主键):先取新号(旧号仍在手,无空窗)→ 注入验证 → 还旧号。
|
|
361
|
-
* 候选最多试 5
|
|
362
|
-
*
|
|
397
|
+
* 候选最多试 5 个;注入失败按类型分流(decideCandidateInjectAction):
|
|
398
|
+
* 浏览器级崩溃 → 熔断重启;确定性号死(会话过期/身份不符)→ 立即释放回池换下一个;
|
|
399
|
+
* 瞬态/环境失败 → 同一候选原号重试(共 3 次尝试,间隔 15s)全败才释放;
|
|
400
|
+
* 池空/服务器不可达 → 保旧号返回 false,由主循环「无在手账号」补取逻辑兑底。
|
|
363
401
|
* @param code 受控换号原因(SWITCH_REASON,上报服务端 switchStats;可选);
|
|
364
402
|
* 首选按外层 call site 上报,候选注入失败后的再取号 = inject_fail
|
|
365
403
|
*/
|
|
366
|
-
async function switchPoolAccount(reason, code) {
|
|
404
|
+
async function switchPoolAccount(reason, code, opts = {}) {
|
|
367
405
|
console.error(`\n[换号] ${reason},从账号中心取新账号...`);
|
|
406
|
+
// 崩溃熔断:退避窗口内不取号(保护账号池),分片 sleep 响应 shuttingDown
|
|
407
|
+
const backoffWaitMs = Math.max(0, crashBackoffUntil - Date.now());
|
|
408
|
+
if (backoffWaitMs > 0) {
|
|
409
|
+
console.error(
|
|
410
|
+
`[浏览器] 崩溃熔断中,${Math.ceil(backoffWaitMs / 1000)}s 后再试(不取号,保护账号池)`,
|
|
411
|
+
);
|
|
412
|
+
if (!(await sleepSliced(backoffWaitMs))) return false;
|
|
413
|
+
}
|
|
414
|
+
// 候选注入失败命中浏览器级崩溃签名时的处置(首判与重试中途重判共用):
|
|
415
|
+
// crashStreak++ 按轮退避 + 释放候选 + 上报 + 转同号重启;返回值由调用方直接 return
|
|
416
|
+
async function abortCandidateLoopOnCrash(crashReason, candidate, fromRecovery) {
|
|
417
|
+
// 浏览器级崩溃:不怪账号,立即中止候选循环(不消耗 inject_fail/recover_fail 口径),
|
|
418
|
+
// 已取候选照常 release,转同号重启(与内存超限路径同语义);按轮熔断退避
|
|
419
|
+
crashStreak++;
|
|
420
|
+
const backoffMs = crashBackoffMs(crashStreak);
|
|
421
|
+
crashBackoffUntil = Date.now() + backoffMs;
|
|
422
|
+
console.error(
|
|
423
|
+
`[换号] @${candidate.username} 注入失败为浏览器级崩溃(${crashReason}),中止候选循环(第 ${crashStreak} 轮,熔断 ${Math.round(backoffMs / 1000)}s),释放候选...`,
|
|
424
|
+
);
|
|
425
|
+
await poolClient.release(candidate).catch(() => {});
|
|
426
|
+
await errorReporter.report({
|
|
427
|
+
username: poolAccount ? poolAccount.username : "unknown",
|
|
428
|
+
errorType: "account_switch_failed",
|
|
429
|
+
errorMessage: `注入失败为浏览器级崩溃,中止候选循环转同号重启(第${crashStreak}轮,熔断${Math.round(backoffMs / 1000)}s): ${crashReason}`,
|
|
430
|
+
stage: "account-switch",
|
|
431
|
+
}).catch(() => {});
|
|
432
|
+
if (fromRecovery) {
|
|
433
|
+
// 恢复链中(重启后恢复失败触发的换号)不再递归重启,保留现场等熔断窗口
|
|
434
|
+
console.error(`[换号] 恢复链中不再递归重启浏览器,保留现场等待熔断窗口`);
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
return await restartPoolBrowser(
|
|
438
|
+
`注入验证遭遇浏览器级崩溃(${crashReason})`,
|
|
439
|
+
);
|
|
440
|
+
}
|
|
368
441
|
for (let attempt = 0; attempt < 5; attempt++) {
|
|
369
442
|
let next = null;
|
|
370
443
|
try {
|
|
@@ -375,15 +448,71 @@ export async function handleExplore(options) {
|
|
|
375
448
|
);
|
|
376
449
|
return false;
|
|
377
450
|
}
|
|
378
|
-
|
|
451
|
+
let r = await injectPoolAccount(page, next, {
|
|
379
452
|
verifyUsername: next.username,
|
|
380
453
|
}).catch((e) => ({ ok: false, reason: e.message }));
|
|
381
454
|
if (!r.ok) {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
455
|
+
// 注入失败三路分流(f11 48h 实测:~17% 瞬态环境失败曾连烧 5 候选全死于同一环境):
|
|
456
|
+
// 崩溃 → 既有熔断(中止候选循环);确定性号死 → 立即弃号换下一个候选(现状行为);
|
|
457
|
+
// 瞬态/环境失败 → 同一候选原号重试(共 3 次尝试,间隔 15s),重试期间租约保持在手
|
|
458
|
+
let action = decideCandidateInjectAction(r.reason, isBrowserClosedError);
|
|
459
|
+
if (action === "circuit-break") {
|
|
460
|
+
return await abortCandidateLoopOnCrash(r.reason, next, opts.fromRecovery);
|
|
461
|
+
}
|
|
462
|
+
if (action === "retry") {
|
|
463
|
+
// 瞬态/环境失败(超时/AbortError/net::ERR_*/Execution context destroyed/无账号数据/导航类):
|
|
464
|
+
// 原号重试最多 2 次(共 3 次尝试),间隔 15s,重试期间不 release(租约保持在手);
|
|
465
|
+
// 确定性号死(会话过期/身份不符)不进此分支,直接弃号换下一个候选
|
|
466
|
+
r = await retryRestoreOnNavFailure(
|
|
467
|
+
() =>
|
|
468
|
+
injectPoolAccount(page, next, { verifyUsername: next.username }),
|
|
469
|
+
r.reason,
|
|
470
|
+
{
|
|
471
|
+
isRetryable: isRetryableInjectFailure,
|
|
472
|
+
maxRetries: CANDIDATE_INJECT_MAX_RETRIES,
|
|
473
|
+
delayMs: CANDIDATE_INJECT_DELAY_MS,
|
|
474
|
+
sleep: (ms) => sleepSliced(ms),
|
|
475
|
+
shouldAbort: () => shuttingDown,
|
|
476
|
+
onRetry: (n, delayMs, why) =>
|
|
477
|
+
console.error(
|
|
478
|
+
`[换号] @${next.username} 第${n}次注入失败(瞬态,${why}),${Math.round(delayMs / 1000)}s 后原号重试`,
|
|
479
|
+
),
|
|
480
|
+
},
|
|
481
|
+
);
|
|
482
|
+
if (r.aborted) {
|
|
483
|
+
// shuttingDown 中断:释放候选退出(租约主动还干净,服务端 TTL 兑底)
|
|
484
|
+
console.error(`[换号] @${next.username} 原号重试等待中被关闭信号中断,释放候选`);
|
|
485
|
+
await poolClient.release(next).catch(() => {});
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
if (r.ok && r.attempts > 1) {
|
|
489
|
+
console.error(
|
|
490
|
+
`[换号] @${next.username} 原号重试成功(第 ${r.attempts} 次尝试)`,
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
if (!r.ok) {
|
|
494
|
+
// 重试中途失败签名可能升级为浏览器级崩溃(如首败 timeout、再败 Page crashed):
|
|
495
|
+
// 对最终原因重判,命中熔断则中止候选循环(不弃号给下一候选——坏浏览器再吃一个号),
|
|
496
|
+
// 升级为确定性号死则 fall through 到下方弃号分支换候选
|
|
497
|
+
action = decideCandidateInjectAction(r.reason, isBrowserClosedError);
|
|
498
|
+
if (action === "circuit-break") {
|
|
499
|
+
return await abortCandidateLoopOnCrash(r.reason, next, opts.fromRecovery);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
if (!r.ok) {
|
|
504
|
+
if (r.attempts > 1) {
|
|
505
|
+
console.error(
|
|
506
|
+
`[换号] @${next.username} 原号重试 ${r.attempts - 1} 次后仍失败(共 ${r.attempts} 次尝试,${r.reason}),释放候选换下一个...`,
|
|
507
|
+
);
|
|
508
|
+
} else {
|
|
509
|
+
console.error(
|
|
510
|
+
`[换号] @${next.username} 注入验证失败(${r.reason}),释放候选换下一个...`,
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
await poolClient.release(next).catch(() => {});
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
387
516
|
}
|
|
388
517
|
// 新号验证通过:切换在手状态,再还旧号(谁持账号谁还)
|
|
389
518
|
const prev = poolAccount;
|
|
@@ -391,6 +520,7 @@ export async function handleExplore(options) {
|
|
|
391
520
|
poolAccountSince = Date.now();
|
|
392
521
|
loggedIn = true;
|
|
393
522
|
lastFollowSuccessTime = Date.now();
|
|
523
|
+
crashStreak = 0; // 注入验证成功即清零熔断轮数
|
|
394
524
|
captchaCount = 0;
|
|
395
525
|
blockedCount = 0;
|
|
396
526
|
cdn.reset();
|
|
@@ -407,8 +537,7 @@ export async function handleExplore(options) {
|
|
|
407
537
|
return true;
|
|
408
538
|
}
|
|
409
539
|
console.error(`[换号] 连续 5 个候选注入验证失败,保留现场等待下轮重试`);
|
|
410
|
-
await
|
|
411
|
-
userId,
|
|
540
|
+
await errorReporter.report({
|
|
412
541
|
username: poolAccount ? poolAccount.username : "unknown",
|
|
413
542
|
errorType: "account_switch_failed",
|
|
414
543
|
errorMessage: "连续5个候选账号注入验证失败(详情见节点日志)",
|
|
@@ -420,21 +549,41 @@ export async function handleExplore(options) {
|
|
|
420
549
|
/** 浏览器重建/重启后恢复登录态:磁盘态一致则免注入,否则用内存 cookies 补注入 */
|
|
421
550
|
async function restorePoolLogin() {
|
|
422
551
|
if (!poolAccount) return false;
|
|
423
|
-
|
|
552
|
+
let r = await ensurePoolLogin(page, poolAccount).catch((e) => ({
|
|
424
553
|
ok: false,
|
|
425
554
|
reason: e.message,
|
|
426
555
|
}));
|
|
556
|
+
if (!r.ok) {
|
|
557
|
+
// 导航类瞬态失败(net::ERR_* / 超时 / 崩溃)重试后再弃号,重试期间不弃号;
|
|
558
|
+
// 账号级失败(身份不符/会话过期)不重试直接走换号
|
|
559
|
+
r = await retryRestoreOnNavFailure(
|
|
560
|
+
() => ensurePoolLogin(page, poolAccount),
|
|
561
|
+
r.reason,
|
|
562
|
+
{
|
|
563
|
+
isRetryable: isRetryableRestoreFailure,
|
|
564
|
+
sleep: (ms) => sleepSliced(ms),
|
|
565
|
+
shouldAbort: () => shuttingDown,
|
|
566
|
+
},
|
|
567
|
+
);
|
|
568
|
+
if (r.aborted) return false;
|
|
569
|
+
if (!r.ok && r.attempts > 1) {
|
|
570
|
+
console.error(`[登录池] 登录态恢复重试 ${r.attempts} 次后仍失败`);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
427
573
|
if (r.ok) {
|
|
428
574
|
loggedIn = true;
|
|
575
|
+
crashStreak = 0; // 恢复成功(含注入验证成功)即清零熔断轮数
|
|
429
576
|
console.error(
|
|
430
577
|
`[登录池] 登录态已恢复 @${poolAccount.username}${r.injected ? "(重新注入)" : "(磁盘态一致)"}`,
|
|
431
578
|
);
|
|
432
579
|
return true;
|
|
433
580
|
}
|
|
434
581
|
console.error(`[登录池] 登录态恢复失败(${r.reason}),转入换号流程`);
|
|
582
|
+
// 恢复链中的换号不再嵌套浏览器重启(fromRecovery):崩溃则熔断退避,不烧池
|
|
435
583
|
return await switchPoolAccount(
|
|
436
584
|
`浏览器重启后无法恢复 @${poolAccount.username} 的登录态`,
|
|
437
585
|
SWITCH_REASON.RECOVER_FAIL,
|
|
586
|
+
{ fromRecovery: true },
|
|
438
587
|
);
|
|
439
588
|
}
|
|
440
589
|
|
|
@@ -450,9 +599,11 @@ export async function handleExplore(options) {
|
|
|
450
599
|
Object.assign(page, rebuiltPage);
|
|
451
600
|
} catch (e) {
|
|
452
601
|
console.error(`[浏览器] 重启失败: ${e.message},转入换号流程`);
|
|
602
|
+
// 恢复链中的换号不再嵌套浏览器重启(fromRecovery):崩溃则熔断退避,不烧池
|
|
453
603
|
return await switchPoolAccount(
|
|
454
604
|
`浏览器重启失败(${e.message})`,
|
|
455
605
|
SWITCH_REASON.RECOVER_FAIL,
|
|
606
|
+
{ fromRecovery: true },
|
|
456
607
|
);
|
|
457
608
|
}
|
|
458
609
|
return await restorePoolLogin();
|
|
@@ -508,8 +659,7 @@ export async function handleExplore(options) {
|
|
|
508
659
|
console.error(
|
|
509
660
|
`[致命] 账户切换后导航持续失败,已重启浏览器 ${rebuildCount - 1} 次仍无法恢复,上报后退出`,
|
|
510
661
|
);
|
|
511
|
-
await
|
|
512
|
-
userId,
|
|
662
|
+
await errorReporter.report({
|
|
513
663
|
username: "account-switch",
|
|
514
664
|
errorType: "account_switch_failed",
|
|
515
665
|
errorMessage: `账户切换后导航持续失败: ${err.message}`,
|
|
@@ -570,8 +720,7 @@ export async function handleExplore(options) {
|
|
|
570
720
|
}),
|
|
571
721
|
reportError: (result, username) =>
|
|
572
722
|
withRetry("report cdn-blocked", () =>
|
|
573
|
-
|
|
574
|
-
userId,
|
|
723
|
+
errorReporter.report({
|
|
575
724
|
username,
|
|
576
725
|
errorType: "cdn_blocked",
|
|
577
726
|
errorMessage: result.error || "CDN限流 (Access Denied)",
|
|
@@ -666,7 +815,7 @@ export async function handleExplore(options) {
|
|
|
666
815
|
// 登录池模式:内存超限→同号重启浏览器;48min→换号(原地切号,不换端口)
|
|
667
816
|
const memRatio = healthChecker.memoryUsage();
|
|
668
817
|
const elapsedMs = Date.now() - poolAccountSince;
|
|
669
|
-
if (memRatio >=
|
|
818
|
+
if (memRatio >= healthChecker.memoryThreshold) {
|
|
670
819
|
await restartPoolBrowser(`系统内存 ${(memRatio * 100).toFixed(1)}% 超阈值`);
|
|
671
820
|
} else if (elapsedMs >= ROTATE_INTERVAL_MS) {
|
|
672
821
|
await handleAccountSwitch(
|
|
@@ -716,8 +865,7 @@ export async function handleExplore(options) {
|
|
|
716
865
|
|
|
717
866
|
// 上报
|
|
718
867
|
await withRetry("report captcha", () =>
|
|
719
|
-
|
|
720
|
-
userId,
|
|
868
|
+
errorReporter.report({
|
|
721
869
|
username: "unknown",
|
|
722
870
|
errorType: "captcha",
|
|
723
871
|
errorMessage: resolved
|
|
@@ -920,8 +1068,7 @@ export async function handleExplore(options) {
|
|
|
920
1068
|
? "network"
|
|
921
1069
|
: "other";
|
|
922
1070
|
await withRetry("report error", () =>
|
|
923
|
-
|
|
924
|
-
userId,
|
|
1071
|
+
errorReporter.report({
|
|
925
1072
|
username,
|
|
926
1073
|
errorType,
|
|
927
1074
|
errorMessage: result.error,
|
|
@@ -956,8 +1103,7 @@ export async function handleExplore(options) {
|
|
|
956
1103
|
console.error(` [验证码] 累计 ${captchaCount} 次`);
|
|
957
1104
|
|
|
958
1105
|
await withRetry("report captcha", () =>
|
|
959
|
-
|
|
960
|
-
userId,
|
|
1106
|
+
errorReporter.report({
|
|
961
1107
|
username,
|
|
962
1108
|
errorType: "captcha",
|
|
963
1109
|
errorMessage: result.captchaMessage || "页面出现验证码",
|