@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,195 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ const LOG_ROOT = path.join(os.homedir(), '.webauto', 'logs');
5
+ const OPS_LOG_FILE = path.join(LOG_ROOT, 'ops.jsonl');
6
+ const ERR_LOG_FILE = path.join(LOG_ROOT, 'ops-errors.jsonl');
7
+ const MAX_RECENT = 50;
8
+ let seq = 0;
9
+ const recentOps = [];
10
+ let logReady = false;
11
+ function ensureLogDir() {
12
+ if (logReady)
13
+ return;
14
+ try {
15
+ fs.mkdirSync(LOG_ROOT, { recursive: true });
16
+ }
17
+ catch {
18
+ // ignore
19
+ }
20
+ logReady = true;
21
+ }
22
+ function nextId() {
23
+ seq += 1;
24
+ return seq;
25
+ }
26
+ function truncateString(value, maxLen = 400) {
27
+ if (value.length <= maxLen)
28
+ return value;
29
+ return `${value.slice(0, maxLen)}...[len=${value.length}]`;
30
+ }
31
+ function shouldOmitKey(key) {
32
+ const k = key.toLowerCase();
33
+ return (k.includes('script') ||
34
+ k.includes('html') ||
35
+ k.includes('cookie') ||
36
+ k.includes('buffer') ||
37
+ k.includes('base64') ||
38
+ k.includes('screenshot') ||
39
+ k.includes('image'));
40
+ }
41
+ function summarizeValue(value, depth = 0) {
42
+ if (depth > 3)
43
+ return '[max-depth]';
44
+ if (value === null || value === undefined)
45
+ return value;
46
+ if (typeof value === 'string')
47
+ return truncateString(value);
48
+ if (typeof value === 'number' || typeof value === 'boolean')
49
+ return value;
50
+ if (Array.isArray(value)) {
51
+ const len = value.length;
52
+ if (len === 0)
53
+ return [];
54
+ if (len <= 5)
55
+ return value.map((v) => summarizeValue(v, depth + 1));
56
+ return {
57
+ _type: 'array',
58
+ length: len,
59
+ sample: value.slice(0, 2).map((v) => summarizeValue(v, depth + 1)),
60
+ };
61
+ }
62
+ if (typeof value === 'object') {
63
+ const out = {};
64
+ const keys = Object.keys(value);
65
+ let count = 0;
66
+ for (const key of keys) {
67
+ if (count >= 20)
68
+ break;
69
+ count += 1;
70
+ if (shouldOmitKey(key)) {
71
+ out[key] = '[omitted]';
72
+ continue;
73
+ }
74
+ out[key] = summarizeValue(value[key], depth + 1);
75
+ }
76
+ if (keys.length > count)
77
+ out._more = keys.length - count;
78
+ return out;
79
+ }
80
+ return String(value);
81
+ }
82
+ function summarizeRecent(entry) {
83
+ return {
84
+ opId: entry.opId,
85
+ kind: entry.kind,
86
+ action: entry.action,
87
+ sessionId: entry.sessionId,
88
+ context: entry.context,
89
+ reason: entry.reason,
90
+ };
91
+ }
92
+ function pushRecent(entry) {
93
+ recentOps.push(entry);
94
+ if (recentOps.length > MAX_RECENT) {
95
+ recentOps.splice(0, recentOps.length - MAX_RECENT);
96
+ }
97
+ }
98
+ export function logOperation(entry) {
99
+ ensureLogDir();
100
+ const opId = entry.opId ?? nextId();
101
+ const record = {
102
+ ts: new Date().toISOString(),
103
+ opId,
104
+ kind: entry.kind,
105
+ action: entry.action || null,
106
+ sessionId: entry.sessionId || null,
107
+ context: entry.context || null,
108
+ reason: entry.reason || null,
109
+ payload: entry.payload ? summarizeValue(entry.payload) : null,
110
+ result: entry.result ? summarizeValue(entry.result) : null,
111
+ target: entry.target ? summarizeValue(entry.target) : null,
112
+ meta: entry.meta ? summarizeValue(entry.meta) : null,
113
+ };
114
+ try {
115
+ fs.appendFileSync(OPS_LOG_FILE, `${JSON.stringify(record)}\n`);
116
+ }
117
+ catch {
118
+ // ignore
119
+ }
120
+ pushRecent(record);
121
+ return opId;
122
+ }
123
+ export function logError(entry) {
124
+ ensureLogDir();
125
+ const opId = entry.opId ?? nextId();
126
+ const record = {
127
+ ts: new Date().toISOString(),
128
+ opId,
129
+ kind: entry.kind,
130
+ action: entry.action || null,
131
+ sessionId: entry.sessionId || null,
132
+ context: entry.context || null,
133
+ reason: entry.reason || null,
134
+ error: entry.error || null,
135
+ payload: entry.payload ? summarizeValue(entry.payload) : null,
136
+ meta: entry.meta ? summarizeValue(entry.meta) : null,
137
+ recent: recentOps.map(summarizeRecent),
138
+ };
139
+ try {
140
+ fs.appendFileSync(ERR_LOG_FILE, `${JSON.stringify(record)}\n`);
141
+ }
142
+ catch {
143
+ // ignore
144
+ }
145
+ return opId;
146
+ }
147
+ export function logControllerActionStart(action, payload, meta = {}) {
148
+ const sessionId = payload?.profileId || payload?.profile || payload?.sessionId || null;
149
+ const reason = payload?.reason ||
150
+ payload?.context ||
151
+ payload?.operationId ||
152
+ payload?.actionId ||
153
+ null;
154
+ const target = payload?.containerId
155
+ ? {
156
+ containerId: payload.containerId,
157
+ operationId: payload?.operationId || null,
158
+ index: payload?.index ?? null,
159
+ }
160
+ : payload?.x !== undefined && payload?.y !== undefined
161
+ ? { x: payload.x, y: payload.y }
162
+ : payload?.coordinates
163
+ ? { coordinates: payload.coordinates }
164
+ : null;
165
+ return logOperation({
166
+ kind: 'controller_action',
167
+ action,
168
+ sessionId,
169
+ reason,
170
+ target,
171
+ payload,
172
+ meta,
173
+ });
174
+ }
175
+ export function logControllerActionResult(opId, action, result, meta = {}) {
176
+ logOperation({
177
+ kind: 'controller_result',
178
+ action,
179
+ opId,
180
+ result,
181
+ meta,
182
+ });
183
+ }
184
+ export function logControllerActionError(opId, action, error, payload, meta = {}) {
185
+ const err = error instanceof Error ? error.message : String(error);
186
+ logError({
187
+ kind: 'controller_error',
188
+ action,
189
+ opId,
190
+ error: err,
191
+ payload,
192
+ meta,
193
+ });
194
+ }
195
+ //# sourceMappingURL=operationLogger.js.map
@@ -0,0 +1,25 @@
1
+ /**
2
+ * SearchPageState helper
3
+ *
4
+ * 处理搜索页面状态验证和 URL 关键词校验
5
+ */
6
+ export interface PageStateConfig {
7
+ profile: string;
8
+ controllerUrl: string;
9
+ }
10
+ export interface EnsureHomePageResult {
11
+ success: boolean;
12
+ url: string;
13
+ onSearchPage: boolean;
14
+ hasDetailOverlay?: boolean;
15
+ onCaptchaPage?: boolean;
16
+ }
17
+ export interface ProbeSearchPageStateResult {
18
+ hasItems: boolean;
19
+ hasNoResultText: boolean;
20
+ }
21
+ export declare function urlKeywordEquals(url: string, keyword: string): boolean;
22
+ export declare function getCurrentUrl(config: PageStateConfig): Promise<string>;
23
+ export declare function ensureHomePage(config: PageStateConfig): Promise<EnsureHomePageResult>;
24
+ export declare function probeSearchPageState(config: PageStateConfig): Promise<ProbeSearchPageStateResult>;
25
+ //# sourceMappingURL=searchPageState.d.ts.map
@@ -0,0 +1,164 @@
1
+ /**
2
+ * SearchPageState helper
3
+ *
4
+ * 处理搜索页面状态验证和 URL 关键词校验
5
+ */
6
+ function safeDecodeURIComponent(value) {
7
+ try {
8
+ return decodeURIComponent(value);
9
+ }
10
+ catch {
11
+ return value;
12
+ }
13
+ }
14
+ export function urlKeywordEquals(url, keyword) {
15
+ const raw = url || '';
16
+ if (!raw.includes('/search_result'))
17
+ return false;
18
+ try {
19
+ const u = new URL(raw);
20
+ const kw = u.searchParams.get('keyword');
21
+ if (!kw)
22
+ return false;
23
+ const decoded = safeDecodeURIComponent(safeDecodeURIComponent(kw)).trim();
24
+ return decoded === String(keyword || '').trim();
25
+ }
26
+ catch {
27
+ const decodedUrl = safeDecodeURIComponent(safeDecodeURIComponent(raw));
28
+ if (!decodedUrl.includes('/search_result'))
29
+ return false;
30
+ try {
31
+ const u2 = new URL(decodedUrl);
32
+ const kw2 = u2.searchParams.get('keyword');
33
+ if (!kw2)
34
+ return false;
35
+ const decoded2 = safeDecodeURIComponent(safeDecodeURIComponent(kw2)).trim();
36
+ return decoded2 === String(keyword || '').trim();
37
+ }
38
+ catch {
39
+ return false;
40
+ }
41
+ }
42
+ }
43
+ export async function getCurrentUrl(config) {
44
+ const { profile, controllerUrl } = config;
45
+ const response = await fetch(controllerUrl, {
46
+ method: 'POST',
47
+ headers: { 'Content-Type': 'application/json' },
48
+ body: JSON.stringify({
49
+ action: 'browser:execute',
50
+ payload: {
51
+ profile,
52
+ script: 'location.href'
53
+ }
54
+ })
55
+ });
56
+ const data = await response.json();
57
+ return data.data?.result || '';
58
+ }
59
+ export async function ensureHomePage(config) {
60
+ const { profile, controllerUrl } = config;
61
+ const url = await getCurrentUrl({ profile, controllerUrl });
62
+ if (!url.includes('xiaohongshu.com')) {
63
+ throw new Error(`Not on xiaohongshu.com (current url=${url || 'unknown'}), please navigate manually before searching.`);
64
+ }
65
+ try {
66
+ const detailState = await fetch(controllerUrl, {
67
+ method: 'POST',
68
+ headers: { 'Content-Type': 'application/json' },
69
+ body: JSON.stringify({
70
+ action: 'browser:execute',
71
+ payload: {
72
+ profile,
73
+ script: `(() => {
74
+ const selectors = [
75
+ '.note-detail-mask',
76
+ '.note-detail-page',
77
+ '.note-detail-dialog',
78
+ '.note-detail',
79
+ '.detail-container',
80
+ '.media-container'
81
+ ];
82
+ const isVisible = (el) => {
83
+ if (!el) return false;
84
+ const style = window.getComputedStyle(el);
85
+ if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false;
86
+ const r = el.getBoundingClientRect();
87
+ if (!r.width || !r.height) return false;
88
+ if (r.bottom <= 0 || r.top >= window.innerHeight) return false;
89
+ return true;
90
+ };
91
+ let visibleOverlay = null;
92
+ for (const sel of selectors) {
93
+ const el = document.querySelector(sel);
94
+ if (el && isVisible(el)) {
95
+ visibleOverlay = el;
96
+ break;
97
+ }
98
+ }
99
+ return { hasDetailOverlayVisible: !!visibleOverlay };
100
+ })()`
101
+ },
102
+ }),
103
+ }).then((r) => r.json());
104
+ const payload = detailState.data?.result ?? detailState.result ?? {};
105
+ if (payload.hasDetailOverlayVisible) {
106
+ throw new Error('Currently in visible detail overlay, please exit detail before searching.');
107
+ }
108
+ }
109
+ catch (err) {
110
+ if (err?.message?.includes('Currently in detail overlay')) {
111
+ throw err;
112
+ }
113
+ console.warn('[SearchPageState] Detail-overlay check failed:', err?.message || err);
114
+ }
115
+ const onSearchPage = url.includes('/search_result');
116
+ const onCaptchaPage = url.includes('captcha') || url.includes('verify');
117
+ if (onCaptchaPage) {
118
+ throw new Error('Detected CAPTCHA page, please solve it manually.');
119
+ }
120
+ return {
121
+ success: true,
122
+ url,
123
+ onSearchPage,
124
+ onCaptchaPage
125
+ };
126
+ }
127
+ export async function probeSearchPageState(config) {
128
+ const { profile, controllerUrl } = config;
129
+ try {
130
+ const response = await fetch(controllerUrl, {
131
+ method: 'POST',
132
+ headers: { 'Content-Type': 'application/json' },
133
+ body: JSON.stringify({
134
+ action: 'browser:execute',
135
+ payload: {
136
+ profile,
137
+ script: `(() => {
138
+ const cards = document.querySelectorAll('.note-item').length;
139
+ const emptyEl =
140
+ document.querySelector('[class*="no-result"], [class*="noResult"], [class*="empty"], .search-empty, .empty') ||
141
+ null;
142
+ const emptyText = (emptyEl ? (emptyEl.textContent || '') : '').trim();
143
+ const bodyText = (document.body && document.body.innerText ? document.body.innerText.slice(0, 1200) : '');
144
+ const hasNoResultText =
145
+ emptyText.includes('没找到相关内容') ||
146
+ emptyText.includes('换个词试试') ||
147
+ bodyText.includes('没找到相关内容') ||
148
+ bodyText.includes('换个词试试');
149
+ return { cards, hasNoResultText };
150
+ })()`
151
+ },
152
+ }),
153
+ signal: AbortSignal.timeout ? AbortSignal.timeout(5000) : undefined,
154
+ });
155
+ const data = await response.json().catch(() => ({}));
156
+ const payload = data.data?.result ?? data.result ?? {};
157
+ const cards = Number(payload.cards || 0);
158
+ return { hasItems: cards > 0, hasNoResultText: Boolean(payload.hasNoResultText) };
159
+ }
160
+ catch {
161
+ return { hasItems: false, hasNoResultText: false };
162
+ }
163
+ }
164
+ //# sourceMappingURL=searchPageState.js.map
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Block: 评论命中(MatchComments)
3
+ *
4
+ * 职责:
5
+ * - 基于容器提取“视口内评论”
6
+ * - 按可配置规则匹配关键字(任意一个/任意两个/必选过滤/排除词等)
7
+ * - 命中后高亮 + 截图留证(可选)
8
+ *
9
+ * 说明:
10
+ * - 不负责导航(safeUrl 由上层负责)
11
+ * - 不做 DOM click,所有交互均通过 container:operation / 系统能力
12
+ */
13
+ import { type CommentKeywordMatchRule } from './helpers/commentMatcher.js';
14
+ import { type CommentMatchDslRule } from './helpers/commentMatchDsl.js';
15
+ export interface MatchCommentsInput {
16
+ sessionId: string;
17
+ /**
18
+ * Rule can be:
19
+ * - legacy keyword rule (any/must/mustNot/should/minAnyMatches...)
20
+ * - DSL rule (require/exclude/prefer with boolean expr)
21
+ */
22
+ rule: CommentKeywordMatchRule | CommentMatchDslRule;
23
+ unifiedApiUrl?: string;
24
+ maxScrolls?: number;
25
+ maxItems?: number;
26
+ maxMatches?: number;
27
+ openComments?: boolean;
28
+ highlightOnFirstMatch?: boolean;
29
+ screenshotOnFirstMatch?: boolean;
30
+ noteId?: string;
31
+ env?: string;
32
+ keyword?: string;
33
+ }
34
+ export interface MatchCommentsOutput {
35
+ success: boolean;
36
+ rounds: number;
37
+ reachedBottom: boolean;
38
+ matches: Array<{
39
+ index: number;
40
+ userId: string;
41
+ userName: string;
42
+ content: string;
43
+ timestamp: string;
44
+ matched: {
45
+ hits: string[];
46
+ score: number;
47
+ legacy?: {
48
+ anyHits: string[];
49
+ mustHits: string[];
50
+ shouldHits: string[];
51
+ anyCount: number;
52
+ shouldCount: number;
53
+ };
54
+ dsl?: {
55
+ requireHits: string[];
56
+ preferHits: string[];
57
+ };
58
+ };
59
+ }>;
60
+ evidence?: {
61
+ firstMatchScreenshot?: string | null;
62
+ };
63
+ error?: string;
64
+ }
65
+ export declare function execute(input: MatchCommentsInput): Promise<MatchCommentsOutput>;
66
+ //# sourceMappingURL=MatchCommentsBlock.d.ts.map
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Block: 评论命中(MatchComments)
3
+ *
4
+ * 职责:
5
+ * - 基于容器提取“视口内评论”
6
+ * - 按可配置规则匹配关键字(任意一个/任意两个/必选过滤/排除词等)
7
+ * - 命中后高亮 + 截图留证(可选)
8
+ *
9
+ * 说明:
10
+ * - 不负责导航(safeUrl 由上层负责)
11
+ * - 不做 DOM click,所有交互均通过 container:operation / 系统能力
12
+ */
13
+ import path from 'node:path';
14
+ import { promises as fs } from 'node:fs';
15
+ import { controllerAction, delay } from '../utils/controllerAction.js';
16
+ import { isLegacyKeywordRule, matchCommentText } from './helpers/commentMatcher.js';
17
+ import { matchCommentTextDsl } from './helpers/commentMatchDsl.js';
18
+ import { ensureCommentsOpened, extractVisibleComments, highlightCommentRow, isCommentEnd, scrollComments, } from './helpers/xhsComments.js';
19
+ import { resolveDownloadRoot, savePngBase64, takeScreenshotBase64 } from './helpers/evidence.js';
20
+ export async function execute(input) {
21
+ const { sessionId, rule, unifiedApiUrl = 'http://127.0.0.1:7701', maxScrolls = 12, maxItems = 60, maxMatches = 3, openComments = true, highlightOnFirstMatch = true, screenshotOnFirstMatch = true, noteId = `unknown-${Date.now()}`, env = 'debug', keyword = 'unknown', } = input;
22
+ const matches = [];
23
+ const seen = new Set();
24
+ let rounds = 0;
25
+ let reachedBottom = false;
26
+ let firstMatchScreenshot = null;
27
+ try {
28
+ if (openComments) {
29
+ await ensureCommentsOpened(sessionId, unifiedApiUrl);
30
+ }
31
+ const outDir = path.join(resolveDownloadRoot(), 'xiaohongshu', env, keyword, 'comment-match', noteId);
32
+ for (let round = 0; round < maxScrolls; round += 1) {
33
+ rounds = round + 1;
34
+ const rows = await extractVisibleComments(sessionId, unifiedApiUrl, maxItems);
35
+ for (let i = 0; i < rows.length; i += 1) {
36
+ if (matches.length >= maxMatches)
37
+ break;
38
+ const r = rows[i] || {};
39
+ const text = String(r.text || '').trim();
40
+ if (!text)
41
+ continue;
42
+ const key = `${String(r.user_id || '')}:${text}`;
43
+ if (seen.has(key))
44
+ continue;
45
+ seen.add(key);
46
+ const matched = isLegacyKeywordRule(rule)
47
+ ? (() => {
48
+ const m = matchCommentText(text, rule);
49
+ if (!m.ok)
50
+ return null;
51
+ return {
52
+ ok: true,
53
+ hits: Array.from(new Set([...m.anyHits, ...m.mustHits, ...m.shouldHits])),
54
+ score: m.shouldCount * 100 + m.anyCount + m.mustHits.length,
55
+ legacy: {
56
+ anyHits: m.anyHits,
57
+ mustHits: m.mustHits,
58
+ shouldHits: m.shouldHits,
59
+ anyCount: m.anyCount,
60
+ shouldCount: m.shouldCount,
61
+ },
62
+ };
63
+ })()
64
+ : (() => {
65
+ const d = matchCommentTextDsl(text, rule);
66
+ if (!d.ok)
67
+ return null;
68
+ return {
69
+ ok: true,
70
+ hits: d.hits,
71
+ score: d.score,
72
+ dsl: { requireHits: d.requireHits, preferHits: d.preferHits },
73
+ };
74
+ })();
75
+ if (!matched)
76
+ continue;
77
+ matches.push({
78
+ index: i,
79
+ userId: String(r.user_id || ''),
80
+ userName: String(r.user_name || ''),
81
+ content: text,
82
+ timestamp: String(r.timestamp || ''),
83
+ matched: {
84
+ hits: matched.hits,
85
+ score: matched.score,
86
+ legacy: Object.prototype.hasOwnProperty.call(matched, 'legacy') ? matched.legacy : undefined,
87
+ dsl: Object.prototype.hasOwnProperty.call(matched, 'dsl') ? matched.dsl : undefined,
88
+ },
89
+ });
90
+ if (matches.length === 1 && highlightOnFirstMatch) {
91
+ const hl = await highlightCommentRow(sessionId, i, unifiedApiUrl, 'comment-match-first').catch(() => null);
92
+ await delay(450);
93
+ if (screenshotOnFirstMatch && hl?.inViewport === true && !firstMatchScreenshot) {
94
+ const base64 = await takeScreenshotBase64(sessionId, unifiedApiUrl);
95
+ if (base64) {
96
+ await fs.mkdir(outDir, { recursive: true });
97
+ firstMatchScreenshot = await savePngBase64(base64, path.join(outDir, `match-first-${Date.now()}.png`));
98
+ }
99
+ }
100
+ }
101
+ }
102
+ if (matches.length >= maxMatches)
103
+ break;
104
+ reachedBottom = await isCommentEnd(sessionId, unifiedApiUrl);
105
+ if (reachedBottom)
106
+ break;
107
+ await scrollComments(sessionId, unifiedApiUrl, 650).catch(() => { });
108
+ await delay(900);
109
+ }
110
+ return {
111
+ success: true,
112
+ rounds,
113
+ reachedBottom,
114
+ matches,
115
+ evidence: { firstMatchScreenshot },
116
+ };
117
+ }
118
+ catch (e) {
119
+ return {
120
+ success: false,
121
+ rounds,
122
+ reachedBottom,
123
+ matches,
124
+ evidence: { firstMatchScreenshot },
125
+ error: e?.message || String(e),
126
+ };
127
+ }
128
+ finally {
129
+ // 尽量不破坏会话,仅清理一次高亮 channel(不强制)
130
+ await controllerAction('browser:execute', {
131
+ profile: sessionId,
132
+ script: `(() => {
133
+ try { window.__webautoRuntime?.highlight?.clear?.('comment-match-first'); } catch {}
134
+ return true;
135
+ })()`,
136
+ }, unifiedApiUrl).catch(() => null);
137
+ }
138
+ }
139
+ //# sourceMappingURL=MatchCommentsBlock.js.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Phase 1 Block: 确保基础服务就绪
3
+ */
4
+ export interface EnsureServicesInput {
5
+ unifiedApiUrl?: string;
6
+ browserServiceUrl?: string;
7
+ timeout?: number;
8
+ }
9
+ export interface EnsureServicesOutput {
10
+ unifiedApiOk: boolean;
11
+ browserServiceOk: boolean;
12
+ unifiedApiUrl: string;
13
+ browserServiceUrl: string;
14
+ }
15
+ export declare function execute(input?: EnsureServicesInput): Promise<EnsureServicesOutput>;
16
+ //# sourceMappingURL=Phase1EnsureServicesBlock.d.ts.map
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Phase 1 Block: 确保基础服务就绪
3
+ */
4
+ export async function execute(input = {}) {
5
+ const { unifiedApiUrl = 'http://127.0.0.1:7701', browserServiceUrl = 'http://127.0.0.1:7704', timeout = 5000, } = input;
6
+ console.log('[Phase1EnsureServices] 检查服务状态...');
7
+ let unifiedApiOk = false;
8
+ try {
9
+ const res = await fetch(`${unifiedApiUrl}/health`, {
10
+ signal: AbortSignal.timeout(timeout),
11
+ });
12
+ unifiedApiOk = res.ok;
13
+ console.log(`[Phase1] Unified API: ${unifiedApiOk ? '✅' : '❌'}`);
14
+ }
15
+ catch (err) {
16
+ console.warn(`[Phase1] Unified API 不可达: ${err?.message || String(err)}`);
17
+ }
18
+ let browserServiceOk = false;
19
+ try {
20
+ const res = await fetch(`${browserServiceUrl}/health`, {
21
+ signal: AbortSignal.timeout(timeout),
22
+ });
23
+ browserServiceOk = res.ok;
24
+ console.log(`[Phase1] Browser Service: ${browserServiceOk ? '✅' : '❌'}`);
25
+ }
26
+ catch (err) {
27
+ console.warn(`[Phase1] Browser Service 不可达: ${err?.message || String(err)}`);
28
+ }
29
+ return {
30
+ unifiedApiOk,
31
+ browserServiceOk,
32
+ unifiedApiUrl,
33
+ browserServiceUrl,
34
+ };
35
+ }
36
+ //# sourceMappingURL=Phase1EnsureServicesBlock.js.map
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Phase 1 Block: Cookie 监控与稳定保存
3
+ *
4
+ * 规则:
5
+ * - 不论是否登录成功,都要每 15 秒扫描一次 cookie
6
+ * - 只有在“登录锚点确认成功”后:cookie 发生变化并且稳定(连续 stableCount 次一致)才保存
7
+ * - 保存走 Browser Service 的 saveCookiesIfStable(避免自己造格式)
8
+ */
9
+ export interface Phase1MonitorCookieInput {
10
+ profile: string;
11
+ unifiedApiUrl?: string;
12
+ browserServiceUrl?: string;
13
+ scanIntervalMs?: number;
14
+ stableCount?: number;
15
+ cookiePath?: string;
16
+ }
17
+ export interface Phase1MonitorCookieOutput {
18
+ success: boolean;
19
+ profile: string;
20
+ loggedIn: boolean;
21
+ saved: boolean;
22
+ cookiePath: string;
23
+ scanRounds: number;
24
+ autoCookiesStarted: boolean;
25
+ }
26
+ export declare function execute(input: Phase1MonitorCookieInput): Promise<Phase1MonitorCookieOutput>;
27
+ //# sourceMappingURL=Phase1MonitorCookieBlock.d.ts.map