@web-auto/webauto 0.1.3 → 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 +14 -5
  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
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Phase 3-4 Block: 确保固定 Tab 池(5 个:0=搜索页, 1-4=帖子详情页)
3
+ *
4
+ * 职责:
5
+ * 1. 检查当前 tab 数量,若不足 5 个则补齐到 5 个
6
+ * 2. 返回固定 tab 池索引 [0, 1, 2, 3, 4](0 保留给搜索页,1-4 用于帖子轮转)
7
+ * 3. 确保后续轮转使用固定 tab,不再开新 tab
8
+ */
9
+ async function controllerAction(action, payload, apiUrl) {
10
+ const res = await fetch(`${apiUrl}/v1/controller/action`, {
11
+ method: 'POST',
12
+ headers: { 'Content-Type': 'application/json' },
13
+ body: JSON.stringify({ action, payload }),
14
+ signal: AbortSignal.timeout(20000),
15
+ });
16
+ const data = await res.json().catch(() => ({}));
17
+ return data.data || data;
18
+ }
19
+ async function listPages(profile, apiUrl) {
20
+ const res = await controllerAction('browser:page:list', { profileId: profile }, apiUrl);
21
+ if (res?.error) {
22
+ console.warn(`[Phase34OpenTabs] page:list error: ${res.error}`);
23
+ }
24
+ return res?.pages || res?.data?.pages || [];
25
+ }
26
+ async function getInputMode(apiUrl) {
27
+ const res = await controllerAction('system:input-mode:get', {}, apiUrl).catch(() => null);
28
+ const mode = String(res?.data?.mode || res?.mode || 'system').trim().toLowerCase();
29
+ return mode === 'protocol' ? 'protocol' : 'system';
30
+ }
31
+ async function openTab(profile, apiUrl, inputMode) {
32
+ const maxRetries = 4;
33
+ for (let attempt = 1; attempt <= maxRetries; attempt += 1) {
34
+ const before = await listPages(profile, apiUrl);
35
+ const beforeCount = before.length;
36
+ const tryCountIncrease = async (waitMs = 450) => {
37
+ await new Promise((r) => setTimeout(r, waitMs));
38
+ const after = await listPages(profile, apiUrl);
39
+ return after.length > beforeCount;
40
+ };
41
+ if (inputMode === 'protocol') {
42
+ await controllerAction('browser:page:new', { profileId: profile }, apiUrl).catch(() => null);
43
+ if (await tryCountIncrease(350))
44
+ return;
45
+ await controllerAction('browser:execute', { profile, script: 'window.open("about:blank", "_blank"); true;' }, apiUrl).catch(() => null);
46
+ if (await tryCountIncrease(350))
47
+ return;
48
+ await controllerAction('browser:page:switch', { profileId: profile, index: 0 }, apiUrl).catch(() => null);
49
+ await controllerAction('system:shortcut', { app: 'camoufox', shortcut: 'new-tab' }, apiUrl).catch(() => null);
50
+ if (await tryCountIncrease(500)) {
51
+ console.log('[Phase34OpenTabs] protocol fallback -> system shortcut succeeded');
52
+ return;
53
+ }
54
+ }
55
+ else {
56
+ await controllerAction('browser:page:switch', { profileId: profile, index: 0 }, apiUrl).catch(() => null);
57
+ await controllerAction('system:shortcut', { app: 'camoufox', shortcut: 'new-tab' }, apiUrl).catch(() => null);
58
+ if (await tryCountIncrease(500))
59
+ return;
60
+ }
61
+ console.warn(`[Phase34OpenTabs] ${inputMode} open-tab attempt ${attempt}/${maxRetries} failed (count=${beforeCount})`);
62
+ }
63
+ throw new Error('open_tab_failed_after_retries');
64
+ }
65
+ export async function execute(input) {
66
+ const { profile = 'xiaohongshu_fresh', tabCount = 4, unifiedApiUrl = 'http://127.0.0.1:7701', } = input;
67
+ const requiredTotal = tabCount + 1; // tab0=搜索页 + tab1~4=帖子页
68
+ console.log(`[Phase34OpenTabs] 确保固定 tab 池: ${requiredTotal} 个 (0=搜索页, 1-${tabCount}=帖子页)`);
69
+ const inputMode = await getInputMode(unifiedApiUrl);
70
+ console.log(`[Phase34OpenTabs] input mode: ${inputMode}`);
71
+ let existing = await listPages(profile, unifiedApiUrl);
72
+ if (!existing.length) {
73
+ console.log('[Phase34OpenTabs] 未检测到 session,尝试创建会话');
74
+ await controllerAction('session:create', { profile, url: 'https://www.xiaohongshu.com' }, unifiedApiUrl);
75
+ await new Promise(r => setTimeout(r, 800));
76
+ existing = await listPages(profile, unifiedApiUrl);
77
+ }
78
+ const currentCount = existing.length;
79
+ console.log(`[Phase34OpenTabs] 当前已有 ${currentCount} 个 tab`);
80
+ const needed = Math.max(0, requiredTotal - currentCount);
81
+ if (needed > 0) {
82
+ console.log(`[Phase34OpenTabs] 需要补齐 ${needed} 个 tab`);
83
+ for (let i = 0; i < needed; i++) {
84
+ await openTab(profile, unifiedApiUrl, inputMode);
85
+ console.log(`[Phase34OpenTabs] 新 tab ${currentCount + i + 1} 已打开`);
86
+ }
87
+ }
88
+ else {
89
+ console.log(`[Phase34OpenTabs] tab 数量已满足,无需新开`);
90
+ }
91
+ // 验证最终 tab 池
92
+ const finalPages = await listPages(profile, unifiedApiUrl);
93
+ console.log(`[Phase34OpenTabs] 最终 tab 池: ${finalPages.length} 个`);
94
+ finalPages.forEach((p, i) => {
95
+ const role = i === 0 ? '(搜索页)' : `(帖子页-${i})`;
96
+ console.log(` [${i}] index=${p.index} ${role} url=${p.url?.substring(0, 60)}`);
97
+ });
98
+ // 返回固定 tab 索引池(跳过 tab0,返回 tab1~4)
99
+ const tabs = finalPages.slice(1, requiredTotal).map((p) => ({
100
+ index: p.index,
101
+ pageId: undefined,
102
+ }));
103
+ console.log(`[Phase34OpenTabs] 固定帖子页 tab 索引: [${tabs.map(t => t.index).join(', ')}]`);
104
+ return {
105
+ tabs,
106
+ currentTab: 0,
107
+ };
108
+ }
109
+ //# sourceMappingURL=Phase34OpenTabsBlock.js.map
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Phase 3-4 Block: 持久化详情内容
3
+ *
4
+ * 职责:
5
+ * - 创建目录:~/.webauto/download/xiaohongshu/{env}/{keyword}/{noteId}/
6
+ * - 下载图片到 images/ 目录
7
+ * - 生成 README.md(含相对路径引用)
8
+ */
9
+ export interface PersistDetailInput {
10
+ noteId: string;
11
+ detail: {
12
+ title?: string;
13
+ content?: string;
14
+ authorName?: string;
15
+ authorId?: string;
16
+ publishTime?: string;
17
+ images?: string[];
18
+ };
19
+ keyword: string;
20
+ env?: string;
21
+ unifiedApiUrl?: string;
22
+ }
23
+ export interface PersistDetailOutput {
24
+ success: boolean;
25
+ noteDir: string;
26
+ readmePath: string;
27
+ imagesDir: string;
28
+ imageCount: number;
29
+ error?: string;
30
+ }
31
+ export declare function execute(input: PersistDetailInput): Promise<PersistDetailOutput>;
32
+ //# sourceMappingURL=Phase34PersistDetailBlock.d.ts.map
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Phase 3-4 Block: 持久化详情内容
3
+ *
4
+ * 职责:
5
+ * - 创建目录:~/.webauto/download/xiaohongshu/{env}/{keyword}/{noteId}/
6
+ * - 下载图片到 images/ 目录
7
+ * - 生成 README.md(含相对路径引用)
8
+ */
9
+ import { promises as fs } from 'node:fs';
10
+ import os from 'node:os';
11
+ import path from 'node:path';
12
+ function resolveDownloadRoot() {
13
+ const custom = process.env.WEBAUTO_DOWNLOAD_ROOT || process.env.WEBAUTO_DOWNLOAD_DIR;
14
+ if (custom && custom.trim())
15
+ return custom;
16
+ const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
17
+ return path.join(home, '.webauto', 'download');
18
+ }
19
+ function delay(ms) {
20
+ return new Promise((resolve) => setTimeout(resolve, ms));
21
+ }
22
+ async function ensureDir(dir) {
23
+ await fs.mkdir(dir, { recursive: true });
24
+ }
25
+ async function downloadImage(url, destPath, timeout = 30000) {
26
+ const controller = new AbortController();
27
+ const timer = setTimeout(() => controller.abort(), timeout);
28
+ try {
29
+ const response = await fetch(url, { signal: controller.signal });
30
+ if (!response.ok) {
31
+ throw new Error(`HTTP ${response.status}`);
32
+ }
33
+ const buffer = Buffer.from(await response.arrayBuffer());
34
+ await fs.writeFile(destPath, buffer);
35
+ }
36
+ finally {
37
+ clearTimeout(timer);
38
+ }
39
+ }
40
+ function getFileExtension(url) {
41
+ const match = url.match(/\\.([a-z0-9]+)(?:\\?|$)/i);
42
+ return match ? `.${match[1].toLowerCase()}` : '.jpg';
43
+ }
44
+ export async function execute(input) {
45
+ const { noteId, detail, keyword, env = 'download', unifiedApiUrl = 'http://127.0.0.1:7701', } = input;
46
+ console.log(`[Phase34PersistDetail] 持久化: ${noteId}`);
47
+ try {
48
+ // 1. 创建目录结构
49
+ const baseDir = path.join(resolveDownloadRoot(), 'xiaohongshu', env, keyword, noteId);
50
+ const imagesDir = path.join(baseDir, 'images');
51
+ const readmePath = path.join(baseDir, 'README.md');
52
+ await ensureDir(baseDir);
53
+ await ensureDir(imagesDir);
54
+ // 2. 下载图片
55
+ const images = detail.images || [];
56
+ let downloadedCount = 0;
57
+ for (let i = 0; i < images.length; i++) {
58
+ try {
59
+ const imgUrl = images[i];
60
+ const ext = getFileExtension(imgUrl);
61
+ const destPath = path.join(imagesDir, `${i}${ext}`);
62
+ console.log(`[Phase34PersistDetail] 下载图片 ${i + 1}/${images.length}: ${imgUrl.slice(0, 60)}...`);
63
+ await downloadImage(imgUrl, destPath);
64
+ downloadedCount++;
65
+ // 避免请求过快
66
+ if (i < images.length - 1) {
67
+ await delay(200);
68
+ }
69
+ }
70
+ catch (err) {
71
+ console.warn(`[Phase34PersistDetail] 图片下载失败 [${i}]: ${err.message}`);
72
+ }
73
+ }
74
+ // 3. 生成 README.md
75
+ const lines = [];
76
+ lines.push(`# ${detail.title || '无标题'}`);
77
+ lines.push('');
78
+ lines.push(`## 元数据`);
79
+ lines.push(`- **Note ID**: ${noteId}`);
80
+ lines.push(`- **作者**: ${detail.authorName || '未知'} (${detail.authorId || 'N/A'})`);
81
+ lines.push(`- **发布时间**: ${detail.publishTime || '未知'}`);
82
+ lines.push(`- **原始链接**: \`https://www.xiaohongshu.com/explore/${noteId}\``);
83
+ lines.push('');
84
+ lines.push(`## 正文`);
85
+ lines.push(detail.content || '无正文内容');
86
+ lines.push('');
87
+ if (downloadedCount > 0) {
88
+ lines.push(`## 图片 (${downloadedCount}张)`);
89
+ for (let i = 0; i < downloadedCount; i++) {
90
+ lines.push(`![图片${i + 1}](./images/${i}.jpg)`);
91
+ }
92
+ lines.push('');
93
+ }
94
+ const readmeContent = lines.join('\\n');
95
+ await fs.writeFile(readmePath, readmeContent, 'utf8');
96
+ console.log(`[Phase34PersistDetail] ✅ 持久化完成: ${baseDir}`);
97
+ return {
98
+ success: true,
99
+ noteDir: baseDir,
100
+ readmePath,
101
+ imagesDir,
102
+ imageCount: downloadedCount,
103
+ };
104
+ }
105
+ catch (err) {
106
+ console.error(`[Phase34PersistDetail] ❌ 失败: ${err.message}`);
107
+ return {
108
+ success: false,
109
+ noteDir: '',
110
+ readmePath: '',
111
+ imagesDir: '',
112
+ imageCount: 0,
113
+ error: err.message,
114
+ };
115
+ }
116
+ }
117
+ //# sourceMappingURL=Phase34PersistDetailBlock.js.map
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Phase 3-4 Block: 处理单条笔记(详情 + 评论)
3
+ *
4
+ * 职责:
5
+ * - 打开详情页(使用 safeUrl)
6
+ * - 提取详情内容
7
+ * - 采集评论(支持分批)
8
+ * - 持久化结果
9
+ * - 返回搜索页
10
+ */
11
+ export interface ProcessSingleNoteInput {
12
+ noteId: string;
13
+ safeUrl: string;
14
+ searchUrl: string;
15
+ keyword: string;
16
+ env?: string;
17
+ profile?: string;
18
+ unifiedApiUrl?: string;
19
+ maxCommentRounds?: number;
20
+ commentBatchSize?: number;
21
+ timeoutMs?: number;
22
+ maxRetries?: number;
23
+ }
24
+ export interface ProcessSingleNoteOutput {
25
+ success: boolean;
26
+ noteId: string;
27
+ detail: any;
28
+ comments: any[];
29
+ noteDir?: string;
30
+ readmePath?: string;
31
+ error?: string;
32
+ retryCount?: number;
33
+ }
34
+ export declare function execute(input: ProcessSingleNoteInput): Promise<ProcessSingleNoteOutput>;
35
+ //# sourceMappingURL=Phase34ProcessSingleNoteBlock.d.ts.map
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Phase 3-4 Block: 处理单条笔记(详情 + 评论)
3
+ *
4
+ * 职责:
5
+ * - 打开详情页(使用 safeUrl)
6
+ * - 提取详情内容
7
+ * - 采集评论(支持分批)
8
+ * - 持久化结果
9
+ * - 返回搜索页
10
+ */
11
+ import { execute as openDetail } from './Phase34OpenDetailBlock.js';
12
+ import { execute as extractDetail } from './Phase34ExtractDetailBlock.js';
13
+ import { execute as collectComments } from './Phase34CollectCommentsBlock.js';
14
+ import { execute as persistDetail } from './Phase34PersistDetailBlock.js';
15
+ import { execute as closeDetail } from './Phase34CloseDetailBlock.js';
16
+ function delay(ms) {
17
+ return new Promise((resolve) => setTimeout(resolve, ms));
18
+ }
19
+ async function withTimeout(promise, timeoutMs, errorMessage) {
20
+ const controller = new AbortController();
21
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
22
+ try {
23
+ const result = await Promise.race([
24
+ promise,
25
+ new Promise((_, reject) => {
26
+ controller.signal.addEventListener('abort', () => reject(new Error(errorMessage)));
27
+ })
28
+ ]);
29
+ clearTimeout(timer);
30
+ return result;
31
+ }
32
+ catch (err) {
33
+ clearTimeout(timer);
34
+ throw err;
35
+ }
36
+ }
37
+ export async function execute(input) {
38
+ const { noteId, safeUrl, searchUrl, keyword, env = 'download', profile = 'xiaohongshu_fresh', unifiedApiUrl = 'http://127.0.0.1:7701', maxCommentRounds = 50, commentBatchSize = 50, timeoutMs = 180000, maxRetries = 2, } = input;
39
+ console.log(`[Phase34ProcessSingleNote] 开始处理: ${noteId}`);
40
+ let retryCount = 0;
41
+ let lastError;
42
+ for (retryCount = 0; retryCount <= maxRetries; retryCount++) {
43
+ if (retryCount > 0) {
44
+ console.log(`[Phase34ProcessSingleNote] 重试 ${retryCount}/${maxRetries}: ${noteId}`);
45
+ await delay(2000);
46
+ }
47
+ try {
48
+ // 1. 打开详情页(带超时)
49
+ await withTimeout(openDetail({ noteId, safeUrl, profile, unifiedApiUrl }), 30000, '打开详情页超时');
50
+ // 2. 提取详情内容(带超时)
51
+ const detailResult = await withTimeout(extractDetail({ noteId, profile, unifiedApiUrl }), 30000, '提取详情超时');
52
+ if (!detailResult.success) {
53
+ throw new Error(`提取详情失败: ${detailResult.error || 'unknown'}`);
54
+ }
55
+ // 3. 采集评论(带超时)
56
+ const commentsResult = await withTimeout(collectComments({
57
+ sessionId: profile,
58
+ maxRounds: maxCommentRounds,
59
+ batchSize: commentBatchSize,
60
+ unifiedApiUrl,
61
+ }), timeoutMs, '采集评论超时');
62
+ // 4. 持久化详情(带超时)
63
+ const persistResult = await withTimeout(persistDetail({
64
+ noteId,
65
+ detail: detailResult.detail || {},
66
+ keyword,
67
+ env,
68
+ unifiedApiUrl,
69
+ }), 60000, '持久化详情超时');
70
+ // 5. 返回搜索页(无论成功失败都要返回)
71
+ try {
72
+ await closeDetail({ profile, unifiedApiUrl });
73
+ await delay(1000);
74
+ }
75
+ catch (err) {
76
+ console.warn(`[Phase34ProcessSingleNote] 返回搜索页失败: ${err.message}`);
77
+ }
78
+ console.log(`[Phase34ProcessSingleNote] ✅ 完成: ${noteId} (重试${retryCount}次)`);
79
+ return {
80
+ success: true,
81
+ noteId,
82
+ detail: detailResult.detail || null,
83
+ comments: commentsResult.comments,
84
+ noteDir: persistResult.noteDir,
85
+ readmePath: persistResult.readmePath,
86
+ retryCount,
87
+ };
88
+ }
89
+ catch (err) {
90
+ lastError = err.message;
91
+ console.error(`[Phase34ProcessSingleNote] ❌ 尝试${retryCount + 1}失败: ${noteId}`, err.message);
92
+ // 失败也要返回搜索页
93
+ try {
94
+ await closeDetail({ profile, unifiedApiUrl });
95
+ await delay(1000);
96
+ }
97
+ catch (closeErr) {
98
+ console.warn(`[Phase34ProcessSingleNote] 返回搜索页失败: ${closeErr.message}`);
99
+ }
100
+ if (retryCount >= maxRetries) {
101
+ break;
102
+ }
103
+ }
104
+ }
105
+ return {
106
+ success: false,
107
+ noteId,
108
+ detail: null,
109
+ comments: [],
110
+ error: lastError || '未知错误',
111
+ retryCount,
112
+ };
113
+ }
114
+ //# sourceMappingURL=Phase34ProcessSingleNoteBlock.js.map
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Phase 3-4 Block: 链接前置校验
3
+ *
4
+ * 职责:
5
+ * 1. 确认当前在搜索结果页
6
+ * 2. 读取 phase2-links.jsonl
7
+ * 3. 过滤有效链接(safeUrl 含 xsec_token)
8
+ */
9
+ export interface ValidateLinksInput {
10
+ keyword: string;
11
+ env?: string;
12
+ linksPath?: string;
13
+ shardIndex?: number;
14
+ shardCount?: number;
15
+ shardBy?: 'noteId-hash' | 'index-mod';
16
+ maxNotes?: number;
17
+ profile?: string;
18
+ unifiedApiUrl?: string;
19
+ }
20
+ export interface ValidateLinksOutput {
21
+ success: boolean;
22
+ links: Array<{
23
+ noteId: string;
24
+ safeUrl: string;
25
+ searchUrl: string;
26
+ ts: string;
27
+ }>;
28
+ error?: string;
29
+ totalCount: number;
30
+ validCount: number;
31
+ currentUrl: string;
32
+ }
33
+ export declare function execute(input: ValidateLinksInput): Promise<ValidateLinksOutput>;
34
+ //# sourceMappingURL=Phase34ValidateLinksBlock.d.ts.map
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Phase 3-4 Block: 链接前置校验
3
+ *
4
+ * 职责:
5
+ * 1. 确认当前在搜索结果页
6
+ * 2. 读取 phase2-links.jsonl
7
+ * 3. 过滤有效链接(safeUrl 含 xsec_token)
8
+ */
9
+ import os from 'node:os';
10
+ import path from 'node:path';
11
+ import { normalizeShard, shardFilterByIndexMod, shardFilterByNoteIdHash, } from './helpers/sharding.js';
12
+ async function controllerAction(action, payload, apiUrl) {
13
+ const res = await fetch(`${apiUrl}/v1/controller/action`, {
14
+ method: 'POST',
15
+ headers: { 'Content-Type': 'application/json' },
16
+ body: JSON.stringify({ action, payload }),
17
+ signal: AbortSignal.timeout(20000),
18
+ });
19
+ const data = await res.json().catch(() => ({}));
20
+ return data.data || data;
21
+ }
22
+ async function readJsonl(filePath) {
23
+ const { readFile } = await import('node:fs/promises');
24
+ try {
25
+ const content = await readFile(filePath, 'utf8');
26
+ return content
27
+ .trim()
28
+ .split('\n')
29
+ .filter((line) => line.trim())
30
+ .map((line) => JSON.parse(line));
31
+ }
32
+ catch {
33
+ return [];
34
+ }
35
+ }
36
+ function resolveDownloadRoot() {
37
+ const custom = process.env.WEBAUTO_DOWNLOAD_ROOT || process.env.WEBAUTO_DOWNLOAD_DIR;
38
+ if (custom && custom.trim())
39
+ return custom;
40
+ const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
41
+ return path.join(home, '.webauto', 'download');
42
+ }
43
+ export async function execute(input) {
44
+ const { keyword, env = 'debug', linksPath, shardIndex, shardCount, shardBy = 'noteId-hash', maxNotes, profile = 'xiaohongshu_fresh', unifiedApiUrl = 'http://127.0.0.1:7701', } = input;
45
+ console.log('[Phase34ValidateLinks] 开始校验链接...');
46
+ // Phase34 只依赖 phase2-links.jsonl,不依赖 .collect-state.json
47
+ // .collect-state.json 只是 phase2 内部重入状态管理
48
+ const currentUrl = await controllerAction('browser:execute', {
49
+ profile,
50
+ script: 'window.location.href',
51
+ }, unifiedApiUrl).then((res) => res?.result || res?.data?.result || '');
52
+ console.log(`[Phase34ValidateLinks] 当前页面: ${currentUrl}`);
53
+ // 读取 phase2-links.jsonl
54
+ const defaultPath = path.join(resolveDownloadRoot(), 'xiaohongshu', env, keyword, 'phase2-links.jsonl');
55
+ const targetPath = linksPath || defaultPath;
56
+ console.log(`[Phase34ValidateLinks] 读取链接文件: ${targetPath}`);
57
+ const allLinks = await readJsonl(targetPath);
58
+ if (allLinks.length === 0) {
59
+ console.warn('[Phase34ValidateLinks] 链接文件为空或不存在');
60
+ }
61
+ console.log(`[Phase34ValidateLinks] 总链接数: ${allLinks.length}`);
62
+ // 过滤有效链接:safeUrl 必须包含 xsec_token
63
+ const validLinks = allLinks.filter((item) => {
64
+ const hasToken = typeof item?.safeUrl === 'string' && item.safeUrl.includes('xsec_token');
65
+ return hasToken;
66
+ });
67
+ const cappedLimit = Number.isFinite(Number(maxNotes)) ? Math.max(1, Math.floor(Number(maxNotes))) : null;
68
+ const cappedLinks = cappedLimit ? validLinks.slice(0, cappedLimit) : validLinks;
69
+ const shard = normalizeShard({ index: shardIndex, count: shardCount, by: shardBy });
70
+ const sharded = shard
71
+ ? shard.by === 'index-mod'
72
+ ? shardFilterByIndexMod(cappedLinks, shard)
73
+ : shardFilterByNoteIdHash(cappedLinks, shard)
74
+ : cappedLinks;
75
+ console.log(`[Phase34ValidateLinks] 有效链接数: ${validLinks.length}`);
76
+ if (cappedLimit) {
77
+ console.log(`[Phase34ValidateLinks] 全局上限: ${cappedLimit} -> ${cappedLinks.length} 条`);
78
+ }
79
+ if (shard) {
80
+ console.log(`[Phase34ValidateLinks] Shard(${shard.by}): ${shard.index}/${shard.count} -> ${sharded.length} 条`);
81
+ }
82
+ return {
83
+ success: true,
84
+ links: sharded,
85
+ totalCount: allLinks.length,
86
+ validCount: sharded.length,
87
+ currentUrl,
88
+ };
89
+ }
90
+ //# sourceMappingURL=Phase34ValidateLinksBlock.js.map
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Phase 3 Block: 闂備浇宕垫慨鏉懨洪妶鍥e亾濮樼厧鐏︽い銏$懇楠炲鎮欏▓鎸庨敜闂備礁婀遍崕銈夊垂娴e喚鏉介梻鍌欑劍鐎笛呯矙閹存繐鑰挎繛鎾次焑ract闂?
3
+ *
4
+ * 闂傚倷鑳堕崢褍鐣烽鍕剹濞撴埃鍋撻柟顖氳嫰铻栭柛娑卞枟濞?
5
+ * - 闂傚倷鑳堕幊鎾绘倶濮樿泛绠伴柛婵勫劜椤洟鏌熺€电校闁哄棙绮撻弻鈥崇暤椤斿吋鍣洪柟灞傚€濋弻锝夋偐閸欏銈梻鍌氬鐎氭澘鐣烽弴锛勭杸闁哄倽顔婄花鑽ょ磼閻愵剚绶叉い锕佷含缁牓宕滈崫绔慹Url闂傚倷鐒︾€笛呯矙閹达附鍤愭い鏍ㄧ矋瀹曟煡鏌嶈閸撴瑩鈥旈崘顔嘉ч柛顐亜濞堫參姊?xsec_token闂?
6
+ * - 闂備浇顕х换鎺楀磻閻愯娲冀椤愶綆娼熼梺鐟邦嚟婵數鈧碍宀搁弻娑㈠即閵娿儲鐝梺鍝ュ枎閻楁捇寮?
7
+ * - 濠电姷鏁告慨鎾晝閵夆晜鍤岄柣鎰靛墯閸欏繘鏌嶉崫鍕殲閻庢碍宀搁弻娑㈠即閵娿儲鐝梺鍝ュ枎閻楁捇寮诲☉銏犲唨妞ゆ劑鍨诲▓銈囩磽娴d粙鍝虹紒璇插閸掓帡妫冨☉鎺擃潔闂佸啿鐏堥弲娑欑閹岀唵閻犺櫣灏ㄥ銉╂煟閿曗偓閻栧ジ寮诲☉姗嗘僵闁绘挸绨肩花濠氭⒑閸涘浼曢柛銉e妿閸欏棗顪冮妶鍡橆梿闁稿鍔欓獮妤呮偄閸忚偐鍘介梺闈涱焾閸庨亶顢旈鍕厽闁挎洍鍋撴繛瀵稿厴楠?
8
+ * - 婵犲痉鏉库偓鏇㈠磹閸︻厽绠掗梺璇查閻忔氨鏁敓鐘茬畺婵炲棙鍨堕崗婊堟煕濞戝崬鐏辨繛绮瑰亾闂傚倷绀佸﹢閬嶁€﹂崼銉嬪洭鎮界粙璺唶闂佺懓澧庨弲顐㈢暤娓氣偓閺屾盯骞橀懠顒夋М婵炲濯崹鍫曞蓟濞戙垹绠i柨婵嗘噹閹偤姊虹粙娆惧劀缂佹彃娼¢獮?
9
+ * - 闂傚倷娴囬~澶愬箚鐏炲墽顩叉繝濠傚幘閻熼偊娼ㄩ柍褜鍓熼獮蹇涘礃椤旇棄浠奸柣蹇曞仜婢т粙鏁嶅鈧鍝劽虹紒妯衡枏闂佸憡鏌ㄩ鍥嚍闁稁鏁嗗〒姘处椤旀棃鎮楅崗澶婁壕闂佸憡鍔︽禍婵嬶綖瀹ュ鈷戦柟绋挎捣閳藉鏌eΔ浣瑰磳闁诡喚鏁婚、娆撴偩瀹€濠冮敜婵$偑鍊栧濠氬储瑜旇矾?闂傚倷鑳剁划顖炲礉濡ゅ懌鈧焦绻濋崟顓犵効闂佸湱澧楀妯兼喆閿曞倸绠归柟纰卞幖閻忥絿绱掗埀?
10
+ */
11
+ export interface InteractRoundStats {
12
+ round: number;
13
+ visible: number;
14
+ harvestedNew: number;
15
+ harvestedTotal: number;
16
+ ruleHits: number;
17
+ hitTotal?: number;
18
+ skippedTotal?: number;
19
+ likedTotalActual?: number;
20
+ hitCheckOk?: boolean;
21
+ gateBlocked: number;
22
+ dedupSkipped: number;
23
+ alreadyLikedSkipped: number;
24
+ notVisibleSkipped: number;
25
+ nestedParentSkipped?: number;
26
+ clickFailed: number;
27
+ verifyFailed: number;
28
+ newLikes: number;
29
+ likedTotal: number;
30
+ reachedBottom: boolean;
31
+ endReason?: string;
32
+ ms: number;
33
+ }
34
+ export interface InteractInput {
35
+ sessionId: string;
36
+ noteId: string;
37
+ safeUrl: string;
38
+ likeKeywords: string[];
39
+ maxLikesPerRound?: number;
40
+ dryRun?: boolean;
41
+ unifiedApiUrl?: string;
42
+ keyword?: string;
43
+ env?: string;
44
+ reuseCurrentDetail?: boolean;
45
+ commentsAlreadyOpened?: boolean;
46
+ collectComments?: boolean;
47
+ persistCollectedComments?: boolean;
48
+ commentsFilePath?: string;
49
+ evidenceDir?: string;
50
+ onRound?: (stats: InteractRoundStats) => void;
51
+ }
52
+ export interface InteractOutput {
53
+ success: boolean;
54
+ noteId: string;
55
+ likedCount: number;
56
+ scannedCount: number;
57
+ hitCount?: number;
58
+ skippedCount?: number;
59
+ likedTotal?: number;
60
+ hitCheckOk?: boolean;
61
+ mismatchEvidence?: {
62
+ postScreenshot?: string | null;
63
+ };
64
+ errorEvidence?: {
65
+ screenshot?: string | null;
66
+ };
67
+ likedComments: Array<{
68
+ index: number;
69
+ userId: string;
70
+ userName: string;
71
+ content: string;
72
+ timestamp: string;
73
+ screenshots?: {
74
+ before?: string | null;
75
+ after?: string | null;
76
+ };
77
+ matchedRule?: string;
78
+ }>;
79
+ commentsAdded?: number;
80
+ commentsTotal?: number;
81
+ commentsPath?: string;
82
+ evidenceDir?: string;
83
+ dedupSkipped?: number;
84
+ alreadyLikedSkipped?: number;
85
+ reachedBottom: boolean;
86
+ stopReason?: string;
87
+ error?: string;
88
+ }
89
+ export type LikeRule = {
90
+ kind: 'contains';
91
+ include: string;
92
+ raw: string;
93
+ } | {
94
+ kind: 'and';
95
+ includeA: string;
96
+ includeB: string;
97
+ raw: string;
98
+ } | {
99
+ kind: 'include_without';
100
+ include: string;
101
+ exclude: string;
102
+ raw: string;
103
+ };
104
+ export declare function compileLikeRules(likeKeywords: string[]): LikeRule[];
105
+ export declare function matchLikeText(textRaw: string, rules: LikeRule[]): {
106
+ ok: boolean;
107
+ reason: string;
108
+ matchedRule?: string;
109
+ };
110
+ export declare function execute(input: InteractInput): Promise<InteractOutput>;
111
+ //# sourceMappingURL=Phase3InteractBlock.d.ts.map