tt-help-cli-ycl 1.4.11 → 1.4.12
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/src/cli/comments.js +27 -2
- package/src/watch/data-store.js +4 -12
- package/src/watch/db-stats.js +4 -4
- package/src/watch/public/app.js +1 -35
- package/src/watch/public/index.html +0 -5
package/package.json
CHANGED
package/src/cli/comments.js
CHANGED
|
@@ -69,6 +69,8 @@ const CFG = {
|
|
|
69
69
|
switchPortOnRecycle: true,
|
|
70
70
|
/** 连续任务处理错误阈值(达到后上报服务端) */
|
|
71
71
|
consecutiveTaskErrorsMax: 20,
|
|
72
|
+
/** 单端口最大运行时间(ms),超时自动切换端口(默认 48 分钟) */
|
|
73
|
+
maxSessionRunTimeMs: 48 * 60 * 1000,
|
|
72
74
|
};
|
|
73
75
|
|
|
74
76
|
/** 从环境变量覆写配置 */
|
|
@@ -105,6 +107,10 @@ function loadEnvConfig() {
|
|
|
105
107
|
1,
|
|
106
108
|
int("COMMENTS_CONSECUTIVE_TASK_ERRORS_MAX", CFG.consecutiveTaskErrorsMax),
|
|
107
109
|
);
|
|
110
|
+
CFG.maxSessionRunTimeMs = Math.max(
|
|
111
|
+
60000,
|
|
112
|
+
int("COMMENTS_MAX_SESSION_RUN_TIME_MS", CFG.maxSessionRunTimeMs),
|
|
113
|
+
);
|
|
108
114
|
}
|
|
109
115
|
|
|
110
116
|
// ────────────────────────────────────────
|
|
@@ -283,6 +289,7 @@ class CdpSessionManager {
|
|
|
283
289
|
this.browser = null;
|
|
284
290
|
this.page = null;
|
|
285
291
|
this.lastRecycleAt = 0;
|
|
292
|
+
this.sessionStartTime = Date.now();
|
|
286
293
|
this._lastMemCheck = 0;
|
|
287
294
|
this._shuttingDown = false;
|
|
288
295
|
}
|
|
@@ -362,14 +369,28 @@ class CdpSessionManager {
|
|
|
362
369
|
this.browser = await ensureBrowserReady(opts);
|
|
363
370
|
this.page = await this._ensurePage();
|
|
364
371
|
this.lastRecycleAt = Date.now();
|
|
372
|
+
// 端口切换时重置运行计时
|
|
373
|
+
if (oldPort !== undefined && oldPort !== this.port) {
|
|
374
|
+
this.sessionStartTime = Date.now();
|
|
375
|
+
}
|
|
365
376
|
}
|
|
366
377
|
|
|
367
|
-
/** 内存检查 + 按需回收(每 30s 最多采样一次) */
|
|
378
|
+
/** 内存检查 + 定时轮换 + 按需回收(每 30s 最多采样一次) */
|
|
368
379
|
async maybeRecycleForMemory(processedCount) {
|
|
369
380
|
const now = Date.now();
|
|
370
381
|
if (now - this._lastMemCheck < 30000) return;
|
|
371
382
|
this._lastMemCheck = now;
|
|
372
383
|
|
|
384
|
+
// 定时轮换:单端口运行超时自动切换
|
|
385
|
+
const sessionElapsed = now - this.sessionStartTime;
|
|
386
|
+
if (sessionElapsed >= CFG.maxSessionRunTimeMs) {
|
|
387
|
+
const mins = Math.round(sessionElapsed / 60000);
|
|
388
|
+
await this.recycleSession(`定时轮换 (运行 ${mins} 分钟)`, {
|
|
389
|
+
switchPort: true,
|
|
390
|
+
});
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
|
|
373
394
|
const mem = process.memoryUsage();
|
|
374
395
|
const rssMB = mem.rss / 1024 / 1024;
|
|
375
396
|
const heapMB = mem.heapUsed / 1024 / 1024;
|
|
@@ -783,8 +804,12 @@ async function runAutoMode(options) {
|
|
|
783
804
|
|
|
784
805
|
// 每 10 个任务输出内存汇总
|
|
785
806
|
if (totalTasks() % 10 === 0) {
|
|
807
|
+
const remainingMs =
|
|
808
|
+
session.sessionStartTime + CFG.maxSessionRunTimeMs - Date.now();
|
|
809
|
+
const remainingMins =
|
|
810
|
+
remainingMs > 0 ? Math.round(remainingMs / 60000) : 0;
|
|
786
811
|
console.error(
|
|
787
|
-
`── #${totalTasks()} ── ${formatMemoryWithBrowser(session.port)} 处理=${processedCount} 跳过=${skippedCount} 错误=${errorCount}`,
|
|
812
|
+
`── #${totalTasks()} ── ${formatMemoryWithBrowser(session.port)} 处理=${processedCount} 跳过=${skippedCount} 错误=${errorCount} 距切换≈${remainingMins}min`,
|
|
788
813
|
);
|
|
789
814
|
}
|
|
790
815
|
} catch (err) {
|
package/src/watch/data-store.js
CHANGED
|
@@ -2647,8 +2647,7 @@ export function createStore(filePath, options = {}) {
|
|
|
2647
2647
|
let sql = `
|
|
2648
2648
|
SELECT *
|
|
2649
2649
|
FROM jobs_base
|
|
2650
|
-
WHERE
|
|
2651
|
-
AND COALESCE(user_update_count, 0) <= 0
|
|
2650
|
+
WHERE COALESCE(user_update_count, 0) <= 0
|
|
2652
2651
|
`;
|
|
2653
2652
|
const sqlParams = [];
|
|
2654
2653
|
|
|
@@ -2698,13 +2697,7 @@ export function createStore(filePath, options = {}) {
|
|
|
2698
2697
|
const pending = data
|
|
2699
2698
|
.filter((u) => {
|
|
2700
2699
|
const updateCount = u.userUpdateCount;
|
|
2701
|
-
//
|
|
2702
|
-
const ttSellerEligible =
|
|
2703
|
-
u.ttSeller === null ||
|
|
2704
|
-
u.ttSeller === undefined ||
|
|
2705
|
-
u.ttSeller === "" ||
|
|
2706
|
-
u.ttSeller === 1;
|
|
2707
|
-
if (!ttSellerEligible) return false;
|
|
2700
|
+
// jobs_base 中所有 user_update_count <= 0 的记录都可以领取
|
|
2708
2701
|
if (
|
|
2709
2702
|
updateCount === null ||
|
|
2710
2703
|
updateCount === undefined ||
|
|
@@ -3395,14 +3388,13 @@ export function createStore(filePath, options = {}) {
|
|
|
3395
3388
|
};
|
|
3396
3389
|
}
|
|
3397
3390
|
|
|
3398
|
-
// 5. 验证:统计 jobs_base 中可被 attach 领取的记录数(
|
|
3391
|
+
// 5. 验证:统计 jobs_base 中可被 attach 领取的记录数(user_update_count<=0)
|
|
3399
3392
|
let available = 0;
|
|
3400
3393
|
try {
|
|
3401
3394
|
const row = db
|
|
3402
3395
|
.prepare(
|
|
3403
3396
|
`SELECT COUNT(*) as total FROM jobs_base
|
|
3404
|
-
WHERE
|
|
3405
|
-
AND COALESCE(user_update_count, 0) <= 0
|
|
3397
|
+
WHERE COALESCE(user_update_count, 0) <= 0
|
|
3406
3398
|
AND video_count = 0`,
|
|
3407
3399
|
)
|
|
3408
3400
|
.get();
|
package/src/watch/db-stats.js
CHANGED
|
@@ -50,7 +50,7 @@ export function getDashboardStatsFromDb(targetLocations = []) {
|
|
|
50
50
|
|
|
51
51
|
const userUpdateTasksRow = db
|
|
52
52
|
.prepare(
|
|
53
|
-
`SELECT COUNT(*) as userUpdateTasks FROM jobs_base WHERE COALESCE(
|
|
53
|
+
`SELECT COUNT(*) as userUpdateTasks FROM jobs_base WHERE COALESCE(user_update_count, 0) <= 0`,
|
|
54
54
|
)
|
|
55
55
|
.get();
|
|
56
56
|
|
|
@@ -147,7 +147,7 @@ export function getUserUpdateByCountryFromDb() {
|
|
|
147
147
|
.prepare(
|
|
148
148
|
`SELECT COALESCE(guessed_location, '未知') as country, COUNT(*) as count
|
|
149
149
|
FROM jobs_base
|
|
150
|
-
WHERE
|
|
150
|
+
WHERE COALESCE(user_update_count, 0) <= 0
|
|
151
151
|
GROUP BY COALESCE(guessed_location, '未知')
|
|
152
152
|
ORDER BY count DESC`,
|
|
153
153
|
)
|
|
@@ -161,7 +161,7 @@ export function getAttachStuckByCountryFromDb() {
|
|
|
161
161
|
.prepare(
|
|
162
162
|
`SELECT COALESCE(guessed_location, '未知') as country, COUNT(*) as count
|
|
163
163
|
FROM jobs_base
|
|
164
|
-
WHERE
|
|
164
|
+
WHERE COALESCE(user_update_count, 0) = 1
|
|
165
165
|
GROUP BY COALESCE(guessed_location, '未知')
|
|
166
166
|
ORDER BY count DESC`,
|
|
167
167
|
)
|
|
@@ -194,7 +194,7 @@ export function restoreAttachStuckByCountry(country) {
|
|
|
194
194
|
};
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
-
const whereSql = `COALESCE(
|
|
197
|
+
const whereSql = `COALESCE(user_update_count, 0) = 1 AND COALESCE(guessed_location, '未知') = ?`;
|
|
198
198
|
const count =
|
|
199
199
|
db
|
|
200
200
|
.prepare(`SELECT COUNT(*) as c FROM jobs_base WHERE ${whereSql}`)
|
package/src/watch/public/app.js
CHANGED
|
@@ -163,7 +163,6 @@ function renderStats() {
|
|
|
163
163
|
if (statJobsBase)
|
|
164
164
|
statJobsBase.textContent = `jobs_base: ${formatStatNum(d.jobsBaseTotal || 0)}`;
|
|
165
165
|
flashEl("statError", d.errorUsers);
|
|
166
|
-
flashEl("statRestricted", d.restrictedUsers);
|
|
167
166
|
flashEl("statTarget", d.targetUsers, { full: true });
|
|
168
167
|
flashEl("statUserUpdateTasks", d.userUpdateTasks || 0, { full: true });
|
|
169
168
|
flashEl("statRawJobs", d.rawJobs || 0);
|
|
@@ -376,7 +375,6 @@ function renderSourceChart(sources) {
|
|
|
376
375
|
following: "关注",
|
|
377
376
|
follower: "粉丝",
|
|
378
377
|
processed: "已处理",
|
|
379
|
-
restricted: "受限(跳过)",
|
|
380
378
|
error: "错误(待重试)",
|
|
381
379
|
noVideo: "无视频",
|
|
382
380
|
};
|
|
@@ -419,13 +417,10 @@ function renderTable(users) {
|
|
|
419
417
|
const nowStatus = u.status;
|
|
420
418
|
const changed =
|
|
421
419
|
wasStatus !== nowStatus &&
|
|
422
|
-
(nowStatus === "done" ||
|
|
423
|
-
nowStatus === "restricted" ||
|
|
424
|
-
nowStatus === "error");
|
|
420
|
+
(nowStatus === "done" || nowStatus === "error");
|
|
425
421
|
const rowClass = changed ? ' class="row-flash"' : "";
|
|
426
422
|
|
|
427
423
|
const statusTags = {
|
|
428
|
-
restricted: '<span class="tag error">受限(跳过)</span>',
|
|
429
424
|
error: '<span class="tag error">错误(待重试)</span>',
|
|
430
425
|
done: '<span class="tag processed">已完成</span>',
|
|
431
426
|
processing: '<span class="tag processing">处理中</span>',
|
|
@@ -991,33 +986,6 @@ async function resetJob(uniqueId) {
|
|
|
991
986
|
}
|
|
992
987
|
}
|
|
993
988
|
|
|
994
|
-
async function moveRestrictedToBase() {
|
|
995
|
-
const count = currentStats?.restrictedUsers || 0;
|
|
996
|
-
if (count === 0) {
|
|
997
|
-
showToast("没有受限用户");
|
|
998
|
-
return;
|
|
999
|
-
}
|
|
1000
|
-
if (!confirm(`确定将 ${count} 个受限用户移到 jobs_base 吗?`)) return;
|
|
1001
|
-
showLoading("迁移中...");
|
|
1002
|
-
try {
|
|
1003
|
-
const res = await fetch("/api/jobs/move-restricted-to-base", {
|
|
1004
|
-
method: "POST",
|
|
1005
|
-
});
|
|
1006
|
-
const data = await res.json();
|
|
1007
|
-
if (data.error) {
|
|
1008
|
-
showToast(data.error, true);
|
|
1009
|
-
return;
|
|
1010
|
-
}
|
|
1011
|
-
showToast(`已迁移 ${data.moved} 个受限用户`);
|
|
1012
|
-
fetchStats();
|
|
1013
|
-
fetchUsers();
|
|
1014
|
-
} catch (e) {
|
|
1015
|
-
showToast("迁移失败: " + e.message, true);
|
|
1016
|
-
} finally {
|
|
1017
|
-
hideLoading();
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
989
|
async function batchResetErrors() {
|
|
1022
990
|
const errorUsers = currentUsers.filter((u) => u.status === "error");
|
|
1023
991
|
if (errorUsers.length === 0) {
|
|
@@ -1762,8 +1730,6 @@ function renderTargetTable() {
|
|
|
1762
1730
|
statusTag = '<span class="tag pending">待处理</span>';
|
|
1763
1731
|
else if (u.status === "error")
|
|
1764
1732
|
statusTag = '<span class="tag error">错误</span>';
|
|
1765
|
-
else if (u.status === "restricted")
|
|
1766
|
-
statusTag = '<span class="tag error">受限</span>';
|
|
1767
1733
|
else statusTag = u.status || "-";
|
|
1768
1734
|
|
|
1769
1735
|
return `<tr data-user="${u.uniqueId}">
|
|
@@ -41,10 +41,6 @@
|
|
|
41
41
|
<div class="label">错误</div>
|
|
42
42
|
<div class="value error" id="statError">0</div>
|
|
43
43
|
</div>
|
|
44
|
-
<div class="stat-card clickable" onclick="moveRestrictedToBase()">
|
|
45
|
-
<div class="label">受限</div>
|
|
46
|
-
<div class="value error" id="statRestricted">0</div>
|
|
47
|
-
</div>
|
|
48
44
|
<div class="stat-card clickable pending-card" id="statUserUpdateCard" onclick="navigateToUserUpdate()">
|
|
49
45
|
<div class="label">待补资料</div>
|
|
50
46
|
<div class="value target" id="statUserUpdateTasks">0</div>
|
|
@@ -122,7 +118,6 @@
|
|
|
122
118
|
<button data-filter="pending" onclick="setFilter('pending')">待处理</button>
|
|
123
119
|
<button data-filter="done" onclick="setFilter('done')">已完成</button>
|
|
124
120
|
<button data-filter="error" onclick="setFilter('error')">错误</button>
|
|
125
|
-
<button data-filter="restricted" onclick="setFilter('restricted')">受限</button>
|
|
126
121
|
<button data-filter="target" onclick="setFilter('target')" style="background:#7c3aed;color:#fff">目标商家</button>
|
|
127
122
|
<select id="targetLocationFilter" onchange="onTargetLocationChange()"
|
|
128
123
|
style="padding:6px 10px;border:1px solid #7c3aed;border-radius:6px;background:#2a2a3a;color:#ccc;font-size:12px;cursor:pointer;outline:none;display:none">
|