@web-auto/webauto 0.1.12 → 0.1.14
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/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/lib/camo-cli.mjs +10 -3
- 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 +2 -0
- package/bin/webauto.mjs +74 -47
- package/modules/camo-runtime/src/autoscript/action-providers/index.mjs +14 -3
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/comments.mjs +40 -10
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/interaction.mjs +56 -4
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +51 -1
- 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/utils/browser-service.mjs +67 -45
- package/package.json +4 -3
|
@@ -524,6 +524,7 @@ async function runProfile(spec, argv, baseOverrides = {}) {
|
|
|
524
524
|
'xhs.unified.stop_screenshot',
|
|
525
525
|
'xhs.unified.profile_failed',
|
|
526
526
|
'autoscript:operation_done',
|
|
527
|
+
'autoscript:operation_progress',
|
|
527
528
|
'autoscript:operation_error',
|
|
528
529
|
'autoscript:operation_terminal',
|
|
529
530
|
'autoscript:operation_recovery_failed',
|
|
@@ -601,6 +602,7 @@ async function runProfile(spec, argv, baseOverrides = {}) {
|
|
|
601
602
|
|| eventName === 'autoscript:stop'
|
|
602
603
|
|| eventName === 'autoscript:impact'
|
|
603
604
|
|| eventName === 'autoscript:operation_start'
|
|
605
|
+
|| eventName === 'autoscript:operation_progress'
|
|
604
606
|
|| eventName === 'autoscript:operation_done'
|
|
605
607
|
|| eventName === 'autoscript:operation_error'
|
|
606
608
|
|| eventName === 'autoscript:operation_recovery_failed'
|
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() {
|
|
@@ -477,17 +478,12 @@ async function runInDir(dir, cmd, args) {
|
|
|
477
478
|
}
|
|
478
479
|
|
|
479
480
|
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
|
-
);
|
|
481
|
+
const electronName = process.platform === 'win32' ? 'electron.exe' : 'electron';
|
|
482
|
+
const candidates = [
|
|
483
|
+
path.join(ROOT, 'node_modules', 'electron', 'dist', electronName),
|
|
484
|
+
path.join(ROOT, 'apps', 'desktop-console', 'node_modules', 'electron', 'dist', electronName),
|
|
485
|
+
];
|
|
486
|
+
return candidates.some((p) => exists(p));
|
|
491
487
|
}
|
|
492
488
|
|
|
493
489
|
function checkDesktopConsoleBuilt() {
|
|
@@ -510,6 +506,15 @@ async function ensureDepsAndBuild() {
|
|
|
510
506
|
|
|
511
507
|
// Global package should already ship renderer build.
|
|
512
508
|
if (isGlobalInstall()) {
|
|
509
|
+
if (!checkDesktopConsoleDeps()) {
|
|
510
|
+
console.log('[webauto] Installing desktop-console runtime dependencies...');
|
|
511
|
+
const npm = npmRunner();
|
|
512
|
+
await run(npm.cmd, [...npm.prefix, '--prefix', appDir, '--workspaces=false', 'install', '--omit=dev']);
|
|
513
|
+
}
|
|
514
|
+
if (!checkDesktopConsoleDeps()) {
|
|
515
|
+
console.error('❌ electron runtime installation failed for desktop-console.');
|
|
516
|
+
process.exit(1);
|
|
517
|
+
}
|
|
513
518
|
if (!checkDesktopConsoleBuilt()) {
|
|
514
519
|
console.error('❌ desktop-console dist missing from package. Please reinstall @web-auto/webauto.');
|
|
515
520
|
process.exit(1);
|
|
@@ -540,61 +545,79 @@ async function ensureDepsAndBuild() {
|
|
|
540
545
|
console.log('[webauto] Setup complete!');
|
|
541
546
|
}
|
|
542
547
|
|
|
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
|
-
}
|
|
548
|
+
async function ensureUiRuntimeReady({ build, install }) {
|
|
549
|
+
let okServices = checkServicesBuilt();
|
|
550
|
+
let okDeps = checkDesktopConsoleDeps();
|
|
551
|
+
let okUiBuilt = checkDesktopConsoleBuilt();
|
|
557
552
|
|
|
558
|
-
// For global install, auto-setup on first run
|
|
559
553
|
if (isGlobalInstall()) {
|
|
560
554
|
const state = loadState();
|
|
561
555
|
const pkgJson = JSON.parse(readFileSync(path.join(ROOT, 'package.json'), 'utf-8'));
|
|
562
|
-
if (!state.initialized || state.version !== pkgJson.version) {
|
|
556
|
+
if (!state.initialized || state.version !== pkgJson.version || !okDeps || !okUiBuilt) {
|
|
563
557
|
await ensureDepsAndBuild();
|
|
558
|
+
okDeps = checkDesktopConsoleDeps();
|
|
559
|
+
okUiBuilt = checkDesktopConsoleBuilt();
|
|
564
560
|
}
|
|
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']);
|
|
561
|
+
} else if (!okServices) {
|
|
562
|
+
if (!build) {
|
|
563
|
+
console.error('❌ missing dist/ (services/modules). Run: npm run build:services');
|
|
564
|
+
process.exit(2);
|
|
574
565
|
}
|
|
566
|
+
const npm = npmRunner();
|
|
567
|
+
await run(npm.cmd, [...npm.prefix, 'run', 'build:services']);
|
|
568
|
+
okServices = checkServicesBuilt();
|
|
575
569
|
}
|
|
576
570
|
|
|
577
571
|
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');
|
|
572
|
+
if (isGlobalInstall()) {
|
|
573
|
+
console.error('❌ electron runtime missing from installed package after setup.');
|
|
582
574
|
process.exit(2);
|
|
583
575
|
}
|
|
584
|
-
if (!
|
|
585
|
-
|
|
586
|
-
|
|
576
|
+
if (!install && !build) {
|
|
577
|
+
console.error('❌ missing apps/desktop-console/node_modules. Run: npm --prefix apps/desktop-console install');
|
|
578
|
+
process.exit(2);
|
|
587
579
|
}
|
|
580
|
+
const npm = npmRunner();
|
|
581
|
+
await runInDir(path.join(ROOT, 'apps', 'desktop-console'), npm.cmd, [...npm.prefix, 'install']);
|
|
582
|
+
okDeps = checkDesktopConsoleDeps();
|
|
588
583
|
}
|
|
589
584
|
|
|
590
585
|
if (!okUiBuilt) {
|
|
586
|
+
if (isGlobalInstall()) {
|
|
587
|
+
console.error('❌ missing apps/desktop-console/dist in installed package after setup.');
|
|
588
|
+
process.exit(2);
|
|
589
|
+
}
|
|
591
590
|
if (!build) {
|
|
592
591
|
console.error('❌ missing apps/desktop-console/dist. Run: npm --prefix apps/desktop-console run build');
|
|
593
592
|
process.exit(2);
|
|
594
593
|
}
|
|
595
594
|
const npm = npmRunner();
|
|
596
595
|
await runInDir(path.join(ROOT, 'apps', 'desktop-console'), npm.cmd, [...npm.prefix, 'run', 'build']);
|
|
596
|
+
okUiBuilt = checkDesktopConsoleBuilt();
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
if (!okDeps || !okUiBuilt || (!isGlobalInstall() && !okServices)) {
|
|
600
|
+
console.error('❌ ui runtime prerequisites are not ready');
|
|
601
|
+
process.exit(2);
|
|
597
602
|
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
async function uiConsole({ build, install, checkOnly, noDaemon }) {
|
|
606
|
+
console.log(`[webauto] version ${ROOT_VERSION}`);
|
|
607
|
+
const okServices = checkServicesBuilt();
|
|
608
|
+
const okDeps = checkDesktopConsoleDeps();
|
|
609
|
+
const okUiBuilt = checkDesktopConsoleBuilt();
|
|
610
|
+
|
|
611
|
+
if (checkOnly) {
|
|
612
|
+
console.log(`[check] repoRoot: ${ROOT}`);
|
|
613
|
+
console.log(`[check] dist/services: ${okServices ? 'OK' : 'MISSING'}`);
|
|
614
|
+
console.log(`[check] desktop-console deps: ${okDeps ? 'OK' : 'MISSING'}`);
|
|
615
|
+
console.log(`[check] desktop-console dist: ${okUiBuilt ? 'OK' : 'MISSING'}`);
|
|
616
|
+
console.log(`[check] isGlobalInstall: ${isGlobalInstall()}`);
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
await ensureUiRuntimeReady({ build, install });
|
|
598
621
|
|
|
599
622
|
const uiScript = uiConsoleScriptPath();
|
|
600
623
|
const uiArgs = [];
|
|
@@ -746,6 +769,10 @@ async function main() {
|
|
|
746
769
|
}
|
|
747
770
|
|
|
748
771
|
if (cmd === 'ui' && sub === 'cli') {
|
|
772
|
+
await ensureUiRuntimeReady({
|
|
773
|
+
build: args.build === true,
|
|
774
|
+
install: args.install === true,
|
|
775
|
+
});
|
|
749
776
|
const script = path.join(ROOT, 'apps', 'desktop-console', 'entry', 'ui-cli.mjs');
|
|
750
777
|
await run(process.execPath, [script, ...rawArgv.slice(2)]);
|
|
751
778
|
return;
|
|
@@ -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
|
-
|
|
@@ -29,6 +29,13 @@ export function buildCommentsHarvestScript(params = {}) {
|
|
|
29
29
|
const metricsState = state.metrics && typeof state.metrics === 'object' ? state.metrics : {};
|
|
30
30
|
state.metrics = metricsState;
|
|
31
31
|
metricsState.searchCount = Number(metricsState.searchCount || 0);
|
|
32
|
+
const actionTrace = [];
|
|
33
|
+
const pushTrace = (payload) => {
|
|
34
|
+
actionTrace.push({
|
|
35
|
+
ts: new Date().toISOString(),
|
|
36
|
+
...payload,
|
|
37
|
+
});
|
|
38
|
+
};
|
|
32
39
|
const detailSelectors = [
|
|
33
40
|
'.note-detail-mask',
|
|
34
41
|
'.note-detail-page',
|
|
@@ -208,7 +215,13 @@ export function buildCommentsHarvestScript(params = {}) {
|
|
|
208
215
|
no_effect: 0,
|
|
209
216
|
no_new_comments: 0,
|
|
210
217
|
};
|
|
211
|
-
const performScroll = async (deltaY, waitMs = settleMs) => {
|
|
218
|
+
const performScroll = async (deltaY, waitMs = settleMs, meta = {}) => {
|
|
219
|
+
pushTrace({
|
|
220
|
+
kind: 'scroll',
|
|
221
|
+
stage: 'xhs_comments_harvest',
|
|
222
|
+
deltaY: Number(deltaY),
|
|
223
|
+
...meta,
|
|
224
|
+
});
|
|
212
225
|
if (typeof scroller?.scrollBy === 'function') {
|
|
213
226
|
scroller.scrollBy({ top: deltaY, behavior: 'auto' });
|
|
214
227
|
} else {
|
|
@@ -240,17 +253,23 @@ export function buildCommentsHarvestScript(params = {}) {
|
|
|
240
253
|
}
|
|
241
254
|
|
|
242
255
|
const prevTop = beforeMetrics.scrollTop;
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
await new Promise((resolve) => setTimeout(resolve, settleMs));
|
|
256
|
+
await performScroll(scrollStep, settleMs, {
|
|
257
|
+
round,
|
|
258
|
+
reason: 'main_scroll',
|
|
259
|
+
});
|
|
249
260
|
collect(round);
|
|
250
261
|
let afterMetrics = readMetrics();
|
|
251
262
|
let moved = Math.abs(afterMetrics.scrollTop - prevTop) > 1;
|
|
252
263
|
if (!moved && typeof window.scrollBy === 'function') {
|
|
253
|
-
|
|
264
|
+
const fallbackStep = Math.max(120, Math.floor(scrollStep / 2));
|
|
265
|
+
pushTrace({
|
|
266
|
+
kind: 'scroll',
|
|
267
|
+
stage: 'xhs_comments_harvest',
|
|
268
|
+
round,
|
|
269
|
+
reason: 'fallback_scroll',
|
|
270
|
+
deltaY: Number(fallbackStep),
|
|
271
|
+
});
|
|
272
|
+
window.scrollBy({ top: fallbackStep, behavior: 'auto' });
|
|
254
273
|
await new Promise((resolve) => setTimeout(resolve, settleMs));
|
|
255
274
|
collect(round);
|
|
256
275
|
afterMetrics = readMetrics();
|
|
@@ -298,13 +317,23 @@ export function buildCommentsHarvestScript(params = {}) {
|
|
|
298
317
|
recoveries += 1;
|
|
299
318
|
recoveryReasonCounts[recoveryTrigger] += 1;
|
|
300
319
|
for (let i = 0; i < recoveryUpRounds; i += 1) {
|
|
301
|
-
await performScroll(-recoveryUpStep, settleMs + 120
|
|
320
|
+
await performScroll(-recoveryUpStep, settleMs + 120, {
|
|
321
|
+
round,
|
|
322
|
+
reason: 'recovery_up',
|
|
323
|
+
recoveryTrigger,
|
|
324
|
+
recoveryStep: i + 1,
|
|
325
|
+
});
|
|
302
326
|
collect(round);
|
|
303
327
|
}
|
|
304
328
|
const downBoost = Math.min(maxRecoveryDownBoost, Math.max(0, recoveries - 1) * recoveryDownBoostPerAttempt);
|
|
305
329
|
const downRounds = recoveryDownRounds + downBoost;
|
|
306
330
|
for (let i = 0; i < downRounds; i += 1) {
|
|
307
|
-
await performScroll(recoveryDownStep, settleMs + 180
|
|
331
|
+
await performScroll(recoveryDownStep, settleMs + 180, {
|
|
332
|
+
round,
|
|
333
|
+
reason: 'recovery_down',
|
|
334
|
+
recoveryTrigger,
|
|
335
|
+
recoveryStep: i + 1,
|
|
336
|
+
});
|
|
308
337
|
collect(round);
|
|
309
338
|
}
|
|
310
339
|
const recoveredMetrics = readMetrics();
|
|
@@ -405,6 +434,7 @@ export function buildCommentsHarvestScript(params = {}) {
|
|
|
405
434
|
budgetExpectedCommentsCount,
|
|
406
435
|
scroll: metrics,
|
|
407
436
|
};
|
|
437
|
+
payload.actionTrace = actionTrace;
|
|
408
438
|
if (includeComments) {
|
|
409
439
|
const bounded = commentsLimit > 0 ? comments.slice(0, commentsLimit) : comments;
|
|
410
440
|
payload.comments = bounded;
|
|
@@ -165,9 +165,16 @@ function buildCollectLikeTargetsScript() {
|
|
|
165
165
|
function buildClickLikeByIndexScript(index, highlight, skipAlreadyCheck = false) {
|
|
166
166
|
return `(async () => {
|
|
167
167
|
const idx = Number(${JSON.stringify(index)});
|
|
168
|
+
const actionTrace = [];
|
|
169
|
+
const pushTrace = (payload) => {
|
|
170
|
+
actionTrace.push({
|
|
171
|
+
ts: new Date().toISOString(),
|
|
172
|
+
...payload,
|
|
173
|
+
});
|
|
174
|
+
};
|
|
168
175
|
const items = Array.from(document.querySelectorAll('.comment-item'));
|
|
169
176
|
const item = items[idx];
|
|
170
|
-
if (!item) return { clicked: false, reason: 'comment_item_not_found', index: idx };
|
|
177
|
+
if (!item) return { clicked: false, reason: 'comment_item_not_found', index: idx, actionTrace };
|
|
171
178
|
const findLikeControl = (node) => {
|
|
172
179
|
const selectors = [
|
|
173
180
|
'.like-wrapper',
|
|
@@ -195,12 +202,14 @@ function buildClickLikeByIndexScript(index, highlight, skipAlreadyCheck = false)
|
|
|
195
202
|
};
|
|
196
203
|
|
|
197
204
|
const likeControl = findLikeControl(item);
|
|
198
|
-
if (!likeControl) return { clicked: false, reason: 'like_control_not_found', index: idx };
|
|
205
|
+
if (!likeControl) return { clicked: false, reason: 'like_control_not_found', index: idx, actionTrace };
|
|
199
206
|
const beforeLiked = isAlreadyLiked(likeControl);
|
|
200
207
|
if (beforeLiked && !${skipAlreadyCheck ? 'true' : 'false'}) {
|
|
201
|
-
return { clicked: false, alreadyLiked: true, reason: 'already_liked', index: idx };
|
|
208
|
+
return { clicked: false, alreadyLiked: true, reason: 'already_liked', index: idx, actionTrace };
|
|
202
209
|
}
|
|
210
|
+
pushTrace({ kind: 'scroll', stage: 'xhs_comment_like', index: idx, target: 'comment_item', via: 'scrollIntoView' });
|
|
203
211
|
item.scrollIntoView({ behavior: 'auto', block: 'center' });
|
|
212
|
+
pushTrace({ kind: 'scroll', stage: 'xhs_comment_like', index: idx, target: 'like_control', via: 'scrollIntoView' });
|
|
204
213
|
likeControl.scrollIntoView({ behavior: 'auto', block: 'center' });
|
|
205
214
|
await new Promise((resolve) => setTimeout(resolve, 120));
|
|
206
215
|
if (${highlight ? 'true' : 'false'}) {
|
|
@@ -208,6 +217,7 @@ function buildClickLikeByIndexScript(index, highlight, skipAlreadyCheck = false)
|
|
|
208
217
|
likeControl.style.outline = '2px solid #00d6ff';
|
|
209
218
|
setTimeout(() => { likeControl.style.outline = prev; }, 450);
|
|
210
219
|
}
|
|
220
|
+
pushTrace({ kind: 'click', stage: 'xhs_comment_like', index: idx, target: 'like_control' });
|
|
211
221
|
likeControl.click();
|
|
212
222
|
await new Promise((resolve) => setTimeout(resolve, 220));
|
|
213
223
|
return {
|
|
@@ -216,6 +226,7 @@ function buildClickLikeByIndexScript(index, highlight, skipAlreadyCheck = false)
|
|
|
216
226
|
likedAfter: isAlreadyLiked(likeControl),
|
|
217
227
|
reason: 'clicked',
|
|
218
228
|
index: idx,
|
|
229
|
+
actionTrace,
|
|
219
230
|
};
|
|
220
231
|
})()`;
|
|
221
232
|
}
|
|
@@ -231,7 +242,28 @@ async function captureScreenshotToFile({ profileId, filePath }) {
|
|
|
231
242
|
}
|
|
232
243
|
}
|
|
233
244
|
|
|
234
|
-
|
|
245
|
+
function emitProgress(context, payload = {}) {
|
|
246
|
+
const emit = context?.emitProgress;
|
|
247
|
+
if (typeof emit !== 'function') return;
|
|
248
|
+
emit(payload);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function emitActionTrace(context, actionTrace = [], extra = {}) {
|
|
252
|
+
if (!Array.isArray(actionTrace) || actionTrace.length === 0) return;
|
|
253
|
+
for (let i = 0; i < actionTrace.length; i += 1) {
|
|
254
|
+
const row = actionTrace[i];
|
|
255
|
+
if (!row || typeof row !== 'object') continue;
|
|
256
|
+
const kind = String(row.kind || row.action || '').trim().toLowerCase() || 'trace';
|
|
257
|
+
emitProgress(context, {
|
|
258
|
+
kind,
|
|
259
|
+
step: i + 1,
|
|
260
|
+
...extra,
|
|
261
|
+
...row,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export async function executeCommentLikeOperation({ profileId, params = {}, context = {} }) {
|
|
235
267
|
const maxLikes = Math.max(1, Number(params.maxLikes ?? params.maxLikesPerRound ?? 1) || 1);
|
|
236
268
|
const rawKeywords = normalizeArray(params.keywords || params.likeKeywords);
|
|
237
269
|
const rules = compileLikeRules(rawKeywords);
|
|
@@ -353,6 +385,16 @@ export async function executeCommentLikeOperation({ profileId, params = {} }) {
|
|
|
353
385
|
highlight: false,
|
|
354
386
|
});
|
|
355
387
|
const clickResult = extractEvaluateResultData(clickRaw) || {};
|
|
388
|
+
const clickTrace = Array.isArray(clickResult.actionTrace) ? clickResult.actionTrace : [];
|
|
389
|
+
if (clickTrace.length > 0) {
|
|
390
|
+
emitActionTrace(context, clickTrace, {
|
|
391
|
+
stage: 'xhs_comment_like',
|
|
392
|
+
noteId: output.noteId,
|
|
393
|
+
commentIndex: Number(row.index),
|
|
394
|
+
fallback: false,
|
|
395
|
+
});
|
|
396
|
+
delete clickResult.actionTrace;
|
|
397
|
+
}
|
|
356
398
|
|
|
357
399
|
const afterPath = saveEvidence
|
|
358
400
|
? await captureScreenshotToFile({
|
|
@@ -454,6 +496,16 @@ export async function executeCommentLikeOperation({ profileId, params = {} }) {
|
|
|
454
496
|
highlight: false,
|
|
455
497
|
});
|
|
456
498
|
const clickResult = extractEvaluateResultData(clickRaw) || {};
|
|
499
|
+
const clickTrace = Array.isArray(clickResult.actionTrace) ? clickResult.actionTrace : [];
|
|
500
|
+
if (clickTrace.length > 0) {
|
|
501
|
+
emitActionTrace(context, clickTrace, {
|
|
502
|
+
stage: 'xhs_comment_like',
|
|
503
|
+
noteId: output.noteId,
|
|
504
|
+
commentIndex: Number(row.index),
|
|
505
|
+
fallback: true,
|
|
506
|
+
});
|
|
507
|
+
delete clickResult.actionTrace;
|
|
508
|
+
}
|
|
457
509
|
const afterPath = saveEvidence
|
|
458
510
|
? await captureScreenshotToFile({
|
|
459
511
|
profileId,
|
|
@@ -6,6 +6,13 @@ export function buildSubmitSearchScript(params = {}) {
|
|
|
6
6
|
state.metrics = metrics;
|
|
7
7
|
metrics.searchCount = Number(metrics.searchCount || 0) + 1;
|
|
8
8
|
metrics.lastSearchAt = new Date().toISOString();
|
|
9
|
+
const actionTrace = [];
|
|
10
|
+
const pushTrace = (payload) => {
|
|
11
|
+
actionTrace.push({
|
|
12
|
+
ts: new Date().toISOString(),
|
|
13
|
+
...payload,
|
|
14
|
+
});
|
|
15
|
+
};
|
|
9
16
|
const input = document.querySelector('#search-input, input.search-input');
|
|
10
17
|
if (!(input instanceof HTMLInputElement)) {
|
|
11
18
|
throw new Error('SEARCH_INPUT_NOT_FOUND');
|
|
@@ -28,8 +35,12 @@ export function buildSubmitSearchScript(params = {}) {
|
|
|
28
35
|
for (const selector of candidates) {
|
|
29
36
|
const button = document.querySelector(selector);
|
|
30
37
|
if (!button) continue;
|
|
31
|
-
if (button instanceof HTMLElement)
|
|
38
|
+
if (button instanceof HTMLElement) {
|
|
39
|
+
pushTrace({ kind: 'scroll', stage: 'submit_search', selector, via: 'scrollIntoView' });
|
|
40
|
+
button.scrollIntoView({ behavior: 'auto', block: 'center' });
|
|
41
|
+
}
|
|
32
42
|
await new Promise((resolve) => setTimeout(resolve, 80));
|
|
43
|
+
pushTrace({ kind: 'click', stage: 'submit_search', selector });
|
|
33
44
|
button.click();
|
|
34
45
|
clickedSelector = selector;
|
|
35
46
|
break;
|
|
@@ -46,6 +57,7 @@ export function buildSubmitSearchScript(params = {}) {
|
|
|
46
57
|
beforeUrl,
|
|
47
58
|
afterUrl: window.location.href,
|
|
48
59
|
searchCount: metrics.searchCount,
|
|
60
|
+
actionTrace,
|
|
49
61
|
};
|
|
50
62
|
})()`;
|
|
51
63
|
}
|
|
@@ -100,6 +112,13 @@ export function buildOpenDetailScript(params = {}) {
|
|
|
100
112
|
};
|
|
101
113
|
|
|
102
114
|
const state = loadState();
|
|
115
|
+
const actionTrace = [];
|
|
116
|
+
const pushTrace = (payload) => {
|
|
117
|
+
actionTrace.push({
|
|
118
|
+
ts: new Date().toISOString(),
|
|
119
|
+
...payload,
|
|
120
|
+
});
|
|
121
|
+
};
|
|
103
122
|
if (!Array.isArray(state.visitedNoteIds)) state.visitedNoteIds = [];
|
|
104
123
|
const requestedKeyword = ${JSON.stringify(keyword)};
|
|
105
124
|
const mode = ${JSON.stringify(mode)};
|
|
@@ -156,11 +175,23 @@ export function buildOpenDetailScript(params = {}) {
|
|
|
156
175
|
const maxRounds = Number(${seedCollectMaxRounds});
|
|
157
176
|
const targetCount = Number(${seedCollectCount});
|
|
158
177
|
for (let round = 0; round < maxRounds && seedCollectedSet.size < targetCount; round += 1) {
|
|
178
|
+
pushTrace({
|
|
179
|
+
kind: 'scroll',
|
|
180
|
+
stage: 'seed_collect',
|
|
181
|
+
round: round + 1,
|
|
182
|
+
deltaY: Number(${seedCollectStep}),
|
|
183
|
+
});
|
|
159
184
|
window.scrollBy({ top: Number(${seedCollectStep}), left: 0, behavior: 'auto' });
|
|
160
185
|
await new Promise((resolve) => setTimeout(resolve, Number(${seedCollectSettleMs})));
|
|
161
186
|
collectVisible();
|
|
162
187
|
}
|
|
163
188
|
if (${seedResetToTop ? 'true' : 'false'}) {
|
|
189
|
+
pushTrace({
|
|
190
|
+
kind: 'scroll',
|
|
191
|
+
stage: 'seed_collect',
|
|
192
|
+
round: 'reset_to_top',
|
|
193
|
+
toTop: true,
|
|
194
|
+
});
|
|
164
195
|
window.scrollTo({ top: 0, behavior: 'auto' });
|
|
165
196
|
await new Promise((resolve) => setTimeout(resolve, Number(${seedCollectSettleMs})));
|
|
166
197
|
}
|
|
@@ -190,6 +221,12 @@ export function buildOpenDetailScript(params = {}) {
|
|
|
190
221
|
let stagnantRounds = 0;
|
|
191
222
|
for (let round = 0; !next && round < Number(${nextSeekRounds}); round += 1) {
|
|
192
223
|
const beforeTop = window.scrollY || document.documentElement.scrollTop || document.body.scrollTop || 0;
|
|
224
|
+
pushTrace({
|
|
225
|
+
kind: 'scroll',
|
|
226
|
+
stage: 'seek_next_detail',
|
|
227
|
+
round: round + 1,
|
|
228
|
+
deltaY: seekStep,
|
|
229
|
+
});
|
|
193
230
|
window.scrollBy({ top: seekStep, left: 0, behavior: 'auto' });
|
|
194
231
|
await new Promise((resolve) => setTimeout(resolve, Number(${nextSeekSettleMs})));
|
|
195
232
|
nodes = mapNodes();
|
|
@@ -238,9 +275,21 @@ export function buildOpenDetailScript(params = {}) {
|
|
|
238
275
|
return !searchVisible;
|
|
239
276
|
};
|
|
240
277
|
|
|
278
|
+
pushTrace({
|
|
279
|
+
kind: 'scroll',
|
|
280
|
+
stage: 'open_detail',
|
|
281
|
+
noteId: next.noteId,
|
|
282
|
+
via: 'scrollIntoView',
|
|
283
|
+
});
|
|
241
284
|
next.cover.scrollIntoView({ behavior: 'auto', block: 'center' });
|
|
242
285
|
await new Promise((resolve) => setTimeout(resolve, 140));
|
|
243
286
|
const beforeUrl = window.location.href;
|
|
287
|
+
pushTrace({
|
|
288
|
+
kind: 'click',
|
|
289
|
+
stage: 'open_detail',
|
|
290
|
+
noteId: next.noteId,
|
|
291
|
+
selector: 'a.cover',
|
|
292
|
+
});
|
|
244
293
|
next.cover.click();
|
|
245
294
|
|
|
246
295
|
let detailReady = false;
|
|
@@ -274,6 +323,7 @@ export function buildOpenDetailScript(params = {}) {
|
|
|
274
323
|
excludedCount: excludedNoteIds.size,
|
|
275
324
|
seedCollectedCount: seedCollectedSet.size,
|
|
276
325
|
seedCollectedNoteIds: Array.from(seedCollectedSet),
|
|
326
|
+
actionTrace,
|
|
277
327
|
};
|
|
278
328
|
})()`;
|
|
279
329
|
}
|