@web-auto/camo 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/README.md +5 -1
- package/package.json +1 -1
- package/src/autoscript/xhs-unified-template.mjs +11 -8
- package/src/container/subscription-registry.mjs +3 -3
- package/src/core/browser.mjs +1 -5
- package/src/lifecycle/lock.mjs +2 -2
- package/src/lifecycle/session-registry.mjs +2 -2
- package/src/lifecycle/session-watchdog.mjs +2 -2
- package/src/utils/browser-service.mjs +89 -10
- package/src/utils/config.mjs +57 -2
- package/src/utils/help.mjs +5 -1
package/README.md
CHANGED
|
@@ -459,7 +459,11 @@ Condition types:
|
|
|
459
459
|
### Environment Variables
|
|
460
460
|
|
|
461
461
|
- `WEBAUTO_BROWSER_URL` - Browser service URL (default: `http://127.0.0.1:7704`)
|
|
462
|
-
- `
|
|
462
|
+
- `WEBAUTO_INSTALL_DIR` - `@web-auto/webauto` 安装目录(可选,首次安装兜底)
|
|
463
|
+
- `WEBAUTO_REPO_ROOT` - WebAuto repository root (optional, dev mode)
|
|
464
|
+
- `WEBAUTO_DATA_ROOT` / `WEBAUTO_HOME` - 用户数据目录(Windows 默认 `D:/webauto`,无 D 盘回退 `~/.webauto`)
|
|
465
|
+
- `WEBAUTO_PROFILE_ROOT` - Profile 目录覆盖(默认 `<data-root>/profiles`)
|
|
466
|
+
- `WEBAUTO_ROOT` - 兼容旧变量(当值不是 `webauto/.webauto` 目录时会自动补 `.webauto`)
|
|
463
467
|
- `WEBAUTO_CONTAINER_ROOT` - User container root override (default: `~/.webauto/container-lib`)
|
|
464
468
|
- `CAMO_PROGRESS_EVENTS_FILE` - Optional progress event JSONL path override
|
|
465
469
|
- `CAMO_PROGRESS_WS_HOST` / `CAMO_PROGRESS_WS_PORT` - Progress websocket daemon bind address (default: `127.0.0.1:7788`)
|
package/package.json
CHANGED
|
@@ -31,12 +31,15 @@ function splitCsv(value) {
|
|
|
31
31
|
.filter(Boolean);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
if (options.
|
|
37
|
-
if (options.
|
|
38
|
-
if (
|
|
39
|
-
return '
|
|
34
|
+
function pickCloseDependencies(options) {
|
|
35
|
+
const deps = [];
|
|
36
|
+
if (options.doLikes) deps.push('comment_like');
|
|
37
|
+
if (options.doReply) deps.push('comment_reply');
|
|
38
|
+
if (deps.length > 0) return deps;
|
|
39
|
+
if (options.matchGateEnabled) return ['comment_match_gate'];
|
|
40
|
+
if (options.commentsHarvestEnabled) return ['comments_harvest'];
|
|
41
|
+
if (options.detailHarvestEnabled) return ['detail_harvest'];
|
|
42
|
+
return ['open_first_detail'];
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
function buildOpenFirstDetailScript(maxNotes, keyword) {
|
|
@@ -486,7 +489,7 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
|
|
|
486
489
|
const detailHarvestEnabled = doHomepage || doImages || doComments || doLikes || doReply || doOcr;
|
|
487
490
|
const commentsHarvestEnabled = doComments || doLikes || doReply;
|
|
488
491
|
const matchGateEnabled = doLikes || doReply;
|
|
489
|
-
const closeDependsOn =
|
|
492
|
+
const closeDependsOn = pickCloseDependencies({
|
|
490
493
|
doReply,
|
|
491
494
|
doLikes,
|
|
492
495
|
matchGateEnabled,
|
|
@@ -793,7 +796,7 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
|
|
|
793
796
|
action: 'xhs_close_detail',
|
|
794
797
|
params: {},
|
|
795
798
|
trigger: 'detail_modal.exist',
|
|
796
|
-
dependsOn:
|
|
799
|
+
dependsOn: closeDependsOn,
|
|
797
800
|
once: false,
|
|
798
801
|
oncePerAppear: true,
|
|
799
802
|
pacing: { operationMinIntervalMs: 2500, eventCooldownMs: 1300, jitterMs: 180 },
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import os from 'node:os';
|
|
4
3
|
import { findRepoRootCandidate } from '../utils/browser-service.mjs';
|
|
4
|
+
import { CONFIG_DIR } from '../utils/config.mjs';
|
|
5
5
|
|
|
6
6
|
const CONTAINER_ROOT_ENV = process.env.WEBAUTO_CONTAINER_ROOT || process.env.ROUTECODEX_CONTAINER_ROOT;
|
|
7
7
|
|
|
8
|
-
export const USER_CONTAINER_ROOT = CONTAINER_ROOT_ENV || path.join(
|
|
9
|
-
export const SUBSCRIPTION_ROOT = path.join(
|
|
8
|
+
export const USER_CONTAINER_ROOT = CONTAINER_ROOT_ENV || path.join(CONFIG_DIR, 'container-lib');
|
|
9
|
+
export const SUBSCRIPTION_ROOT = path.join(CONFIG_DIR, 'container-subscriptions');
|
|
10
10
|
export const SUBSCRIPTION_SETS_DIR = path.join(SUBSCRIPTION_ROOT, 'sets');
|
|
11
11
|
export const SUBSCRIPTION_INDEX_FILE = path.join(SUBSCRIPTION_ROOT, 'index.json');
|
|
12
12
|
export const SUBSCRIPTION_TARGETS_FILE = path.join(SUBSCRIPTION_ROOT, 'targets.json');
|
package/src/core/browser.mjs
CHANGED
|
@@ -5,10 +5,7 @@
|
|
|
5
5
|
import { spawn, execSync } from 'node:child_process';
|
|
6
6
|
import fs from 'node:fs';
|
|
7
7
|
import path from 'node:path';
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
const CONFIG_DIR = path.join(os.homedir(), '.webauto');
|
|
11
|
-
const PROFILES_DIR = path.join(CONFIG_DIR, 'profiles');
|
|
8
|
+
import { PROFILES_DIR } from '../utils/config.mjs';
|
|
12
9
|
|
|
13
10
|
// Active browser instances registry (in-memory)
|
|
14
11
|
const activeBrowsers = new Map();
|
|
@@ -267,4 +264,3 @@ export async function getCurrentPage(profileId) {
|
|
|
267
264
|
export function getActiveBrowser(profileId) {
|
|
268
265
|
return activeBrowsers.get(profileId) || null;
|
|
269
266
|
}
|
|
270
|
-
|
package/src/lifecycle/lock.mjs
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import fs from 'node:fs';
|
|
7
7
|
import path from 'node:path';
|
|
8
|
-
import
|
|
8
|
+
import { CONFIG_DIR } from '../utils/config.mjs';
|
|
9
9
|
|
|
10
|
-
const LOCK_DIR = path.join(
|
|
10
|
+
const LOCK_DIR = path.join(CONFIG_DIR, 'locks');
|
|
11
11
|
|
|
12
12
|
function ensureLockDir() {
|
|
13
13
|
if (!fs.existsSync(LOCK_DIR)) {
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import fs from 'node:fs';
|
|
7
7
|
import path from 'node:path';
|
|
8
|
-
import os from 'node:os';
|
|
9
8
|
import crypto from 'node:crypto';
|
|
9
|
+
import { CONFIG_DIR } from '../utils/config.mjs';
|
|
10
10
|
|
|
11
|
-
const SESSION_DIR = path.join(
|
|
11
|
+
const SESSION_DIR = path.join(CONFIG_DIR, 'sessions');
|
|
12
12
|
|
|
13
13
|
function ensureSessionDir() {
|
|
14
14
|
if (!fs.existsSync(SESSION_DIR)) {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from 'node:fs';
|
|
3
|
-
import os from 'node:os';
|
|
4
3
|
import path from 'node:path';
|
|
5
4
|
import { spawn } from 'node:child_process';
|
|
6
5
|
import { fileURLToPath } from 'node:url';
|
|
7
6
|
import { callAPI } from '../utils/browser-service.mjs';
|
|
7
|
+
import { CONFIG_DIR } from '../utils/config.mjs';
|
|
8
8
|
import { releaseLock } from './lock.mjs';
|
|
9
9
|
import { getSessionInfo, markSessionClosed } from './session-registry.mjs';
|
|
10
10
|
|
|
11
|
-
const WATCHDOG_DIR = path.join(
|
|
11
|
+
const WATCHDOG_DIR = path.join(CONFIG_DIR, 'run', 'camo-watchdogs');
|
|
12
12
|
const DEFAULT_HEADLESS_IDLE_TIMEOUT_MS = 30 * 60 * 1000;
|
|
13
13
|
|
|
14
14
|
function ensureWatchdogDir() {
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { execSync } from 'node:child_process';
|
|
2
|
+
import { execSync, spawn } from 'node:child_process';
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import os from 'node:os';
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
6
8
|
import { BROWSER_SERVICE_URL, loadConfig, setRepoRoot } from './config.mjs';
|
|
7
9
|
import { touchSessionActivity } from '../lifecycle/session-registry.mjs';
|
|
8
10
|
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
|
|
9
13
|
function shouldTrackSessionActivity(action, payload) {
|
|
10
14
|
const profileId = String(payload?.profileId || '').trim();
|
|
11
15
|
if (!profileId) return false;
|
|
@@ -304,12 +308,18 @@ export function ensureCamoufox() {
|
|
|
304
308
|
}
|
|
305
309
|
|
|
306
310
|
const START_SCRIPT_REL = path.join('runtime', 'infra', 'utils', 'scripts', 'service', 'start-browser-service.mjs');
|
|
311
|
+
const CONTROLLER_SERVER_REL = path.join('services', 'controller', 'src', 'server.mjs');
|
|
307
312
|
|
|
308
313
|
function hasStartScript(root) {
|
|
309
314
|
if (!root) return false;
|
|
310
315
|
return fs.existsSync(path.join(root, START_SCRIPT_REL));
|
|
311
316
|
}
|
|
312
317
|
|
|
318
|
+
function hasControllerServer(root) {
|
|
319
|
+
if (!root) return false;
|
|
320
|
+
return fs.existsSync(path.join(root, CONTROLLER_SERVER_REL));
|
|
321
|
+
}
|
|
322
|
+
|
|
313
323
|
function walkUpForRepoRoot(startDir) {
|
|
314
324
|
if (!startDir) return null;
|
|
315
325
|
let cursor = path.resolve(startDir);
|
|
@@ -356,6 +366,27 @@ function scanCommonRepoRoots() {
|
|
|
356
366
|
return null;
|
|
357
367
|
}
|
|
358
368
|
|
|
369
|
+
function scanCommonInstallRoots() {
|
|
370
|
+
const home = os.homedir();
|
|
371
|
+
const appData = String(process.env.APPDATA || path.join(home, 'AppData', 'Roaming'));
|
|
372
|
+
const npmPrefix = String(process.env.npm_config_prefix || '').trim();
|
|
373
|
+
const nodeModuleRoots = [
|
|
374
|
+
path.join(appData, 'npm', 'node_modules'),
|
|
375
|
+
path.join(home, 'AppData', 'Roaming', 'npm', 'node_modules'),
|
|
376
|
+
npmPrefix ? path.join(npmPrefix, 'node_modules') : '',
|
|
377
|
+
npmPrefix ? path.join(npmPrefix, 'lib', 'node_modules') : '',
|
|
378
|
+
'/usr/local/lib/node_modules',
|
|
379
|
+
'/usr/lib/node_modules',
|
|
380
|
+
path.join(home, '.npm-global', 'lib', 'node_modules'),
|
|
381
|
+
].filter(Boolean);
|
|
382
|
+
|
|
383
|
+
for (const root of nodeModuleRoots) {
|
|
384
|
+
const candidate = path.join(root, '@web-auto', 'webauto');
|
|
385
|
+
if (hasControllerServer(candidate)) return candidate;
|
|
386
|
+
}
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
|
|
359
390
|
export function findRepoRootCandidate() {
|
|
360
391
|
const cfg = loadConfig();
|
|
361
392
|
const candidates = [
|
|
@@ -404,21 +435,69 @@ export function findRepoRootCandidate() {
|
|
|
404
435
|
return null;
|
|
405
436
|
}
|
|
406
437
|
|
|
438
|
+
export function findInstallRootCandidate() {
|
|
439
|
+
const cfg = loadConfig();
|
|
440
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
441
|
+
const siblingInScopedNodeModules = path.resolve(currentDir, '..', '..', '..', 'webauto');
|
|
442
|
+
const candidates = [
|
|
443
|
+
process.env.WEBAUTO_INSTALL_DIR,
|
|
444
|
+
process.env.WEBAUTO_PACKAGE_ROOT,
|
|
445
|
+
process.env.WEBAUTO_REPO_ROOT,
|
|
446
|
+
cfg.repoRoot,
|
|
447
|
+
siblingInScopedNodeModules,
|
|
448
|
+
process.cwd(),
|
|
449
|
+
].filter(Boolean);
|
|
450
|
+
|
|
451
|
+
try {
|
|
452
|
+
const pkgPath = require.resolve('@web-auto/webauto/package.json');
|
|
453
|
+
candidates.push(path.dirname(pkgPath));
|
|
454
|
+
} catch {
|
|
455
|
+
// ignore resolution failures in npx-only environments
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const seen = new Set();
|
|
459
|
+
for (const raw of candidates) {
|
|
460
|
+
const resolved = path.resolve(String(raw));
|
|
461
|
+
if (seen.has(resolved)) continue;
|
|
462
|
+
seen.add(resolved);
|
|
463
|
+
if (hasControllerServer(resolved)) return resolved;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return scanCommonInstallRoots();
|
|
467
|
+
}
|
|
468
|
+
|
|
407
469
|
export async function ensureBrowserService() {
|
|
408
470
|
if (await checkBrowserService()) return;
|
|
409
471
|
|
|
410
472
|
const repoRoot = findRepoRootCandidate();
|
|
411
|
-
if (
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
473
|
+
if (repoRoot) {
|
|
474
|
+
const scriptPath = path.join(repoRoot, START_SCRIPT_REL);
|
|
475
|
+
console.log('Starting browser-service daemon...');
|
|
476
|
+
execSync(`node "${scriptPath}"`, { stdio: 'inherit', cwd: repoRoot });
|
|
477
|
+
} else {
|
|
478
|
+
const installRoot = findInstallRootCandidate();
|
|
479
|
+
if (!installRoot) {
|
|
480
|
+
throw new Error(
|
|
481
|
+
`Cannot locate browser-service launcher (${START_SCRIPT_REL} or ${CONTROLLER_SERVER_REL}). ` +
|
|
482
|
+
'Set WEBAUTO_INSTALL_DIR=<@web-auto/webauto install dir> or WEBAUTO_REPO_ROOT=<repo root>.',
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
const scriptPath = path.join(installRoot, CONTROLLER_SERVER_REL);
|
|
486
|
+
const env = {
|
|
487
|
+
...process.env,
|
|
488
|
+
WEBAUTO_REPO_ROOT: String(process.env.WEBAUTO_REPO_ROOT || '').trim() || installRoot,
|
|
489
|
+
};
|
|
490
|
+
const child = spawn(process.execPath, [scriptPath], {
|
|
491
|
+
cwd: installRoot,
|
|
492
|
+
detached: true,
|
|
493
|
+
stdio: 'ignore',
|
|
494
|
+
windowsHide: true,
|
|
495
|
+
env,
|
|
496
|
+
});
|
|
497
|
+
child.unref();
|
|
498
|
+
console.log(`Starting browser-service daemon (packaged webauto, pid=${child.pid || 'unknown'})...`);
|
|
416
499
|
}
|
|
417
500
|
|
|
418
|
-
const scriptPath = path.join(repoRoot, START_SCRIPT_REL);
|
|
419
|
-
console.log('Starting browser-service daemon...');
|
|
420
|
-
execSync(`node "${scriptPath}"`, { stdio: 'inherit', cwd: repoRoot });
|
|
421
|
-
|
|
422
501
|
for (let i = 0; i < 20; i += 1) {
|
|
423
502
|
await new Promise((r) => setTimeout(r, 400));
|
|
424
503
|
if (await checkBrowserService()) {
|
package/src/utils/config.mjs
CHANGED
|
@@ -3,8 +3,63 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
function hasDrive(letter) {
|
|
7
|
+
if (process.platform !== 'win32') return false;
|
|
8
|
+
try {
|
|
9
|
+
return fs.existsSync(`${String(letter || '').replace(/[^A-Za-z]/g, '').toUpperCase()}:\\`);
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizePathForPlatform(input, platform = process.platform) {
|
|
16
|
+
const raw = String(input || '').trim();
|
|
17
|
+
const isWinPath = platform === 'win32' || /^[A-Za-z]:[\\/]/.test(raw);
|
|
18
|
+
const pathApi = isWinPath ? path.win32 : path;
|
|
19
|
+
return isWinPath ? pathApi.normalize(raw) : path.resolve(raw);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function normalizeLegacyWebautoRoot(input, platform = process.platform) {
|
|
23
|
+
const pathApi = platform === 'win32' ? path.win32 : path;
|
|
24
|
+
const resolved = normalizePathForPlatform(input, platform);
|
|
25
|
+
const base = pathApi.basename(resolved).toLowerCase();
|
|
26
|
+
if (base === '.webauto' || base === 'webauto') return resolved;
|
|
27
|
+
return pathApi.join(resolved, '.webauto');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function resolveWebautoRoot(options = {}) {
|
|
31
|
+
const env = options.env || process.env;
|
|
32
|
+
const platform = String(options.platform || process.platform);
|
|
33
|
+
const pathApi = platform === 'win32' ? path.win32 : path;
|
|
34
|
+
const homeDir = String(options.homeDir || os.homedir());
|
|
35
|
+
const explicitDataRoot = String(env.WEBAUTO_DATA_ROOT || env.WEBAUTO_HOME || '').trim();
|
|
36
|
+
if (explicitDataRoot) return normalizePathForPlatform(explicitDataRoot, platform);
|
|
37
|
+
|
|
38
|
+
const legacyRoot = String(env.WEBAUTO_ROOT || env.WEBAUTO_PORTABLE_ROOT || '').trim();
|
|
39
|
+
if (legacyRoot) return normalizeLegacyWebautoRoot(legacyRoot, platform);
|
|
40
|
+
|
|
41
|
+
const dDriveExists = typeof options.hasDDrive === 'boolean'
|
|
42
|
+
? options.hasDDrive
|
|
43
|
+
: hasDrive('D');
|
|
44
|
+
if (platform === 'win32') {
|
|
45
|
+
return dDriveExists ? 'D:\\webauto' : pathApi.join(homeDir, '.webauto');
|
|
46
|
+
}
|
|
47
|
+
return pathApi.join(homeDir, '.webauto');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function resolveProfilesDir(options = {}) {
|
|
51
|
+
const env = options.env || process.env;
|
|
52
|
+
const platform = String(options.platform || process.platform);
|
|
53
|
+
const explicitProfileRoot = String(env.WEBAUTO_PROFILE_ROOT || '').trim();
|
|
54
|
+
if (explicitProfileRoot) {
|
|
55
|
+
return normalizePathForPlatform(explicitProfileRoot, platform);
|
|
56
|
+
}
|
|
57
|
+
const pathApi = platform === 'win32' ? path.win32 : path;
|
|
58
|
+
return pathApi.join(resolveWebautoRoot(options), 'profiles');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const CONFIG_DIR = resolveWebautoRoot();
|
|
62
|
+
export const PROFILES_DIR = resolveProfilesDir();
|
|
8
63
|
export const CONFIG_FILE = path.join(CONFIG_DIR, 'camo-cli.json');
|
|
9
64
|
export const PROFILE_META_FILE = 'camo-profile.json';
|
|
10
65
|
export const BROWSER_SERVICE_URL = process.env.WEBAUTO_BROWSER_URL || 'http://127.0.0.1:7704';
|
package/src/utils/help.mjs
CHANGED
|
@@ -173,7 +173,11 @@ PROGRESS EVENTS:
|
|
|
173
173
|
|
|
174
174
|
ENV:
|
|
175
175
|
WEBAUTO_BROWSER_URL Default: http://127.0.0.1:7704
|
|
176
|
-
|
|
176
|
+
WEBAUTO_INSTALL_DIR Optional @web-auto/webauto install dir
|
|
177
|
+
WEBAUTO_REPO_ROOT Optional webauto repo root (dev mode)
|
|
178
|
+
WEBAUTO_DATA_ROOT / WEBAUTO_HOME Optional data root (Windows default D:\\webauto)
|
|
179
|
+
WEBAUTO_PROFILE_ROOT Optional profile dir override
|
|
180
|
+
WEBAUTO_ROOT Legacy data root (auto-appends .webauto if needed)
|
|
177
181
|
CAMO_PROGRESS_EVENTS_FILE Optional path override for progress jsonl
|
|
178
182
|
CAMO_PROGRESS_WS_HOST / CAMO_PROGRESS_WS_PORT Progress daemon host/port (defaults: 127.0.0.1:7788)
|
|
179
183
|
`);
|