@web-auto/webauto 0.1.18 → 0.1.19

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 (65) hide show
  1. package/README.md +122 -53
  2. package/apps/desktop-console/dist/main/index.mjs +227 -12
  3. package/apps/desktop-console/dist/renderer/index.js +237 -8
  4. package/apps/desktop-console/entry/ui-cli.mjs +282 -16
  5. package/apps/desktop-console/entry/ui-console.mjs +46 -15
  6. package/apps/webauto/entry/account.mjs +126 -27
  7. package/apps/webauto/entry/lib/account-detect.mjs +399 -9
  8. package/apps/webauto/entry/lib/account-store.mjs +201 -109
  9. package/apps/webauto/entry/lib/iflow-reply.mjs +194 -0
  10. package/apps/webauto/entry/lib/profile-policy.mjs +48 -0
  11. package/apps/webauto/entry/lib/profilepool.mjs +12 -0
  12. package/apps/webauto/entry/lib/schedule-store.mjs +29 -2
  13. package/apps/webauto/entry/lib/session-init.mjs +227 -0
  14. package/apps/webauto/entry/lib/upgrade-check.mjs +269 -0
  15. package/apps/webauto/entry/lib/xhs-unified-blocks.mjs +160 -0
  16. package/apps/webauto/entry/lib/xhs-unified-output-blocks.mjs +83 -0
  17. package/apps/webauto/entry/lib/xhs-unified-plan-blocks.mjs +55 -0
  18. package/apps/webauto/entry/lib/xhs-unified-profile-blocks.mjs +542 -0
  19. package/apps/webauto/entry/lib/xhs-unified-runtime-blocks.mjs +436 -0
  20. package/apps/webauto/entry/profilepool.mjs +56 -9
  21. package/apps/webauto/entry/smart-reply-cli.mjs +267 -0
  22. package/apps/webauto/entry/weibo-unified.mjs +84 -11
  23. package/apps/webauto/entry/xhs-orchestrate.mjs +43 -1
  24. package/apps/webauto/entry/xhs-unified.mjs +92 -997
  25. package/bin/webauto.mjs +22 -4
  26. package/dist/modules/camo-backend/src/index.js +33 -0
  27. package/dist/modules/camo-backend/src/internal/BrowserSession.js +232 -49
  28. package/dist/modules/camo-backend/src/internal/engine-manager.js +14 -13
  29. package/dist/modules/camo-backend/src/internal/ws-server.js +16 -19
  30. package/dist/modules/camo-runtime/src/utils/browser-service.mjs +38 -6
  31. package/dist/modules/workflow/blocks/EnsureSession.js +0 -8
  32. package/dist/modules/workflow/blocks/WeiboCollectFromLinksBlock.js +78 -6
  33. package/dist/modules/workflow/blocks/WeiboCollectSearchLinksBlock.js +266 -192
  34. package/dist/modules/workflow/definitions/weibo-search-workflow-v1.js +2 -0
  35. package/dist/modules/workflow/src/runner.js +2 -0
  36. package/dist/modules/xiaohongshu/app/src/blocks/ReplyInteractBlock.js +150 -37
  37. package/dist/modules/xiaohongshu/app/src/blocks/SmartReplyBlock.js +491 -0
  38. package/modules/camo-backend/src/index.ts +31 -0
  39. package/modules/camo-backend/src/internal/BrowserSession.ts +224 -53
  40. package/modules/camo-backend/src/internal/engine-manager.ts +14 -15
  41. package/modules/camo-backend/src/internal/ws-server.ts +17 -17
  42. package/modules/camo-runtime/src/autoscript/action-providers/xhs/common.mjs +12 -2
  43. package/modules/camo-runtime/src/autoscript/action-providers/xhs/persistence.mjs +57 -0
  44. package/modules/camo-runtime/src/autoscript/action-providers/xhs.mjs +2475 -243
  45. package/modules/camo-runtime/src/autoscript/runtime.mjs +35 -30
  46. package/modules/camo-runtime/src/autoscript/xhs-unified-template.mjs +80 -443
  47. package/modules/camo-runtime/src/container/runtime-core/checkpoint.mjs +39 -6
  48. package/modules/camo-runtime/src/container/runtime-core/operations/index.mjs +206 -39
  49. package/modules/camo-runtime/src/container/runtime-core/operations/tab-pool.mjs +0 -79
  50. package/modules/camo-runtime/src/container/runtime-core/operations/viewport.mjs +46 -0
  51. package/modules/camo-runtime/src/utils/browser-service.mjs +41 -6
  52. package/modules/camo-runtime/src/utils/js-policy.mjs +28 -0
  53. package/modules/workflow/blocks/EnsureSession.ts +0 -4
  54. package/modules/workflow/blocks/WeiboCollectFromLinksBlock.ts +81 -6
  55. package/modules/workflow/blocks/WeiboCollectSearchLinksBlock.ts +316 -0
  56. package/modules/workflow/definitions/weibo-search-workflow-v1.ts +2 -0
  57. package/modules/workflow/src/runner.ts +2 -0
  58. package/modules/xiaohongshu/app/src/blocks/ReplyInteractBlock.ts +198 -53
  59. package/modules/xiaohongshu/app/src/blocks/SmartReplyBlock.ts +706 -0
  60. package/package.json +2 -2
  61. package/modules/camo-runtime/src/autoscript/action-providers/xhs/comments.mjs +0 -498
  62. package/modules/camo-runtime/src/autoscript/action-providers/xhs/detail.mjs +0 -181
  63. package/modules/camo-runtime/src/autoscript/action-providers/xhs/interaction.mjs +0 -691
  64. package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +0 -388
  65. package/modules/camo-runtime/src/container/runtime-core/operations/selector-scripts.mjs +0 -135
