tt-help-cli-ycl 1.3.29 → 1.3.31

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,73 +1,52 @@
1
1
  import { delay, getDelayConfig, closeCommentPanel } from "./page-helpers.js";
2
- import { scrollAndCollect } from "./scroll-collector.js";
3
2
  import { waitAndGetCaptcha } from "./captcha-handler.js";
4
-
5
- async function openCommentPanel(page) {
6
- const tabs = page.locator('[class*="tabbar-item"]');
7
- const commentTab = tabs.filter({ hasText: "评论" }).first();
8
- await commentTab.click();
9
-
10
- // 等待短暂时间让页面渲染
11
- await new Promise(r => setTimeout(r, 2000));
12
-
13
- // 检测验证码
14
- const captchaResult = await waitAndGetCaptcha(page, {
15
- waitMs: 180000,
16
- pollInterval: 5000,
17
- log: console.error,
18
- });
19
-
20
- await page
21
- .waitForSelector('[class*="CommentListContainer"]', { timeout: 5000 })
22
- .catch(() => {});
23
- await page
24
- .waitForFunction(
25
- () => {
26
- const list = document.querySelector('[class*="CommentListContainer"]');
27
- return list && list.children.length > 0;
28
- },
29
- { timeout: 10000 },
30
- )
31
- .catch(() => {});
32
-
33
- return { captchaDetected: !!captchaResult.detected };
34
- }
3
+ import { fetchUserCommentsAPI } from "../../lib/api-interceptor-comment.js";
35
4
 
