191 lines
8.5 KiB
Python
191 lines
8.5 KiB
Python
#!/usr/bin/env python3
|
|
import re
|
|
import argparse
|
|
import atexit # Import atexit
|
|
import shutil # Import shutil for directory removal
|
|
import logging # Import the logging module
|
|
import sys # Import sys for stdout
|
|
import os # Import os module
|
|
from camoufox.server import launch_server
|
|
|
|
def parse_proxy_url(url):
|
|
"""Parse proxy URL in format proto://user:pass@host:port"""
|
|
pattern = r'([^:]+)://(?:([^:]+):([^@]+)@)?([^:]+):(\d+)'
|
|
match = re.match(pattern, url)
|
|
if not match:
|
|
raise ValueError('Invalid proxy URL format. Expected proto://[user:pass@]host:port')
|
|
|
|
proto, username, password, host, port = match.groups()
|
|
|
|
# Ensure username and password are strings, not None
|
|
proxy_config = {
|
|
'server': f'{proto}://{host}:{port}',
|
|
'username': username or '',
|
|
'password': password or ''
|
|
}
|
|
|
|
# Remove empty credentials
|
|
if not proxy_config['username']:
|
|
del proxy_config['username']
|
|
if not proxy_config['password']:
|
|
del proxy_config['password']
|
|
|
|
return proxy_config
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='Launch Camoufox server with optional proxy support')
|
|
parser.add_argument('--proxy-url', help='Optional proxy URL in format proto://user:pass@host:port (supports http, https, socks5)')
|
|
parser.add_argument('--ws-host', default='localhost', help='WebSocket server host address (e.g., localhost, 0.0.0.0)')
|
|
parser.add_argument('--port', type=int, default=0, help='WebSocket server port (0 for random)')
|
|
parser.add_argument('--ws-path', default='camoufox', help='WebSocket server path')
|
|
parser.add_argument('--headless', action='store_true', help='Run browser in headless mode')
|
|
parser.add_argument('--geoip', nargs='?', const=True, default=False,
|
|
help='Enable geo IP protection. Can specify IP address or use True for automatic detection')
|
|
parser.add_argument('--locale', help='Locale(s) to use (e.g. "en-US" or "en-US,fr-FR")')
|
|
parser.add_argument('--block-images', action='store_true', help='Block image requests to save bandwidth')
|
|
parser.add_argument('--block-webrtc', action='store_true', help='Block WebRTC entirely')
|
|
parser.add_argument('--humanize', nargs='?', const=True, type=float,
|
|
help='Humanize cursor movements. Can specify max duration in seconds')
|
|
parser.add_argument('--extensions', type=str,
|
|
help='Comma-separated list of extension paths to enable (XPI files or extracted directories). Use quotes if paths contain spaces.')
|
|
|
|
args = parser.parse_args()
|
|
|
|
proxy_config = None
|
|
if args.proxy_url:
|
|
try:
|
|
proxy_config = parse_proxy_url(args.proxy_url)
|
|
print(f"Using proxy configuration: {args.proxy_url}")
|
|
except ValueError as e:
|
|
print(f'Error parsing proxy URL: {e}')
|
|
return
|
|
else:
|
|
print("No proxy URL provided. Running without proxy.")
|
|
|
|
# --- Basic Logging Configuration ---
|
|
# Configure the root logger to show INFO level messages
|
|
# This might capture logs from camoufox or its dependencies (like websockets)
|
|
log_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
log_handler = logging.StreamHandler(sys.stdout) # Log to standard output
|
|
log_handler.setFormatter(log_formatter)
|
|
|
|
root_logger = logging.getLogger()
|
|
# Remove existing handlers to avoid duplicates if script is re-run in same process
|
|
for handler in root_logger.handlers[:]:
|
|
root_logger.removeHandler(handler)
|
|
root_logger.addHandler(log_handler)
|
|
# Set level to DEBUG for more detailed output from Camoufox/Playwright
|
|
root_logger.setLevel(logging.DEBUG)
|
|
|
|
logging.debug("DEBUG logging enabled. Starting Camoufox server setup...")
|
|
# --- End Logging Configuration ---
|
|
|
|
try:
|
|
# --- Check DISPLAY environment variable ---
|
|
display_var = os.environ.get('DISPLAY')
|
|
logging.info(f"Value of DISPLAY environment variable: {display_var}")
|
|
# --- End Check ---
|
|
|
|
# Build config dictionary
|
|
config = {
|
|
'headless': args.headless,
|
|
'geoip': args.geoip,
|
|
# 'proxy': proxy_config, # Add proxy config only if it exists
|
|
'host': args.ws_host, # Add the host argument
|
|
'port': args.port,
|
|
'ws_path': args.ws_path,
|
|
# Explicitly pass DISPLAY environment variable to Playwright
|
|
'env': {'DISPLAY': os.environ.get('DISPLAY')}
|
|
}
|
|
# Add proxy to config only if it was successfully parsed
|
|
if proxy_config:
|
|
config['proxy'] = proxy_config
|
|
|
|
# Add optional parameters
|
|
if args.locale:
|
|
config['locale'] = args.locale
|
|
if args.block_images:
|
|
config['block_images'] = True
|
|
if args.block_webrtc:
|
|
config['block_webrtc'] = True
|
|
if args.humanize:
|
|
config['humanize'] = args.humanize if isinstance(args.humanize, float) else True
|
|
|
|
# Exclude default addons including uBlock Origin
|
|
config['exclude_addons'] = ['ublock_origin', 'default_addons']
|
|
print('Excluded default addons including uBlock Origin')
|
|
|
|
# Add custom extensions if specified
|
|
if args.extensions:
|
|
from pathlib import Path
|
|
valid_extensions = []
|
|
|
|
# Split comma-separated extensions
|
|
extensions_list = [ext.strip() for ext in args.extensions.split(',')]
|
|
temp_dirs_to_cleanup = [] # List to store temp dirs
|
|
|
|
# Register cleanup function
|
|
def cleanup_temp_dirs():
|
|
for temp_dir in temp_dirs_to_cleanup:
|
|
try:
|
|
shutil.rmtree(temp_dir)
|
|
print(f"Cleaned up temporary extension directory: {temp_dir}")
|
|
except Exception as e:
|
|
print(f"Warning: Failed to clean up temp dir {temp_dir}: {e}")
|
|
atexit.register(cleanup_temp_dirs)
|
|
|
|
for ext_path in extensions_list:
|
|
# Convert to absolute path
|
|
ext_path = Path(ext_path).absolute()
|
|
|
|
if not ext_path.exists():
|
|
print(f"Warning: Extension path does not exist: {ext_path}")
|
|
continue
|
|
|
|
if ext_path.is_file() and ext_path.suffix == '.xpi':
|
|
# Extract XPI to temporary directory
|
|
import tempfile
|
|
import zipfile
|
|
|
|
try:
|
|
temp_dir = tempfile.mkdtemp(prefix=f"camoufox_ext_{ext_path.stem}_")
|
|
temp_dirs_to_cleanup.append(temp_dir) # Add to cleanup list
|
|
with zipfile.ZipFile(ext_path, 'r') as zip_ref:
|
|
zip_ref.extractall(temp_dir)
|
|
valid_extensions.append(temp_dir)
|
|
print(f"Successfully loaded extension: {ext_path.name} (extracted to {temp_dir})")
|
|
except Exception as e:
|
|
print(f"Error loading extension {ext_path}: {str(e)}")
|
|
# Remove from cleanup list if extraction failed before adding to valid_extensions
|
|
if temp_dir in temp_dirs_to_cleanup:
|
|
temp_dirs_to_cleanup.remove(temp_dir)
|
|
continue
|
|
elif ext_path.is_dir():
|
|
# Check if it's a valid Firefox extension
|
|
if (ext_path / 'manifest.json').exists():
|
|
valid_extensions.append(str(ext_path))
|
|
print(f"Successfully loaded extension: {ext_path.name}")
|
|
else:
|
|
print(f"Warning: Directory is not a valid Firefox extension: {ext_path}")
|
|
else:
|
|
print(f"Warning: Invalid extension path: {ext_path}")
|
|
|
|
if valid_extensions:
|
|
config['addons'] = valid_extensions
|
|
print(f"Loaded {len(valid_extensions)} extensions")
|
|
else:
|
|
print("Warning: No valid extensions were loaded")
|
|
|
|
server = launch_server(**config)
|
|
except Exception as e:
|
|
print(f'Error launching server: {str(e)}')
|
|
if 'Browser.setBrowserProxy' in str(e):
|
|
print('Note: The browser may not support SOCKS5 proxy authentication')
|
|
return
|
|
|
|
print(f'\nCamoufox server started successfully!')
|
|
print(f'WebSocket endpoint: {server.ws_endpoint}\n')
|
|
|
|
if __name__ == '__main__':
|
|
main()
|