@web-auto/camo 0.1.2
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/LICENSE +21 -0
- package/README.md +243 -0
- package/bin/camo.mjs +22 -0
- package/package.json +39 -0
- package/scripts/build.mjs +19 -0
- package/scripts/bump-version.mjs +38 -0
- package/scripts/install.mjs +76 -0
- package/scripts/release.sh +54 -0
- package/src/cli.mjs +199 -0
- package/src/commands/browser.mjs +462 -0
- package/src/commands/cookies.mjs +69 -0
- package/src/commands/create.mjs +98 -0
- package/src/commands/init.mjs +68 -0
- package/src/commands/lifecycle.mjs +256 -0
- package/src/commands/mouse.mjs +49 -0
- package/src/commands/profile.mjs +46 -0
- package/src/commands/system.mjs +14 -0
- package/src/commands/window.mjs +31 -0
- package/src/lifecycle/cleanup.mjs +83 -0
- package/src/lifecycle/lock.mjs +122 -0
- package/src/lifecycle/session-registry.mjs +163 -0
- package/src/utils/args.mjs +25 -0
- package/src/utils/browser-service.mjs +194 -0
- package/src/utils/config.mjs +90 -0
- package/src/utils/fingerprint.mjs +181 -0
- package/src/utils/help.mjs +128 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import https from 'node:https';
|
|
5
|
+
import { execSync } from 'node:child_process';
|
|
6
|
+
import { CONFIG_DIR, ensureDir } from './config.mjs';
|
|
7
|
+
|
|
8
|
+
const GEOIP_DIR = path.join(CONFIG_DIR, 'geoip');
|
|
9
|
+
const GEOIP_MMDB = path.join(GEOIP_DIR, 'GeoLite2-City.mmdb');
|
|
10
|
+
|
|
11
|
+
// GeoIP regions mapping
|
|
12
|
+
export const GEOIP_REGIONS = {
|
|
13
|
+
'us': { country: 'United States', timezone: 'America/New_York', locale: 'en-US', city: 'New York' },
|
|
14
|
+
'us-west': { country: 'United States', timezone: 'America/Los_Angeles', locale: 'en-US', city: 'Los Angeles' },
|
|
15
|
+
'uk': { country: 'United Kingdom', timezone: 'Europe/London', locale: 'en-GB', city: 'London' },
|
|
16
|
+
'de': { country: 'Germany', timezone: 'Europe/Berlin', locale: 'de-DE', city: 'Berlin' },
|
|
17
|
+
'fr': { country: 'France', timezone: 'Europe/Paris', locale: 'fr-FR', city: 'Paris' },
|
|
18
|
+
'jp': { country: 'Japan', timezone: 'Asia/Tokyo', locale: 'ja-JP', city: 'Tokyo' },
|
|
19
|
+
'sg': { country: 'Singapore', timezone: 'Asia/Singapore', locale: 'en-SG', city: 'Singapore' },
|
|
20
|
+
'au': { country: 'Australia', timezone: 'Australia/Sydney', locale: 'en-AU', city: 'Sydney' },
|
|
21
|
+
'br': { country: 'Brazil', timezone: 'America/Sao_Paulo', locale: 'pt-BR', city: 'Sao Paulo' },
|
|
22
|
+
'in': { country: 'India', timezone: 'Asia/Kolkata', locale: 'en-IN', city: 'Mumbai' },
|
|
23
|
+
'hk': { country: 'Hong Kong', timezone: 'Asia/Hong_Kong', locale: 'zh-HK', city: 'Hong Kong' },
|
|
24
|
+
'tw': { country: 'Taiwan', timezone: 'Asia/Taipei', locale: 'zh-TW', city: 'Taipei' },
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// OS options for fingerprint
|
|
28
|
+
export const OS_OPTIONS = {
|
|
29
|
+
'mac': { platform: 'darwin', os: 'Macintosh', osVersion: '14.0', cpuCores: 8, memory: 16 },
|
|
30
|
+
'mac-m1': { platform: 'darwin', os: 'Macintosh', osVersion: '14.0', cpuCores: 8, memory: 16, arch: 'arm64' },
|
|
31
|
+
'mac-intel': { platform: 'darwin', os: 'Macintosh', osVersion: '14.0', cpuCores: 8, memory: 16, arch: 'x64' },
|
|
32
|
+
'windows': { platform: 'win32', os: 'Windows', osVersion: '11', cpuCores: 8, memory: 16 },
|
|
33
|
+
'windows-10': { platform: 'win32', os: 'Windows', osVersion: '10', cpuCores: 4, memory: 8 },
|
|
34
|
+
'linux': { platform: 'linux', os: 'Linux', osVersion: 'Ubuntu 22.04', cpuCores: 4, memory: 8 },
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export function hasGeoIP() {
|
|
38
|
+
return fs.existsSync(GEOIP_MMDB);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getGeoIPPath() {
|
|
42
|
+
return GEOIP_MMDB;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function downloadGeoIP(progressCb = console.log) {
|
|
46
|
+
ensureDir(GEOIP_DIR);
|
|
47
|
+
|
|
48
|
+
if (hasGeoIP()) {
|
|
49
|
+
progressCb('GeoIP database already exists.');
|
|
50
|
+
return GEOIP_MMDB;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
progressCb('Downloading GeoLite2-City database...');
|
|
54
|
+
|
|
55
|
+
// Use MaxMind's public GeoLite2 database
|
|
56
|
+
const url = 'https://git.io/GeoLite2-City.mmdb';
|
|
57
|
+
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
const file = fs.createWriteStream(GEOIP_MMDB + '.tmp');
|
|
60
|
+
|
|
61
|
+
https.get(url, (response) => {
|
|
62
|
+
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
63
|
+
// Follow redirect
|
|
64
|
+
const locUrl = response.headers.location;
|
|
65
|
+
https.get(locUrl, (resp) => {
|
|
66
|
+
const total = parseInt(resp.headers['content-length'], 10);
|
|
67
|
+
let downloaded = 0;
|
|
68
|
+
|
|
69
|
+
resp.on('data', (chunk) => {
|
|
70
|
+
downloaded += chunk.length;
|
|
71
|
+
if (total) {
|
|
72
|
+
const pct = Math.floor((downloaded / total) * 100);
|
|
73
|
+
if (pct % 10 === 0) {
|
|
74
|
+
progressCb(`Downloading GeoIP: ${pct}%`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
resp.pipe(file);
|
|
80
|
+
file.on('finish', () => {
|
|
81
|
+
file.close();
|
|
82
|
+
fs.renameSync(GEOIP_MMDB + '.tmp', GEOIP_MMDB);
|
|
83
|
+
progressCb('GeoIP database downloaded successfully.');
|
|
84
|
+
resolve(GEOIP_MMDB);
|
|
85
|
+
});
|
|
86
|
+
}).on('error', (err) => {
|
|
87
|
+
fs.unlinkSync(GEOIP_MMDB + '.tmp');
|
|
88
|
+
reject(new Error(`Failed to download GeoIP: ${err.message}`));
|
|
89
|
+
});
|
|
90
|
+
} else {
|
|
91
|
+
response.pipe(file);
|
|
92
|
+
file.on('finish', () => {
|
|
93
|
+
file.close();
|
|
94
|
+
fs.renameSync(GEOIP_MMDB + '.tmp', GEOIP_MMDB);
|
|
95
|
+
progressCb('GeoIP database downloaded successfully.');
|
|
96
|
+
resolve(GEOIP_MMDB);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}).on('error', (err) => {
|
|
100
|
+
fs.unlinkSync(GEOIP_MMDB + '.tmp');
|
|
101
|
+
reject(new Error(`Failed to download GeoIP: ${err.message}`));
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function generateFingerprint(options = {}) {
|
|
107
|
+
const osKey = options.os || 'mac';
|
|
108
|
+
const regionKey = options.region || 'us';
|
|
109
|
+
|
|
110
|
+
const osConfig = OS_OPTIONS[osKey] || OS_OPTIONS['mac'];
|
|
111
|
+
const regionConfig = GEOIP_REGIONS[regionKey] || GEOIP_REGIONS['us'];
|
|
112
|
+
|
|
113
|
+
const screenResolutions = [
|
|
114
|
+
{ width: 1920, height: 1080 },
|
|
115
|
+
{ width: 2560, height: 1440 },
|
|
116
|
+
{ width: 1440, height: 900 },
|
|
117
|
+
{ width: 1366, height: 768 },
|
|
118
|
+
{ width: 1536, height: 864 },
|
|
119
|
+
];
|
|
120
|
+
|
|
121
|
+
const webGLRenderers = {
|
|
122
|
+
'mac': ['Apple M1', 'Apple M2', 'Intel Iris Plus Graphics'],
|
|
123
|
+
'mac-m1': ['Apple M1', 'Apple M2'],
|
|
124
|
+
'mac-intel': ['Intel Iris Plus Graphics', 'Intel UHD Graphics 630'],
|
|
125
|
+
'windows': ['NVIDIA GeForce RTX 3080', 'NVIDIA GeForce RTX 3070', 'AMD Radeon RX 6800'],
|
|
126
|
+
'windows-10': ['NVIDIA GeForce GTX 1660', 'AMD Radeon RX 580'],
|
|
127
|
+
'linux': ['NVIDIA GeForce RTX 3060', 'AMD Radeon RX 6600', 'Mesa Intel Graphics'],
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const screen = screenResolutions[Math.floor(Math.random() * screenResolutions.length)];
|
|
131
|
+
const webglRenderer = (webGLRenderers[osKey] || webGLRenderers['mac'])[Math.floor(Math.random() * (webGLRenderers[osKey] || webGLRenderers['mac']).length)];
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
os: osConfig.os,
|
|
135
|
+
osVersion: osConfig.osVersion,
|
|
136
|
+
platform: osConfig.platform,
|
|
137
|
+
arch: osConfig.arch || (osConfig.platform === 'darwin' ? 'arm64' : 'x64'),
|
|
138
|
+
cpuCores: osConfig.cpuCores,
|
|
139
|
+
memory: osConfig.memory,
|
|
140
|
+
timezone: regionConfig.timezone,
|
|
141
|
+
locale: regionConfig.locale,
|
|
142
|
+
country: regionConfig.country,
|
|
143
|
+
city: regionConfig.city,
|
|
144
|
+
screen,
|
|
145
|
+
webgl: {
|
|
146
|
+
vendor: 'Google Inc. (Apple)',
|
|
147
|
+
renderer: webglRenderer,
|
|
148
|
+
},
|
|
149
|
+
userAgent: buildUserAgent(osConfig, regionConfig),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function buildUserAgent(osConfig, regionConfig) {
|
|
154
|
+
const chromeVersion = '120.0.0.0';
|
|
155
|
+
|
|
156
|
+
if (osConfig.platform === 'darwin') {
|
|
157
|
+
return `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36`;
|
|
158
|
+
} else if (osConfig.platform === 'win32') {
|
|
159
|
+
return `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36`;
|
|
160
|
+
} else {
|
|
161
|
+
return `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36`;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function listAvailableRegions() {
|
|
166
|
+
return Object.entries(GEOIP_REGIONS).map(([key, config]) => ({
|
|
167
|
+
key,
|
|
168
|
+
country: config.country,
|
|
169
|
+
city: config.city,
|
|
170
|
+
timezone: config.timezone,
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function listAvailableOS() {
|
|
175
|
+
return Object.entries(OS_OPTIONS).map(([key, config]) => ({
|
|
176
|
+
key,
|
|
177
|
+
os: config.os,
|
|
178
|
+
osVersion: config.osVersion,
|
|
179
|
+
platform: config.platform,
|
|
180
|
+
}));
|
|
181
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
export function printHelp() {
|
|
2
|
+
console.log(`
|
|
3
|
+
camo CLI - Camoufox browser controller
|
|
4
|
+
|
|
5
|
+
USAGE:
|
|
6
|
+
camo <command> [options]
|
|
7
|
+
|
|
8
|
+
PROFILE MANAGEMENT:
|
|
9
|
+
profiles List profiles with default profile
|
|
10
|
+
profile [list] List profiles (same as profiles)
|
|
11
|
+
profile create <profileId> Create a profile
|
|
12
|
+
profile delete <profileId> Delete a profile
|
|
13
|
+
profile default [profileId] Get or set default profile
|
|
14
|
+
|
|
15
|
+
INITIALIZATION:
|
|
16
|
+
init Ensure camoufox + browser-service
|
|
17
|
+
init geoip Download GeoIP database
|
|
18
|
+
init list List available OS and region options
|
|
19
|
+
create fingerprint --os <os> --region <r> Create browser fingerprint
|
|
20
|
+
create profile <profileId> Create a new profile
|
|
21
|
+
|
|
22
|
+
CONFIG:
|
|
23
|
+
config repo-root [path] Get or set persisted webauto repo root
|
|
24
|
+
|
|
25
|
+
BROWSER CONTROL:
|
|
26
|
+
init Ensure camoufox + ensure browser-service daemon
|
|
27
|
+
start [profileId] [--url <url>] [--headless]
|
|
28
|
+
stop [profileId]
|
|
29
|
+
status [profileId]
|
|
30
|
+
list Alias of status
|
|
31
|
+
|
|
32
|
+
LIFECYCLE & CLEANUP:
|
|
33
|
+
sessions List active browser sessions
|
|
34
|
+
cleanup [profileId] Cleanup session (release lock + stop)
|
|
35
|
+
cleanup all Cleanup all active sessions
|
|
36
|
+
cleanup locks Cleanup stale lock files
|
|
37
|
+
force-stop [profileId] Force stop session (for stuck sessions)
|
|
38
|
+
lock list List active session locks
|
|
39
|
+
lock [profileId] Show lock info for profile
|
|
40
|
+
unlock [profileId] Release lock for profile
|
|
41
|
+
|
|
42
|
+
NAVIGATION:
|
|
43
|
+
goto [profileId] <url> Navigate to URL (uses default if profileId omitted)
|
|
44
|
+
back [profileId] Navigate back (uses default)
|
|
45
|
+
screenshot [profileId] [--output <file>] [--full]
|
|
46
|
+
|
|
47
|
+
INTERACTION:
|
|
48
|
+
scroll [profileId] [--down|--up|--left|--right] [--amount <px>]
|
|
49
|
+
click [profileId] <selector> Click element by CSS selector
|
|
50
|
+
type [profileId] <selector> <text> Type text into element
|
|
51
|
+
highlight [profileId] <selector> Highlight element (red border, 2s)
|
|
52
|
+
clear-highlight [profileId] Clear all highlights
|
|
53
|
+
viewport [profileId] --width <w> --height <h>
|
|
54
|
+
|
|
55
|
+
PAGES:
|
|
56
|
+
new-page [profileId] [--url <url>]
|
|
57
|
+
close-page [profileId] [index]
|
|
58
|
+
switch-page [profileId] <index>
|
|
59
|
+
list-pages [profileId]
|
|
60
|
+
|
|
61
|
+
COOKIES:
|
|
62
|
+
cookies get [profileId] Get all cookies for profile
|
|
63
|
+
cookies save [profileId] --path <file> Save cookies to file
|
|
64
|
+
cookies load [profileId] --path <file> Load cookies from file
|
|
65
|
+
cookies auto start [profileId] [--interval <ms>] Start auto-saving cookies
|
|
66
|
+
cookies auto stop [profileId] Stop auto-saving
|
|
67
|
+
cookies auto status [profileId] Check auto-save status
|
|
68
|
+
|
|
69
|
+
WINDOW:
|
|
70
|
+
window move [profileId] --x <x> --y <y> Move browser window
|
|
71
|
+
window resize [profileId] --width <w> --height <h> Resize browser window
|
|
72
|
+
|
|
73
|
+
MOUSE:
|
|
74
|
+
mouse move [profileId] --x <x> --y <y> [--steps <n>] Move mouse to coordinates
|
|
75
|
+
mouse click [profileId] --x <x> --y <y> [--button left|right|middle] [--clicks <n>] [--delay <ms>] Click at coordinates
|
|
76
|
+
mouse wheel [profileId] [--deltax <px>] [--deltay <px>] Scroll wheel
|
|
77
|
+
|
|
78
|
+
SYSTEM:
|
|
79
|
+
system display Show display metrics
|
|
80
|
+
|
|
81
|
+
SYSTEM:
|
|
82
|
+
shutdown Shutdown browser-service (stops all sessions)
|
|
83
|
+
help
|
|
84
|
+
|
|
85
|
+
EXAMPLES:
|
|
86
|
+
camo init
|
|
87
|
+
camo init geoip
|
|
88
|
+
camo init list
|
|
89
|
+
camo create fingerprint --os mac --region us
|
|
90
|
+
camo create fingerprint --os windows --region uk
|
|
91
|
+
camo profile create myprofile
|
|
92
|
+
camo profile default myprofile
|
|
93
|
+
camo start --url https://example.com
|
|
94
|
+
camo goto https://www.xiaohongshu.com
|
|
95
|
+
camo scroll --down --amount 500
|
|
96
|
+
camo click "#search-input"
|
|
97
|
+
camo type "#search-input" "hello world"
|
|
98
|
+
camo highlight ".post-card"
|
|
99
|
+
camo viewport --width 1920 --height 1080
|
|
100
|
+
camo cookies get
|
|
101
|
+
camo cookies save --path /tmp/cookies.json
|
|
102
|
+
camo cookies load --path /tmp/cookies.json
|
|
103
|
+
camo cookies auto start --interval 5000
|
|
104
|
+
camo window move --x 100 --y 100
|
|
105
|
+
camo window resize --width 1920 --height 1080
|
|
106
|
+
camo mouse move --x 500 --y 300
|
|
107
|
+
camo mouse click --x 500 --y 300 --button left
|
|
108
|
+
camo mouse wheel --deltay -300
|
|
109
|
+
camo system display
|
|
110
|
+
camo sessions
|
|
111
|
+
camo cleanup myprofile
|
|
112
|
+
camo force-stop myprofile
|
|
113
|
+
camo lock list
|
|
114
|
+
camo unlock myprofile
|
|
115
|
+
camo stop
|
|
116
|
+
|
|
117
|
+
ENV:
|
|
118
|
+
WEBAUTO_BROWSER_URL Default: http://127.0.0.1:7704
|
|
119
|
+
WEBAUTO_REPO_ROOT Optional explicit webauto repo root
|
|
120
|
+
`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function printProfilesAndHint(listProfiles, getDefaultProfile) {
|
|
124
|
+
const profiles = listProfiles();
|
|
125
|
+
const defaultProfile = getDefaultProfile();
|
|
126
|
+
console.log(JSON.stringify({ ok: true, profiles, defaultProfile, count: profiles.length }, null, 2));
|
|
127
|
+
console.log('\\nRun \`camo help\` for usage.');
|
|
128
|
+
}
|