@@ -8,6 +8,7 @@ import { fileURLToPath } from 'node:url';
8
8
  import { BROWSER_SERVICE_URL, loadConfig, setRepoRoot } from './config.mjs';
9
9
  const requireFromHere = createRequire(import.meta.url);
10
10
  const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
11
+ const DEFAULT_API_TIMEOUT_MS = 30000;
11
12
  function resolveNodeBin() {
12
13
  const explicit = String(process.env.WEBAUTO_NODE_BIN || '').trim();
13
14
  if (explicit)
@@ -53,12 +54,43 @@ function runCamoCli(args = [], options = {}) {
53
54
  entry,
54
55
  };
55
56
  }
56
- export async function callAPI(action, payload = {}) {
57
- const r = await fetch(`${BROWSER_SERVICE_URL}/command`, {
58
- method: 'POST',
59
- headers: { 'Content-Type': 'application/json' },
60
- body: JSON.stringify({ action, args: payload }),
61
- });
57
+ function resolveApiTimeoutMs(options = {}) {
58
+ const optionValue = Number(options?.timeoutMs);
59
+ if (Number.isFinite(optionValue) && optionValue > 0) {
60
+ return Math.max(1000, Math.floor(optionValue));
61
+ }
62
+ const envValue = Number(process.env.CAMO_API_TIMEOUT_MS);
63
+ if (Number.isFinite(envValue) && envValue > 0) {
64
+ return Math.max(1000, Math.floor(envValue));
65
+ }
66
+ return DEFAULT_API_TIMEOUT_MS;
67
+ }
68
+ function isTimeoutError(error) {
69
+ const name = String(error?.name || '').toLowerCase();
70
+ const message = String(error?.message || '').toLowerCase();
71
+ return (name.includes('timeout')
72
+ || name.includes('abort')
73
+ || message.includes('timeout')
74
+ || message.includes('timed out')
75
+ || message.includes('aborted'));
76
+ }
77
+ export async function callAPI(action, payload = {}, options = {}) {
78
+ const timeoutMs = resolveApiTimeoutMs(options);
79
+ let r;
80
+ try {
81
+ r = await fetch(`${BROWSER_SERVICE_URL}/command`, {
82
+ method: 'POST',
83
+ headers: { 'Content-Type': 'application/json' },
84
+ body: JSON.stringify({ action, args: payload }),
85
+ signal: AbortSignal.timeout(timeoutMs),
86
+ });
87
+ }
88
+ catch (error) {
89
+ if (isTimeoutError(error)) {
90
+ throw new Error(`browser-service timeout after ${timeoutMs}ms: ${action}`);
91
+ }
92
+ throw error;
93
+ }
62
94
  let body;
63
95
  try {
64
96
  body = await r.json();
@@ -160,8 +160,6 @@ export async function execute(input) {
160
160
  return 1;
161
161
  }
162
162
  async function clearCssZoom() {
163
- if (!profileId.startsWith('xiaohongshu_'))
164
- return;
165
163
  try {
166
164
  await fetch(statusUrl, {
167
165
  method: 'POST',
@@ -184,8 +182,6 @@ export async function execute(input) {
184
182
  }
185
183
  }
186
184
  async function applyZoom() {
187
- if (!profileId.startsWith('xiaohongshu_'))
188
- return;
189
185
  const metrics = await loadMetrics();
190
186
  const zoom = resolveZoom(metrics);
191
187
  if (zoom === null) {
@@ -239,8 +235,6 @@ export async function execute(input) {
239
235
  return null;
240
236
  }
241
237
  async function applyBrowserZoom() {
242
- if (!profileId.startsWith('xiaohongshu_'))
243
- return;
244
238
  const target = resolveBrowserZoomTarget();
245
239
  if (!target || Math.abs(target - 1) < 0.01)
246
240
  return;
@@ -278,8 +272,6 @@ export async function execute(input) {
278
272
  console.log(`[EnsureSession] browser zoom applied target~${target} steps=${steps} key=${key} profile=${profileId}`);
279
273
  }
280
274
  async function resetBrowserZoom() {
281
- if (!profileId.startsWith('xiaohongshu_'))
282
- return;
283
275
  if (process.env.WEBAUTO_RESET_BROWSER_ZOOM === '0')
284
276
  return;
285
277
  const key = os.platform() === 'darwin' ? 'Meta+0' : 'Control+0';
@@ -96,9 +96,11 @@ async function saveMarkdown(keywordDir, entry, content, comments, commentStats,
96
96
  }
97
97
  export async function execute(input) {
98
98
  const { sessionId, keyword, env = 'debug', targetCount, maxComments = 0, collectComments: enableComments = false, // 默认不采集评论,加快速度
99
- serviceUrl = 'http://127.0.0.1:7704', } = input;
99
+ tabCount: requestedTabCount = 1, tabOpenDelayMs: requestedTabOpenDelayMs = 800, serviceUrl = 'http://127.0.0.1:7704', } = input;
100
100
  const profile = sessionId;
101
101
  const controllerUrl = `${serviceUrl}/command`;
102
+ const tabCount = Math.max(1, Math.min(8, Number(requestedTabCount || 1) || 1));
103
+ const tabOpenDelayMs = Math.max(0, Number(requestedTabOpenDelayMs || 0) || 0);
102
104
  const keywordDir = path.join(resolveDownloadRoot(), 'weibo', env, sanitizeFilenamePart(keyword));
103
105
  const linksPath = path.join(keywordDir, 'phase2-links.jsonl');
104
106
  const links = await readJsonl(linksPath);
@@ -109,7 +111,7 @@ export async function execute(input) {
109
111
  linksPath,
110
112
  processedCount: 0,
111
113
  persistedCount: 0,
112
- stats: { postsProcessed: 0, totalComments: 0, errors: 0 },
114
+ stats: { postsProcessed: 0, totalComments: 0, errors: 0, tabsUsed: 0 },
113
115
  error: 'No links found in phase2-links.jsonl',
114
116
  };
115
117
  }
@@ -153,6 +155,67 @@ export async function execute(input) {
153
155
  await controllerAction('goto', { url });
154
156
  await new Promise(r => setTimeout(r, 500)); // 减少间隔 // 减少等待时间
155
157
  }
158
+ async function listPagesDetailed() {
159
+ const res = await controllerAction('browser:page:list', { profileId: profile });
160
+ const value = unwrapResult(res);
161
+ const pages = Array.isArray(value?.pages) ? value.pages : (Array.isArray(value) ? value : []);
162
+ return pages
163
+ .map((item) => ({
164
+ index: Number(item?.index),
165
+ url: String(item?.url || ''),
166
+ active: item?.active === true,
167
+ }))
168
+ .filter((item) => Number.isFinite(item.index));
169
+ }
170
+ async function switchToPage(index) {
171
+ await controllerAction('browser:page:switch', { profileId: profile, index });
172
+ await new Promise((r) => setTimeout(r, 260));
173
+ }
174
+ async function openNewTabAndResolveIndex(existingIndexes) {
175
+ await controllerAction('system:shortcut', { app: 'camoufox', shortcut: 'new-tab' });
176
+ if (tabOpenDelayMs > 0) {
177
+ await new Promise((r) => setTimeout(r, tabOpenDelayMs));
178
+ }
179
+ const after = await listPagesDetailed();
180
+ const active = after.find((item) => item.active);
181
+ if (active && !existingIndexes.has(active.index))
182
+ return active.index;
183
+ const added = after.find((item) => !existingIndexes.has(item.index));
184
+ if (added)
185
+ return added.index;
186
+ const fallback = after
187
+ .map((item) => item.index)
188
+ .filter((idx) => !existingIndexes.has(idx))
189
+ .sort((a, b) => a - b);
190
+ return fallback.length > 0 ? fallback[fallback.length - 1] : null;
191
+ }
192
+ async function ensureTabPool(count) {
193
+ const pages = await listPagesDetailed().catch(() => []);
194
+ const active = pages.find((item) => item.active);
195
+ const pool = [];
196
+ if (active && Number.isFinite(active.index)) {
197
+ pool.push(active.index);
198
+ }
199
+ else if (pages.length > 0) {
200
+ pool.push(pages[0].index);
201
+ }
202
+ else {
203
+ pool.push(0);
204
+ }
205
+ while (pool.length < count) {
206
+ const idx = await openNewTabAndResolveIndex(new Set(pool));
207
+ if (!Number.isFinite(Number(idx)))
208
+ break;
209
+ const next = Number(idx);
210
+ if (pool.includes(next))
211
+ break;
212
+ pool.push(next);
213
+ }
214
+ if (pool.length > 0) {
215
+ await switchToPage(pool[0]).catch(() => null);
216
+ }
217
+ return pool;
218
+ }
156
219
  async function extractPostContent() {
157
220
  const script = `
158
221
  (() => {
@@ -223,12 +286,20 @@ export async function execute(input) {
223
286
  let persistedCount = 0;
224
287
  let totalComments = 0;
225
288
  let errors = 0;
289
+ let tabsUsed = 1;
226
290
  try {
227
- const targetLinks = links.slice(0, targetCount);
228
- for (const link of targetLinks) {
291
+ const targetLinks = links.slice(0, Math.max(1, Number(targetCount || 0) || 1));
292
+ const tabPool = await ensureTabPool(tabCount).catch(() => [0]);
293
+ const roundRobinTabs = tabPool.length > 0 ? tabPool : [0];
294
+ tabsUsed = roundRobinTabs.length;
295
+ console.log(`[WeiboCollectFromLinks] tab pool ready: [${roundRobinTabs.join(', ')}]`);
296
+ for (let idx = 0; idx < targetLinks.length; idx += 1) {
297
+ const link = targetLinks[idx];
229
298
  processedCount++;
230
- console.log(`[WeiboCollectFromLinks] Processing: ${link.statusId}`);
299
+ const tabIndex = roundRobinTabs[idx % roundRobinTabs.length];
300
+ console.log(`[WeiboCollectFromLinks] Processing: ${link.statusId} (tab=${tabIndex})`);
231
301
  try {
302
+ await switchToPage(tabIndex);
232
303
  await gotoUrl(link.safeUrl);
233
304
  let currentUrl = await getCurrentUrl();
234
305
  if (!currentUrl) {
@@ -265,6 +336,7 @@ export async function execute(input) {
265
336
  postsProcessed: processedCount,
266
337
  totalComments,
267
338
  errors,
339
+ tabsUsed,
268
340
  },
269
341
  };
270
342
  }
@@ -275,7 +347,7 @@ export async function execute(input) {
275
347
  linksPath,
276
348
  processedCount,
277
349
  persistedCount,
278
- stats: { postsProcessed: processedCount, totalComments, errors },
350
+ stats: { postsProcessed: processedCount, totalComments, errors, tabsUsed },
279
351
  error: error.message,
280
352
  };
281
353
  }