36
5
  async function extractCommentAuthors(page, maxComments = 10) {
37
- const panelResult = await openCommentPanel(page);
38
-
39
6
  const config = getDelayConfig();
40
- const allAuthors = await scrollAndCollect(page, {
41
- container: '[class*="CommentMain"]',
42
- findScrollable: true,
43
- collectFn: (container) => {
44
- const list = document.querySelector('[class*="CommentListContainer"]');
45
- if (!list) return { items: [] };
46
- const authors = [];
47
- Array.from(list.children).forEach((wrapper) => {
48
- const link = wrapper.querySelector(
49
- '[class*="UsernameContentWrapper"] a',
50
- );
51
- if (link) {
52
- const href = link.href || link.getAttribute("href");
53
- const m = href && href.match(/@([^/]+)/);
54
- if (m) authors.push("@" + m[1]);
55
- }
56
- });
57
- return { items: authors };
58
- },
59
- uniqueKey: (a) => a,
60
- maxItems: maxComments,
61
- delayRange: [Math.round(config.commentMax * 0.3), config.commentMax],
62
- staleThreshold: 2,
7
+
8
+ // 关闭可能已打开的评论面板(用 evaluate 而非 locator,
9
+ // 因为 closeCommentPanel 是点 overlay 不是点 tab)
10
+ await closeCommentPanel(page);
11
+ await delay(800, 1500);
12
+
13
+ // API 拦截获取评论
14
+ const apiResult = await fetchUserCommentsAPI(page, {
15
+ maxComments,
16
+ log: console.log,
17
+ onCaptcha: (pg) => waitAndGetCaptcha(pg, {
18
+ waitMs: 180000,
19
+ pollInterval: 5000,
20
+ log: console.error,
21
+ }),
63
22
  });
64
23
 
24
+ // 关闭评论面板
65
25
  await closeCommentPanel(page);
66
26
  await delay(Math.round(config.commentMax * 0.3), config.commentMax);
67
27
 
28
+ if (apiResult.error || !apiResult.comments || apiResult.comments.length === 0) {
29
+ return {
30
+ authors: [],
31
+ captchaDetected: apiResult.captchaDetected || false,
32
+ };
33
+ }
34
+
35
+ // 提取作者名(保持原有接口)
36
+ const authors = [];
37
+ for (const comment of apiResult.comments) {
38
+ if (authors.length >= maxComments) break;
39
+ const uniqueId = comment.user?.unique_id;
40
+ if (uniqueId) {
41
+ authors.push("@" + uniqueId);
42
+ }
43
+ }
44
+
45
+ const uniqueAuthors = [...new Set(authors)].slice(0, maxComments);
46
+
68
47
  return {
69
- authors: allAuthors.slice(0, maxComments),
70
- captchaDetected: panelResult.captchaDetected,
48
+ authors: uniqueAuthors,
49
+ captchaDetected: apiResult.captchaDetected || false,
71
50
  };
72
51
  }
73
52
 
@@ -813,6 +813,33 @@ export function createStore(filePath) {
813
813
  return videos.length;
814
814
  }
815
815
 
816
+ function getPendingCommentTasks(limit) {
817
+ // 筛选待处理视频(userUpdateCount <= 0 或 null/undefined)
818
+ const pending = videos.filter(v => (v.userUpdateCount || 0) <= 0);
819
+ // ttSeller=true 优先
820
+ pending.sort((a, b) => {
821
+ if (a.ttSeller && !b.ttSeller) return -1;
822
+ if (!a.ttSeller && b.ttSeller) return 1;
823
+ return (a.registeredAt || 0) - (b.registeredAt || 0);
824
+ });
825
+ // 取前 limit 个
826
+ const tasks = pending.slice(0, limit);
827
+ // userUpdateCount +1
828
+ for (const task of tasks) {
829
+ task.userUpdateCount = (task.userUpdateCount || 0) + 1;
830
+ }
831
+ saveVideos();
832
+ return tasks;
833
+ }
834
+
835
+ function commitCommentTask(videoId) {
836
+ const video = videos.find(v => v.id === videoId);
837
+ if (!video) return { ok: false, error: 'video not found' };
838
+ video.userUpdateCount = (video.userUpdateCount || 0) + 1;
839
+ saveVideos();
840
+ return { ok: true, userUpdateCount: video.userUpdateCount };
841
+ }
842
+
816
843
  return {
817
844
  save, flushSave, getUser, hasUser, userExists, addUser,
818
845
  getPendingUsers, getProcessedUsers, getAllUsers, getStats,
@@ -821,7 +848,7 @@ export function createStore(filePath) {
821
848
  getNextRedoJob, commitRedoJob,
822
849
  getPendingUserUpdateTasks, updateUserInfo, batchUpdateUserInfo,
823
850
  reportClientError, deleteClientError, getClientErrors,
824
- registerVideos, getVideos, getVideoCount,
851
+ registerVideos, getVideos, getVideoCount, getPendingCommentTasks, commitCommentTask,
825
852
  stopBackup,
826
853
  data,
827
854
  };
@@ -204,17 +204,18 @@ export function startWatchServer(outputFile, port = 3000, existingStore) {
204
204
 
205
205
  if (req.method === 'POST' && routePath === '/api/users') {
206
206
  try {
207
- const { usernames } = await readBody(req);
207
+ const { usernames, sources, guessedLocation } = await readBody(req);
208
208
  if (!Array.isArray(usernames) || usernames.length === 0) {
209
209
  sendJSON(res, 400, { error: 'usernames \u6570\u7ec4\u4e0d\u80fd\u4e3a\u7a7a' });
210
210
  return;
211
211
  }
212
+ const userSources = sources || ['seed'];
212
213
  const existingIds = new Set(store.getAllUsers().map(u => u.uniqueId));
213
214
  const newUsers = usernames
214
215
  .map(u => u.replace(/^@/, '').trim())
215
216
  .filter(u => u && !existingIds.has(u));
216
217
  for (const nu of newUsers) {
217
- store.addUser({ uniqueId: nu, sources: ['seed'], status: 'pending' });
218
+ store.addUser({ uniqueId: nu, sources: userSources, status: 'pending', guessedLocation: guessedLocation || null });
218
219
  }
219
220
  store.save();
220
221
  sendJSON(res, 200, {
@@ -416,6 +417,33 @@ export function startWatchServer(outputFile, port = 3000, existingStore) {
416
417
  return;
417
418
  }
418
419
 
420
+ if (req.method === 'GET' && routePath === '/api/comment-tasks') {
421
+ const limit = parseInt(params.limit) || 1;
422
+ const tasks = store.getPendingCommentTasks(limit);
423
+ const ts = new Date().toISOString().slice(11, 19);
424
+ console.error(`[JOB ${ts}] COMMENT-TASKS: ${tasks.length} tasks`);
425
+ sendJSON(res, 200, { total: tasks.length, tasks });
426
+ return;
427
+ }
428
+
429
+ const commentTaskMatch = routePath.match(/^\/api\/comment-task\/([^/]+)$/);
430
+ if (req.method === 'PUT' && commentTaskMatch) {
431
+ const videoId = commentTaskMatch[1];
432
+ try {
433
+ const ret = store.commitCommentTask(videoId);
434
+ if (ret.error) {
435
+ sendJSON(res, 404, { error: ret.error });
436
+ return;
437
+ }
438
+ const ts = new Date().toISOString().slice(11, 19);
439
+ console.error(`[JOB ${ts}] COMMENT-TASK-COMMIT: ${videoId} (userUpdateCount=${ret.userUpdateCount})`);
440
+ sendJSON(res, 200, ret);
441
+ } catch (e) {
442
+ sendJSON(res, 400, { error: e.message });
443
+ }
444
+ return;
445
+ }
446
+
419
447
  const redoCommitMatch = routePath.match(/^\/api\/redo-job\/([^/]+)$/);
420
448
  if (req.method === 'POST' && redoCommitMatch) {
421
449
  const uniqueId = redoCommitMatch[1];