@web-auto/camo 0.1.14 → 0.1.16
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 +19 -37
- package/package.json +1 -1
- package/src/autoscript/action-providers/index.mjs +3 -6
- package/src/autoscript/runtime.mjs +14 -12
- package/src/autoscript/schema.mjs +6 -0
- package/src/cli.mjs +5 -1
- package/src/commands/autoscript.mjs +14 -103
- package/src/commands/browser.mjs +247 -19
- package/src/commands/mouse.mjs +9 -3
- package/src/container/runtime-core/checkpoint.mjs +21 -7
- package/src/container/runtime-core/operations/index.mjs +392 -38
- package/src/container/runtime-core/subscription.mjs +79 -7
- package/src/container/runtime-core/validation.mjs +2 -2
- package/src/utils/browser-service.mjs +41 -6
- package/src/utils/help.mjs +0 -1
- package/src/utils/js-policy.mjs +13 -0
- package/src/autoscript/action-providers/xhs/comments.mjs +0 -412
- package/src/autoscript/action-providers/xhs/common.mjs +0 -77
- package/src/autoscript/action-providers/xhs/detail.mjs +0 -181
- package/src/autoscript/action-providers/xhs/interaction.mjs +0 -466
- package/src/autoscript/action-providers/xhs/like-rules.mjs +0 -57
- package/src/autoscript/action-providers/xhs/persistence.mjs +0 -167
- package/src/autoscript/action-providers/xhs/search.mjs +0 -174
- package/src/autoscript/action-providers/xhs.mjs +0 -133
- package/src/autoscript/xhs-unified-template.mjs +0 -934
package/README.md
CHANGED
|
@@ -302,7 +302,6 @@ camo container list [profileId]
|
|
|
302
302
|
### Autoscript
|
|
303
303
|
|
|
304
304
|
```bash
|
|
305
|
-
camo autoscript scaffold xhs-unified [--output <file>] [options]
|
|
306
305
|
camo autoscript validate <file>
|
|
307
306
|
camo autoscript explain <file>
|
|
308
307
|
camo autoscript snapshot <jsonl-file> [--out <snapshot-file>]
|
|
@@ -375,55 +374,38 @@ camo container watch xiaohongshu-batch-1 --throttle 500
|
|
|
375
374
|
### Autoscript Mode (Subscription + Operations)
|
|
376
375
|
|
|
377
376
|
```bash
|
|
378
|
-
# Generate xiaohongshu unified-harvest migration script from webauto phase-unified-harvest
|
|
379
|
-
camo autoscript scaffold xhs-unified \
|
|
380
|
-
--output ./autoscripts/xiaohongshu/unified-harvest.autoscript.json \
|
|
381
|
-
--profile xiaohongshu-batch-1 \
|
|
382
|
-
--keyword "手机膜" \
|
|
383
|
-
--tab-count 4 \
|
|
384
|
-
--note-interval 900 \
|
|
385
|
-
--do-comments \
|
|
386
|
-
--do-likes \
|
|
387
|
-
--max-notes 30
|
|
388
|
-
|
|
389
377
|
# Validate + explain + run
|
|
390
|
-
camo autoscript validate ./autoscripts/
|
|
391
|
-
camo autoscript explain ./autoscripts/
|
|
392
|
-
camo autoscript run ./autoscripts/
|
|
393
|
-
--profile
|
|
394
|
-
--jsonl-file ./runs/
|
|
395
|
-
--summary-file ./runs/
|
|
378
|
+
camo autoscript validate ./autoscripts/my-flow.autoscript.json
|
|
379
|
+
camo autoscript explain ./autoscripts/my-flow.autoscript.json
|
|
380
|
+
camo autoscript run ./autoscripts/my-flow.autoscript.json \
|
|
381
|
+
--profile my-profile \
|
|
382
|
+
--jsonl-file ./runs/my-flow/run.jsonl \
|
|
383
|
+
--summary-file ./runs/my-flow/run.summary.json
|
|
396
384
|
|
|
397
385
|
# Build snapshot + replay summary from existing JSONL
|
|
398
|
-
camo autoscript snapshot ./runs/
|
|
399
|
-
--out ./runs/
|
|
400
|
-
camo autoscript replay ./runs/
|
|
401
|
-
--summary-file ./runs/
|
|
386
|
+
camo autoscript snapshot ./runs/my-flow/run.jsonl \
|
|
387
|
+
--out ./runs/my-flow/run.snapshot.json
|
|
388
|
+
camo autoscript replay ./runs/my-flow/run.jsonl \
|
|
389
|
+
--summary-file ./runs/my-flow/replay.summary.json
|
|
402
390
|
|
|
403
391
|
# Resume from a snapshot (optionally force rerun from a node)
|
|
404
|
-
camo autoscript resume ./autoscripts/
|
|
405
|
-
--snapshot ./runs/
|
|
406
|
-
--from-node
|
|
407
|
-
--profile
|
|
392
|
+
camo autoscript resume ./autoscripts/my-flow.autoscript.json \
|
|
393
|
+
--snapshot ./runs/my-flow/run.snapshot.json \
|
|
394
|
+
--from-node some_operation \
|
|
395
|
+
--profile my-profile
|
|
408
396
|
|
|
409
397
|
# Mock replay mode for deterministic local debugging
|
|
410
|
-
camo autoscript mock-run ./autoscripts/
|
|
411
|
-
--fixture ./autoscripts/
|
|
412
|
-
--summary-file ./runs/
|
|
398
|
+
camo autoscript mock-run ./autoscripts/my-flow.autoscript.json \
|
|
399
|
+
--fixture ./autoscripts/fixtures/mock-run.json \
|
|
400
|
+
--summary-file ./runs/my-flow/mock.summary.json
|
|
413
401
|
```
|
|
414
402
|
|
|
415
|
-
The xhs-unified scaffold includes anti-risk defaults:
|
|
416
|
-
- operation pacing (`operationMinIntervalMs`, `eventCooldownMs`, `jitterMs`)
|
|
417
|
-
- navigation/tab switch cooldown (`navigationMinIntervalMs`)
|
|
418
|
-
- per-operation timeout budget (`timeoutMs`)
|
|
419
|
-
- multi-tab rotation (`ensure_tab_pool`, `tab_pool_switch_next`)
|
|
420
|
-
|
|
421
403
|
Example script:
|
|
422
404
|
|
|
423
405
|
```json
|
|
424
406
|
{
|
|
425
|
-
"name": "
|
|
426
|
-
"profileId": "
|
|
407
|
+
"name": "generic-login-flow",
|
|
408
|
+
"profileId": "my-profile",
|
|
427
409
|
"throttle": 500,
|
|
428
410
|
"subscriptions": [
|
|
429
411
|
{ "id": "login_input", "selector": "#login-input" },
|
package/package.json
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import { executeXhsAutoscriptOperation, isXhsAutoscriptAction } from './xhs.mjs';
|
|
2
|
-
|
|
3
1
|
export async function executeAutoscriptAction({ profileId, action, params = {} }) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
void profileId;
|
|
3
|
+
void action;
|
|
4
|
+
void params;
|
|
7
5
|
return null;
|
|
8
6
|
}
|
|
9
|
-
|
|
@@ -339,15 +339,6 @@ export class AutoscriptRunner {
|
|
|
339
339
|
'tab_pool_switch_slot',
|
|
340
340
|
'sync_window_viewport',
|
|
341
341
|
'verify_subscriptions',
|
|
342
|
-
'xhs_submit_search',
|
|
343
|
-
'xhs_open_detail',
|
|
344
|
-
'xhs_detail_harvest',
|
|
345
|
-
'xhs_expand_replies',
|
|
346
|
-
'xhs_comments_harvest',
|
|
347
|
-
'xhs_comment_match',
|
|
348
|
-
'xhs_comment_like',
|
|
349
|
-
'xhs_comment_reply',
|
|
350
|
-
'xhs_close_detail',
|
|
351
342
|
].includes(action)) {
|
|
352
343
|
return 45_000;
|
|
353
344
|
}
|
|
@@ -363,6 +354,14 @@ export class AutoscriptRunner {
|
|
|
363
354
|
return this.getDefaultTimeoutMs(operation);
|
|
364
355
|
}
|
|
365
356
|
|
|
357
|
+
resolvePlatform(operation) {
|
|
358
|
+
const candidate = operation?.params?.platform
|
|
359
|
+
?? operation?.platform
|
|
360
|
+
?? this.script?.defaults?.platform
|
|
361
|
+
?? 'generic';
|
|
362
|
+
return String(candidate || 'generic').trim().toLowerCase() || 'generic';
|
|
363
|
+
}
|
|
364
|
+
|
|
366
365
|
buildTriggerKey(operation, event) {
|
|
367
366
|
const trigger = operation?.trigger || { type: 'startup' };
|
|
368
367
|
if (trigger.type === 'startup') return 'startup';
|
|
@@ -477,13 +476,14 @@ export class AutoscriptRunner {
|
|
|
477
476
|
data: { phase },
|
|
478
477
|
};
|
|
479
478
|
}
|
|
479
|
+
const platform = this.resolvePlatform(operation);
|
|
480
480
|
const validation = operation.validation || {};
|
|
481
481
|
return validateOperation({
|
|
482
482
|
profileId: this.profileId,
|
|
483
483
|
validationSpec: validation,
|
|
484
484
|
phase,
|
|
485
485
|
context,
|
|
486
|
-
platform
|
|
486
|
+
platform,
|
|
487
487
|
});
|
|
488
488
|
}
|
|
489
489
|
|
|
@@ -567,11 +567,12 @@ export class AutoscriptRunner {
|
|
|
567
567
|
return { ok: false, code: 'RECOVERY_NOT_CONFIGURED', message: 'recovery not configured' };
|
|
568
568
|
}
|
|
569
569
|
|
|
570
|
+
const platform = this.resolvePlatform(operation);
|
|
570
571
|
const checkpointDoc = await captureCheckpoint({
|
|
571
572
|
profileId: this.profileId,
|
|
572
573
|
containerId: checkpoint.containerId || null,
|
|
573
574
|
selector: operation.params?.selector || null,
|
|
574
|
-
platform
|
|
575
|
+
platform,
|
|
575
576
|
});
|
|
576
577
|
const baseCheckpoint = checkpointDoc?.data || {};
|
|
577
578
|
|
|
@@ -585,7 +586,7 @@ export class AutoscriptRunner {
|
|
|
585
586
|
containerId: checkpoint.containerId || null,
|
|
586
587
|
selector: operation.params?.selector || null,
|
|
587
588
|
targetCheckpoint: checkpoint.targetCheckpoint || null,
|
|
588
|
-
platform
|
|
589
|
+
platform,
|
|
589
590
|
});
|
|
590
591
|
this.log('autoscript:recovery_action', {
|
|
591
592
|
operationId: operation.id,
|
|
@@ -969,6 +970,7 @@ export class AutoscriptRunner {
|
|
|
969
970
|
profileId: this.profileId,
|
|
970
971
|
subscriptions: this.script.subscriptions,
|
|
971
972
|
throttle: this.script.throttle,
|
|
973
|
+
filterMode: this.script?.defaults?.filterMode || 'strict',
|
|
972
974
|
onEvent: async (event) => {
|
|
973
975
|
this.log('autoscript:event', {
|
|
974
976
|
type: event.type,
|
|
@@ -127,9 +127,14 @@ function normalizeSubscription(item, index, defaults) {
|
|
|
127
127
|
const selector = toTrimmedString(item.selector);
|
|
128
128
|
if (!selector) return null;
|
|
129
129
|
const events = toArray(item.events).map((name) => toTrimmedString(name)).filter(Boolean);
|
|
130
|
+
const pageUrlIncludes = toArray(item.pageUrlIncludes).map((token) => toTrimmedString(token)).filter(Boolean);
|
|
131
|
+
const pageUrlExcludes = toArray(item.pageUrlExcludes).map((token) => toTrimmedString(token)).filter(Boolean);
|
|
130
132
|
return {
|
|
131
133
|
id,
|
|
132
134
|
selector,
|
|
135
|
+
visible: item.visible !== false,
|
|
136
|
+
pageUrlIncludes,
|
|
137
|
+
pageUrlExcludes,
|
|
133
138
|
events: events.length > 0 ? events : ['appear', 'exist', 'disappear', 'change'],
|
|
134
139
|
dependsOn: toArray(item.dependsOn).map((x) => toTrimmedString(x)).filter(Boolean),
|
|
135
140
|
retry: normalizeRetry(item.retry, defaults.retry),
|
|
@@ -243,6 +248,7 @@ export function normalizeAutoscript(raw, sourcePath = null) {
|
|
|
243
248
|
retry: normalizeRetry(defaults.retry, {}),
|
|
244
249
|
impact: toTrimmedString(defaults.impact || 'op') || 'op',
|
|
245
250
|
onFailure: toTrimmedString(defaults.onFailure || 'chain_stop') || 'chain_stop',
|
|
251
|
+
filterMode: toTrimmedString(defaults.filterMode || 'strict') || 'strict',
|
|
246
252
|
validationMode: toTrimmedString(defaults.validationMode || 'none') || 'none',
|
|
247
253
|
recovery: normalizeRecovery(defaults.recovery, {}),
|
|
248
254
|
pacing: normalizePacing(defaults.pacing, {}),
|
package/src/cli.mjs
CHANGED
|
@@ -160,7 +160,11 @@ async function handleConfigCommand(args) {
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
async function main() {
|
|
163
|
-
const
|
|
163
|
+
const rawArgs = process.argv.slice(2);
|
|
164
|
+
const jsEnabled = rawArgs.includes('--js');
|
|
165
|
+
if (jsEnabled) process.env.CAMO_ALLOW_JS = '1';
|
|
166
|
+
else delete process.env.CAMO_ALLOW_JS;
|
|
167
|
+
const args = rawArgs.filter((arg) => arg !== '--js');
|
|
164
168
|
const cmd = args[0];
|
|
165
169
|
|
|
166
170
|
if (!cmd) {
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { getDefaultProfile } from '../utils/config.mjs';
|
|
3
|
+
import { getDefaultProfile, listProfiles } from '../utils/config.mjs';
|
|
4
4
|
import { explainAutoscript, loadAndValidateAutoscript } from '../autoscript/schema.mjs';
|
|
5
5
|
import { AutoscriptRunner } from '../autoscript/runtime.mjs';
|
|
6
|
-
import { buildXhsUnifiedAutoscript } from '../autoscript/xhs-unified-template.mjs';
|
|
7
6
|
import { safeAppendProgressEvent } from '../events/progress-log.mjs';
|
|
8
7
|
|
|
9
8
|
function readFlagValue(args, names) {
|
|
@@ -31,20 +30,16 @@ function collectPositionals(args, startIndex = 2, valueFlags = new Set(['--profi
|
|
|
31
30
|
return out;
|
|
32
31
|
}
|
|
33
32
|
|
|
34
|
-
function
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (raw === null || raw === undefined || raw === '') return fallback;
|
|
45
|
-
const num = Number(raw);
|
|
46
|
-
if (!Number.isFinite(num)) return fallback;
|
|
47
|
-
return Math.max(min, Math.floor(num));
|
|
33
|
+
function assertExistingProfile(profileId) {
|
|
34
|
+
const id = String(profileId || '').trim();
|
|
35
|
+
if (!id) {
|
|
36
|
+
throw new Error('profileId is required');
|
|
37
|
+
}
|
|
38
|
+
const known = new Set(listProfiles());
|
|
39
|
+
if (!known.has(id)) {
|
|
40
|
+
throw new Error(`profile not found: ${id}. create it first with "camo profile create ${id}"`);
|
|
41
|
+
}
|
|
42
|
+
return id;
|
|
48
43
|
}
|
|
49
44
|
|
|
50
45
|
function appendJsonLine(filePath, payload) {
|
|
@@ -593,89 +588,6 @@ function createMockOperationExecutor(fixture) {
|
|
|
593
588
|
};
|
|
594
589
|
}
|
|
595
590
|
|
|
596
|
-
async function handleScaffold(args) {
|
|
597
|
-
const valueFlags = new Set([
|
|
598
|
-
'--profile', '-p',
|
|
599
|
-
'--keyword', '-k',
|
|
600
|
-
'--env',
|
|
601
|
-
'--output', '-o',
|
|
602
|
-
'--output-root',
|
|
603
|
-
'--throttle',
|
|
604
|
-
'--tab-count',
|
|
605
|
-
'--note-interval',
|
|
606
|
-
'--max-notes',
|
|
607
|
-
'--max-likes',
|
|
608
|
-
'--match-mode',
|
|
609
|
-
'--match-min-hits',
|
|
610
|
-
'--match-keywords',
|
|
611
|
-
'--like-keywords',
|
|
612
|
-
'--reply-text',
|
|
613
|
-
'--jsonl-file',
|
|
614
|
-
'--jsonl',
|
|
615
|
-
]);
|
|
616
|
-
const positionals = collectPositionals(args, 2, valueFlags);
|
|
617
|
-
const template = positionals[0];
|
|
618
|
-
if (template !== 'xhs-unified') {
|
|
619
|
-
throw new Error('Usage: camo autoscript scaffold xhs-unified [--output <file>] [options]');
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
const outputPath = readFlagValue(args, ['--output', '-o'])
|
|
623
|
-
|| path.resolve('autoscripts/xiaohongshu/unified-harvest.autoscript.json');
|
|
624
|
-
const profileId = readFlagValue(args, ['--profile', '-p']) || getDefaultProfile() || 'xiaohongshu-batch-1';
|
|
625
|
-
const keyword = readFlagValue(args, ['--keyword', '-k']) || '手机膜';
|
|
626
|
-
const env = readFlagValue(args, ['--env']) || 'debug';
|
|
627
|
-
const outputRoot = readFlagValue(args, ['--output-root']) || '';
|
|
628
|
-
const throttle = readIntegerFlag(args, ['--throttle'], 500, 100);
|
|
629
|
-
const tabCount = readIntegerFlag(args, ['--tab-count'], 4, 1);
|
|
630
|
-
const noteIntervalMs = readIntegerFlag(args, ['--note-interval'], 900, 200);
|
|
631
|
-
const maxNotes = readIntegerFlag(args, ['--max-notes'], 30, 1);
|
|
632
|
-
const maxLikesPerRound = readIntegerFlag(args, ['--max-likes'], 2, 1);
|
|
633
|
-
const matchMinHits = readIntegerFlag(args, ['--match-min-hits'], 1, 1);
|
|
634
|
-
const matchMode = readFlagValue(args, ['--match-mode']) || 'any';
|
|
635
|
-
const matchKeywords = readFlagValue(args, ['--match-keywords']) || keyword;
|
|
636
|
-
const likeKeywords = readFlagValue(args, ['--like-keywords']) || '';
|
|
637
|
-
const replyText = readFlagValue(args, ['--reply-text']) || '感谢分享,已关注';
|
|
638
|
-
|
|
639
|
-
const script = buildXhsUnifiedAutoscript({
|
|
640
|
-
profileId,
|
|
641
|
-
keyword,
|
|
642
|
-
env,
|
|
643
|
-
outputRoot,
|
|
644
|
-
throttle,
|
|
645
|
-
tabCount,
|
|
646
|
-
noteIntervalMs,
|
|
647
|
-
maxNotes,
|
|
648
|
-
maxLikesPerRound,
|
|
649
|
-
matchMode,
|
|
650
|
-
matchMinHits,
|
|
651
|
-
matchKeywords,
|
|
652
|
-
likeKeywords,
|
|
653
|
-
replyText,
|
|
654
|
-
doHomepage: readToggleFlag(args, 'do-homepage', true),
|
|
655
|
-
doImages: readToggleFlag(args, 'do-images', false),
|
|
656
|
-
doComments: readToggleFlag(args, 'do-comments', true),
|
|
657
|
-
doLikes: readToggleFlag(args, 'do-likes', false),
|
|
658
|
-
doReply: readToggleFlag(args, 'do-reply', false),
|
|
659
|
-
doOcr: readToggleFlag(args, 'do-ocr', false),
|
|
660
|
-
persistComments: readToggleFlag(args, 'persist-comments', true),
|
|
661
|
-
});
|
|
662
|
-
|
|
663
|
-
const resolvedPath = path.resolve(outputPath);
|
|
664
|
-
fs.mkdirSync(path.dirname(resolvedPath), { recursive: true });
|
|
665
|
-
fs.writeFileSync(resolvedPath, `${JSON.stringify(script, null, 2)}\n`, 'utf8');
|
|
666
|
-
|
|
667
|
-
console.log(JSON.stringify({
|
|
668
|
-
ok: true,
|
|
669
|
-
command: 'autoscript.scaffold',
|
|
670
|
-
template,
|
|
671
|
-
file: resolvedPath,
|
|
672
|
-
profileId: script.profileId,
|
|
673
|
-
operationCount: script.operations.length,
|
|
674
|
-
subscriptionCount: script.subscriptions.length,
|
|
675
|
-
hint: `Run: camo autoscript validate ${resolvedPath}`,
|
|
676
|
-
}, null, 2));
|
|
677
|
-
}
|
|
678
|
-
|
|
679
591
|
async function handleValidate(args) {
|
|
680
592
|
const filePath = collectPositionals(args)[0];
|
|
681
593
|
if (!filePath) {
|
|
@@ -883,6 +795,7 @@ async function handleRun(args) {
|
|
|
883
795
|
if (!profileId) {
|
|
884
796
|
throw new Error('profileId is required. Set in script or pass --profile <id>');
|
|
885
797
|
}
|
|
798
|
+
assertExistingProfile(profileId);
|
|
886
799
|
|
|
887
800
|
await executeAutoscriptRuntime({
|
|
888
801
|
commandName: 'autoscript.run',
|
|
@@ -996,6 +909,7 @@ async function handleResume(args) {
|
|
|
996
909
|
if (!profileId) {
|
|
997
910
|
throw new Error('profileId is required. Set in script or pass --profile <id>');
|
|
998
911
|
}
|
|
912
|
+
assertExistingProfile(profileId);
|
|
999
913
|
const resumeState = buildResumeStateFromSnapshot(script, snapshot, fromNode || null);
|
|
1000
914
|
await executeAutoscriptRuntime({
|
|
1001
915
|
commandName: 'autoscript.resume',
|
|
@@ -1067,8 +981,6 @@ async function handleMockRun(args) {
|
|
|
1067
981
|
export async function handleAutoscriptCommand(args) {
|
|
1068
982
|
const sub = args[1];
|
|
1069
983
|
switch (sub) {
|
|
1070
|
-
case 'scaffold':
|
|
1071
|
-
return handleScaffold(args);
|
|
1072
984
|
case 'validate':
|
|
1073
985
|
return handleValidate(args);
|
|
1074
986
|
case 'explain':
|
|
@@ -1084,10 +996,9 @@ export async function handleAutoscriptCommand(args) {
|
|
|
1084
996
|
case 'mock-run':
|
|
1085
997
|
return handleMockRun(args);
|
|
1086
998
|
default:
|
|
1087
|
-
console.log(`Usage: camo autoscript <
|
|
999
|
+
console.log(`Usage: camo autoscript <validate|explain|snapshot|replay|run|resume|mock-run> [args]
|
|
1088
1000
|
|
|
1089
1001
|
Commands:
|
|
1090
|
-
scaffold xhs-unified [--output <file>] [options] Generate xiaohongshu unified-harvest autoscript
|
|
1091
1002
|
validate <file> Validate autoscript schema and references
|
|
1092
1003
|
explain <file> Print normalized graph and defaults
|
|
1093
1004
|
snapshot <jsonl-file> [--out <snapshot-file>] Build resumable snapshot from run JSONL
|