mirror of
https://forge.fsky.io/oneflux/omegafox.git
synced 2026-02-10 06:32:05 -08:00
pythonlib: Add remote server launching #7
Uses a hacky work around to run Javascript in Playwright's internal library to gain access to the launchServer method.
This commit is contained in:
parent
7985eec493
commit
79c436e506
7 changed files with 172 additions and 6 deletions
|
|
@ -45,7 +45,9 @@ Options:
|
|||
|
||||
Commands:
|
||||
fetch Fetch the latest version of Camoufox
|
||||
path Display the path to the Camoufox executable
|
||||
remove Remove all downloaded files
|
||||
server Launch a Playwright server
|
||||
test Open the Playwright inspector
|
||||
version Display the current version
|
||||
```
|
||||
|
|
@ -192,6 +194,53 @@ with Camoufox(
|
|||
|
||||
<hr width=50>
|
||||
|
||||
### Remote Server (experimental)
|
||||
|
||||
> [!WARNING]
|
||||
> This feature is experimental and not meant for production use. It uses a hacky workaround to gain access to undocumented Playwright methods.
|
||||
|
||||
#### Launching
|
||||
|
||||
To launch a remote server, run the following CLI command:
|
||||
|
||||
```bash
|
||||
python -m camoufox server
|
||||
```
|
||||
|
||||
Or, configure the server with a launch script:
|
||||
|
||||
```python
|
||||
from camoufox.server import launch_server
|
||||
|
||||
launch_server(
|
||||
headless=True,
|
||||
geoip=True,
|
||||
proxy={
|
||||
'server': 'http://example.com:8080',
|
||||
'username': 'username',
|
||||
'password': 'password'
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
#### Connecting
|
||||
|
||||
To connect to the remote server, use Playwright's `connect` method:
|
||||
|
||||
```python
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
with sync_playwright() as p:
|
||||
# Example endpoint
|
||||
browser = p.firefox.connect('ws://localhost:34091/8c7c6cdea3368d937ef7db2277d6647b')
|
||||
page = browser.new_page()
|
||||
...
|
||||
```
|
||||
|
||||
**Note:** Because servers only use **one browser instance**, fingerprints will not rotate between sessions. If you plan on using Camoufox at scale, consider rotating the server between sessions.
|
||||
|
||||
<hr width=50>
|
||||
|
||||
### BrowserForge Integration
|
||||
|
||||
Camoufox is compatible with [BrowserForge](https://github.com/daijro/browserforge) fingerprints.
|
||||
|
|
|
|||
|
|
@ -104,6 +104,16 @@ def test(url: Optional[str] = None) -> None:
|
|||
page.pause() # Open the Playwright inspector
|
||||
|
||||
|
||||
@cli.command(name='server')
|
||||
def server() -> None:
|
||||
"""
|
||||
Launch a Playwright server
|
||||
"""
|
||||
from .server import launch_server
|
||||
|
||||
launch_server()
|
||||
|
||||
|
||||
@cli.command(name='path')
|
||||
def path() -> None:
|
||||
"""
|
||||
|
|
|
|||
40
pythonlib/camoufox/launchServer.js
Normal file
40
pythonlib/camoufox/launchServer.js
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
// Workaround that accesses Playwright's undocumented `launchServer` method in Python
|
||||
// Without having to use the Node.js Playwright library.
|
||||
|
||||
const { BrowserServerLauncherImpl } = require(`${process.cwd()}/lib/browserServerImpl.js`)
|
||||
|
||||
function collectData() {
|
||||
return new Promise((resolve) => {
|
||||
let data = '';
|
||||
process.stdin.setEncoding('utf8');
|
||||
|
||||
process.stdin.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
process.stdin.on('end', () => {
|
||||
resolve(JSON.parse(data));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
collectData().then((options) => {
|
||||
console.time('Server launched');
|
||||
console.info('Launching server...');
|
||||
|
||||
const server = new BrowserServerLauncherImpl('firefox')
|
||||
|
||||
// Call Playwright's `launchServer` method
|
||||
server.launchServer(options).then(browserServer => {
|
||||
console.timeEnd('Server launched');
|
||||
console.log('Websocket endpoint:\x1b[93m', browserServer.wsEndpoint(), '\x1b[0m');
|
||||
// Continue forever
|
||||
process.stdin.resume();
|
||||
}).catch(error => {
|
||||
console.error('Error launching server:', error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
}).catch((error) => {
|
||||
console.error('Error collecting data:', error.message);
|
||||
process.exit(1); // Exit with error code
|
||||
});
|
||||
|
|
@ -1,14 +1,12 @@
|
|||
import os
|
||||
import xml.etree.ElementTree as ET # nosec
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from random import choice as randchoice
|
||||
from typing import Any, Dict, List, Optional, Tuple, cast
|
||||
|
||||
import numpy as np
|
||||
from language_tags import tags
|
||||
|
||||
from camoufox.pkgman import rprint, webdl
|
||||
from camoufox.pkgman import LOCAL_DATA, rprint, webdl
|
||||
|
||||
from .exceptions import NotInstalledGeoIPExtra, UnknownIPLocation, UnknownTerritory
|
||||
from .ip import validate_ip
|
||||
|
|
@ -20,8 +18,6 @@ except ImportError:
|
|||
else:
|
||||
ALLOW_GEOIP = True
|
||||
|
||||
LOCAL_DATA = Path(os.path.abspath(__file__)).parent
|
||||
|
||||
|
||||
"""
|
||||
Data structures for locale and geolocation info
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ if sys.platform not in OS_MAP:
|
|||
OS_NAME: Literal['mac', 'win', 'lin'] = OS_MAP[sys.platform]
|
||||
|
||||
INSTALL_DIR: Path = Path(user_cache_dir("camoufox"))
|
||||
LOCAL_DATA: Path = Path(os.path.abspath(__file__)).parent
|
||||
|
||||
# The supported architectures for each OS
|
||||
OS_ARCH_MATRIX: dict[str, List[str]] = {
|
||||
|
|
|
|||
70
pythonlib/camoufox/server.py
Normal file
70
pythonlib/camoufox/server.py
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, NoReturn, Tuple, Union
|
||||
|
||||
import orjson
|
||||
from playwright._impl._driver import compute_driver_executable
|
||||
|
||||
from camoufox.pkgman import LOCAL_DATA
|
||||
from camoufox.utils import get_launch_options
|
||||
|
||||
LAUNCH_SCRIPT: Path = LOCAL_DATA / "launchServer.js"
|
||||
|
||||
|
||||
def camel_case(snake_str: str) -> str:
|
||||
"""
|
||||
Convert a string to camelCase
|
||||
"""
|
||||
if len(snake_str) < 2:
|
||||
return snake_str
|
||||
camel_case_str = ''.join(x.capitalize() for x in snake_str.lower().split('_'))
|
||||
return camel_case_str[0].lower() + camel_case_str[1:]
|
||||
|
||||
|
||||
def to_camel_case_dict(data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Convert a dictionary to camelCase
|
||||
"""
|
||||
return {camel_case(key): value for key, value in data.items()}
|
||||
|
||||
|
||||
def get_nodejs() -> str:
|
||||
"""
|
||||
Get the bundled Node.js executable
|
||||
"""
|
||||
# Note: Older versions of Playwright return a string rather than a tuple.
|
||||
_nodejs: Union[str, Tuple[str, ...]] = compute_driver_executable()[0]
|
||||
if isinstance(_nodejs, tuple):
|
||||
return _nodejs[0]
|
||||
return _nodejs
|
||||
|
||||
|
||||
def launch_server(**kwargs) -> NoReturn:
|
||||
"""
|
||||
Launch a Playwright server. Takes the same arguments as `Camoufox()`.
|
||||
Prints the websocket endpoint to the console.
|
||||
"""
|
||||
config = get_launch_options(**kwargs)
|
||||
nodejs = get_nodejs()
|
||||
|
||||
data = orjson.dumps(to_camel_case_dict(config)).decode()
|
||||
|
||||
process = subprocess.Popen( # nosec
|
||||
[
|
||||
nodejs,
|
||||
str(LAUNCH_SCRIPT),
|
||||
],
|
||||
cwd=Path(nodejs).parent / "package",
|
||||
stdin=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
# Write data to stdin and close the stream
|
||||
if process.stdin:
|
||||
process.stdin.write(data)
|
||||
process.stdin.close()
|
||||
|
||||
# Wait forever
|
||||
process.wait()
|
||||
|
||||
# Add an explicit return statement to satisfy the NoReturn type hint
|
||||
raise RuntimeError("Server process terminated unexpectedly")
|
||||
|
|
@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|||
|
||||
[tool.poetry]
|
||||
name = "camoufox"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
description = "Wrapper around Playwright to help launch Camoufox"
|
||||
authors = ["daijro <daijro.dev@gmail.com>"]
|
||||
license = "MIT"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue