@web-auto/webauto 0.1.4 → 0.1.6

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.
Files changed (174) hide show
  1. package/apps/desktop-console/default-settings.json +2 -2
  2. package/apps/desktop-console/dist/main/index.mjs +915 -85
  3. package/apps/desktop-console/dist/main/preload.mjs +7 -0
  4. package/apps/desktop-console/dist/renderer/index.html +622 -50
  5. package/apps/desktop-console/dist/renderer/index.js +2415 -470
  6. package/apps/desktop-console/dist/renderer/run.mts +6 -5
  7. package/apps/desktop-console/entry/ui-cli.mjs +672 -0
  8. package/apps/desktop-console/entry/ui-console.mjs +416 -29
  9. package/apps/webauto/entry/account.mjs +89 -53
  10. package/apps/webauto/entry/browser-status.mjs +7 -10
  11. package/apps/webauto/entry/lib/account-detect.mjs +254 -28
  12. package/apps/webauto/entry/lib/account-store.mjs +219 -30
  13. package/apps/webauto/entry/lib/bus-publish.mjs +63 -0
  14. package/apps/webauto/entry/lib/camo-cli.mjs +93 -0
  15. package/apps/webauto/entry/lib/profilepool.mjs +14 -5
  16. package/apps/webauto/entry/lib/quota-status.mjs +23 -0
  17. package/apps/webauto/entry/lib/schedule-store.mjs +1068 -0
  18. package/apps/webauto/entry/profilepool.mjs +106 -17
  19. package/apps/webauto/entry/schedule.mjs +612 -0
  20. package/apps/webauto/entry/weibo-unified.mjs +134 -0
  21. package/apps/webauto/entry/xhs-install.mjs +236 -29
  22. package/apps/webauto/entry/xhs-status.mjs +5 -2
  23. package/apps/webauto/entry/xhs-unified.mjs +631 -98
  24. package/apps/webauto/resources/container-library/weibo/weibo_detail_page/comment_item/container.json +40 -0
  25. package/apps/webauto/resources/container-library/weibo/weibo_detail_page/reply_expand_button/container.json +38 -0
  26. package/apps/webauto/resources/container-library/weibo/weibo_detail_page/reply_list/container.json +37 -0
  27. package/apps/webauto/resources/container-library/weibo/weibo_search_page/container.json +8 -3
  28. package/apps/webauto/resources/container-library/weibo/weibo_search_page/login_anchor/container.json +30 -0
  29. package/apps/webauto/resources/container-library/weibo/weibo_search_page/search_bar/container.json +47 -0
  30. package/apps/webauto/resources/container-library/weibo/weibo_search_page/search_button/container.json +39 -0
  31. package/bin/camoufox-cli.mjs +61 -0
  32. package/bin/webauto.mjs +301 -54
  33. package/dist/modules/camo-backend/src/index.js +49 -1
  34. package/dist/modules/camo-backend/src/internal/BrowserSession.js +572 -3
  35. package/dist/modules/camo-backend/src/internal/SessionManager.js +13 -1
  36. package/dist/modules/camo-backend/src/internal/storage-paths.js +6 -0
  37. package/dist/modules/collection-manager/bloom-filter.js +91 -0
  38. package/dist/modules/collection-manager/date-utils.js +275 -0
  39. package/dist/modules/collection-manager/index.js +258 -0
  40. package/dist/modules/collection-manager/storage.js +195 -0
  41. package/dist/modules/collection-manager/types.js +47 -0
  42. package/dist/modules/logging/src/index.js +1 -1
  43. package/dist/modules/process-registry/index.js +230 -0
  44. package/dist/modules/rate-limiter/index.js +242 -0
  45. package/dist/modules/workflow/blocks/ExecuteWeiboSearchBlock.js +128 -0
  46. package/dist/modules/workflow/blocks/PersistXhsNoteBlock.js +7 -3
  47. package/dist/modules/workflow/blocks/RenderMarkdown.js +4 -1
  48. package/dist/modules/workflow/blocks/WeiboCollectCommentsBlock.js +282 -0
  49. package/dist/modules/workflow/blocks/WeiboCollectFromLinksBlock.js +283 -0
  50. package/dist/modules/workflow/blocks/WeiboCollectSearchLinksBlock.js +208 -0
  51. package/dist/modules/workflow/blocks/WeiboCollectTimelineListBlock.js +128 -0
  52. package/dist/modules/workflow/blocks/WeiboCollectUserPostsListBlock.js +127 -0
  53. package/dist/modules/workflow/blocks/helpers/downloadPaths.js +21 -0
  54. package/dist/modules/workflow/config/workflowRegistry.js +2 -0
  55. package/dist/modules/workflow/definitions/weibo-search-workflow-v1.js +47 -0
  56. package/dist/modules/workflow/src/runner.js +6 -0
  57. package/dist/modules/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.js +4 -0
  58. package/dist/modules/xiaohongshu/app/src/blocks/Phase3InteractBlock.js +2 -2
  59. package/dist/modules/xiaohongshu/app/src/blocks/helpers/sharding.js +123 -0
  60. package/dist/modules/xiaohongshu/app/src/container-registry/src/index.d.ts +37 -0
  61. package/dist/modules/xiaohongshu/app/src/container-registry/src/index.js +184 -0
  62. package/dist/modules/xiaohongshu/app/src/workflow/blocks/AnchorVerificationBlock.d.ts +31 -0
  63. package/dist/modules/xiaohongshu/app/src/workflow/blocks/AnchorVerificationBlock.js +71 -0
  64. package/dist/modules/xiaohongshu/app/src/workflow/blocks/DetectPageStateBlock.d.ts +48 -0
  65. package/dist/modules/xiaohongshu/app/src/workflow/blocks/DetectPageStateBlock.js +259 -0
  66. package/dist/modules/xiaohongshu/app/src/workflow/blocks/ErrorRecoveryBlock.d.ts +28 -0
  67. package/dist/modules/xiaohongshu/app/src/workflow/blocks/ErrorRecoveryBlock.js +319 -0
  68. package/dist/modules/xiaohongshu/app/src/workflow/blocks/WaitSearchPermitBlock.d.ts +36 -0
  69. package/dist/modules/xiaohongshu/app/src/workflow/blocks/WaitSearchPermitBlock.js +162 -0
  70. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/containerAnchors.d.ts +36 -0
  71. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/containerAnchors.js +301 -0
  72. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/operationLogger.d.ts +29 -0
  73. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/operationLogger.js +195 -0
  74. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/searchPageState.d.ts +25 -0
  75. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/searchPageState.js +164 -0
  76. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/MatchCommentsBlock.d.ts +66 -0
  77. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/MatchCommentsBlock.js +139 -0
  78. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1EnsureServicesBlock.d.ts +16 -0
  79. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1EnsureServicesBlock.js +36 -0
  80. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1MonitorCookieBlock.d.ts +27 -0
  81. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1MonitorCookieBlock.js +213 -0
  82. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.d.ts +18 -0
  83. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.js +121 -0
  84. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2CollectLinksBlock.d.ts +34 -0
  85. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2CollectLinksBlock.js +1249 -0
  86. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2SearchBlock.d.ts +17 -0
  87. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2SearchBlock.js +703 -0
  88. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseDetailBlock.d.ts +15 -0
  89. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseDetailBlock.js +41 -0
  90. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseTabsBlock.d.ts +26 -0
  91. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseTabsBlock.js +44 -0
  92. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CollectCommentsBlock.d.ts +29 -0
  93. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CollectCommentsBlock.js +150 -0
  94. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ExtractDetailBlock.d.ts +38 -0
  95. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ExtractDetailBlock.js +117 -0
  96. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenDetailBlock.d.ts +30 -0
  97. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenDetailBlock.js +102 -0
  98. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenTabsBlock.d.ts +23 -0
  99. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenTabsBlock.js +109 -0
  100. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.d.ts +32 -0
  101. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.js +117 -0
  102. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ProcessSingleNoteBlock.d.ts +35 -0
  103. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ProcessSingleNoteBlock.js +114 -0
  104. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ValidateLinksBlock.d.ts +34 -0
  105. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ValidateLinksBlock.js +90 -0
  106. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase3InteractBlock.d.ts +111 -0
  107. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase3InteractBlock.js +1009 -0
  108. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase4MultiTabHarvestBlock.d.ts +20 -0
  109. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase4MultiTabHarvestBlock.js +233 -0
  110. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/ReplyInteractBlock.d.ts +48 -0
  111. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/ReplyInteractBlock.js +291 -0
  112. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/XhsDiscoverFallbackBlock.d.ts +23 -0
  113. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/XhsDiscoverFallbackBlock.js +240 -0
  114. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatchDsl.d.ts +55 -0
  115. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatchDsl.js +126 -0
  116. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatcher.d.ts +21 -0
  117. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatcher.js +99 -0
  118. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/evidence.d.ts +5 -0
  119. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/evidence.js +27 -0
  120. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/sharding.d.ts +37 -0
  121. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/sharding.js +165 -0
  122. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/xhsComments.d.ts +33 -0
  123. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/xhsComments.js +270 -0
  124. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/index.d.ts +9 -0
  125. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/index.js +9 -0
  126. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/checkpoints.d.ts +50 -0
  127. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/checkpoints.js +222 -0
  128. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/controllerAction.d.ts +10 -0
  129. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/controllerAction.js +43 -0
  130. package/dist/services/shared/serviceProcessLogger.js +1 -1
  131. package/dist/services/unified-api/server.js +105 -11
  132. package/modules/camo-backend/src/index.ts +46 -1
  133. package/modules/camo-backend/src/internal/BrowserSession.ts +619 -3
  134. package/modules/camo-backend/src/internal/SessionManager.ts +12 -1
  135. package/modules/camo-backend/src/internal/storage-paths.ts +5 -0
  136. package/modules/camo-runtime/src/autoscript/action-providers/xhs/comments.mjs +38 -2
  137. package/modules/camo-runtime/src/autoscript/action-providers/xhs/interaction.mjs +47 -2
  138. package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +94 -11
  139. package/modules/camo-runtime/src/autoscript/action-providers/xhs.mjs +208 -2
  140. package/modules/camo-runtime/src/autoscript/runtime.mjs +7 -1
  141. package/modules/camo-runtime/src/autoscript/xhs-unified-template.mjs +76 -43
  142. package/modules/camo-runtime/src/container/runtime-core/operations/index.mjs +75 -1
  143. package/modules/camo-runtime/src/container/runtime-core/operations/selector-scripts.mjs +71 -4
  144. package/modules/camo-runtime/src/container/runtime-core/operations/tab-pool.mjs +183 -27
  145. package/modules/collection-manager/bloom-filter.ts +112 -0
  146. package/modules/collection-manager/date-utils.ts +316 -0
  147. package/modules/collection-manager/index.ts +309 -0
  148. package/modules/collection-manager/package.json +10 -0
  149. package/modules/collection-manager/storage.ts +174 -0
  150. package/modules/collection-manager/types.ts +156 -0
  151. package/modules/logging/src/index.ts +1 -1
  152. package/modules/process-registry/index.ts +284 -0
  153. package/modules/rate-limiter/index.ts +322 -0
  154. package/modules/state/src/paths.ts +9 -1
  155. package/modules/task-scheduler/index.ts +293 -0
  156. package/modules/workflow/blocks/ExecuteWeiboSearchBlock.ts +167 -0
  157. package/modules/workflow/blocks/PersistXhsNoteBlock.ts +7 -3
  158. package/modules/workflow/blocks/RenderMarkdown.ts +4 -1
  159. package/modules/workflow/blocks/WeiboCollectCommentsBlock.ts +339 -0
  160. package/modules/workflow/blocks/WeiboCollectFromLinksBlock.ts +338 -0
  161. package/modules/workflow/blocks/helpers/downloadPaths.ts +16 -0
  162. package/modules/workflow/config/workflowRegistry.ts +2 -0
  163. package/modules/workflow/definitions/weibo-search-workflow-v1.ts +47 -0
  164. package/modules/workflow/src/runner.ts +6 -0
  165. package/modules/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.ts +1 -1
  166. package/modules/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.ts +4 -0
  167. package/modules/xiaohongshu/app/src/blocks/Phase3InteractBlock.ts +2 -3
  168. package/modules/xiaohongshu/app/src/blocks/helpers/sharding.ts +152 -0
  169. package/package.json +13 -4
  170. package/scripts/postinstall-resources.mjs +62 -0
  171. package/scripts/test/run-coverage.mjs +76 -0
  172. package/scripts/weibo/search.ts +49 -0
  173. package/services/shared/serviceProcessLogger.ts +1 -1
  174. package/services/unified-api/server.ts +98 -12
@@ -19,6 +19,13 @@ function toPositiveInt(value, fallback, min = 1) {
19
19
  return Math.max(min, Math.floor(num));
20
20
  }
21
21
 
22
+ function toNonNegativeInt(value, fallback = 0) {
23
+ if (value === null || value === undefined || value === '') return fallback;
24
+ const num = Number(value);
25
+ if (!Number.isFinite(num)) return fallback;
26
+ return Math.max(0, Math.floor(num));
27
+ }
28
+
22
29
  function splitCsv(value) {
23
30
  if (Array.isArray(value)) {
24
31
  return value
@@ -32,9 +39,6 @@ function splitCsv(value) {
32
39
  }
33
40
 
34
41
  function pickCloseDependency(options) {
35
- if (options.doReply || options.doLikes) return 'comment_match_gate';
36
- if (options.matchGateEnabled) return 'comment_match_gate';
37
- if (options.commentsHarvestEnabled) return 'comments_harvest';
38
42
  if (options.detailHarvestEnabled) return 'detail_harvest';
39
43
  return 'open_first_detail';
40
44
  }
@@ -73,7 +77,9 @@ function buildOpenFirstDetailScript(maxNotes, keyword) {
73
77
  const cover = item.querySelector('a.cover');
74
78
  if (!cover) return null;
75
79
  const href = String(cover.getAttribute('href') || '').trim();
76
- const noteId = href.split('/').filter(Boolean).pop() || ('idx_' + index);
80
+ const lastSegment = href.split('/').filter(Boolean).pop() || '';
81
+ const normalized = lastSegment.split('?')[0].split('#')[0];
82
+ const noteId = normalized || ('idx_' + index);
77
83
  return { item, cover, href, noteId };
78
84
  })
79
85
  .filter(Boolean);
@@ -137,7 +143,9 @@ function buildOpenNextDetailScript(maxNotes) {
137
143
  const cover = item.querySelector('a.cover');
138
144
  if (!cover) return null;
139
145
  const href = String(cover.getAttribute('href') || '').trim();
140
- const noteId = href.split('/').filter(Boolean).pop() || ('idx_' + index);
146
+ const lastSegment = href.split('/').filter(Boolean).pop() || '';
147
+ const normalized = lastSegment.split('?')[0].split('#')[0];
148
+ const noteId = normalized || ('idx_' + index);
141
149
  return { item, cover, href, noteId };
142
150
  })
143
151
  .filter(Boolean);
@@ -320,6 +328,7 @@ function buildCommentLikeScript(likeKeywords, maxLikesPerRound) {
320
328
  const state = window.__camoXhsState || (window.__camoXhsState = {});
321
329
  const keywords = ${JSON.stringify(likeKeywords)};
322
330
  const maxLikes = Number(${maxLikesPerRound});
331
+ const maxLikesLimit = maxLikes > 0 ? maxLikes : Number.POSITIVE_INFINITY;
323
332
  const nodes = Array.from(document.querySelectorAll('.comment-item'));
324
333
  const matches = Array.isArray(state.matchedComments) ? state.matchedComments : [];
325
334
  const targetIndexes = new Set(matches.map((row) => Number(row.index)));
@@ -327,7 +336,7 @@ function buildCommentLikeScript(likeKeywords, maxLikesPerRound) {
327
336
  let likedCount = 0;
328
337
  let skippedActive = 0;
329
338
  for (let idx = 0; idx < nodes.length; idx += 1) {
330
- if (likedCount >= maxLikes) break;
339
+ if (likedCount >= maxLikesLimit) break;
331
340
  if (targetIndexes.size > 0 && !targetIndexes.has(idx)) continue;
332
341
  const item = nodes[idx];
333
342
  const text = String((item.querySelector('.content, .comment-content, p') || {}).textContent || '').trim();
@@ -460,16 +469,23 @@ function buildAbortScript(code) {
460
469
  export function buildXhsUnifiedAutoscript(rawOptions = {}) {
461
470
  const profileId = toTrimmedString(rawOptions.profileId, 'xiaohongshu-batch-1');
462
471
  const keyword = toTrimmedString(rawOptions.keyword, '手机膜');
463
- const env = toTrimmedString(rawOptions.env, 'debug');
472
+ const env = toTrimmedString(rawOptions.env, 'prod');
464
473
  const outputRoot = toTrimmedString(rawOptions.outputRoot, '');
465
474
  const throttle = toPositiveInt(rawOptions.throttle, 900, 100);
466
475
  const tabCount = toPositiveInt(rawOptions.tabCount, 4, 1);
467
476
  const noteIntervalMs = toPositiveInt(rawOptions.noteIntervalMs, 1200, 200);
468
477
  const maxNotes = toPositiveInt(rawOptions.maxNotes, 30, 1);
469
- const maxLikesPerRound = toPositiveInt(rawOptions.maxLikesPerRound, 2, 1);
478
+ const maxComments = toNonNegativeInt(rawOptions.maxComments, 0);
479
+ const resume = toBoolean(rawOptions.resume, false);
480
+ const incrementalMax = toBoolean(rawOptions.incrementalMax, true);
481
+ const maxLikesPerRound = toNonNegativeInt(rawOptions.maxLikesPerRound ?? rawOptions.maxLikes, 0);
470
482
  const matchMode = toTrimmedString(rawOptions.matchMode, 'any');
471
483
  const matchMinHits = toPositiveInt(rawOptions.matchMinHits, 1, 1);
472
484
  const replyText = toTrimmedString(rawOptions.replyText, '感谢分享,已关注');
485
+ const sharedHarvestPath = toTrimmedString(rawOptions.sharedHarvestPath, '');
486
+ const searchSerialKey = toTrimmedString(rawOptions.searchSerialKey, `${env}:${keyword}`);
487
+ const seedCollectCount = toNonNegativeInt(rawOptions.seedCollectCount, 0);
488
+ const seedCollectMaxRounds = toNonNegativeInt(rawOptions.seedCollectMaxRounds, 0);
473
489
 
474
490
  const doHomepage = toBoolean(rawOptions.doHomepage, true);
475
491
  const doImages = toBoolean(rawOptions.doImages, false);
@@ -506,6 +522,7 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
506
522
  profileId,
507
523
  throttle,
508
524
  defaults: {
525
+ disableTimeout: true,
509
526
  retry: { attempts: 2, backoffMs: 500 },
510
527
  impact: 'subscription',
511
528
  onFailure: 'chain_stop',
@@ -516,9 +533,9 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
516
533
  eventCooldownMs: 300,
517
534
  jitterMs: 220,
518
535
  navigationMinIntervalMs: 1800,
519
- timeoutMs: 180000,
536
+ timeoutMs: 0,
520
537
  },
521
- timeoutMs: 180000,
538
+ timeoutMs: 0,
522
539
  },
523
540
  metadata: {
524
541
  keyword,
@@ -527,6 +544,10 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
527
544
  tabCount,
528
545
  noteIntervalMs,
529
546
  maxNotes,
547
+ maxComments,
548
+ maxLikesPerRound,
549
+ resume,
550
+ incrementalMax,
530
551
  doHomepage,
531
552
  doImages,
532
553
  doComments,
@@ -539,9 +560,14 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
539
560
  matchKeywords,
540
561
  likeKeywords,
541
562
  replyText,
563
+ sharedHarvestPath,
564
+ searchSerialKey,
565
+ seedCollectCount,
566
+ seedCollectMaxRounds,
542
567
  notes: [
543
568
  'open_next_detail intentionally stops script by throwing AUTOSCRIPT_DONE_* when exhausted.',
544
569
  'dev mode uses deterministic no-recovery policy (checkpoint recovery disabled).',
570
+ 'resume=true keeps visited note history for断点续传; incrementalMax=true treats maxNotes as增量配额.',
545
571
  'when persistComments=true, xhs_comments_harvest emits full comments in operation result for downstream jsonl/file persistence.',
546
572
  ],
547
573
  },
@@ -608,7 +634,11 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
608
634
  {
609
635
  id: 'submit_search',
610
636
  action: 'xhs_submit_search',
611
- params: { keyword },
637
+ params: {
638
+ keyword,
639
+ searchSerialKey,
640
+ sharedHarvestPath,
641
+ },
612
642
  trigger: 'home_search_input.exist',
613
643
  dependsOn: ['fill_keyword'],
614
644
  once: true,
@@ -626,15 +656,22 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
626
656
  {
627
657
  id: 'open_first_detail',
628
658
  action: 'xhs_open_detail',
629
- params: { mode: 'first', maxNotes, keyword },
659
+ params: {
660
+ mode: 'first',
661
+ maxNotes,
662
+ keyword,
663
+ resume,
664
+ incrementalMax,
665
+ sharedHarvestPath,
666
+ seedCollectCount,
667
+ seedCollectMaxRounds,
668
+ },
630
669
  trigger: 'search_result_item.exist',
631
670
  dependsOn: ['submit_search'],
632
671
  once: true,
633
672
  timeoutMs: 90000,
634
- validation: {
635
- mode: 'post',
636
- post: { page: { hostIncludes: ['xiaohongshu.com'], checkpointIn: ['detail_ready', 'comments_ready', 'search_ready'] } },
637
- },
673
+ onFailure: 'continue',
674
+ validation: { mode: 'none' },
638
675
  checkpoint: {
639
676
  containerId: 'xiaohongshu_search.search_result_item',
640
677
  targetCheckpoint: 'detail_ready',
@@ -655,17 +692,7 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
655
692
  impact: 'op',
656
693
  onFailure: 'continue',
657
694
  pacing: { operationMinIntervalMs: 2000, eventCooldownMs: 1200, jitterMs: 260 },
658
- validation: {
659
- mode: 'both',
660
- pre: {
661
- page: { hostIncludes: ['xiaohongshu.com'], checkpointIn: ['detail_ready', 'comments_ready'] },
662
- container: { selector: '.note-detail-mask, .note-detail-page, .note-detail-dialog', mustExist: true, minCount: 1 },
663
- },
664
- post: {
665
- page: { hostIncludes: ['xiaohongshu.com'], checkpointIn: ['detail_ready', 'comments_ready'] },
666
- container: { selector: '.note-content, .note-scroller, .media-container', mustExist: true, minCount: 1 },
667
- },
668
- },
695
+ validation: { mode: 'none' },
669
696
  checkpoint: {
670
697
  containerId: 'xiaohongshu_detail.modal_shell',
671
698
  targetCheckpoint: 'detail_ready',
@@ -696,6 +723,7 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
696
723
  keyword,
697
724
  outputRoot,
698
725
  persistComments,
726
+ commentsLimit: maxComments,
699
727
  maxRounds: 48,
700
728
  scrollStep: 360,
701
729
  settleMs: 260,
@@ -717,9 +745,9 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
717
745
  dependsOn: [detailHarvestEnabled ? 'detail_harvest' : 'open_first_detail'],
718
746
  once: false,
719
747
  oncePerAppear: true,
720
- timeoutMs: 90000,
748
+ timeoutMs: 180000,
721
749
  retry: { attempts: 1, backoffMs: 0 },
722
- impact: 'op',
750
+ impact: 'script',
723
751
  onFailure: 'continue',
724
752
  pacing: { operationMinIntervalMs: 2400, eventCooldownMs: 1500, jitterMs: 280 },
725
753
  validation: {
@@ -745,7 +773,7 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
745
773
  action: 'xhs_comment_match',
746
774
  params: { keywords: matchKeywords, mode: matchMode, minHits: matchMinHits },
747
775
  trigger: 'detail_modal.exist',
748
- dependsOn: [commentsHarvestEnabled ? 'comments_harvest' : (detailHarvestEnabled ? 'detail_harvest' : 'open_first_detail')],
776
+ dependsOn: [detailHarvestEnabled ? 'detail_harvest' : 'open_first_detail'],
749
777
  once: false,
750
778
  oncePerAppear: true,
751
779
  pacing: { operationMinIntervalMs: 2400, eventCooldownMs: 1200, jitterMs: 160 },
@@ -797,10 +825,7 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
797
825
  once: false,
798
826
  oncePerAppear: true,
799
827
  pacing: { operationMinIntervalMs: 2500, eventCooldownMs: 1300, jitterMs: 180 },
800
- validation: {
801
- mode: 'post',
802
- post: { page: { hostIncludes: ['xiaohongshu.com'], checkpointIn: ['home_ready', 'search_ready'] } },
803
- },
828
+ validation: { mode: 'none' },
804
829
  checkpoint: {
805
830
  containerId: 'xiaohongshu_detail.discover_button',
806
831
  targetCheckpoint: 'search_ready',
@@ -814,7 +839,7 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
814
839
  trigger: 'search_result_item.exist',
815
840
  dependsOn: ['close_detail'],
816
841
  once: false,
817
- oncePerAppear: true,
842
+ oncePerAppear: false,
818
843
  retry: { attempts: 1, backoffMs: 0 },
819
844
  impact: 'op',
820
845
  onFailure: 'continue',
@@ -827,7 +852,7 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
827
852
  trigger: 'search_result_item.exist',
828
853
  dependsOn: ['wait_between_notes', 'ensure_tab_pool'],
829
854
  once: false,
830
- oncePerAppear: true,
855
+ oncePerAppear: false,
831
856
  timeoutMs: 180000,
832
857
  retry: { attempts: 2, backoffMs: 500 },
833
858
  impact: 'op',
@@ -836,15 +861,21 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
836
861
  {
837
862
  id: 'open_next_detail',
838
863
  action: 'xhs_open_detail',
839
- params: { mode: 'next', maxNotes },
864
+ params: {
865
+ mode: 'next',
866
+ maxNotes,
867
+ resume,
868
+ incrementalMax,
869
+ sharedHarvestPath,
870
+ },
840
871
  trigger: 'search_result_item.exist',
841
872
  dependsOn: ['switch_tab_round_robin'],
842
873
  once: false,
843
- oncePerAppear: true,
874
+ oncePerAppear: false,
844
875
  timeoutMs: 90000,
845
- retry: { attempts: 1, backoffMs: 0 },
846
- impact: 'script',
847
- onFailure: 'stop_all',
876
+ retry: { attempts: 3, backoffMs: 1000 },
877
+ impact: 'op',
878
+ onFailure: 'continue',
848
879
  checkpoint: {
849
880
  containerId: 'xiaohongshu_search.search_result_item',
850
881
  targetCheckpoint: 'detail_ready',
@@ -914,6 +945,8 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
914
945
  params: {
915
946
  acrossPages: true,
916
947
  settleMs: 320,
948
+ pageUrlIncludes: ['/search_result'],
949
+ requireMatchedPages: true,
917
950
  selectors: [
918
951
  { id: 'home_search_input', selector: '#search-input, input.search-input' },
919
952
  { id: 'search_result_item', selector: '.note-item', visible: false, minCount: 1 },
@@ -923,8 +956,8 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
923
956
  dependsOn: ['ensure_tab_pool'],
924
957
  once: true,
925
958
  timeoutMs: 90000,
926
- impact: 'script',
927
- onFailure: 'stop_all',
959
+ impact: 'op',
960
+ onFailure: 'continue',
928
961
  },
929
962
  ],
930
963
  };
@@ -137,6 +137,56 @@ async function executeVerifySubscriptions({ profileId, params }) {
137
137
 
138
138
  const acrossPages = params.acrossPages === true;
139
139
  const settleMs = Math.max(0, Number(params.settleMs ?? 280) || 280);
140
+ const pageUrlIncludes = normalizeArray(params.pageUrlIncludes)
141
+ .map((item) => String(item || '').trim())
142
+ .filter(Boolean);
143
+ const pageUrlExcludes = normalizeArray(params.pageUrlExcludes)
144
+ .map((item) => String(item || '').trim())
145
+ .filter(Boolean);
146
+ const pageUrlRegex = String(params.pageUrlRegex || '').trim();
147
+ const pageUrlNotRegex = String(params.pageUrlNotRegex || '').trim();
148
+ const requireMatchedPages = params.requireMatchedPages !== false;
149
+
150
+ let includeRegex = null;
151
+ if (pageUrlRegex) {
152
+ try {
153
+ includeRegex = new RegExp(pageUrlRegex);
154
+ } catch {
155
+ return asErrorPayload('OPERATION_FAILED', `invalid pageUrlRegex: ${pageUrlRegex}`);
156
+ }
157
+ }
158
+ let excludeRegex = null;
159
+ if (pageUrlNotRegex) {
160
+ try {
161
+ excludeRegex = new RegExp(pageUrlNotRegex);
162
+ } catch {
163
+ return asErrorPayload('OPERATION_FAILED', `invalid pageUrlNotRegex: ${pageUrlNotRegex}`);
164
+ }
165
+ }
166
+
167
+ const hasPageFilter = (
168
+ pageUrlIncludes.length > 0
169
+ || pageUrlExcludes.length > 0
170
+ || Boolean(includeRegex)
171
+ || Boolean(excludeRegex)
172
+ );
173
+
174
+ const shouldVerifyPage = (rawUrl) => {
175
+ const url = String(rawUrl || '').trim();
176
+ if (pageUrlIncludes.length > 0 && !pageUrlIncludes.some((part) => url.includes(part))) {
177
+ return false;
178
+ }
179
+ if (pageUrlExcludes.length > 0 && pageUrlExcludes.some((part) => url.includes(part))) {
180
+ return false;
181
+ }
182
+ if (includeRegex && !includeRegex.test(url)) {
183
+ return false;
184
+ }
185
+ if (excludeRegex && excludeRegex.test(url)) {
186
+ return false;
187
+ }
188
+ return true;
189
+ };
140
190
 
141
191
  const collectForCurrentPage = async () => {
142
192
  const snapshot = await getDomSnapshotByProfile(profileId);
@@ -156,6 +206,7 @@ async function executeVerifySubscriptions({ profileId, params }) {
156
206
 
157
207
  let pagesResult = [];
158
208
  let overallOk = true;
209
+ let matchedPageCount = 0;
159
210
  if (!acrossPages) {
160
211
  const current = await collectForCurrentPage();
161
212
  overallOk = current.matches.every((item) => item.count >= item.minCount);
@@ -165,6 +216,16 @@ async function executeVerifySubscriptions({ profileId, params }) {
165
216
  const { pages, activeIndex } = extractPageList(listed);
166
217
  for (const page of pages) {
167
218
  const pageIndex = Number(page.index);
219
+ const listedUrl = String(page.url || '');
220
+ if (!shouldVerifyPage(listedUrl)) {
221
+ pagesResult.push({
222
+ index: pageIndex,
223
+ url: listedUrl,
224
+ skipped: true,
225
+ ok: true,
226
+ });
227
+ continue;
228
+ }
168
229
  if (Number.isFinite(activeIndex) && activeIndex !== pageIndex) {
169
230
  await callAPI('page:switch', { profileId, index: pageIndex });
170
231
  if (settleMs > 0) await new Promise((resolve) => setTimeout(resolve, settleMs));
@@ -173,15 +234,28 @@ async function executeVerifySubscriptions({ profileId, params }) {
173
234
  const pageOk = current.matches.every((item) => item.count >= item.minCount);
174
235
  overallOk = overallOk && pageOk;
175
236
  pagesResult.push({ index: pageIndex, ...current, ok: pageOk });
237
+ matchedPageCount += 1;
176
238
  }
177
239
  if (Number.isFinite(activeIndex)) {
178
240
  await callAPI('page:switch', { profileId, index: activeIndex });
179
241
  }
180
242
  }
181
243
 
244
+ if (acrossPages && hasPageFilter && requireMatchedPages && matchedPageCount === 0) {
245
+ return asErrorPayload('SUBSCRIPTION_MISMATCH', 'no page matched verify_subscriptions pageUrl filter', {
246
+ acrossPages,
247
+ pageUrlIncludes,
248
+ pageUrlExcludes,
249
+ pageUrlRegex: pageUrlRegex || null,
250
+ pageUrlNotRegex: pageUrlNotRegex || null,
251
+ pages: pagesResult,
252
+ });
253
+ }
254
+
182
255
  if (!overallOk) {
183
256
  return asErrorPayload('SUBSCRIPTION_MISMATCH', 'subscription selectors missing on one or more pages', {
184
257
  acrossPages,
258
+ matchedPageCount,
185
259
  pages: pagesResult,
186
260
  });
187
261
  }
@@ -190,7 +264,7 @@ async function executeVerifySubscriptions({ profileId, params }) {
190
264
  ok: true,
191
265
  code: 'OPERATION_DONE',
192
266
  message: 'verify_subscriptions done',
193
- data: { acrossPages, pages: pagesResult },
267
+ data: { acrossPages, matchedPageCount, pages: pagesResult },
194
268
  };
195
269
  }
196
270
 
@@ -33,7 +33,20 @@ export function buildSelectorClickScript({ selector, highlight }) {
33
33
  }
34
34
  el.scrollIntoView({ behavior: 'smooth', block: 'center' });
35
35
  await new Promise((r) => setTimeout(r, 150));
36
- el.click();
36
+ if (el instanceof HTMLElement) {
37
+ try { el.focus({ preventScroll: true }); } catch {}
38
+ const common = { bubbles: true, cancelable: true, view: window };
39
+ try {
40
+ if (typeof PointerEvent === 'function') {
41
+ el.dispatchEvent(new PointerEvent('pointerdown', { ...common, pointerType: 'mouse', button: 0 }));
42
+ el.dispatchEvent(new PointerEvent('pointerup', { ...common, pointerType: 'mouse', button: 0 }));
43
+ }
44
+ } catch {}
45
+ try { el.dispatchEvent(new MouseEvent('mousedown', { ...common, button: 0 })); } catch {}
46
+ try { el.dispatchEvent(new MouseEvent('mouseup', { ...common, button: 0 })); } catch {}
47
+ }
48
+ if (typeof el.click === 'function') el.click();
49
+ else el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window, button: 0 }));
37
50
  if (${highlightLiteral} && el instanceof HTMLElement) {
38
51
  setTimeout(() => { el.style.outline = restoreOutline; }, 260);
39
52
  }
@@ -56,9 +69,63 @@ export function buildSelectorTypeScript({ selector, highlight, text }) {
56
69
  }
57
70
  el.scrollIntoView({ behavior: 'smooth', block: 'center' });
58
71
  await new Promise((r) => setTimeout(r, 150));
59
- el.focus();
60
- el.value = ${textLiteral};
61
- el.dispatchEvent(new Event('input', { bubbles: true }));
72
+ if (el instanceof HTMLElement) {
73
+ try { el.focus({ preventScroll: true }); } catch {}
74
+ if (typeof el.click === 'function') el.click();
75
+ }
76
+ const value = ${textLiteral};
77
+ const fireInputEvent = (target, name, init) => {
78
+ try {
79
+ if (typeof InputEvent === 'function') {
80
+ target.dispatchEvent(new InputEvent(name, init));
81
+ return;
82
+ }
83
+ } catch {}
84
+ target.dispatchEvent(new Event(name, { bubbles: true, cancelable: init?.cancelable === true }));
85
+ };
86
+ const assignControlValue = (target, next) => {
87
+ if (target instanceof HTMLInputElement) {
88
+ const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set;
89
+ if (setter) setter.call(target, next);
90
+ else target.value = next;
91
+ if (typeof target.setSelectionRange === 'function') {
92
+ const cursor = String(next).length;
93
+ try { target.setSelectionRange(cursor, cursor); } catch {}
94
+ }
95
+ return true;
96
+ }
97
+ if (target instanceof HTMLTextAreaElement) {
98
+ const setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')?.set;
99
+ if (setter) setter.call(target, next);
100
+ else target.value = next;
101
+ if (typeof target.setSelectionRange === 'function') {
102
+ const cursor = String(next).length;
103
+ try { target.setSelectionRange(cursor, cursor); } catch {}
104
+ }
105
+ return true;
106
+ }
107
+ return false;
108
+ };
109
+ const editableAssigned = assignControlValue(el, value);
110
+ if (!editableAssigned) {
111
+ if (el instanceof HTMLElement && el.isContentEditable) {
112
+ el.textContent = value;
113
+ } else {
114
+ throw new Error('Element not editable: ' + ${selectorLiteral});
115
+ }
116
+ }
117
+ fireInputEvent(el, 'beforeinput', {
118
+ bubbles: true,
119
+ cancelable: true,
120
+ data: value,
121
+ inputType: 'insertText',
122
+ });
123
+ fireInputEvent(el, 'input', {
124
+ bubbles: true,
125
+ cancelable: false,
126
+ data: value,
127
+ inputType: 'insertText',
128
+ });
62
129
  el.dispatchEvent(new Event('change', { bubbles: true }));
63
130
  if (${highlightLiteral} && el instanceof HTMLElement) {
64
131
  setTimeout(() => { el.style.outline = restoreOutline; }, 260);