tt-help-cli-ycl 1.3.35 → 1.3.37

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.
@@ -1,9 +1,9 @@
1
- import { chromium } from 'playwright';
2
- import { fetchUserCommentsAPI } from '../lib/api-interceptor-comment.js';
3
- import { closeCommentPanel } from '../lib/browser/page.js';
4
- import { server as defaultServer } from '../lib/constants.js';
1
+ import { chromium } from "playwright";
2
+ import { fetchUserCommentsAPI } from "../lib/api-interceptor-comment.js";
3
+ import { closeCommentPanel } from "../lib/browser/page.js";
4
+ import { server as defaultServer } from "../lib/constants.js";
5
5
 
6
- const TARGET_LOCATIONS = ['ES', 'PL', 'NL', 'BE', 'DE', 'FR', 'IT', 'IE'];
6
+ const TARGET_LOCATIONS = ["ES", "PL", "NL", "BE", "DE", "FR", "IT", "IE", "AT"];
7
7
 
8
8
  async function waitForPageReady(page, timeout = 30000) {
9
9
  const startTime = Date.now();
@@ -16,7 +16,7 @@ async function waitForPageReady(page, timeout = 30000) {
16
16
  } catch {
17
17
  // Page may have navigated, retry
18
18
  }
19
- await new Promise(r => setTimeout(r, 1000));
19
+ await new Promise((r) => setTimeout(r, 1000));
20
20
  }
21
21
  return false;
22
22
  }
