@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,240 @@
1
+ /**
2
+ * XHS Discover Fallback Block
3
+ *
4
+ * 独立的回退 Block:当出现 shell-page(URL=/explore/<id> 但 DOM=搜索结果)时,
5
+ * 通过点击「发现」按钮重置状态,然后重新搜索以获取正确的 /search_result URL。
6
+ *
7
+ * 约束:全系统级操作(container + keyboard/mouse),禁止 goto/refresh/url 拼接。
8
+ */
9
+ import os from 'node:os';
10
+ import path from 'node:path';
11
+ import { promises as fs } from 'node:fs';
12
+ import { ContainerRegistry } from '../../../../container-registry/src/index.js';
13
+ import { execute as phase2Search } from './Phase2SearchBlock.js';
14
+ import { detectXhsCheckpoint } from '../utils/checkpoints.js';
15
+ import { controllerAction, delay } from '../utils/controllerAction.js';
16
+ function resolveDownloadRoot() {
17
+ const custom = process.env.WEBAUTO_DOWNLOAD_ROOT || process.env.WEBAUTO_DOWNLOAD_DIR;
18
+ if (custom && custom.trim())
19
+ return custom;
20
+ const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
21
+ return path.join(home, '.webauto', 'download');
22
+ }
23
+ function isDebugArtifactsEnabled() {
24
+ return (process.env.WEBAUTO_DEBUG === '1' ||
25
+ process.env.WEBAUTO_DEBUG_ARTIFACTS === '1' ||
26
+ process.env.WEBAUTO_DEBUG_SCREENSHOT === '1');
27
+ }
28
+ function decodeURIComponentSafe(value) {
29
+ try {
30
+ return decodeURIComponent(value);
31
+ }
32
+ catch {
33
+ return value;
34
+ }
35
+ }
36
+ function decodeRepeated(value, maxRounds = 3) {
37
+ let current = value;
38
+ for (let i = 0; i < maxRounds; i += 1) {
39
+ const next = decodeURIComponentSafe(current);
40
+ if (next === current)
41
+ break;
42
+ current = next;
43
+ }
44
+ return current;
45
+ }
46
+ function getKeywordFromSearchUrl(searchUrl) {
47
+ try {
48
+ const url = new URL(searchUrl);
49
+ const raw = url.searchParams.get('keyword') || '';
50
+ if (raw)
51
+ return decodeRepeated(raw).trim();
52
+ }
53
+ catch {
54
+ // ignore
55
+ }
56
+ return null;
57
+ }
58
+ async function saveScreenshot(base64, env, keyword) {
59
+ if (!isDebugArtifactsEnabled())
60
+ return null;
61
+ const dir = path.join(resolveDownloadRoot(), 'xiaohongshu', env, keyword, 'discover-fallback');
62
+ await fs.mkdir(dir, { recursive: true });
63
+ const buf = Buffer.from(base64, 'base64');
64
+ const filePath = path.join(dir, `fallback-${Date.now()}.png`);
65
+ await fs.writeFile(filePath, buf);
66
+ return filePath;
67
+ }
68
+ async function saveDomDump(dom, env, keyword) {
69
+ if (!isDebugArtifactsEnabled())
70
+ return null;
71
+ const dir = path.join(resolveDownloadRoot(), 'xiaohongshu', env, keyword, 'discover-fallback');
72
+ await fs.mkdir(dir, { recursive: true });
73
+ const filePath = path.join(dir, `fallback-${Date.now()}.json`);
74
+ await fs.writeFile(filePath, JSON.stringify(dom, null, 2), 'utf8');
75
+ return filePath;
76
+ }
77
+ export async function execute(input) {
78
+ const { keyword, profile = 'xiaohongshu_fresh', unifiedApiUrl = 'http://127.0.0.1:7701', env = 'debug', } = input;
79
+ console.log(`[XhsDiscoverFallback] start keyword=${keyword} profile=${profile}`);
80
+ const registry = new ContainerRegistry();
81
+ await registry.load();
82
+ async function waitCheckpoint(maxWaitMs) {
83
+ const start = Date.now();
84
+ let last = await detectXhsCheckpoint({ sessionId: profile, serviceUrl: unifiedApiUrl });
85
+ while (Date.now() - start < maxWaitMs) {
86
+ if (last.checkpoint !== 'detail_ready' && last.checkpoint !== 'comments_ready')
87
+ return last;
88
+ await delay(500);
89
+ last = await detectXhsCheckpoint({ sessionId: profile, serviceUrl: unifiedApiUrl });
90
+ }
91
+ return last;
92
+ }
93
+ // If we are in detail/comments modal, exit first (do NOT click Discover under modal).
94
+ let checkpoint = await detectXhsCheckpoint({ sessionId: profile, serviceUrl: unifiedApiUrl });
95
+ if (checkpoint.checkpoint === 'detail_ready' || checkpoint.checkpoint === 'comments_ready') {
96
+ console.log(`[XhsDiscoverFallback] in ${checkpoint.checkpoint}, exit modal before discover`);
97
+ try {
98
+ const r = await controllerAction('container:operation', { containerId: 'xiaohongshu_detail.close_button', operationId: 'click', sessionId: profile, timeoutMs: 15000 }, unifiedApiUrl);
99
+ console.log(`[XhsDiscoverFallback] close_button click: success=${Boolean(r?.success !== false)}`);
100
+ }
101
+ catch {
102
+ console.log('[XhsDiscoverFallback] close_button click failed (ignored)');
103
+ }
104
+ checkpoint = await waitCheckpoint(8000);
105
+ if (checkpoint.checkpoint === 'detail_ready' || checkpoint.checkpoint === 'comments_ready') {
106
+ // Fallback: ESC twice
107
+ for (let i = 0; i < 2; i += 1) {
108
+ console.log(`[XhsDiscoverFallback] still in ${checkpoint.checkpoint}, press ESC (round=${i + 1})`);
109
+ await controllerAction('keyboard:press', { profileId: profile, key: 'Escape' }, unifiedApiUrl);
110
+ checkpoint = await waitCheckpoint(8000);
111
+ if (checkpoint.checkpoint !== 'detail_ready' && checkpoint.checkpoint !== 'comments_ready')
112
+ break;
113
+ }
114
+ }
115
+ // If still in detail/comments, stop (do not proceed to discover).
116
+ if (checkpoint.checkpoint === 'detail_ready' || checkpoint.checkpoint === 'comments_ready') {
117
+ let screenshotPath;
118
+ let domDumpPath;
119
+ try {
120
+ const shot = await controllerAction('browser:screenshot', { profileId: profile, fullPage: false }, unifiedApiUrl)
121
+ .then((res) => res?.data || res?.result || res?.data?.data || '');
122
+ if (typeof shot === 'string' && shot) {
123
+ const saved = await saveScreenshot(shot, env, keyword);
124
+ if (saved)
125
+ screenshotPath = saved;
126
+ }
127
+ }
128
+ catch { }
129
+ try {
130
+ const dom = await controllerAction('browser:execute', {
131
+ profile,
132
+ script: '(() => ({ url: window.location.href, title: document.title, readyState: document.readyState, text: document.body ? document.body.innerText.slice(0, 800) : "" }))()'
133
+ }, unifiedApiUrl).then((res) => res?.result || res?.data?.result || null);
134
+ if (dom) {
135
+ const saved = await saveDomDump(dom, env, keyword);
136
+ if (saved)
137
+ domDumpPath = saved;
138
+ }
139
+ }
140
+ catch { }
141
+ return {
142
+ success: false,
143
+ finalUrl: checkpoint.url || '',
144
+ finalCheckpoint: checkpoint.checkpoint,
145
+ screenshotPath,
146
+ domDumpPath,
147
+ };
148
+ }
149
+ }
150
+ const defs = registry.getContainersForUrl('https://www.xiaohongshu.com/explore/');
151
+ const discoverDef = defs?.['xiaohongshu_detail.discover_button'] || defs?.['xiaohongshu_home.discover_button'];
152
+ const selectorDefs = Array.isArray(discoverDef?.selectors) ? discoverDef.selectors : [];
153
+ const primarySelectorDef = selectorDefs.find((s) => s?.variant === 'primary') || selectorDefs[0];
154
+ const discoverSelector = String(primarySelectorDef?.css || 'a[href="/"]');
155
+ console.log(`[XhsDiscoverFallback] click discover selector=${discoverSelector}`);
156
+ let clicked = false;
157
+ // Prefer container operations (system-level) to avoid selector drift.
158
+ const candidates = [
159
+ 'xiaohongshu_home.discover_button',
160
+ 'xiaohongshu_search.discover_button',
161
+ ];
162
+ for (const id of candidates) {
163
+ try {
164
+ const r = await controllerAction('container:operation', { containerId: id, operationId: 'click', sessionId: profile, timeoutMs: 15000 }, unifiedApiUrl);
165
+ if (r?.success !== false) {
166
+ clicked = true;
167
+ console.log(`[XhsDiscoverFallback] container click ok: ${id}`);
168
+ break;
169
+ }
170
+ }
171
+ catch {
172
+ // ignore and try next
173
+ }
174
+ }
175
+ // Fallback: raw selector click (still system-level via container:click)
176
+ if (!clicked) {
177
+ for (let i = 0; i < 3; i += 1) {
178
+ const result = await controllerAction('container:click', { profileId: profile, selector: discoverSelector, highlight: true, timeoutMs: 8000 }, unifiedApiUrl).then((res) => Boolean(res?.ok ?? res?.result?.ok ?? res?.data?.ok));
179
+ if (result) {
180
+ clicked = true;
181
+ console.log('[XhsDiscoverFallback] selector click ok');
182
+ break;
183
+ }
184
+ await delay(600);
185
+ }
186
+ }
187
+ console.log(`[XhsDiscoverFallback] click discover done clicked=${clicked}`);
188
+ // Let XHS settle. Avoid refresh/goto.
189
+ await delay(2000);
190
+ // Re-run search.
191
+ const searchRes = await phase2Search({ keyword, profile, unifiedApiUrl });
192
+ const finalUrl = searchRes.finalUrl;
193
+ const finalCheckpoint = await detectXhsCheckpoint({ sessionId: profile, serviceUrl: unifiedApiUrl });
194
+ const isSearchResultPage = finalUrl.includes('/search_result') && getKeywordFromSearchUrl(finalUrl) === keyword;
195
+ const isSearchReady = finalCheckpoint.checkpoint === 'search_ready';
196
+ console.log(`[XhsDiscoverFallback] final checkpoint=${finalCheckpoint.checkpoint} url=${finalUrl}`);
197
+ if (!isSearchResultPage || !isSearchReady) {
198
+ let screenshotPath;
199
+ let domDumpPath;
200
+ try {
201
+ const shot = await controllerAction('browser:screenshot', { profileId: profile, fullPage: false }, unifiedApiUrl)
202
+ .then((res) => res?.data || res?.result || res?.data?.data || '');
203
+ if (typeof shot === 'string' && shot) {
204
+ const saved = await saveScreenshot(shot, env, keyword);
205
+ if (saved)
206
+ screenshotPath = saved;
207
+ }
208
+ }
209
+ catch {
210
+ // ignore
211
+ }
212
+ try {
213
+ const dom = await controllerAction('browser:execute', {
214
+ profile,
215
+ script: '(() => ({ url: window.location.href, title: document.title, readyState: document.readyState, text: document.body ? document.body.innerText.slice(0, 800) : "" }))()'
216
+ }, unifiedApiUrl).then((res) => res?.result || res?.data?.result || null);
217
+ if (dom) {
218
+ const saved = await saveDomDump(dom, env, keyword);
219
+ if (saved)
220
+ domDumpPath = saved;
221
+ }
222
+ }
223
+ catch {
224
+ // ignore
225
+ }
226
+ return {
227
+ success: false,
228
+ finalUrl,
229
+ finalCheckpoint: finalCheckpoint.checkpoint,
230
+ screenshotPath,
231
+ domDumpPath,
232
+ };
233
+ }
234
+ return {
235
+ success: true,
236
+ finalUrl,
237
+ finalCheckpoint: finalCheckpoint.checkpoint,
238
+ };
239
+ }
240
+ //# sourceMappingURL=XhsDiscoverFallbackBlock.js.map
@@ -0,0 +1,55 @@
1
+ export type CommentMatchTerm = string;
2
+ export type CommentMatchExpr = {
3
+ op: 'any';
4
+ terms: CommentMatchTerm[];
5
+ } | {
6
+ op: 'all';
7
+ terms: CommentMatchTerm[];
8
+ } | {
9
+ op: 'atLeast';
10
+ terms: CommentMatchTerm[];
11
+ min: number;
12
+ } | {
13
+ op: 'and';
14
+ exprs: CommentMatchExpr[];
15
+ } | {
16
+ op: 'or';
17
+ exprs: CommentMatchExpr[];
18
+ } | {
19
+ op: 'not';
20
+ expr: CommentMatchExpr;
21
+ };
22
+ export interface CommentMatchDslRule {
23
+ /**
24
+ * Default: false (case-insensitive).
25
+ * Applies to all terms in this rule unless a caller normalizes terms separately.
26
+ */
27
+ caseSensitive?: boolean;
28
+ /**
29
+ * Exclusion filter. If matched => reject.
30
+ * Typical: { op:'any', terms:[...] } or { op:'or', exprs:[...] }.
31
+ */
32
+ exclude?: CommentMatchExpr;
33
+ /**
34
+ * Required filter. Must be satisfied to accept.
35
+ * Typical: any/all/atLeast/and/or combinations.
36
+ */
37
+ require?: CommentMatchExpr;
38
+ /**
39
+ * Optional preference signal for ranking/scoring.
40
+ * If provided, matcher will count hits and return `preferHits`.
41
+ * If you want to enforce, just put it into `require`.
42
+ */
43
+ prefer?: CommentMatchExpr;
44
+ }
45
+ export interface CommentMatchDslResult {
46
+ ok: boolean;
47
+ rejectedBy?: 'exclude' | 'require';
48
+ hits: string[];
49
+ requireHits: string[];
50
+ excludeHits: string[];
51
+ preferHits: string[];
52
+ score: number;
53
+ }
54
+ export declare function matchCommentTextDsl(textRaw: string, rule: CommentMatchDslRule): CommentMatchDslResult;
55
+ //# sourceMappingURL=commentMatchDsl.d.ts.map
@@ -0,0 +1,126 @@
1
+ function normalizeText(s, caseSensitive) {
2
+ const t = String(s || '').replace(/\s+/g, ' ').trim();
3
+ return caseSensitive ? t : t.toLowerCase();
4
+ }
5
+ function normalizeTerms(terms, caseSensitive) {
6
+ const list = Array.isArray(terms) ? terms : [];
7
+ const out = list
8
+ .map((x) => normalizeText(String(x || ''), caseSensitive))
9
+ .filter(Boolean);
10
+ return Array.from(new Set(out));
11
+ }
12
+ function termHits(text, terms) {
13
+ const hits = [];
14
+ for (const t of terms) {
15
+ if (!t)
16
+ continue;
17
+ if (text.includes(t))
18
+ hits.push(t);
19
+ }
20
+ return hits;
21
+ }
22
+ function evalExpr(text, expr, caseSensitive) {
23
+ if (!expr || typeof expr !== 'object')
24
+ return { ok: false, hits: [] };
25
+ switch (expr.op) {
26
+ case 'any': {
27
+ const terms = normalizeTerms(expr.terms, caseSensitive);
28
+ const hits = termHits(text, terms);
29
+ return { ok: hits.length > 0, hits };
30
+ }
31
+ case 'all': {
32
+ const terms = normalizeTerms(expr.terms, caseSensitive);
33
+ const hits = termHits(text, terms);
34
+ return { ok: terms.length > 0 ? hits.length === terms.length : true, hits };
35
+ }
36
+ case 'atLeast': {
37
+ const terms = normalizeTerms(expr.terms, caseSensitive);
38
+ const min = Math.max(0, Math.floor(Number(expr.min)));
39
+ const hits = termHits(text, terms);
40
+ return { ok: min <= 0 ? true : hits.length >= min, hits };
41
+ }
42
+ case 'and': {
43
+ const exprs = Array.isArray(expr.exprs) ? expr.exprs : [];
44
+ const hitsAll = [];
45
+ for (const e of exprs) {
46
+ const r = evalExpr(text, e, caseSensitive);
47
+ hitsAll.push(...r.hits);
48
+ if (!r.ok)
49
+ return { ok: false, hits: Array.from(new Set(hitsAll)) };
50
+ }
51
+ return { ok: true, hits: Array.from(new Set(hitsAll)) };
52
+ }
53
+ case 'or': {
54
+ const exprs = Array.isArray(expr.exprs) ? expr.exprs : [];
55
+ const hitsAll = [];
56
+ for (const e of exprs) {
57
+ const r = evalExpr(text, e, caseSensitive);
58
+ hitsAll.push(...r.hits);
59
+ if (r.ok)
60
+ return { ok: true, hits: Array.from(new Set(hitsAll)) };
61
+ }
62
+ return { ok: false, hits: Array.from(new Set(hitsAll)) };
63
+ }
64
+ case 'not': {
65
+ const r = evalExpr(text, expr.expr, caseSensitive);
66
+ return { ok: !r.ok, hits: r.hits };
67
+ }
68
+ default: {
69
+ const _exhaustive = expr;
70
+ return { ok: false, hits: [] };
71
+ }
72
+ }
73
+ }
74
+ export function matchCommentTextDsl(textRaw, rule) {
75
+ const caseSensitive = rule.caseSensitive === true;
76
+ const text = normalizeText(textRaw, caseSensitive);
77
+ const exclude = rule.exclude;
78
+ if (exclude) {
79
+ const r = evalExpr(text, exclude, caseSensitive);
80
+ if (r.ok) {
81
+ const hits = Array.from(new Set(r.hits));
82
+ return {
83
+ ok: false,
84
+ rejectedBy: 'exclude',
85
+ hits,
86
+ requireHits: [],
87
+ excludeHits: hits,
88
+ preferHits: [],
89
+ score: 0,
90
+ };
91
+ }
92
+ }
93
+ let requireHits = [];
94
+ if (rule.require) {
95
+ const r = evalExpr(text, rule.require, caseSensitive);
96
+ requireHits = Array.from(new Set(r.hits));
97
+ if (!r.ok) {
98
+ return {
99
+ ok: false,
100
+ rejectedBy: 'require',
101
+ hits: requireHits,
102
+ requireHits,
103
+ excludeHits: [],
104
+ preferHits: [],
105
+ score: 0,
106
+ };
107
+ }
108
+ }
109
+ let preferHits = [];
110
+ if (rule.prefer) {
111
+ const r = evalExpr(text, rule.prefer, caseSensitive);
112
+ preferHits = Array.from(new Set(r.hits));
113
+ }
114
+ const hits = Array.from(new Set([...requireHits, ...preferHits]));
115
+ // score: prefer hits first, then require hits
116
+ const score = preferHits.length * 100 + requireHits.length;
117
+ return {
118
+ ok: true,
119
+ hits,
120
+ requireHits,
121
+ excludeHits: [],
122
+ preferHits,
123
+ score,
124
+ };
125
+ }
126
+ //# sourceMappingURL=commentMatchDsl.js.map
@@ -0,0 +1,21 @@
1
+ export interface CommentKeywordMatchRule {
2
+ any?: string[];
3
+ minAnyMatches?: number;
4
+ must?: string[];
5
+ should?: string[];
6
+ minShouldMatches?: number;
7
+ mustNot?: string[];
8
+ caseSensitive?: boolean;
9
+ }
10
+ export interface CommentMatchResult {
11
+ ok: boolean;
12
+ anyHits: string[];
13
+ mustHits: string[];
14
+ shouldHits: string[];
15
+ anyCount: number;
16
+ shouldCount: number;
17
+ rejectedBy?: 'mustNot' | 'must' | 'any' | 'should';
18
+ }
19
+ export declare function matchCommentText(textRaw: string, rule: CommentKeywordMatchRule): CommentMatchResult;
20
+ export declare function isLegacyKeywordRule(rule: any): rule is CommentKeywordMatchRule;
21
+ //# sourceMappingURL=commentMatcher.d.ts.map
@@ -0,0 +1,99 @@
1
+ function normalizeText(text, caseSensitive) {
2
+ const s = String(text || '').replace(/\s+/g, ' ').trim();
3
+ return caseSensitive ? s : s.toLowerCase();
4
+ }
5
+ function normalizeKeywords(list, caseSensitive) {
6
+ const arr = Array.isArray(list) ? list : [];
7
+ const out = arr
8
+ .map((x) => normalizeText(String(x || ''), caseSensitive))
9
+ .filter(Boolean);
10
+ // 轻量去重,保持稳定顺序
11
+ return Array.from(new Set(out));
12
+ }
13
+ function hits(text, keywords) {
14
+ const found = [];
15
+ for (const k of keywords) {
16
+ if (!k)
17
+ continue;
18
+ if (text.includes(k))
19
+ found.push(k);
20
+ }
21
+ return found;
22
+ }
23
+ export function matchCommentText(textRaw, rule) {
24
+ const caseSensitive = rule.caseSensitive === true;
25
+ const text = normalizeText(textRaw, caseSensitive);
26
+ const mustNot = normalizeKeywords(rule.mustNot, caseSensitive);
27
+ const must = normalizeKeywords(rule.must, caseSensitive);
28
+ const any = normalizeKeywords(rule.any, caseSensitive);
29
+ const should = normalizeKeywords(rule.should, caseSensitive);
30
+ const mustNotHits = hits(text, mustNot);
31
+ if (mustNotHits.length > 0) {
32
+ return {
33
+ ok: false,
34
+ anyHits: [],
35
+ mustHits: [],
36
+ shouldHits: [],
37
+ anyCount: 0,
38
+ shouldCount: 0,
39
+ rejectedBy: 'mustNot',
40
+ };
41
+ }
42
+ const mustHits = hits(text, must);
43
+ if (must.length > 0 && mustHits.length !== must.length) {
44
+ return {
45
+ ok: false,
46
+ anyHits: [],
47
+ mustHits,
48
+ shouldHits: [],
49
+ anyCount: 0,
50
+ shouldCount: 0,
51
+ rejectedBy: 'must',
52
+ };
53
+ }
54
+ const anyHits = hits(text, any);
55
+ const minAny = typeof rule.minAnyMatches === 'number' ? Math.max(0, Math.floor(rule.minAnyMatches)) : (any.length > 0 ? 1 : 0);
56
+ if (any.length > 0 && anyHits.length < minAny) {
57
+ return {
58
+ ok: false,
59
+ anyHits,
60
+ mustHits,
61
+ shouldHits: [],
62
+ anyCount: anyHits.length,
63
+ shouldCount: 0,
64
+ rejectedBy: 'any',
65
+ };
66
+ }
67
+ const shouldHits = hits(text, should);
68
+ const shouldCount = shouldHits.length;
69
+ if (typeof rule.minShouldMatches === 'number') {
70
+ const minShould = Math.max(0, Math.floor(rule.minShouldMatches));
71
+ if (should.length > 0 && shouldCount < minShould) {
72
+ return {
73
+ ok: false,
74
+ anyHits,
75
+ mustHits,
76
+ shouldHits,
77
+ anyCount: anyHits.length,
78
+ shouldCount,
79
+ rejectedBy: 'should',
80
+ };
81
+ }
82
+ }
83
+ return {
84
+ ok: true,
85
+ anyHits,
86
+ mustHits,
87
+ shouldHits,
88
+ anyCount: anyHits.length,
89
+ shouldCount,
90
+ };
91
+ }
92
+ export function isLegacyKeywordRule(rule) {
93
+ if (!rule || typeof rule !== 'object')
94
+ return false;
95
+ // legacy: uses any/must/mustNot/should keys
96
+ const keys = ['any', 'must', 'mustNot', 'should', 'minAnyMatches', 'minShouldMatches', 'caseSensitive'];
97
+ return keys.some((k) => Object.prototype.hasOwnProperty.call(rule, k));
98
+ }
99
+ //# sourceMappingURL=commentMatcher.js.map
@@ -0,0 +1,5 @@
1
+ export declare function resolveHomeDir(): string;
2
+ export declare function resolveDownloadRoot(): string;
3
+ export declare function takeScreenshotBase64(profileId: string, unifiedApiUrl: string): Promise<string | null>;
4
+ export declare function savePngBase64(base64: string, filePath: string): Promise<string>;
5
+ //# sourceMappingURL=evidence.d.ts.map
@@ -0,0 +1,27 @@
1
+ import path from 'node:path';
2
+ import { promises as fs } from 'node:fs';
3
+ import { controllerAction } from '../../utils/controllerAction.js';
4
+ export function resolveHomeDir() {
5
+ const homeDir = process.env.HOME || process.env.USERPROFILE || '';
6
+ if (!homeDir)
7
+ throw new Error('无法获取用户主目录:HOME/USERPROFILE 未设置');
8
+ return homeDir;
9
+ }
10
+ export function resolveDownloadRoot() {
11
+ const custom = process.env.WEBAUTO_DOWNLOAD_ROOT || process.env.WEBAUTO_DOWNLOAD_DIR;
12
+ if (custom && String(custom).trim())
13
+ return String(custom).trim();
14
+ return path.join(resolveHomeDir(), '.webauto', 'download');
15
+ }
16
+ export async function takeScreenshotBase64(profileId, unifiedApiUrl) {
17
+ const shot = await controllerAction('browser:screenshot', { profileId, fullPage: false }, unifiedApiUrl);
18
+ const base64 = shot?.data || shot?.result || shot?.data?.data;
19
+ return typeof base64 === 'string' && base64 ? base64 : null;
20
+ }
21
+ export async function savePngBase64(base64, filePath) {
22
+ const dir = path.dirname(filePath);
23
+ await fs.mkdir(dir, { recursive: true });
24
+ await fs.writeFile(filePath, Buffer.from(base64, 'base64'));
25
+ return filePath;
26
+ }
27
+ //# sourceMappingURL=evidence.js.map
@@ -0,0 +1,37 @@
1
+ interface XhsCollectedUrl {
2
+ noteId: string;
3
+ safeUrl: string;
4
+ searchUrl?: string;
5
+ timestamp?: number;
6
+ }
7
+ export interface ShardSpec {
8
+ index: number;
9
+ count: number;
10
+ by?: 'noteId-hash' | 'index-mod';
11
+ }
12
+ export interface DynamicShardPlan {
13
+ profileId: string;
14
+ assignedNoteIds: string[];
15
+ totalPending: number;
16
+ }
17
+ export declare function fnv1a32(input: string): number;
18
+ export declare function normalizeShard(spec?: Partial<ShardSpec> | null): ShardSpec | null;
19
+ export declare function shardFilterByNoteIdHash<T extends {
20
+ noteId?: string;
21
+ }>(items: T[], shard: ShardSpec): T[];
22
+ export declare function shardFilterByIndexMod<T>(items: T[], shard: ShardSpec): T[];
23
+ export declare function resolveDownloadRoot(customRoot?: string): string;
24
+ export declare function buildDynamicShardPlan(input: {
25
+ keyword: string;
26
+ env: string;
27
+ downloadRoot?: string;
28
+ validProfiles: string[];
29
+ }): Promise<DynamicShardPlan[]>;
30
+ export declare function getPendingItemsByNoteIds(input: {
31
+ keyword: string;
32
+ env: string;
33
+ downloadRoot?: string;
34
+ noteIds: string[];
35
+ }): Promise<XhsCollectedUrl[]>;
36
+ export {};
37
+ //# sourceMappingURL=sharding.d.ts.map