@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.
@@ -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
+ }