@web-auto/camo 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 +19 -37
- package/package.json +1 -1
- package/src/autoscript/action-providers/index.mjs +3 -6
- package/src/autoscript/runtime.mjs +13 -12
- package/src/commands/autoscript.mjs +1 -104
- package/src/container/runtime-core/checkpoint.mjs +21 -7
- package/src/container/runtime-core/validation.mjs +2 -2
- package/src/utils/help.mjs +0 -1
- 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 -931
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,
|
|
@@ -3,7 +3,6 @@ import path from 'node:path';
|
|
|
3
3
|
import { getDefaultProfile } 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,22 +30,6 @@ function collectPositionals(args, startIndex = 2, valueFlags = new Set(['--profi
|
|
|
31
30
|
return out;
|
|
32
31
|
}
|
|
33
32
|
|
|
34
|
-
function readToggleFlag(args, name, fallback) {
|
|
35
|
-
const on = `--${name}`;
|
|
36
|
-
const off = `--no-${name}`;
|
|
37
|
-
if (args.includes(off)) return false;
|
|
38
|
-
if (args.includes(on)) return true;
|
|
39
|
-
return fallback;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function readIntegerFlag(args, names, fallback, min = 1) {
|
|
43
|
-
const raw = readFlagValue(args, names);
|
|
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));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
33
|
function appendJsonLine(filePath, payload) {
|
|
51
34
|
if (!filePath) return;
|
|
52
35
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
@@ -593,89 +576,6 @@ function createMockOperationExecutor(fixture) {
|
|
|
593
576
|
};
|
|
594
577
|
}
|
|
595
578
|
|
|
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
579
|
async function handleValidate(args) {
|
|
680
580
|
const filePath = collectPositionals(args)[0];
|
|
681
581
|
if (!filePath) {
|
|
@@ -1067,8 +967,6 @@ async function handleMockRun(args) {
|
|
|
1067
967
|
export async function handleAutoscriptCommand(args) {
|
|
1068
968
|
const sub = args[1];
|
|
1069
969
|
switch (sub) {
|
|
1070
|
-
case 'scaffold':
|
|
1071
|
-
return handleScaffold(args);
|
|
1072
970
|
case 'validate':
|
|
1073
971
|
return handleValidate(args);
|
|
1074
972
|
case 'explain':
|
|
@@ -1084,10 +982,9 @@ export async function handleAutoscriptCommand(args) {
|
|
|
1084
982
|
case 'mock-run':
|
|
1085
983
|
return handleMockRun(args);
|
|
1086
984
|
default:
|
|
1087
|
-
console.log(`Usage: camo autoscript <
|
|
985
|
+
console.log(`Usage: camo autoscript <validate|explain|snapshot|replay|run|resume|mock-run> [args]
|
|
1088
986
|
|
|
1089
987
|
Commands:
|
|
1090
|
-
scaffold xhs-unified [--output <file>] [options] Generate xiaohongshu unified-harvest autoscript
|
|
1091
988
|
validate <file> Validate autoscript schema and references
|
|
1092
989
|
explain <file> Print normalized graph and defaults
|
|
1093
990
|
snapshot <jsonl-file> [--out <snapshot-file>] Build resumable snapshot from run JSONL
|
|
@@ -40,14 +40,28 @@ export const XHS_CHECKPOINTS = {
|
|
|
40
40
|
],
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
export async function detectCheckpoint({ profileId, platform = '
|
|
44
|
-
if (platform !== 'xiaohongshu') {
|
|
45
|
-
return asErrorPayload('UNSUPPORTED_PLATFORM', `Unsupported platform: ${platform}`);
|
|
46
|
-
}
|
|
47
|
-
|
|
43
|
+
export async function detectCheckpoint({ profileId, platform = 'generic' }) {
|
|
48
44
|
try {
|
|
49
45
|
const session = await ensureActiveSession(profileId);
|
|
50
46
|
const resolvedProfile = session.profileId || profileId;
|
|
47
|
+
|
|
48
|
+
if (platform !== 'xiaohongshu') {
|
|
49
|
+
const url = await getCurrentUrl(resolvedProfile);
|
|
50
|
+
return {
|
|
51
|
+
ok: true,
|
|
52
|
+
code: 'CHECKPOINT_DETECTED',
|
|
53
|
+
message: 'Checkpoint detected',
|
|
54
|
+
data: {
|
|
55
|
+
profileId: resolvedProfile,
|
|
56
|
+
platform,
|
|
57
|
+
checkpoint: 'unknown',
|
|
58
|
+
url,
|
|
59
|
+
signals: [],
|
|
60
|
+
selectorHits: {},
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
51
65
|
const [url, snapshot] = await Promise.all([
|
|
52
66
|
getCurrentUrl(resolvedProfile),
|
|
53
67
|
getDomSnapshotByProfile(resolvedProfile),
|
|
@@ -103,7 +117,7 @@ export async function captureCheckpoint({
|
|
|
103
117
|
profileId,
|
|
104
118
|
containerId = null,
|
|
105
119
|
selector = null,
|
|
106
|
-
platform = '
|
|
120
|
+
platform = 'generic',
|
|
107
121
|
}) {
|
|
108
122
|
try {
|
|
109
123
|
const session = await ensureActiveSession(profileId);
|
|
@@ -139,7 +153,7 @@ export async function restoreCheckpoint({
|
|
|
139
153
|
containerId = null,
|
|
140
154
|
selector = null,
|
|
141
155
|
targetCheckpoint = null,
|
|
142
|
-
platform = '
|
|
156
|
+
platform = 'generic',
|
|
143
157
|
}) {
|
|
144
158
|
try {
|
|
145
159
|
const session = await ensureActiveSession(profileId);
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
normalizeArray,
|
|
10
10
|
} from './utils.mjs';
|
|
11
11
|
|
|
12
|
-
async function validatePage(profileId, spec = {}, platform = '
|
|
12
|
+
async function validatePage(profileId, spec = {}, platform = 'generic') {
|
|
13
13
|
const url = await getCurrentUrl(profileId);
|
|
14
14
|
const includes = normalizeArray(spec.urlIncludes || []);
|
|
15
15
|
const excludes = normalizeArray(spec.urlExcludes || []);
|
|
@@ -74,7 +74,7 @@ export async function validateOperation({
|
|
|
74
74
|
validationSpec = {},
|
|
75
75
|
phase = 'pre',
|
|
76
76
|
context = {},
|
|
77
|
-
platform = '
|
|
77
|
+
platform = 'generic',
|
|
78
78
|
}) {
|
|
79
79
|
try {
|
|
80
80
|
const mode = String(validationSpec.mode || 'none').toLowerCase();
|
package/src/utils/help.mjs
CHANGED
|
@@ -155,7 +155,6 @@ CONTAINER FILTER & SUBSCRIPTION:
|
|
|
155
155
|
container list [profileId] List visible elements in viewport
|
|
156
156
|
|
|
157
157
|
AUTOSCRIPT (STRATEGY LAYER):
|
|
158
|
-
autoscript scaffold xhs-unified [--output <file>] Generate xiaohongshu unified-harvest autoscript template
|
|
159
158
|
autoscript validate <file> Validate autoscript schema and references
|
|
160
159
|
autoscript explain <file> Print normalized graph and defaults
|
|
161
160
|
autoscript snapshot <jsonl-file> [--out <snapshot-file>] Build resumable snapshot from run JSONL
|