@@ -36,8 +36,10 @@ async function withRetry(label, fn, maxRetries = 3) {
36
36
  return await fn();
37
37
  } catch (err) {
38
38
  if (i < maxRetries - 1) {
39
- console.error(` [${label}] 失败: ${err.message},${backoff / 1000}s 后重试...`);
40
- await new Promise(r => setTimeout(r, backoff));
39
+ console.error(
40
+ ` [${label}] 失败: ${err.message},${backoff / 1000}s 后重试...`,
41
+ );
42
+ await new Promise((r) => setTimeout(r, backoff));
41
43
  backoff *= 2;
42
44
  } else {
43
45
  throw err;
@@ -49,8 +51,8 @@ async function withRetry(label, fn, maxRetries = 3) {
49
51
  async function apiPost(url, body) {
50
52
  return withRetry(`POST ${url}`, async () => {
51
53
  const res = await fetch(url, {
52
- method: 'POST',
53
- headers: { 'Content-Type': 'application/json' },
54
+ method: "POST",
55
+ headers: { "Content-Type": "application/json" },
54
56
  body: JSON.stringify(body),
55
57
  });
56
58
  if (!res.ok) {
@@ -64,8 +66,8 @@ async function apiPost(url, body) {
64
66
  async function apiPut(url) {
65
67
  return withRetry(`PUT ${url}`, async () => {
66
68
  const res = await fetch(url, {
67
- method: 'PUT',
68
- headers: { 'Content-Type': 'application/json' },
69
+ method: "PUT",
70
+ headers: { "Content-Type": "application/json" },
69
71
  });
70
72
  if (!res.ok) {
71
73
  const errText = await res.text();
@@ -88,14 +90,16 @@ async function apiGet(url) {
88
90
 
89
91
  function isBrowserClosedError(err) {
90
92
  if (!err) return false;
91
- const msg = err.message || err.toString() || '';
92
- return msg.includes('Target') && msg.includes('closed') ||
93
- msg.includes('Session') && msg.includes('deleted') ||
94
- msg.includes('Session') && msg.includes('not found') ||
95
- msg.includes('Browser') && msg.includes('closed') ||
96
- msg.includes('Execution context was destroyed') ||
97
- msg.includes('Protocol error') ||
98
- msg.includes('Cannot find context');
93
+ const msg = err.message || err.toString() || "";
94
+ return (
95
+ (msg.includes("Target") && msg.includes("closed")) ||
96
+ (msg.includes("Session") && msg.includes("deleted")) ||
97
+ (msg.includes("Session") && msg.includes("not found")) ||
98
+ (msg.includes("Browser") && msg.includes("closed")) ||
99
+ msg.includes("Execution context was destroyed") ||
100
+ msg.includes("Protocol error") ||
101
+ msg.includes("Cannot find context")
102
+ );
99
103
  }
100
104
 
101
105
  /**
@@ -106,10 +110,13 @@ async function runAutoMode(options) {
106
110
  const actualParallel = Math.max(1, parallel || 1);
107
111
  const actualInterval = interval || 10;
108
112
  const actualMaxComments = maxComments || 200;
113
+ let shuttingDown = false;
109
114
 
110
- console.error(`\n[Comments Auto] 并行: ${actualParallel}, 间隔: ${actualInterval}s, 评论数: ${actualMaxComments}`);
115
+ console.error(
116
+ `\n[Comments Auto] 并行: ${actualParallel}, 间隔: ${actualInterval}s, 评论数: ${actualMaxComments}`,
117
+ );
111
118
  console.error(`服务器: ${serverUrl}`);
112
- console.error(`目标国家: ${TARGET_LOCATIONS.join(', ')}`);
119
+ console.error(`目标国家: ${TARGET_LOCATIONS.join(", ")}`);
113
120
  console.error(`开始循环接收任务...\n`);
114
121
 
115
122
  let browser;
@@ -119,17 +126,38 @@ async function runAutoMode(options) {
119
126
  let errorCount = 0;
120
127
  let consecutiveErrors = 0;
121
128
 
129
+ const shutdown = async (signal) => {
130
+ if (shuttingDown) return;
131
+ shuttingDown = true;
132
+ console.error(`\n[Comments Auto] 收到 ${signal},正在关闭浏览器...`);
133
+ await browser?.close().catch(() => {});
134
+ console.error("[Comments Auto] 已退出");
135
+ process.exit(0);
136
+ };
137
+
138
+ const onSigint = () => {
139
+ void shutdown("SIGINT");
140
+ };
141
+ const onSigterm = () => {
142
+ void shutdown("SIGTERM");
143
+ };
144
+
145
+ process.once("SIGINT", onSigint);
146
+ process.once("SIGTERM", onSigterm);
147
+
122
148
  async function ensureBrowser() {
123
149
  if (browser) {
124
150
  try {
125
151
  await browser.contexts()[0]?.pages()[0]?.url();
126
152
  return browser;
127
153
  } catch {
128
- try { await browser.close(); } catch {}
154
+ try {
155
+ await browser.close();
156
+ } catch {}
129
157
  browser = null;
130
158
  }
131
159
  }
132
- browser = await chromium.connectOverCDP('http://127.0.0.1:9222');
160
+ browser = await chromium.connectOverCDP("http://127.0.0.1:9222");
133
161
  return browser;
134
162
  }
135
163
 
@@ -142,160 +170,200 @@ async function runAutoMode(options) {
142
170
  return null;
143
171
  }
144
172
 
145
- while (true) {
146
- let page;
147
- try {
148
- browser = await ensureBrowser();
149
- page = await getPage(browser);
150
- if (!page) {
151
- console.error('[Comments Auto] 未找到可用页面,等待中...');
152
- await new Promise(r => setTimeout(r, actualInterval * 1000));
153
- continue;
154
- }
155
-
156
- // 获取任务
157
- let tasks;
173
+ try {
174
+ while (!shuttingDown) {
175
+ let page;
158
176
  try {
159
- const resp = await apiGet(`${serverUrl}/api/comment-tasks?limit=${actualParallel}`);
160
- tasks = resp.tasks || [];
161
- } catch (err) {
162
- console.error(`[Comments Auto] 获取任务失败: ${err.message}`);
163
- consecutiveErrors++;
164
- if (consecutiveErrors > 10) {
165
- console.error('[Comments Auto] 连续获取失败超过10次,请检查服务端');
166
- process.exit(1);
177
+ browser = await ensureBrowser();
178
+ page = await getPage(browser);
179
+ if (!page) {
180
+ console.error("[Comments Auto] 未找到可用页面,等待中...");
181
+ await new Promise((r) => setTimeout(r, actualInterval * 1000));
182
+ continue;
167
183
  }
168
- await new Promise(r => setTimeout(r, actualInterval * 1000));
169
- continue;
170
- }
171
- consecutiveErrors = 0;
172
184
 
173
- if (tasks.length === 0) {
174
- console.error(`[Comments Auto] 暂无任务,${actualInterval}s 后重试...`);
175
- await new Promise(r => setTimeout(r, actualInterval * 1000));
176
- continue;
177
- }
185
+ // 获取任务
186
+ let tasks;
187
+ try {
188
+ const resp = await apiGet(
189
+ `${serverUrl}/api/comment-tasks?limit=${actualParallel}`,
190
+ );
191
+ tasks = resp.tasks || [];
192
+ } catch (err) {
193
+ console.error(`[Comments Auto] 获取任务失败: ${err.message}`);
194
+ consecutiveErrors++;
195
+ if (consecutiveErrors > 10) {
196
+ console.error("[Comments Auto] 连续获取失败超过10次,请检查服务端");
197
+ process.exit(1);
198
+ }
199
+ await new Promise((r) => setTimeout(r, actualInterval * 1000));
200
+ continue;
201
+ }
202
+ consecutiveErrors = 0;
178
203
 
179
- console.error(`[Comments Auto] 获取 ${tasks.length} 个任务`);
204
+ if (tasks.length === 0) {
205
+ console.error(
206
+ `[Comments Auto] 暂无任务,${actualInterval}s 后重试...`,
207
+ );
208
+ await new Promise((r) => setTimeout(r, actualInterval * 1000));
209
+ continue;
210
+ }
180
211
 
181
- for (const task of tasks) {
182
- try {
183
- const { id, href, locationCreated, ttSeller } = task;
184
- const loc = (locationCreated || '').toUpperCase();
212
+ console.error(`[Comments Auto] 获取 ${tasks.length} 个任务`);
213
+
214
+ for (const task of tasks) {
215
+ try {
216
+ const { id, href, locationCreated, ttSeller } = task;
217
+ const loc = (locationCreated || "").toUpperCase();
218
+
219
+ // 检查目标国家
220
+ if (loc && !TARGET_LOCATIONS.includes(loc)) {
221
+ // 非目标国家,直接 commit 跳过
222
+ await apiPut(`${serverUrl}/api/comment-task/${id}`);
223
+ skippedCount++;
224
+ console.error(
225
+ ` [跳过] ${id.substring(0, 15)}... 国家: ${loc} (非目标)`,
226
+ );
227
+ continue;
228
+ }
185
229
 
186
- // 检查目标国家
187
- if (loc && !TARGET_LOCATIONS.includes(loc)) {
188
- // 非目标国家,直接 commit 跳过
189
- await apiPut(`${serverUrl}/api/comment-task/${id}`);
190
- skippedCount++;
191
- console.error(` [跳过] ${id.substring(0, 15)}... 国家: ${loc} (非目标)`);
192
- continue;
193
- }
230
+ console.error(
231
+ ` [处理] ${id.substring(0, 15)}... 国家: ${loc || "?"} ttSeller: ${ttSeller} -> ${href}`,
232
+ );
194
233
 
195
- console.error(` [处理] ${id.substring(0, 15)}... 国家: ${loc || '?'} ttSeller: ${ttSeller} -> ${href}`);
234
+ // 确保浏览器可用
235
+ browser = await ensureBrowser();
236
+ page = await getPage(browser);
237
+ if (!page) {
238
+ throw new Error("未找到可用页面");
239
+ }
196
240
 
197
- // 确保浏览器可用
198
- browser = await ensureBrowser();
199
- page = await getPage(browser);
200
- if (!page) {
201
- throw new Error('未找到可用页面');
202
- }
241
+ // 导航到视频页
242
+ await page.goto(href, {
243
+ waitUntil: "domcontentloaded",
244
+ timeout: 30000,
245
+ });
246
+ const ready = await waitForPageReady(page, 30000);
247
+ if (!ready) {
248
+ throw new Error("页面加载超时");
249
+ }
203
250
 
204
- // 导航到视频页
205
- await page.goto(href, { waitUntil: 'domcontentloaded', timeout: 30000 });
206
- const ready = await waitForPageReady(page, 30000);
207
- if (!ready) {
208
- throw new Error('页面加载超时');
209
- }
251
+ // 关闭弹窗
252
+ await safeEvaluate(page, () => {
253
+ document
254
+ .querySelectorAll('[id*="modal-overlay"]')
255
+ .forEach((o) => o.click());
256
+ });
257
+ await page.keyboard.press("Escape");
258
+ await new Promise((r) => setTimeout(r, 1000));
259
+
260
+ // 如果评论面板已打开,先关闭
261
+ const panelOpen = await safeEvaluate(page, () => {
262
+ return !!document.querySelector('[class*="RightPanelContainer"]');
263
+ });
264
+ if (panelOpen) {
265
+ await closeCommentPanel(page);
266
+ await new Promise((r) => setTimeout(r, 800));
267
+ }
210
268
 
211
- // 关闭弹窗
212
- await safeEvaluate(page, () => {
213
- document.querySelectorAll('[id*="modal-overlay"]').forEach(o => o.click());
214
- });
215
- await page.keyboard.press('Escape');
216
- await new Promise(r => setTimeout(r, 1000));
269
+ // 获取评论
270
+ const result = await fetchUserCommentsAPI(page, {
271
+ maxComments: actualMaxComments,
272
+ log: () => {},
273
+ });
217
274
 
218
- // 如果评论面板已打开,先关闭
219
- const panelOpen = await safeEvaluate(page, () => {
220
- return !!document.querySelector('[class*="RightPanelContainer"]');
221
- });
222
- if (panelOpen) {
275
+ // 关闭评论面板
223
276
  await closeCommentPanel(page);
224
- await new Promise(r => setTimeout(r, 800));
225
- }
226
277
 
227
- // 获取评论
228
- const result = await fetchUserCommentsAPI(page, {
229
- maxComments: actualMaxComments,
230
- log: () => {},
231
- });
232
-
233
- // 关闭评论面板
234
- await closeCommentPanel(page);
235
-
236
- if (result.error || !result.comments || result.comments.length === 0) {
237
- console.error(` [无评论] ${result.error || '未获取到评论'}`);
238
- } else {
239
- // 提取去重作者
240
- const authors = [...new Set(
241
- result.comments
242
- .map(c => c.user?.unique_id)
243
- .filter(Boolean)
244
- .map(u => u.replace(/^@/, ''))
245
- )];
246
-
247
- if (authors.length > 0) {
248
- // 提交作者
249
- const saveResult = await apiPost(`${serverUrl}/api/users`, {
250
- usernames: authors.map(a => '@' + a),
251
- sources: ['comment'],
252
- guessedLocation: loc || null,
253
- });
254
- console.error(` [提交] ${saveResult.added} 新增, ${saveResult.skipped} 跳过`);
278
+ if (
279
+ result.error ||
280
+ !result.comments ||
281
+ result.comments.length === 0
282
+ ) {
283
+ console.error(` [无评论] ${result.error || "未获取到评论"}`);
255
284
  } else {
256
- console.error(` [无作者] 获取了 ${result.comments.length} 条评论但无作者信息`);
285
+ // 提取去重作者
286
+ const authors = [
287
+ ...new Set(
288
+ result.comments
289
+ .map((c) => c.user?.unique_id)
290
+ .filter(Boolean)
291
+ .map((u) => u.replace(/^@/, "")),
292
+ ),
293
+ ];
294
+
295
+ if (authors.length > 0) {
296
+ // 提交作者
297
+ const saveResult = await apiPost(`${serverUrl}/api/users`, {
298
+ usernames: authors.map((a) => "@" + a),
299
+ sources: ["comment"],
300
+ guessedLocation: loc || null,
301
+ });
302
+ console.error(
303
+ ` [提交] ${saveResult.added} 新增, ${saveResult.skipped} 跳过`,
304
+ );
305
+ } else {
306
+ console.error(
307
+ ` [无作者] 获取了 ${result.comments.length} 条评论但无作者信息`,
308
+ );
309
+ }
257
310
  }
258
- }
259
-
260
- // commit 任务
261
- await apiPut(`${serverUrl}/api/comment-task/${id}`);
262
- processedCount++;
263
- console.error(` [完成] 累计 ${processedCount} 个视频`);
264
311
 
265
- } catch (err) {
266
- errorCount++;
267
- const isBrowserClosed = isBrowserClosedError(err);
268
- if (isBrowserClosed) {
269
- console.error(` [浏览器异常] ${err.message},将重启浏览器`);
270
- try { await browser.close(); } catch {}
271
- browser = null;
272
- browserRestartCount++;
273
- } else {
274
- console.error(` [错误] ${err.message}`);
312
+ // commit 任务
313
+ await apiPut(`${serverUrl}/api/comment-task/${id}`);
314
+ processedCount++;
315
+ console.error(` [完成] 累计 ${processedCount} 个视频`);
316
+ } catch (err) {
317
+ errorCount++;
318
+ const isBrowserClosed = isBrowserClosedError(err);
319
+ if (isBrowserClosed) {
320
+ console.error(` [浏览器异常] ${err.message},将重启浏览器`);
321
+ try {
322
+ await browser.close();
323
+ } catch {}
324
+ browser = null;
325
+ browserRestartCount++;
326
+ } else {
327
+ console.error(` [错误] ${err.message}`);
328
+ }
329
+ continue;
275
330
  }
276
- continue;
331
+ }
332
+ } catch (err) {
333
+ errorCount++;
334
+ const isBrowserClosed = isBrowserClosedError(err);
335
+ if (isBrowserClosed) {
336
+ console.error(
337
+ `[Comments Auto] 浏览器异常 (${++browserRestartCount}),正在重启...`,
338
+ );
339
+ try {
340
+ await browser.close();
341
+ } catch {}
342
+ browser = null;
343
+ } else {
344
+ console.error(`[Comments Auto] 异常: ${err.message}`);
277
345
  }
278
346
  }
279
347
 
280
- } catch (err) {
281
- errorCount++;
282
- const isBrowserClosed = isBrowserClosedError(err);
283
- if (isBrowserClosed) {
284
- console.error(`[Comments Auto] 浏览器异常 (${++browserRestartCount}),正在重启...`);
285
- try { await browser.close(); } catch {}
286
- browser = null;
287
- } else {
288
- console.error(`[Comments Auto] 异常: ${err.message}`);
289
- }
348
+ // 等待间隔
349
+ await new Promise((r) => setTimeout(r, actualInterval * 1000));
290
350
  }
291
-
292
- // 等待间隔
293
- await new Promise(r => setTimeout(r, actualInterval * 1000));
351
+ } finally {
352
+ process.removeListener("SIGINT", onSigint);
353
+ process.removeListener("SIGTERM", onSigterm);
354
+ await browser?.close().catch(() => {});
294
355
  }
295
356
  }
296
357
 
297
358
  export async function handleComments(options) {
298
- const { commentsUrl, commentsMax, commentsSave, commentsParallel, commentsInterval, commentsServer } = options;
359
+ const {
360
+ commentsUrl,
361
+ commentsMax,
362
+ commentsSave,
363
+ commentsParallel,
364
+ commentsInterval,
365
+ commentsServer,
366
+ } = options;
299
367
 
300
368
  // 自动模式:无URL且传了 -s/--server 参数(或任何参数)
301
369
  if (!commentsUrl && process.argv.length > 3) {
@@ -309,17 +377,21 @@ export async function handleComments(options) {
309
377
 
310
378
  // 手动模式(需要 URL)
311
379
  if (!commentsUrl) {
312
- console.error('用法: tt-help comments <视频URL> [最大评论数] [--save]');
313
- console.error(' tt-help comments [-p N] [-i N] [-s server] [-m maxComments]');
314
- console.error('');
315
- console.error('手动模式: tt-help comments <URL> [N] [--save]');
316
- console.error('自动模式: tt-help comments -p 1 -i 10 -m 200 -s <server>');
317
- console.error('');
318
- console.error('选项: --save 去重后保存到服务端,来源标记为 comment');
319
- console.error(' -p, --parallel 并行数 (默认 1)');
320
- console.error(' -i, --interval 空闲间隔秒 (默认 10)');
321
- console.error(' -s, --server 服务端地址');
322
- console.error(' -m, --max-comments 每视频最大评论数 (默认 200)');
380
+ console.error("用法: tt-help comments <视频URL> [最大评论数] [--save]");
381
+ console.error(
382
+ " tt-help comments [-p N] [-i N] [-s server] [-m maxComments]",
383
+ );
384
+ console.error("");
385
+ console.error("手动模式: tt-help comments <URL> [N] [--save]");
386
+ console.error("自动模式: tt-help comments -p 1 -i 10 -m 200 -s <server>");
387
+ console.error("");
388
+ console.error(
389
+ "选项: --save 去重后保存到服务端,来源标记为 comment",
390
+ );
391
+ console.error(" -p, --parallel 并行数 (默认 1)");
392
+ console.error(" -i, --interval 空闲间隔秒 (默认 10)");
393
+ console.error(" -s, --server 服务端地址");
394
+ console.error(" -m, --max-comments 每视频最大评论数 (默认 200)");
323
395
  process.exit(1);
324
396
  }
325
397
 
@@ -327,7 +399,7 @@ export async function handleComments(options) {
327
399
  // 手动模式 --save
328
400
  let browser;
329
401
  try {
330
- browser = await chromium.connectOverCDP('http://127.0.0.1:9222');
402
+ browser = await chromium.connectOverCDP("http://127.0.0.1:9222");
331
403
  const contexts = browser.contexts();
332
404
  let page;
333
405
 
@@ -339,28 +411,33 @@ export async function handleComments(options) {
339
411
  }
340
412
 
341
413
  if (!page) {
342
- console.error('未找到可用页面');
414
+ console.error("未找到可用页面");
343
415
  process.exit(1);
344
416
  }
345
417
 
346
418
  console.error(`正在打开: ${commentsUrl}`);
347
- await page.goto(commentsUrl, { waitUntil: 'domcontentloaded', timeout: 30000 });
419
+ await page.goto(commentsUrl, {
420
+ waitUntil: "domcontentloaded",
421
+ timeout: 30000,
422
+ });
348
423
  const ready = await waitForPageReady(page, 30000);
349
424
  if (!ready) {
350
- console.error('页面加载超时,tab 未出现');
425
+ console.error("页面加载超时,tab 未出现");
351
426
  process.exit(1);
352
427
  }
353
428
 
354
429
  await safeEvaluate(page, () => {
355
- document.querySelectorAll('[id*="modal-overlay"]').forEach(o => o.click());
430
+ document
431
+ .querySelectorAll('[id*="modal-overlay"]')
432
+ .forEach((o) => o.click());
356
433
  });
357
- await page.keyboard.press('Escape');
358
- await new Promise(r => setTimeout(r, 1000));
434
+ await page.keyboard.press("Escape");
435
+ await new Promise((r) => setTimeout(r, 1000));
359
436
 
360
437
  const videoInfo = await safeEvaluate(page, () => {
361
438
  const result = {};
362
439
  const m = window.location.href.match(/@([^/]+)\/video/);
363
- result.author = m ? '@' + m[1] : '未知';
440
+ result.author = m ? "@" + m[1] : "未知";
364
441
  const html = document.documentElement.outerHTML;
365
442
  const locMatch = html.match(/"locationCreated":"([^"]*)/);
366
443
  result.locationCreated = locMatch ? locMatch[1] : null;
@@ -372,7 +449,7 @@ export async function handleComments(options) {
372
449
  });
373
450
  if (panelOpen) {
374
451
  await closeCommentPanel(page);
375
- await new Promise(r => setTimeout(r, 800));
452
+ await new Promise((r) => setTimeout(r, 800));
376
453
  }
377
454
 
378
455
  const result = await fetchUserCommentsAPI(page, {
@@ -383,23 +460,25 @@ export async function handleComments(options) {
383
460
  await closeCommentPanel(page);
384
461
 
385
462
  if (result.error || !result.comments || result.comments.length === 0) {
386
- console.error(`获取评论失败: ${result.error || '未获取到评论'}`);
463
+ console.error(`获取评论失败: ${result.error || "未获取到评论"}`);
387
464
  process.exit(1);
388
465
  }
389
466
 
390
- const authors = [...new Set(
391
- result.comments
392
- .map(c => c.user?.unique_id)
393
- .filter(Boolean)
394
- .map(u => u.replace(/^@/, ''))
395
- )];
467
+ const authors = [
468
+ ...new Set(
469
+ result.comments
470
+ .map((c) => c.user?.unique_id)
471
+ .filter(Boolean)
472
+ .map((u) => u.replace(/^@/, "")),
473
+ ),
474
+ ];
396
475
 
397
- const guessedLocation = (videoInfo?.locationCreated || '').toUpperCase();
476
+ const guessedLocation = (videoInfo?.locationCreated || "").toUpperCase();
398
477
  const serverUrl = commentsServer || defaultServer;
399
478
 
400
479
  if (guessedLocation && !TARGET_LOCATIONS.includes(guessedLocation)) {
401
480
  console.error(`\n猜测国家: ${guessedLocation},非目标国家,跳过保存`);
402
- console.error(`目标国家: ${TARGET_LOCATIONS.join(', ')}`);
481
+ console.error(`目标国家: ${TARGET_LOCATIONS.join(", ")}`);
403
482
  } else {
404
483
  console.error(`\n正在提交 ${authors.length} 个评论作者到服务端...`);
405
484
  console.error(`服务端: ${serverUrl}`);
@@ -407,17 +486,18 @@ export async function handleComments(options) {
407
486
 
408
487
  try {
409
488
  const saveResult = await apiPost(`${serverUrl}/api/users`, {
410
- usernames: authors.map(a => '@' + a),
411
- sources: ['comment'],
489
+ usernames: authors.map((a) => "@" + a),
490
+ sources: ["comment"],
412
491
  guessedLocation: guessedLocation || null,
413
492
  });
414
- console.error(`\n提交结果: ${saveResult.added} 个新增, ${saveResult.skipped} 个跳过`);
493
+ console.error(
494
+ `\n提交结果: ${saveResult.added} 个新增, ${saveResult.skipped} 个跳过`,
495
+ );
415
496
  } catch (err) {
416
497
  console.error(`\n提交失败: ${err.message}`);
417
498
  process.exit(1);
418
499
  }
419
500
  }
420
-
421
501
  } catch (err) {
422
502
  console.error(`获取评论失败: ${err.message}`);
423
503
  process.exit(1);
@@ -430,7 +510,7 @@ export async function handleComments(options) {
430
510
  // 手动模式:打印到控制台
431
511
  let browser;
432
512
  try {
433
- browser = await chromium.connectOverCDP('http://127.0.0.1:9222');
513
+ browser = await chromium.connectOverCDP("http://127.0.0.1:9222");
434
514
  const contexts = browser.contexts();
435
515
  let page;
436
516
 
@@ -442,28 +522,33 @@ export async function handleComments(options) {
442
522
  }
443
523
 
444
524
  if (!page) {
445
- console.error('未找到可用页面');
525
+ console.error("未找到可用页面");
446
526
  process.exit(1);
447
527
  }
448
528
 
449
529
  console.error(`正在打开: ${commentsUrl}`);
450
- await page.goto(commentsUrl, { waitUntil: 'domcontentloaded', timeout: 30000 });
530
+ await page.goto(commentsUrl, {
531
+ waitUntil: "domcontentloaded",
532
+ timeout: 30000,
533
+ });
451
534
  const ready = await waitForPageReady(page, 30000);
452
535
  if (!ready) {
453
- console.error('页面加载超时,tab 未出现');
536
+ console.error("页面加载超时,tab 未出现");
454
537
  process.exit(1);
455
538
  }
456
539
 
457
540
  await safeEvaluate(page, () => {
458
- document.querySelectorAll('[id*="modal-overlay"]').forEach(o => o.click());
541
+ document
542
+ .querySelectorAll('[id*="modal-overlay"]')
543
+ .forEach((o) => o.click());
459
544
  });
460
- await page.keyboard.press('Escape');
461
- await new Promise(r => setTimeout(r, 1000));
545
+ await page.keyboard.press("Escape");
546
+ await new Promise((r) => setTimeout(r, 1000));
462
547
 
463
548
  const videoInfo = await safeEvaluate(page, () => {
464
549
  const result = {};
465
550
  const m = window.location.href.match(/@([^/]+)\/video/);
466
- result.author = m ? '@' + m[1] : '未知';
551
+ result.author = m ? "@" + m[1] : "未知";
467
552
  const html = document.documentElement.outerHTML;
468
553
  const locMatch = html.match(/"locationCreated":"([^"]*)/);
469
554
  result.locationCreated = locMatch ? locMatch[1] : null;
@@ -475,7 +560,7 @@ export async function handleComments(options) {
475
560
  });
476
561
  if (panelOpen) {
477
562
  await closeCommentPanel(page);
478
- await new Promise(r => setTimeout(r, 800));
563
+ await new Promise((r) => setTimeout(r, 800));
479
564
  }
480
565
 
481
566
  const result = await fetchUserCommentsAPI(page, {
@@ -486,34 +571,40 @@ export async function handleComments(options) {
486
571
  await closeCommentPanel(page);
487
572
 
488
573
  if (result.error || !result.comments || result.comments.length === 0) {
489
- console.error(`获取评论失败: ${result.error || '未获取到评论'}`);
574
+ console.error(`获取评论失败: ${result.error || "未获取到评论"}`);
490
575
  process.exit(1);
491
576
  }
492
577
 
493
- const authors = [...new Set(
494
- result.comments
495
- .map(c => c.user?.unique_id)
496
- .filter(Boolean)
497
- .map(u => u.replace(/^@/, ''))
498
- )];
578
+ const authors = [
579
+ ...new Set(
580
+ result.comments
581
+ .map((c) => c.user?.unique_id)
582
+ .filter(Boolean)
583
+ .map((u) => u.replace(/^@/, "")),
584
+ ),
585
+ ];
499
586
 
500
587
  console.log(`视频: ${commentsUrl}`);
501
- console.log(`作者: ${videoInfo?.author || '未知'}`);
502
- if (videoInfo?.locationCreated) console.log(`猜测国家: ${videoInfo.locationCreated}`);
588
+ console.log(`作者: ${videoInfo?.author || "未知"}`);
589
+ if (videoInfo?.locationCreated)
590
+ console.log(`猜测国家: ${videoInfo.locationCreated}`);
503
591
  console.log(`评论数: ${result.comments.length}`);
504
592
  console.log(`评论作者: ${authors.length}`);
505
- console.log('');
593
+ console.log("");
506
594
 
507
595
  result.comments.forEach((c, i) => {
508
- const author = c.user?.unique_id ? '@' + c.user.unique_id : c.user?.nickname || '未知';
509
- const text = c.text || '';
596
+ const author = c.user?.unique_id
597
+ ? "@" + c.user.unique_id
598
+ : c.user?.nickname || "未知";
599
+ const text = c.text || "";
510
600
  const likes = c.digg_count || 0;
511
601
  console.log(`${i + 1}. [${author}] ${text} (\u2764 ${likes})`);
512
602
  });
513
603
 
514
- console.log('');
515
- console.log(`共 ${result.comments.length} 条评论, ${authors.length} 个唯一作者`);
516
-
604
+ console.log("");
605
+ console.log(
606
+ `共 ${result.comments.length} 条评论, ${authors.length} 个唯一作者`,
607
+ );
517
608
  } catch (err) {
518
609
  console.error(`获取评论失败: ${err.message}`);
519
610
  process.exit(1);