@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,15 @@
1
+ /**
2
+ * Phase 3-4 Block: 关闭详情页(返回搜索页)
3
+ *
4
+ * 职责:从详情页安全返回搜索结果页
5
+ */
6
+ export interface CloseDetailInput {
7
+ profile?: string;
8
+ unifiedApiUrl?: string;
9
+ }
10
+ export interface CloseDetailOutput {
11
+ success: boolean;
12
+ finalUrl: string;
13
+ }
14
+ export declare function execute(input: CloseDetailInput): Promise<CloseDetailOutput>;
15
+ //# sourceMappingURL=Phase34CloseDetailBlock.d.ts.map
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Phase 3-4 Block: 关闭详情页(返回搜索页)
3
+ *
4
+ * 职责:从详情页安全返回搜索结果页
5
+ */
6
+ async function controllerAction(action, payload, apiUrl) {
7
+ const res = await fetch(`${apiUrl}/v1/controller/action`, {
8
+ method: 'POST',
9
+ headers: { 'Content-Type': 'application/json' },
10
+ body: JSON.stringify({ action, payload }),
11
+ signal: AbortSignal.timeout(20000),
12
+ });
13
+ const data = await res.json().catch(() => ({}));
14
+ return data.data || data;
15
+ }
16
+ function delay(ms) {
17
+ return new Promise((resolve) => setTimeout(resolve, ms));
18
+ }
19
+ export async function execute(input) {
20
+ const { profile = 'xiaohongshu_fresh', unifiedApiUrl = 'http://127.0.0.1:7701', } = input;
21
+ console.log(`[Phase34CloseDetail] ESC 返回搜索页`);
22
+ // 系统 ESC 返回
23
+ await controllerAction('keyboard:press', {
24
+ profileId: profile,
25
+ key: 'Escape',
26
+ }, unifiedApiUrl);
27
+ // 等待导航完成
28
+ await delay(1500);
29
+ // 验证已返回搜索页
30
+ const finalUrl = await controllerAction('browser:execute', {
31
+ profile,
32
+ script: 'window.location.href'
33
+ }, unifiedApiUrl).then(res => res?.result || res?.data?.result || '');
34
+ const success = finalUrl.includes('/search_result');
35
+ console.log(`[Phase34CloseDetail] 完成: success=${success} url=${finalUrl}`);
36
+ return {
37
+ success,
38
+ finalUrl,
39
+ };
40
+ }
41
+ //# sourceMappingURL=Phase34CloseDetailBlock.js.map
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Phase 3-4 Block: 关闭所有打开的 Tab
3
+ *
4
+ * 职责:
5
+ * 1. 接收 Tab 列表
6
+ * 2. 循环调用 browser:close_page 关闭每个 Tab
7
+ * 3. 返回关闭结果
8
+ */
9
+ export interface CloseTabsInput {
10
+ sessionId: string;
11
+ tabs?: Array<{
12
+ pageId?: number;
13
+ }>;
14
+ unifiedApiUrl?: string;
15
+ }
16
+ export interface CloseTabsOutput {
17
+ success: boolean;
18
+ closedCount: number;
19
+ failedCount: number;
20
+ errors?: Array<{
21
+ pageId?: number;
22
+ error: string;
23
+ }>;
24
+ }
25
+ export declare function execute(input: CloseTabsInput): Promise<CloseTabsOutput>;
26
+ //# sourceMappingURL=Phase34CloseTabsBlock.d.ts.map
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Phase 3-4 Block: 关闭所有打开的 Tab
3
+ *
4
+ * 职责:
5
+ * 1. 接收 Tab 列表
6
+ * 2. 循环调用 browser:close_page 关闭每个 Tab
7
+ * 3. 返回关闭结果
8
+ */
9
+ import { controllerAction } from '../utils/controllerAction.js';
10
+ export async function execute(input) {
11
+ const { sessionId, tabs = [], unifiedApiUrl = 'http://127.0.0.1:7701' } = input;
12
+ console.log(`[Phase34CloseTabs] 关闭 ${tabs.length} 个 Tab`);
13
+ let closedCount = 0;
14
+ let failedCount = 0;
15
+ const errors = [];
16
+ for (const tab of tabs) {
17
+ if (!tab?.pageId) {
18
+ console.warn(`[Phase34CloseTabs] 跳过无效 Tab: ${JSON.stringify(tab)}`);
19
+ continue;
20
+ }
21
+ try {
22
+ await controllerAction('browser:close_page', {
23
+ profile: sessionId,
24
+ pageId: tab.pageId
25
+ }, unifiedApiUrl);
26
+ closedCount++;
27
+ console.log(`[Phase34CloseTabs] 已关闭 Tab pageId=${tab.pageId}`);
28
+ }
29
+ catch (err) {
30
+ failedCount++;
31
+ const errorMsg = err?.message || String(err);
32
+ errors.push({ pageId: tab.pageId, error: errorMsg });
33
+ console.warn(`[Phase34CloseTabs] 关闭失败 pageId=${tab.pageId}: ${errorMsg}`);
34
+ }
35
+ }
36
+ console.log(`[Phase34CloseTabs] 完成: 成功 ${closedCount}, 失败 ${failedCount}`);
37
+ return {
38
+ success: failedCount === 0,
39
+ closedCount,
40
+ failedCount,
41
+ errors: errors.length > 0 ? errors : undefined
42
+ };
43
+ }
44
+ //# sourceMappingURL=Phase34CloseTabsBlock.js.map
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Phase 3-4 Block: 采集评论
3
+ *
4
+ * 职责:
5
+ * 1. 展开评论区(系统级点击 comment_button)
6
+ * 2. 批量采集可见评论(comment_item extract)
7
+ * 3. 点击“展开更多”+评论区滚动加载
8
+ * 4. 返回去重后的评论数组
9
+ */
10
+ export interface CollectCommentsInput {
11
+ sessionId?: string;
12
+ unifiedApiUrl?: string;
13
+ maxRounds?: number;
14
+ batchSize?: number;
15
+ }
16
+ export interface CollectCommentsOutput {
17
+ success: boolean;
18
+ comments: Array<{
19
+ userName: string;
20
+ userId: string;
21
+ content: string;
22
+ time: string;
23
+ likeCount: number;
24
+ }>;
25
+ totalCollected: number;
26
+ error?: string;
27
+ }
28
+ export declare function execute(input: CollectCommentsInput): Promise<CollectCommentsOutput>;
29
+ //# sourceMappingURL=Phase34CollectCommentsBlock.d.ts.map
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Phase 3-4 Block: 采集评论
3
+ *
4
+ * 职责:
5
+ * 1. 展开评论区(系统级点击 comment_button)
6
+ * 2. 批量采集可见评论(comment_item extract)
7
+ * 3. 点击“展开更多”+评论区滚动加载
8
+ * 4. 返回去重后的评论数组
9
+ */
10
+ import { ensureCommentsOpened, extractVisibleComments, isCommentEnd, scrollComments, expandAllVisibleReplyButtons, } from './helpers/xhsComments.js';
11
+ import { controllerAction, delay } from '../utils/controllerAction.js';
12
+ async function isCommentEmpty(sessionId, apiUrl) {
13
+ const res = await controllerAction('container:operation', {
14
+ containerId: 'xiaohongshu_detail.comment_section.empty_state',
15
+ operationId: 'extract',
16
+ sessionId,
17
+ config: { max_items: 1, visibleOnly: true },
18
+ }, apiUrl).catch(() => null);
19
+ const extracted = Array.isArray(res?.extracted) ? res.extracted : [];
20
+ return extracted.length > 0;
21
+ }
22
+ function toNormalizedComment(item) {
23
+ return {
24
+ userName: String(item.user_name || ''),
25
+ userId: String(item.user_id || ''),
26
+ content: String(item.text || '').replace(/\s+/g, ' ').trim(),
27
+ time: String(item.timestamp || ''),
28
+ likeCount: 0,
29
+ };
30
+ }
31
+ export async function execute(input) {
32
+ const { sessionId = 'xiaohongshu_fresh', unifiedApiUrl = 'http://127.0.0.1:7701', maxRounds = 0, batchSize = 0, } = input;
33
+ const roundLimit = Number.isFinite(Number(maxRounds)) && Number(maxRounds) > 0
34
+ ? Math.floor(Number(maxRounds))
35
+ : Number.POSITIVE_INFINITY;
36
+ const batchLimit = Number.isFinite(Number(batchSize)) && Number(batchSize) > 0
37
+ ? Math.floor(Number(batchSize))
38
+ : Number.POSITIVE_INFINITY;
39
+ console.log(`[Phase34CollectComments] 开始采集评论 (batchSize=${Number.isFinite(batchLimit) ? batchLimit : 'unlimited'}, maxRounds=${Number.isFinite(roundLimit) ? roundLimit : 'unlimited'})`);
40
+ const allComments = [];
41
+ const seen = new Set();
42
+ let round = 0;
43
+ let noNewCount = 0;
44
+ let expandNoClickStreak = 0;
45
+ const MAX_NO_NEW = 3;
46
+ // 1) 展开评论区(允许已展开/无按钮时继续尝试 extract)
47
+ try {
48
+ await ensureCommentsOpened(sessionId, unifiedApiUrl);
49
+ }
50
+ catch (err) {
51
+ console.warn(`[Phase34CollectComments] 评论区展开异常: ${err?.message || String(err)}`);
52
+ }
53
+ await delay(800);
54
+ while (round < roundLimit) {
55
+ round += 1;
56
+ const empty = await isCommentEmpty(sessionId, unifiedApiUrl);
57
+ if (empty) {
58
+ console.log('[Phase34CollectComments] 评论区空状态,停止采集');
59
+ break;
60
+ }
61
+ const expandNow = await expandAllVisibleReplyButtons(sessionId, unifiedApiUrl, {
62
+ maxPasses: 6,
63
+ maxClicksPerPass: 12,
64
+ }).catch(() => ({ clicked: 0, passes: 0, remaining: 0, detected: 0 }));
65
+ if (expandNow.clicked > 0 || expandNow.remaining > 0) {
66
+ console.log(`[Phase34CollectComments] Round ${round}: 先展开可见回复 clicked=${expandNow.clicked} remaining=${expandNow.remaining}`);
67
+ await delay(320);
68
+ }
69
+ const remaining = Number.isFinite(batchLimit)
70
+ ? Math.max(1, batchLimit - allComments.length)
71
+ : 200;
72
+ const visible = await extractVisibleComments(sessionId, unifiedApiUrl, Math.min(200, remaining));
73
+ const prev = allComments.length;
74
+ for (const item of visible) {
75
+ const normalized = toNormalizedComment(item);
76
+ if (!normalized.content)
77
+ continue;
78
+ const key = `${normalized.userId}:${normalized.content}`;
79
+ if (seen.has(key))
80
+ continue;
81
+ seen.add(key);
82
+ allComments.push(normalized);
83
+ }
84
+ const newCount = allComments.length - prev;
85
+ console.log(`[Phase34CollectComments] Round ${round}: 可见${visible.length} 新增${newCount} 总计${allComments.length}`);
86
+ if (newCount > 0) {
87
+ noNewCount = 0;
88
+ }
89
+ else {
90
+ noNewCount += 1;
91
+ console.log(`[Phase34CollectComments] 无新评论计数: ${noNewCount}/${MAX_NO_NEW}`);
92
+ }
93
+ if (Number.isFinite(batchLimit) && allComments.length >= batchLimit) {
94
+ console.log(`[Phase34CollectComments] 已达到批次大小 ${batchLimit},停止采集`);
95
+ break;
96
+ }
97
+ const endReached = await isCommentEnd(sessionId, unifiedApiUrl).catch(() => false);
98
+ if (endReached) {
99
+ console.log('[Phase34CollectComments] 检测到评论区底部,停止采集');
100
+ break;
101
+ }
102
+ if (noNewCount >= MAX_NO_NEW) {
103
+ console.log(`[Phase34CollectComments] 连续 ${MAX_NO_NEW} 轮无新增,停止采集`);
104
+ break;
105
+ }
106
+ // 滚动前先把视口内“展开回复”按钮点完,再决定是否滚动。
107
+ const expandBeforeScroll = await expandAllVisibleReplyButtons(sessionId, unifiedApiUrl, {
108
+ maxPasses: 8,
109
+ maxClicksPerPass: 15,
110
+ }).catch(() => ({ clicked: 0, passes: 0, remaining: 0, detected: 0 }));
111
+ if (expandBeforeScroll.clicked > 0) {
112
+ expandNoClickStreak = 0;
113
+ console.log(`[Phase34CollectComments] Round ${round}: 滚动前展开 clicked=${expandBeforeScroll.clicked} remaining=${expandBeforeScroll.remaining},先重采当前视口`);
114
+ await delay(420);
115
+ continue;
116
+ }
117
+ if (expandBeforeScroll.remaining > 0) {
118
+ expandNoClickStreak += 1;
119
+ console.log(`[Phase34CollectComments] Round ${round}: 检测到 remaining=${expandBeforeScroll.remaining} 但点击为0 (streak=${expandNoClickStreak})`);
120
+ if (expandNoClickStreak < 2) {
121
+ await delay(420);
122
+ continue;
123
+ }
124
+ console.log('[Phase34CollectComments] 展开按钮无法点击,强制执行滚动以避免卡住');
125
+ }
126
+ else {
127
+ expandNoClickStreak = 0;
128
+ }
129
+ let scrolled = null;
130
+ try {
131
+ console.log(`[Phase34CollectComments] Round ${round}: 无可展开按钮,执行滚动`);
132
+ scrolled = await scrollComments(sessionId, unifiedApiUrl, 650);
133
+ }
134
+ catch {
135
+ scrolled = null;
136
+ }
137
+ if (scrolled?.success === false) {
138
+ console.log('[Phase34CollectComments] 评论区滚动失败,停止采集');
139
+ break;
140
+ }
141
+ await delay(900);
142
+ }
143
+ console.log(`[Phase34CollectComments] 完成,共采集 ${allComments.length} 条评论`);
144
+ return {
145
+ success: true,
146
+ comments: allComments,
147
+ totalCollected: allComments.length,
148
+ };
149
+ }
150
+ //# sourceMappingURL=Phase34CollectCommentsBlock.js.map
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Phase 3-4 Block: 提取详情内容
3
+ *
4
+ * 职责:
5
+ * 1. 容器匹配详情页内容区域
6
+ * 2. 高亮验证内容可见
7
+ * 3. 容器操作提取标题、正文、作者、图片
8
+ * 4. 返回结构化数据 + anchor
9
+ */
10
+ export interface ExtractDetailInput {
11
+ noteId: string;
12
+ profile?: string;
13
+ unifiedApiUrl?: string;
14
+ }
15
+ export interface ExtractDetailOutput {
16
+ success: boolean;
17
+ noteId: string;
18
+ detail?: {
19
+ title: string;
20
+ content: string;
21
+ authorName: string;
22
+ authorId: string;
23
+ publishTime: string;
24
+ images: string[];
25
+ };
26
+ anchor?: {
27
+ containerId: string;
28
+ rect: {
29
+ x: number;
30
+ y: number;
31
+ width: number;
32
+ height: number;
33
+ };
34
+ };
35
+ error?: string;
36
+ }
37
+ export declare function execute(input: ExtractDetailInput): Promise<ExtractDetailOutput>;
38
+ //# sourceMappingURL=Phase34ExtractDetailBlock.d.ts.map
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Phase 3-4 Block: 提取详情内容
3
+ *
4
+ * 职责:
5
+ * 1. 容器匹配详情页内容区域
6
+ * 2. 高亮验证内容可见
7
+ * 3. 容器操作提取标题、正文、作者、图片
8
+ * 4. 返回结构化数据 + anchor
9
+ */
10
+ async function controllerAction(action, payload, apiUrl) {
11
+ const res = await fetch(`${apiUrl}/v1/controller/action`, {
12
+ method: 'POST',
13
+ headers: { 'Content-Type': 'application/json' },
14
+ body: JSON.stringify({ action, payload }),
15
+ signal: AbortSignal.timeout(20000),
16
+ });
17
+ const data = await res.json().catch(() => ({}));
18
+ return data.data || data;
19
+ }
20
+ function delay(ms) {
21
+ return new Promise((resolve) => setTimeout(resolve, ms));
22
+ }
23
+ export async function execute(input) {
24
+ const { noteId, profile = 'xiaohongshu_fresh', unifiedApiUrl = 'http://127.0.0.1:7701', } = input;
25
+ console.log(`[Phase34ExtractDetail] 提取详情: ${noteId}`);
26
+ // 1. 验证当前在详情页
27
+ const currentUrl = await controllerAction('browser:execute', {
28
+ profile,
29
+ script: 'window.location.href',
30
+ }, unifiedApiUrl).then(res => res?.result || res?.data?.result || '');
31
+ if (!currentUrl.includes(`/explore/${noteId}`)) {
32
+ return {
33
+ success: false,
34
+ noteId,
35
+ error: `当前不在详情页: ${currentUrl}`,
36
+ };
37
+ }
38
+ // 2. 容器匹配内容区域(以 container-library 为准)
39
+ const contentContainerId = 'xiaohongshu_detail.content';
40
+ const headerContainerId = 'xiaohongshu_detail.header';
41
+ const galleryContainerId = 'xiaohongshu_detail.gallery';
42
+ // 3. 高亮验证
43
+ const highlightResult = await controllerAction('container:operation', {
44
+ containerId: contentContainerId,
45
+ operationId: 'highlight',
46
+ sessionId: profile,
47
+ }, unifiedApiUrl);
48
+ if (!highlightResult?.success) {
49
+ return {
50
+ success: false,
51
+ noteId,
52
+ error: `内容区域不可用: ${contentContainerId}`,
53
+ };
54
+ }
55
+ await delay(500);
56
+ // 4. 提取正文(包含 title/text)
57
+ const contentResult = await controllerAction('container:operation', {
58
+ containerId: contentContainerId,
59
+ operationId: 'extract',
60
+ sessionId: profile,
61
+ config: { fields: ['title', 'text'] },
62
+ }, unifiedApiUrl).catch(() => null);
63
+ const contentRow = Array.isArray(contentResult?.extracted) ? contentResult.extracted[0] : (contentResult?.data?.extracted?.[0] ?? null);
64
+ const title = String(contentRow?.title || '');
65
+ const content = String(contentRow?.text || '');
66
+ // 5. 提取作者
67
+ const headerResult = await controllerAction('container:operation', {
68
+ containerId: headerContainerId,
69
+ operationId: 'extract',
70
+ sessionId: profile,
71
+ config: { fields: ['author_name', 'author_link'] },
72
+ }, unifiedApiUrl).catch(() => null);
73
+ const headerRow = Array.isArray(headerResult?.extracted) ? headerResult.extracted[0] : (headerResult?.data?.extracted?.[0] ?? null);
74
+ const authorName = String(headerRow?.author_name || '');
75
+ const authorLink = String(headerRow?.author_link || '');
76
+ let authorId = '';
77
+ try {
78
+ if (authorLink) {
79
+ const u = new URL(authorLink, 'https://www.xiaohongshu.com');
80
+ authorId = u.pathname.split('/').filter(Boolean).slice(-1)[0] || '';
81
+ }
82
+ }
83
+ catch {
84
+ authorId = '';
85
+ }
86
+ // 6. 提取图片
87
+ const galleryResult = await controllerAction('container:operation', {
88
+ containerId: galleryContainerId,
89
+ operationId: 'extract',
90
+ sessionId: profile,
91
+ config: { fields: ['images'] },
92
+ }, unifiedApiUrl).catch(() => null);
93
+ const galleryRow = Array.isArray(galleryResult?.extracted)
94
+ ? galleryResult.extracted[0]
95
+ : (galleryResult?.data?.extracted?.[0] ?? null);
96
+ const images = Array.isArray(galleryRow?.images) ? galleryRow.images : (galleryRow?.images ? [galleryRow.images] : []);
97
+ // 8. 获取 anchor(用于调试)
98
+ const rect = highlightResult?.anchor?.rect || highlightResult?.rect;
99
+ console.log(`[Phase34ExtractDetail] ✅ 提取完成: 标题="${title.slice(0, 20)}..." 图片=${images.length}张`);
100
+ return {
101
+ success: true,
102
+ noteId,
103
+ detail: {
104
+ title,
105
+ content,
106
+ authorName,
107
+ authorId,
108
+ publishTime: new Date().toISOString(),
109
+ images: Array.isArray(images) ? images : [images],
110
+ },
111
+ anchor: {
112
+ containerId: contentContainerId,
113
+ rect: rect || { x: 0, y: 0, width: 0, height: 0 },
114
+ },
115
+ };
116
+ }
117
+ //# sourceMappingURL=Phase34ExtractDetailBlock.js.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Phase 3-4 Block: 打开详情页
3
+ *
4
+ * 职责:
5
+ * 1. 使用 safeUrl 打开详情页(禁止构造 URL)
6
+ * 2. 等待页面加载完成
7
+ * 3. 高亮验证内容区域可见
8
+ */
9
+ export interface OpenDetailInput {
10
+ noteId: string;
11
+ safeUrl: string;
12
+ profile?: string;
13
+ unifiedApiUrl?: string;
14
+ }
15
+ export interface OpenDetailOutput {
16
+ success: boolean;
17
+ noteId: string;
18
+ currentUrl: string;
19
+ anchor?: {
20
+ containerId: string;
21
+ rect: {
22
+ x: number;
23
+ y: number;
24
+ width: number;
25
+ height: number;
26
+ };
27
+ };
28
+ }
29
+ export declare function execute(input: OpenDetailInput): Promise<OpenDetailOutput>;
30
+ //# sourceMappingURL=Phase34OpenDetailBlock.d.ts.map
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Phase 3-4 Block: 打开详情页
3
+ *
4
+ * 职责:
5
+ * 1. 使用 safeUrl 打开详情页(禁止构造 URL)
6
+ * 2. 等待页面加载完成
7
+ * 3. 高亮验证内容区域可见
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(30000),
15
+ });
16
+ const data = await res.json().catch(() => ({}));
17
+ return data.data || data;
18
+ }
19
+ function delay(ms) {
20
+ return new Promise((resolve) => setTimeout(resolve, ms));
21
+ }
22
+ async function getViewportHeight(profile, apiUrl) {
23
+ try {
24
+ const res = await controllerAction('browser:execute', {
25
+ profile,
26
+ script: 'window.innerHeight',
27
+ }, apiUrl);
28
+ const h = Number(res?.result || res?.data?.result || res?.data || 0);
29
+ return Number.isFinite(h) && h > 0 ? h : 0;
30
+ }
31
+ catch {
32
+ return 0;
33
+ }
34
+ }
35
+ export async function execute(input) {
36
+ const { noteId, safeUrl, profile = 'xiaohongshu_fresh', unifiedApiUrl = 'http://127.0.0.1:7701', } = input;
37
+ console.log(`[Phase34OpenDetail] 打开详情页: ${noteId}`);
38
+ // 1. 校验 safeUrl 包含 xsec_token
39
+ if (!safeUrl.includes('xsec_token')) {
40
+ throw new Error(`[Phase34OpenDetail] safeUrl 缺少 xsec_token: ${safeUrl}`);
41
+ }
42
+ // 2. 使用 browser:goto 导航到详情页
43
+ const gotoResult = await controllerAction('browser:goto', {
44
+ profile,
45
+ url: safeUrl,
46
+ }, unifiedApiUrl);
47
+ // controllerAction 返回形态可能是:
48
+ // - { ok: true }
49
+ // - { success: true, data: { ok: true } }
50
+ // - { success: false, error: ... }
51
+ // 这里把 browser-service 的底层错误透传出来,避免 “unknown” 无法诊断。
52
+ const okFlag = gotoResult?.ok === true ||
53
+ gotoResult?.data?.ok === true ||
54
+ gotoResult?.success === true;
55
+ const gotoOk = Boolean(okFlag);
56
+ if (!gotoOk) {
57
+ const detail = gotoResult?.data?.error || gotoResult?.error || JSON.stringify(gotoResult);
58
+ throw new Error(`[Phase34OpenDetail] 导航失败: ${detail}`);
59
+ }
60
+ // 3. 等待页面加载完成
61
+ await delay(3000);
62
+ // 4. 验证当前 URL 匹配 noteId
63
+ const currentUrl = await controllerAction('browser:execute', {
64
+ profile,
65
+ script: 'window.location.href',
66
+ }, unifiedApiUrl).then(res => res?.result || res?.data?.result || '');
67
+ if (!currentUrl.includes(noteId)) {
68
+ throw new Error(`[Phase34OpenDetail] URL 不匹配,期望 ${noteId},实际 ${currentUrl}`);
69
+ }
70
+ // 5. 高亮验证内容区域可见
71
+ // 容器命名以 container-library 为准:详情正文容器为 xiaohongshu_detail.content。
72
+ const highlightResult = await controllerAction('container:operation', {
73
+ containerId: 'xiaohongshu_detail.content',
74
+ operationId: 'highlight',
75
+ sessionId: profile,
76
+ }, unifiedApiUrl);
77
+ if (highlightResult?.success === false) {
78
+ throw new Error(`[Phase34OpenDetail] 内容区域不可见: xiaohongshu_detail.content`);
79
+ }
80
+ // 6. 视口保护:只做“可见性/有尺寸”校验,不强制要求完全在视口内。
81
+ // 说明:在 Camoufox + 多窗口情况下,window.innerHeight 可能固定较小(如 707),
82
+ // 但页面仍可正常交互。强制 in-viewport 会导致误判并中断。
83
+ const rect = highlightResult?.anchor?.rect || highlightResult?.rect;
84
+ if (rect) {
85
+ const width = Number(rect.width ?? (rect.x2 - rect.x1));
86
+ const height = Number(rect.height ?? (rect.y2 - rect.y1));
87
+ if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
88
+ throw new Error(`[Phase34OpenDetail] 内容区域 rect 无效: ${JSON.stringify(rect)}`);
89
+ }
90
+ const viewportHeightRaw = await getViewportHeight(profile, unifiedApiUrl);
91
+ console.log(`[Phase34OpenDetail] rect ok (w=${width}, h=${height}), viewportHeight=${viewportHeightRaw}`);
92
+ }
93
+ await delay(500);
94
+ console.log(`[Phase34OpenDetail] ✅ 详情页加载完成: ${noteId}`);
95
+ return {
96
+ success: true,
97
+ noteId,
98
+ currentUrl,
99
+ anchor: highlightResult?.anchor,
100
+ };
101
+ }
102
+ //# sourceMappingURL=Phase34OpenDetailBlock.js.map
@@ -0,0 +1,23 @@
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
+ export interface OpenTabsInput {
10
+ profile?: string;
11
+ tabCount?: number;
12
+ unifiedApiUrl?: string;
13
+ }
14
+ export interface TabInfo {
15
+ index: number;
16
+ pageId?: number;
17
+ }
18
+ export interface OpenTabsOutput {
19
+ tabs: TabInfo[];
20
+ currentTab: number;
21
+ }
22
+ export declare function execute(input: OpenTabsInput): Promise<OpenTabsOutput>;
23
+ //# sourceMappingURL=Phase34OpenTabsBlock.d.ts.map