@web-auto/webauto 0.1.13 → 0.1.15
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.
- package/README.md +137 -0
- package/apps/desktop-console/dist/main/index.mjs +93 -161
- package/apps/desktop-console/dist/renderer/index.js +2 -7
- package/apps/desktop-console/entry/ui-console.mjs +89 -8
- package/apps/desktop-console/package.json +24 -0
- package/apps/webauto/entry/flow-gate.mjs +139 -0
- package/apps/webauto/entry/lib/camo-cli.mjs +10 -3
- package/apps/webauto/entry/lib/flow-gate.mjs +466 -0
- package/apps/webauto/entry/xhs-install.mjs +3 -11
- package/apps/webauto/entry/xhs-status.mjs +3 -1
- package/apps/webauto/entry/xhs-unified.mjs +111 -5
- package/bin/webauto.mjs +89 -49
- package/dist/modules/camo-runtime/src/utils/browser-service.mjs +64 -44
- package/modules/camo-runtime/src/autoscript/action-providers/index.mjs +14 -3
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/comments.mjs +69 -19
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/interaction.mjs +56 -4
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +130 -21
- package/modules/camo-runtime/src/autoscript/action-providers/xhs.mjs +90 -14
- package/modules/camo-runtime/src/autoscript/runtime.mjs +14 -0
- package/modules/camo-runtime/src/autoscript/xhs-unified-template.mjs +69 -8
- package/modules/camo-runtime/src/utils/browser-service.mjs +67 -45
- package/package.json +4 -3
|
@@ -60,9 +60,11 @@ function extractErrorEvents(events = [], limit = 20) {
|
|
|
60
60
|
for (const event of events) {
|
|
61
61
|
const payload = event?.data && typeof event.data === 'object' ? event.data : event;
|
|
62
62
|
const type = asText(payload?.type || payload?.event || '').toLowerCase();
|
|
63
|
+
if (type.includes('operation_progress')) continue;
|
|
63
64
|
const hasErrorType = type.includes('error') || type.includes('fail');
|
|
65
|
+
const hasExplicitError = Boolean(payload?.error);
|
|
64
66
|
const errText = asText(payload?.error || payload?.message || payload?.reason || '');
|
|
65
|
-
if (!hasErrorType && !
|
|
67
|
+
if (!hasErrorType && !hasExplicitError) continue;
|
|
66
68
|
items.push({
|
|
67
69
|
ts: asText(payload?.timestamp || payload?.ts || ''),
|
|
68
70
|
type: type || 'error',
|
|
@@ -13,6 +13,7 @@ import { listAccountProfiles, markProfileInvalid } from './lib/account-store.mjs
|
|
|
13
13
|
import { listProfilesForPool } from './lib/profilepool.mjs';
|
|
14
14
|
import { runCamo } from './lib/camo-cli.mjs';
|
|
15
15
|
import { publishBusEvent } from './lib/bus-publish.mjs';
|
|
16
|
+
import { resolvePlatformFlowGate } from './lib/flow-gate.mjs';
|
|
16
17
|
|
|
17
18
|
function nowIso() {
|
|
18
19
|
return new Date().toISOString();
|
|
@@ -45,6 +46,13 @@ function parseNonNegativeInt(value, fallback = 0) {
|
|
|
45
46
|
return Math.max(0, Math.floor(num));
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
function pickRandomInt(min, max) {
|
|
50
|
+
const floorMin = Math.max(0, Math.floor(Number(min) || 0));
|
|
51
|
+
const floorMax = Math.max(floorMin, Math.floor(Number(max) || 0));
|
|
52
|
+
if (floorMax <= floorMin) return floorMin;
|
|
53
|
+
return floorMin + Math.floor(Math.random() * (floorMax - floorMin + 1));
|
|
54
|
+
}
|
|
55
|
+
|
|
48
56
|
function parseProfiles(argv) {
|
|
49
57
|
const profile = String(argv.profile || '').trim();
|
|
50
58
|
const profilesRaw = String(argv.profiles || '').trim();
|
|
@@ -301,7 +309,7 @@ function createTaskReporter(seed = {}) {
|
|
|
301
309
|
};
|
|
302
310
|
}
|
|
303
311
|
|
|
304
|
-
function buildTemplateOptions(argv, profileId, overrides = {}) {
|
|
312
|
+
async function buildTemplateOptions(argv, profileId, overrides = {}) {
|
|
305
313
|
const keyword = String(argv.keyword || argv.k || '').trim();
|
|
306
314
|
const env = String(argv.env || 'prod').trim() || 'prod';
|
|
307
315
|
const inputMode = String(argv['input-mode'] || 'protocol').trim() || 'protocol';
|
|
@@ -309,9 +317,63 @@ function buildTemplateOptions(argv, profileId, overrides = {}) {
|
|
|
309
317
|
const ocrCommand = String(argv['ocr-command'] || '').trim();
|
|
310
318
|
const maxNotes = parseIntFlag(argv['max-notes'] ?? argv.target, 30, 1);
|
|
311
319
|
const maxComments = parseNonNegativeInt(argv['max-comments'], 0);
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
320
|
+
let flowGate = null;
|
|
321
|
+
try {
|
|
322
|
+
flowGate = await resolvePlatformFlowGate('xiaohongshu');
|
|
323
|
+
} catch {
|
|
324
|
+
flowGate = null;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const throttleMin = parseIntFlag(flowGate?.throttle?.minMs, 900, 100);
|
|
328
|
+
const throttleMax = parseIntFlag(flowGate?.throttle?.maxMs, 1800, throttleMin);
|
|
329
|
+
const noteIntervalMin = parseIntFlag(flowGate?.noteInterval?.minMs, 2200, 200);
|
|
330
|
+
const noteIntervalMax = parseIntFlag(flowGate?.noteInterval?.maxMs, 4200, noteIntervalMin);
|
|
331
|
+
const tabCountDefault = parseIntFlag(flowGate?.tabPool?.tabCount, 1, 1);
|
|
332
|
+
const tabOpenDelayMin = parseIntFlag(flowGate?.tabPool?.openDelayMinMs, 1400, 0);
|
|
333
|
+
const tabOpenDelayMax = parseIntFlag(flowGate?.tabPool?.openDelayMaxMs, 2800, tabOpenDelayMin);
|
|
334
|
+
const submitMethodDefault = String(flowGate?.submitSearch?.method || 'click').trim().toLowerCase() || 'click';
|
|
335
|
+
const submitActionDelayMinDefault = parseIntFlag(flowGate?.submitSearch?.actionDelayMinMs, 180, 20);
|
|
336
|
+
const submitActionDelayMaxDefault = parseIntFlag(flowGate?.submitSearch?.actionDelayMaxMs, 620, submitActionDelayMinDefault);
|
|
337
|
+
const submitSettleMinDefault = parseIntFlag(flowGate?.submitSearch?.settleMinMs, 1200, 60);
|
|
338
|
+
const submitSettleMaxDefault = parseIntFlag(flowGate?.submitSearch?.settleMaxMs, 2600, submitSettleMinDefault);
|
|
339
|
+
const openDetailPreClickMinDefault = parseIntFlag(flowGate?.openDetail?.preClickMinMs, 220, 60);
|
|
340
|
+
const openDetailPreClickMaxDefault = parseIntFlag(flowGate?.openDetail?.preClickMaxMs, 700, openDetailPreClickMinDefault);
|
|
341
|
+
const openDetailPollDelayMinDefault = parseIntFlag(flowGate?.openDetail?.pollDelayMinMs, 130, 80);
|
|
342
|
+
const openDetailPollDelayMaxDefault = parseIntFlag(flowGate?.openDetail?.pollDelayMaxMs, 320, openDetailPollDelayMinDefault);
|
|
343
|
+
const openDetailPostOpenMinDefault = parseIntFlag(flowGate?.openDetail?.postOpenMinMs, 420, 120);
|
|
344
|
+
const openDetailPostOpenMaxDefault = parseIntFlag(flowGate?.openDetail?.postOpenMaxMs, 1100, openDetailPostOpenMinDefault);
|
|
345
|
+
const commentsScrollStepMinDefault = parseIntFlag(flowGate?.commentsHarvest?.scrollStepMin, 280, 120);
|
|
346
|
+
const commentsScrollStepMaxDefault = parseIntFlag(flowGate?.commentsHarvest?.scrollStepMax, 420, commentsScrollStepMinDefault);
|
|
347
|
+
const commentsSettleMinDefault = parseIntFlag(flowGate?.commentsHarvest?.settleMinMs, 280, 80);
|
|
348
|
+
const commentsSettleMaxDefault = parseIntFlag(flowGate?.commentsHarvest?.settleMaxMs, 820, commentsSettleMinDefault);
|
|
349
|
+
const defaultOperationMinIntervalDefault = parseIntFlag(flowGate?.pacing?.defaultOperationMinIntervalMs, 1200, 0);
|
|
350
|
+
const defaultEventCooldownDefault = parseIntFlag(flowGate?.pacing?.defaultEventCooldownMs, 700, 0);
|
|
351
|
+
const defaultPacingJitterDefault = parseIntFlag(flowGate?.pacing?.defaultJitterMs, 900, 0);
|
|
352
|
+
const navigationMinIntervalDefault = parseIntFlag(flowGate?.pacing?.navigationMinIntervalMs, 2200, 0);
|
|
353
|
+
|
|
354
|
+
const throttle = parseIntFlag(argv.throttle, pickRandomInt(throttleMin, throttleMax), 100);
|
|
355
|
+
const tabCount = parseIntFlag(argv['tab-count'], tabCountDefault, 1);
|
|
356
|
+
const noteIntervalMs = parseIntFlag(argv['note-interval'], pickRandomInt(noteIntervalMin, noteIntervalMax), 200);
|
|
357
|
+
const tabOpenDelayMs = parseIntFlag(argv['tab-open-delay'], pickRandomInt(tabOpenDelayMin, tabOpenDelayMax), 0);
|
|
358
|
+
const submitMethod = String(argv['search-submit-method'] || submitMethodDefault).trim().toLowerCase() || 'click';
|
|
359
|
+
const submitActionDelayMinMs = parseIntFlag(argv['submit-action-delay-min'], submitActionDelayMinDefault, 20);
|
|
360
|
+
const submitActionDelayMaxMs = parseIntFlag(argv['submit-action-delay-max'], submitActionDelayMaxDefault, submitActionDelayMinMs);
|
|
361
|
+
const submitSettleMinMs = parseIntFlag(argv['submit-settle-min'], submitSettleMinDefault, 60);
|
|
362
|
+
const submitSettleMaxMs = parseIntFlag(argv['submit-settle-max'], submitSettleMaxDefault, submitSettleMinMs);
|
|
363
|
+
const openDetailPreClickMinMs = parseIntFlag(argv['open-detail-preclick-min'], openDetailPreClickMinDefault, 60);
|
|
364
|
+
const openDetailPreClickMaxMs = parseIntFlag(argv['open-detail-preclick-max'], openDetailPreClickMaxDefault, openDetailPreClickMinMs);
|
|
365
|
+
const openDetailPollDelayMinMs = parseIntFlag(argv['open-detail-poll-min'], openDetailPollDelayMinDefault, 80);
|
|
366
|
+
const openDetailPollDelayMaxMs = parseIntFlag(argv['open-detail-poll-max'], openDetailPollDelayMaxDefault, openDetailPollDelayMinMs);
|
|
367
|
+
const openDetailPostOpenMinMs = parseIntFlag(argv['open-detail-postopen-min'], openDetailPostOpenMinDefault, 120);
|
|
368
|
+
const openDetailPostOpenMaxMs = parseIntFlag(argv['open-detail-postopen-max'], openDetailPostOpenMaxDefault, openDetailPostOpenMinMs);
|
|
369
|
+
const commentsScrollStepMin = parseIntFlag(argv['comments-scroll-step-min'], commentsScrollStepMinDefault, 120);
|
|
370
|
+
const commentsScrollStepMax = parseIntFlag(argv['comments-scroll-step-max'], commentsScrollStepMaxDefault, commentsScrollStepMin);
|
|
371
|
+
const commentsSettleMinMs = parseIntFlag(argv['comments-settle-min'], commentsSettleMinDefault, 80);
|
|
372
|
+
const commentsSettleMaxMs = parseIntFlag(argv['comments-settle-max'], commentsSettleMaxDefault, commentsSettleMinMs);
|
|
373
|
+
const defaultOperationMinIntervalMs = parseIntFlag(argv['operation-min-interval'], defaultOperationMinIntervalDefault, 0);
|
|
374
|
+
const defaultEventCooldownMs = parseIntFlag(argv['event-cooldown'], defaultEventCooldownDefault, 0);
|
|
375
|
+
const defaultPacingJitterMs = parseIntFlag(argv['pacing-jitter'], defaultPacingJitterDefault, 0);
|
|
376
|
+
const navigationMinIntervalMs = parseIntFlag(argv['navigation-min-interval'], navigationMinIntervalDefault, 0);
|
|
315
377
|
const maxLikesPerRound = parseNonNegativeInt(argv['max-likes'], 0);
|
|
316
378
|
const matchMode = String(argv['match-mode'] || 'any').trim() || 'any';
|
|
317
379
|
const matchMinHits = parseIntFlag(argv['match-min-hits'], 1, 1);
|
|
@@ -348,7 +410,27 @@ function buildTemplateOptions(argv, profileId, overrides = {}) {
|
|
|
348
410
|
outputRoot,
|
|
349
411
|
throttle,
|
|
350
412
|
tabCount,
|
|
413
|
+
tabOpenDelayMs,
|
|
351
414
|
noteIntervalMs,
|
|
415
|
+
submitMethod,
|
|
416
|
+
submitActionDelayMinMs,
|
|
417
|
+
submitActionDelayMaxMs,
|
|
418
|
+
submitSettleMinMs,
|
|
419
|
+
submitSettleMaxMs,
|
|
420
|
+
openDetailPreClickMinMs,
|
|
421
|
+
openDetailPreClickMaxMs,
|
|
422
|
+
openDetailPollDelayMinMs,
|
|
423
|
+
openDetailPollDelayMaxMs,
|
|
424
|
+
openDetailPostOpenMinMs,
|
|
425
|
+
openDetailPostOpenMaxMs,
|
|
426
|
+
commentsScrollStepMin,
|
|
427
|
+
commentsScrollStepMax,
|
|
428
|
+
commentsSettleMinMs,
|
|
429
|
+
commentsSettleMaxMs,
|
|
430
|
+
defaultOperationMinIntervalMs,
|
|
431
|
+
defaultEventCooldownMs,
|
|
432
|
+
defaultPacingJitterMs,
|
|
433
|
+
navigationMinIntervalMs,
|
|
352
434
|
maxNotes,
|
|
353
435
|
maxComments,
|
|
354
436
|
maxLikesPerRound,
|
|
@@ -524,6 +606,7 @@ async function runProfile(spec, argv, baseOverrides = {}) {
|
|
|
524
606
|
'xhs.unified.stop_screenshot',
|
|
525
607
|
'xhs.unified.profile_failed',
|
|
526
608
|
'autoscript:operation_done',
|
|
609
|
+
'autoscript:operation_progress',
|
|
527
610
|
'autoscript:operation_error',
|
|
528
611
|
'autoscript:operation_terminal',
|
|
529
612
|
'autoscript:operation_recovery_failed',
|
|
@@ -542,7 +625,24 @@ async function runProfile(spec, argv, baseOverrides = {}) {
|
|
|
542
625
|
if (spec.seedCollectMaxRounds !== undefined && spec.seedCollectMaxRounds !== null) {
|
|
543
626
|
overrides.seedCollectMaxRounds = parseNonNegativeInt(spec.seedCollectMaxRounds, 0);
|
|
544
627
|
}
|
|
545
|
-
const options = buildTemplateOptions(argv, profileId, overrides);
|
|
628
|
+
const options = await buildTemplateOptions(argv, profileId, overrides);
|
|
629
|
+
console.log(JSON.stringify({
|
|
630
|
+
event: 'xhs.unified.flow_gate',
|
|
631
|
+
profileId,
|
|
632
|
+
throttle: options.throttle,
|
|
633
|
+
noteIntervalMs: options.noteIntervalMs,
|
|
634
|
+
tabCount: options.tabCount,
|
|
635
|
+
tabOpenDelayMs: options.tabOpenDelayMs,
|
|
636
|
+
submitMethod: options.submitMethod,
|
|
637
|
+
submitActionDelayMinMs: options.submitActionDelayMinMs,
|
|
638
|
+
submitActionDelayMaxMs: options.submitActionDelayMaxMs,
|
|
639
|
+
submitSettleMinMs: options.submitSettleMinMs,
|
|
640
|
+
submitSettleMaxMs: options.submitSettleMaxMs,
|
|
641
|
+
commentsScrollStepMin: options.commentsScrollStepMin,
|
|
642
|
+
commentsScrollStepMax: options.commentsScrollStepMax,
|
|
643
|
+
commentsSettleMinMs: options.commentsSettleMinMs,
|
|
644
|
+
commentsSettleMaxMs: options.commentsSettleMaxMs,
|
|
645
|
+
}));
|
|
546
646
|
const script = buildXhsUnifiedAutoscript(options);
|
|
547
647
|
const normalized = normalizeAutoscript(script, `xhs-unified:${profileId}`);
|
|
548
648
|
const validation = validateAutoscript(normalized);
|
|
@@ -601,6 +701,7 @@ async function runProfile(spec, argv, baseOverrides = {}) {
|
|
|
601
701
|
|| eventName === 'autoscript:stop'
|
|
602
702
|
|| eventName === 'autoscript:impact'
|
|
603
703
|
|| eventName === 'autoscript:operation_start'
|
|
704
|
+
|| eventName === 'autoscript:operation_progress'
|
|
604
705
|
|| eventName === 'autoscript:operation_done'
|
|
605
706
|
|| eventName === 'autoscript:operation_error'
|
|
606
707
|
|| eventName === 'autoscript:operation_recovery_failed'
|
|
@@ -1281,6 +1382,11 @@ async function main() {
|
|
|
1281
1382
|
' --seed-collect-rounds <n> 首账号预采样滚动轮数(默认6)',
|
|
1282
1383
|
' --search-serial-key <key> 搜索阶段串行锁key(默认自动生成)',
|
|
1283
1384
|
' --shared-harvest-path <path> 共享harvest去重列表路径(默认自动生成)',
|
|
1385
|
+
' --search-submit-method <m> 搜索提交方式 click|enter|form(默认 flow-gate)',
|
|
1386
|
+
' --tab-open-delay <ms> 新开 tab 间隔(默认 flow-gate 区间随机)',
|
|
1387
|
+
' --operation-min-interval <ms> 基础操作最小间隔(默认 flow-gate)',
|
|
1388
|
+
' --event-cooldown <ms> 基础事件冷却(默认 flow-gate)',
|
|
1389
|
+
' --pacing-jitter <ms> 基础抖动区间(默认 flow-gate)',
|
|
1284
1390
|
].join('\n'));
|
|
1285
1391
|
return;
|
|
1286
1392
|
}
|
package/bin/webauto.mjs
CHANGED
|
@@ -108,6 +108,7 @@ function resolveOnPath(candidates) {
|
|
|
108
108
|
function wrapWindowsRunner(cmdPath, prefix = []) {
|
|
109
109
|
if (process.platform !== 'win32') return { cmd: cmdPath, prefix };
|
|
110
110
|
const lower = String(cmdPath || '').toLowerCase();
|
|
111
|
+
const quotedCmdPath = /\s/.test(String(cmdPath || '')) ? `"${cmdPath}"` : cmdPath;
|
|
111
112
|
if (lower.endsWith('.ps1')) {
|
|
112
113
|
return {
|
|
113
114
|
cmd: 'powershell.exe',
|
|
@@ -117,7 +118,7 @@ function wrapWindowsRunner(cmdPath, prefix = []) {
|
|
|
117
118
|
if (lower.endsWith('.cmd') || lower.endsWith('.bat')) {
|
|
118
119
|
return {
|
|
119
120
|
cmd: 'cmd.exe',
|
|
120
|
-
prefix: ['/d', '/s', '/c',
|
|
121
|
+
prefix: ['/d', '/s', '/c', quotedCmdPath, ...prefix],
|
|
121
122
|
};
|
|
122
123
|
}
|
|
123
124
|
return { cmd: cmdPath, prefix };
|
|
@@ -125,9 +126,9 @@ function wrapWindowsRunner(cmdPath, prefix = []) {
|
|
|
125
126
|
|
|
126
127
|
function npmRunner() {
|
|
127
128
|
if (process.platform !== 'win32') return { cmd: 'npm', prefix: [] };
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return wrapWindowsRunner(
|
|
129
|
+
// Always prefer PATH-resolved npm.cmd to avoid space-path quoting issues
|
|
130
|
+
// like "C:\Program Files\..." when invoking via cmd /c.
|
|
131
|
+
return wrapWindowsRunner('npm.cmd');
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
function uiConsoleScriptPath() {
|
|
@@ -166,6 +167,7 @@ Core Commands:
|
|
|
166
167
|
webauto xhs install [--download-browser] [--download-geoip] [--ensure-backend]
|
|
167
168
|
webauto xhs unified [xhs options...]
|
|
168
169
|
webauto xhs status [--run-id <id>] [--json]
|
|
170
|
+
webauto xhs gate <get|list|set|reset|path> [--platform <name>] [--patch-json <json>] [--json]
|
|
169
171
|
webauto xhs orchestrate [xhs options...]
|
|
170
172
|
webauto version [--json]
|
|
171
173
|
webauto version bump [patch|minor|major]
|
|
@@ -265,12 +267,14 @@ Usage:
|
|
|
265
267
|
webauto xhs install [--download-browser] [--download-geoip] [--ensure-backend] [--install|--reinstall|--uninstall] [--browser|--geoip|--all]
|
|
266
268
|
webauto xhs unified --profile <id> --keyword <kw> [options...]
|
|
267
269
|
webauto xhs status [--run-id <id>] [--json]
|
|
270
|
+
webauto xhs gate <get|list|set|reset|path> [--platform <name>] [--patch-json <json>] [--json]
|
|
268
271
|
webauto xhs orchestrate --profile <id> --keyword <kw> [options...]
|
|
269
272
|
|
|
270
273
|
Subcommands:
|
|
271
274
|
install 运行资源管理(兼容旧入口),支持检查/安装/卸载/重装 camoufox、geoip,按需拉起 backend
|
|
272
275
|
unified 运行统一脚本(搜索 + 打开详情 + 评论抓取 + 点赞)
|
|
273
276
|
status 查询当前任务状态与错误摘要(支持 runId 详情)
|
|
277
|
+
gate 管理平台流控参数(默认配置可修改并自动生效)
|
|
274
278
|
orchestrate 运行编排入口(默认调用 unified 模式)
|
|
275
279
|
|
|
276
280
|
Unified Required:
|
|
@@ -285,8 +289,8 @@ Unified Common Options:
|
|
|
285
289
|
--concurrency <n> 并行度(默认=账号数)
|
|
286
290
|
--plan-only 仅生成分片计划,不执行
|
|
287
291
|
--tab-count <n> 轮询 tab 数,默认 4
|
|
288
|
-
--throttle <ms>
|
|
289
|
-
--note-interval <ms>
|
|
292
|
+
--throttle <ms> 操作节流(默认走 flow-gate 平台配置并随机化)
|
|
293
|
+
--note-interval <ms> 帖子间等待(默认走 flow-gate 平台配置并随机化)
|
|
290
294
|
--env <name> 输出环境目录,默认 debug
|
|
291
295
|
--output-root <path> 自定义输出根目录
|
|
292
296
|
--dry-run 干跑(禁用点赞/回复)
|
|
@@ -331,6 +335,10 @@ Standard Workflows:
|
|
|
331
335
|
webauto xhs status
|
|
332
336
|
webauto xhs status --run-id <runId> --json
|
|
333
337
|
|
|
338
|
+
7) 查看/修改流控 gate(按平台隔离)
|
|
339
|
+
webauto xhs gate get --platform xiaohongshu --json
|
|
340
|
+
webauto xhs gate set --platform xiaohongshu --patch-json '{"noteInterval":{"minMs":2600,"maxMs":5200}}' --json
|
|
341
|
+
|
|
334
342
|
Output:
|
|
335
343
|
默认目录: ~/.webauto/download/xiaohongshu/<env>/<keyword>/
|
|
336
344
|
典型产物:
|
|
@@ -477,17 +485,12 @@ async function runInDir(dir, cmd, args) {
|
|
|
477
485
|
}
|
|
478
486
|
|
|
479
487
|
function checkDesktopConsoleDeps() {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
return (
|
|
487
|
-
exists(path.join(globalRoot, 'electron')) ||
|
|
488
|
-
exists(path.join(ROOT, 'node_modules', 'electron')) ||
|
|
489
|
-
exists(path.join(ROOT, 'apps', 'desktop-console', 'node_modules', 'electron'))
|
|
490
|
-
);
|
|
488
|
+
const electronName = process.platform === 'win32' ? 'electron.exe' : 'electron';
|
|
489
|
+
const candidates = [
|
|
490
|
+
path.join(ROOT, 'node_modules', 'electron', 'dist', electronName),
|
|
491
|
+
path.join(ROOT, 'apps', 'desktop-console', 'node_modules', 'electron', 'dist', electronName),
|
|
492
|
+
];
|
|
493
|
+
return candidates.some((p) => exists(p));
|
|
491
494
|
}
|
|
492
495
|
|
|
493
496
|
function checkDesktopConsoleBuilt() {
|
|
@@ -510,6 +513,15 @@ async function ensureDepsAndBuild() {
|
|
|
510
513
|
|
|
511
514
|
// Global package should already ship renderer build.
|
|
512
515
|
if (isGlobalInstall()) {
|
|
516
|
+
if (!checkDesktopConsoleDeps()) {
|
|
517
|
+
console.log('[webauto] Installing desktop-console runtime dependencies...');
|
|
518
|
+
const npm = npmRunner();
|
|
519
|
+
await run(npm.cmd, [...npm.prefix, '--prefix', appDir, '--workspaces=false', 'install', '--omit=dev']);
|
|
520
|
+
}
|
|
521
|
+
if (!checkDesktopConsoleDeps()) {
|
|
522
|
+
console.error('❌ electron runtime installation failed for desktop-console.');
|
|
523
|
+
process.exit(1);
|
|
524
|
+
}
|
|
513
525
|
if (!checkDesktopConsoleBuilt()) {
|
|
514
526
|
console.error('❌ desktop-console dist missing from package. Please reinstall @web-auto/webauto.');
|
|
515
527
|
process.exit(1);
|
|
@@ -540,62 +552,80 @@ async function ensureDepsAndBuild() {
|
|
|
540
552
|
console.log('[webauto] Setup complete!');
|
|
541
553
|
}
|
|
542
554
|
|
|
543
|
-
async function
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
const okUiBuilt = checkDesktopConsoleBuilt();
|
|
548
|
-
|
|
549
|
-
if (checkOnly) {
|
|
550
|
-
console.log(`[check] repoRoot: ${ROOT}`);
|
|
551
|
-
console.log(`[check] dist/services: ${okServices ? 'OK' : 'MISSING'}`);
|
|
552
|
-
console.log(`[check] desktop-console deps: ${okDeps ? 'OK' : 'MISSING'}`);
|
|
553
|
-
console.log(`[check] desktop-console dist: ${okUiBuilt ? 'OK' : 'MISSING'}`);
|
|
554
|
-
console.log(`[check] isGlobalInstall: ${isGlobalInstall()}`);
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
555
|
+
async function ensureUiRuntimeReady({ build, install }) {
|
|
556
|
+
let okServices = checkServicesBuilt();
|
|
557
|
+
let okDeps = checkDesktopConsoleDeps();
|
|
558
|
+
let okUiBuilt = checkDesktopConsoleBuilt();
|
|
557
559
|
|
|
558
|
-
// For global install, auto-setup on first run
|
|
559
560
|
if (isGlobalInstall()) {
|
|
560
561
|
const state = loadState();
|
|
561
562
|
const pkgJson = JSON.parse(readFileSync(path.join(ROOT, 'package.json'), 'utf-8'));
|
|
562
|
-
if (!state.initialized || state.version !== pkgJson.version) {
|
|
563
|
+
if (!state.initialized || state.version !== pkgJson.version || !okDeps || !okUiBuilt) {
|
|
563
564
|
await ensureDepsAndBuild();
|
|
565
|
+
okDeps = checkDesktopConsoleDeps();
|
|
566
|
+
okUiBuilt = checkDesktopConsoleBuilt();
|
|
564
567
|
}
|
|
565
|
-
} else {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
console.error('❌ missing dist/ (services/modules). Run: npm run build:services');
|
|
570
|
-
process.exit(2);
|
|
571
|
-
}
|
|
572
|
-
const npm = npmRunner();
|
|
573
|
-
await run(npm.cmd, [...npm.prefix, 'run', 'build:services']);
|
|
568
|
+
} else if (!okServices) {
|
|
569
|
+
if (!build) {
|
|
570
|
+
console.error('❌ missing dist/ (services/modules). Run: npm run build:services');
|
|
571
|
+
process.exit(2);
|
|
574
572
|
}
|
|
573
|
+
const npm = npmRunner();
|
|
574
|
+
await run(npm.cmd, [...npm.prefix, 'run', 'build:services']);
|
|
575
|
+
okServices = checkServicesBuilt();
|
|
575
576
|
}
|
|
576
577
|
|
|
577
578
|
if (!okDeps) {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
if (!isGlobalInstall() && !install && !build) {
|
|
581
|
-
console.error('❌ missing apps/desktop-console/node_modules. Run: npm --prefix apps/desktop-console install');
|
|
579
|
+
if (isGlobalInstall()) {
|
|
580
|
+
console.error('❌ electron runtime missing from installed package after setup.');
|
|
582
581
|
process.exit(2);
|
|
583
582
|
}
|
|
584
|
-
if (!
|
|
585
|
-
|
|
586
|
-
|
|
583
|
+
if (!install && !build) {
|
|
584
|
+
console.error('❌ missing apps/desktop-console/node_modules. Run: npm --prefix apps/desktop-console install');
|
|
585
|
+
process.exit(2);
|
|
587
586
|
}
|
|
587
|
+
const npm = npmRunner();
|
|
588
|
+
await runInDir(path.join(ROOT, 'apps', 'desktop-console'), npm.cmd, [...npm.prefix, 'install']);
|
|
589
|
+
okDeps = checkDesktopConsoleDeps();
|
|
588
590
|
}
|
|
589
591
|
|
|
590
592
|
if (!okUiBuilt) {
|
|
593
|
+
if (isGlobalInstall()) {
|
|
594
|
+
console.error('❌ missing apps/desktop-console/dist in installed package after setup.');
|
|
595
|
+
process.exit(2);
|
|
596
|
+
}
|
|
591
597
|
if (!build) {
|
|
592
598
|
console.error('❌ missing apps/desktop-console/dist. Run: npm --prefix apps/desktop-console run build');
|
|
593
599
|
process.exit(2);
|
|
594
600
|
}
|
|
595
601
|
const npm = npmRunner();
|
|
596
602
|
await runInDir(path.join(ROOT, 'apps', 'desktop-console'), npm.cmd, [...npm.prefix, 'run', 'build']);
|
|
603
|
+
okUiBuilt = checkDesktopConsoleBuilt();
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
if (!okDeps || !okUiBuilt || (!isGlobalInstall() && !okServices)) {
|
|
607
|
+
console.error('❌ ui runtime prerequisites are not ready');
|
|
608
|
+
process.exit(2);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
async function uiConsole({ build, install, checkOnly, noDaemon }) {
|
|
613
|
+
console.log(`[webauto] version ${ROOT_VERSION}`);
|
|
614
|
+
const okServices = checkServicesBuilt();
|
|
615
|
+
const okDeps = checkDesktopConsoleDeps();
|
|
616
|
+
const okUiBuilt = checkDesktopConsoleBuilt();
|
|
617
|
+
|
|
618
|
+
if (checkOnly) {
|
|
619
|
+
console.log(`[check] repoRoot: ${ROOT}`);
|
|
620
|
+
console.log(`[check] dist/services: ${okServices ? 'OK' : 'MISSING'}`);
|
|
621
|
+
console.log(`[check] desktop-console deps: ${okDeps ? 'OK' : 'MISSING'}`);
|
|
622
|
+
console.log(`[check] desktop-console dist: ${okUiBuilt ? 'OK' : 'MISSING'}`);
|
|
623
|
+
console.log(`[check] isGlobalInstall: ${isGlobalInstall()}`);
|
|
624
|
+
return;
|
|
597
625
|
}
|
|
598
626
|
|
|
627
|
+
await ensureUiRuntimeReady({ build, install });
|
|
628
|
+
|
|
599
629
|
const uiScript = uiConsoleScriptPath();
|
|
600
630
|
const uiArgs = [];
|
|
601
631
|
if (noDaemon) uiArgs.push('--no-daemon');
|
|
@@ -746,6 +776,10 @@ async function main() {
|
|
|
746
776
|
}
|
|
747
777
|
|
|
748
778
|
if (cmd === 'ui' && sub === 'cli') {
|
|
779
|
+
await ensureUiRuntimeReady({
|
|
780
|
+
build: args.build === true,
|
|
781
|
+
install: args.install === true,
|
|
782
|
+
});
|
|
749
783
|
const script = path.join(ROOT, 'apps', 'desktop-console', 'entry', 'ui-cli.mjs');
|
|
750
784
|
await run(process.execPath, [script, ...rawArgv.slice(2)]);
|
|
751
785
|
return;
|
|
@@ -842,6 +876,12 @@ async function main() {
|
|
|
842
876
|
return;
|
|
843
877
|
}
|
|
844
878
|
|
|
879
|
+
if (sub === 'gate') {
|
|
880
|
+
const script = path.join(ROOT, 'apps', 'webauto', 'entry', 'flow-gate.mjs');
|
|
881
|
+
await run(process.execPath, [script, ...rawArgv.slice(2)]);
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
|
|
845
885
|
if (sub === 'orchestrate') {
|
|
846
886
|
const script = path.join(ROOT, 'apps', 'webauto', 'entry', 'xhs-orchestrate.mjs');
|
|
847
887
|
await run(process.execPath, [script, ...rawArgv.slice(2)]);
|
|
@@ -1,9 +1,58 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { execSync } from 'node:child_process';
|
|
2
|
+
import { execSync, spawnSync } from 'node:child_process';
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
6
8
|
import { BROWSER_SERVICE_URL, loadConfig, setRepoRoot } from './config.mjs';
|
|
9
|
+
const requireFromHere = createRequire(import.meta.url);
|
|
10
|
+
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
function resolveNodeBin() {
|
|
12
|
+
const explicit = String(process.env.WEBAUTO_NODE_BIN || '').trim();
|
|
13
|
+
if (explicit)
|
|
14
|
+
return explicit;
|
|
15
|
+
const npmNode = String(process.env.npm_node_execpath || '').trim();
|
|
16
|
+
if (npmNode)
|
|
17
|
+
return npmNode;
|
|
18
|
+
return process.execPath;
|
|
19
|
+
}
|
|
20
|
+
function resolveCamoCliEntry() {
|
|
21
|
+
try {
|
|
22
|
+
const resolved = requireFromHere.resolve('@web-auto/camo/bin/camo.mjs');
|
|
23
|
+
if (resolved && fs.existsSync(resolved))
|
|
24
|
+
return resolved;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
function runCamoCli(args = [], options = {}) {
|
|
32
|
+
const entry = resolveCamoCliEntry();
|
|
33
|
+
if (!entry) {
|
|
34
|
+
return {
|
|
35
|
+
ok: false,
|
|
36
|
+
code: null,
|
|
37
|
+
stdout: '',
|
|
38
|
+
stderr: '@web-auto/camo/bin/camo.mjs not found',
|
|
39
|
+
entry: null,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const ret = spawnSync(resolveNodeBin(), [entry, ...args], {
|
|
43
|
+
encoding: 'utf8',
|
|
44
|
+
windowsHide: true,
|
|
45
|
+
stdio: options.stdio || 'pipe',
|
|
46
|
+
env: { ...process.env, ...(options.env || {}) },
|
|
47
|
+
});
|
|
48
|
+
return {
|
|
49
|
+
ok: ret.status === 0,
|
|
50
|
+
code: ret.status,
|
|
51
|
+
stdout: String(ret.stdout || ''),
|
|
52
|
+
stderr: String(ret.stderr || ''),
|
|
53
|
+
entry,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
7
56
|
export async function callAPI(action, payload = {}) {
|
|
8
57
|
const r = await fetch(`${BROWSER_SERVICE_URL}/command`, {
|
|
9
58
|
method: 'POST',
|
|
@@ -319,20 +368,13 @@ function scanCommonRepoRoots() {
|
|
|
319
368
|
}
|
|
320
369
|
export function findRepoRootCandidate() {
|
|
321
370
|
const cfg = loadConfig();
|
|
371
|
+
const cwdRoot = walkUpForRepoRoot(process.cwd());
|
|
372
|
+
const moduleRoot = walkUpForRepoRoot(MODULE_DIR);
|
|
322
373
|
const candidates = [
|
|
323
374
|
process.env.WEBAUTO_REPO_ROOT,
|
|
324
|
-
process.cwd(),
|
|
325
375
|
cfg.repoRoot,
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
path.join(os.homedir(), 'Documents', 'github', 'webauto'),
|
|
329
|
-
path.join(os.homedir(), 'Documents', 'github', 'WebAuto'),
|
|
330
|
-
path.join(os.homedir(), 'github', 'webauto'),
|
|
331
|
-
path.join(os.homedir(), 'github', 'WebAuto'),
|
|
332
|
-
path.join('C:', 'code', 'webauto'),
|
|
333
|
-
path.join('C:', 'code', 'WebAuto'),
|
|
334
|
-
path.join('C:', 'Users', os.userInfo().username, 'code', 'webauto'),
|
|
335
|
-
path.join('C:', 'Users', os.userInfo().username, 'code', 'WebAuto'),
|
|
376
|
+
moduleRoot,
|
|
377
|
+
cwdRoot,
|
|
336
378
|
].filter(Boolean);
|
|
337
379
|
for (const root of candidates) {
|
|
338
380
|
if (!hasContainerLibrary(root))
|
|
@@ -343,20 +385,6 @@ export function findRepoRootCandidate() {
|
|
|
343
385
|
}
|
|
344
386
|
return resolved;
|
|
345
387
|
}
|
|
346
|
-
const walked = walkUpForRepoRoot(process.cwd());
|
|
347
|
-
if (walked) {
|
|
348
|
-
if (cfg.repoRoot !== walked) {
|
|
349
|
-
setRepoRoot(walked);
|
|
350
|
-
}
|
|
351
|
-
return walked;
|
|
352
|
-
}
|
|
353
|
-
const scanned = scanCommonRepoRoots();
|
|
354
|
-
if (scanned) {
|
|
355
|
-
if (cfg.repoRoot !== scanned) {
|
|
356
|
-
setRepoRoot(scanned);
|
|
357
|
-
}
|
|
358
|
-
return scanned;
|
|
359
|
-
}
|
|
360
388
|
return null;
|
|
361
389
|
}
|
|
362
390
|
export function detectCamoufoxPath() {
|
|
@@ -381,12 +409,7 @@ export function detectCamoufoxPath() {
|
|
|
381
409
|
export function ensureCamoufox() {
|
|
382
410
|
if (detectCamoufoxPath())
|
|
383
411
|
return;
|
|
384
|
-
|
|
385
|
-
execSync('npx --yes --package=camoufox camoufox fetch', { stdio: 'inherit' });
|
|
386
|
-
if (!detectCamoufoxPath()) {
|
|
387
|
-
throw new Error('Camoufox install finished but executable was not detected');
|
|
388
|
-
}
|
|
389
|
-
console.log('Camoufox installed.');
|
|
412
|
+
throw new Error('Camoufox is not installed. Run: webauto xhs install --download-browser');
|
|
390
413
|
}
|
|
391
414
|
export async function ensureBrowserService() {
|
|
392
415
|
if (await checkBrowserService())
|
|
@@ -398,20 +421,17 @@ export async function ensureBrowserService() {
|
|
|
398
421
|
}
|
|
399
422
|
if (provider === 'camo') {
|
|
400
423
|
const repoRoot = findRepoRootCandidate();
|
|
401
|
-
if (repoRoot) {
|
|
402
|
-
|
|
403
|
-
execSync(`npx --yes @web-auto/camo config repo-root ${JSON.stringify(repoRoot)}`, { stdio: 'ignore' });
|
|
404
|
-
}
|
|
405
|
-
catch {
|
|
406
|
-
// best-effort only; init will still try using current config
|
|
407
|
-
}
|
|
424
|
+
if (!repoRoot) {
|
|
425
|
+
throw new Error('WEBAUTO_REPO_ROOT is not set and no valid repo root was found');
|
|
408
426
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
427
|
+
const configRet = runCamoCli(['config', 'repo-root', repoRoot], { stdio: 'pipe' });
|
|
428
|
+
if (!configRet.ok) {
|
|
429
|
+
throw new Error(`camo config repo-root failed: ${configRet.stderr.trim() || configRet.stdout.trim() || `exit ${configRet.code ?? 'null'}`}`);
|
|
412
430
|
}
|
|
413
|
-
|
|
414
|
-
|
|
431
|
+
console.log('Starting browser backend via camo init...');
|
|
432
|
+
const initRet = runCamoCli(['init'], { stdio: 'inherit' });
|
|
433
|
+
if (!initRet.ok) {
|
|
434
|
+
throw new Error(`camo init failed: ${initRet.stderr.trim() || initRet.stdout.trim() || `exit ${initRet.code ?? 'null'}`}`);
|
|
415
435
|
}
|
|
416
436
|
for (let i = 0; i < 20; i += 1) {
|
|
417
437
|
await new Promise((r) => setTimeout(r, 400));
|
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
import { executeXhsAutoscriptOperation, isXhsAutoscriptAction } from './xhs.mjs';
|
|
2
2
|
|
|
3
|
-
export async function executeAutoscriptAction({
|
|
3
|
+
export async function executeAutoscriptAction({
|
|
4
|
+
profileId,
|
|
5
|
+
action,
|
|
6
|
+
params = {},
|
|
7
|
+
operation = null,
|
|
8
|
+
context = {},
|
|
9
|
+
}) {
|
|
4
10
|
if (isXhsAutoscriptAction(action)) {
|
|
5
|
-
return executeXhsAutoscriptOperation({
|
|
11
|
+
return executeXhsAutoscriptOperation({
|
|
12
|
+
profileId,
|
|
13
|
+
action,
|
|
14
|
+
params,
|
|
15
|
+
operation,
|
|
16
|
+
context,
|
|
17
|
+
});
|
|
6
18
|
}
|
|
7
19
|
return null;
|
|
8
20
|
}
|
|
9
|
-
|