mirror of
https://github.com/CloakHQ/CloakBrowser.git
synced 2026-06-23 11:41:46 +02:00
feat: Docker Hub image, cloaktest CLI, and example UX improvements
Add cloakhq/cloakbrowser Docker Hub image with Node.js, JS wrapper, Xvfb headed mode, and cloaktest shortcut. Add launch feedback and IP display to all examples. Update README Docker section for Docker Hub.
This commit is contained in:
@@ -9,6 +9,10 @@ Changes are tagged: **[wrapper]** for Python/JS wrapper, **[binary]** for Chromi
|
|||||||
## [0.3.7] — 2026-03-05
|
## [0.3.7] — 2026-03-05
|
||||||
|
|
||||||
- **[wrapper]** Unify timezone parameter: rename `timezone_id` to `timezone` in `launch_context()`, `launch_persistent_context()`, and `launch_persistent_context_async()` (Python). Old `timezone_id` still works with a deprecation warning. JS: deprecate `timezoneId` on `LaunchContextOptions` — use `timezone` (inherited from `LaunchOptions`)
|
- **[wrapper]** Unify timezone parameter: rename `timezone_id` to `timezone` in `launch_context()`, `launch_persistent_context()`, and `launch_persistent_context_async()` (Python). Old `timezone_id` still works with a deprecation warning. JS: deprecate `timezoneId` on `LaunchContextOptions` — use `timezone` (inherited from `LaunchOptions`)
|
||||||
|
- **[wrapper]** Docker Hub image (`cloakhq/cloakbrowser`) — pre-built with Python + JS wrappers, Xvfb for headed mode, and `cloaktest` CLI shortcut. One-liner: `docker run --rm cloakhq/cloakbrowser cloaktest`
|
||||||
|
- **[wrapper]** Add "Launching stealth browser..." feedback to all examples for better UX in Docker/CI
|
||||||
|
- **[wrapper]** Comprehensive unit tests: 169 Python + 88 JS (up from 59 + 47)
|
||||||
|
- **[docs]** Streamline READMEs for launch — reorder for conversion, collapse fingerprint flags, update Docker section
|
||||||
|
|
||||||
## [0.3.6] — 2026-03-04
|
## [0.3.6] — 2026-03-04
|
||||||
|
|
||||||
|
|||||||
+24
-3
@@ -1,6 +1,6 @@
|
|||||||
FROM python:3.12-slim
|
FROM python:3.12-slim
|
||||||
|
|
||||||
# Chromium system deps (matches fingerprint-chromium 142+ requirements)
|
# Chromium system deps + Node.js
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 \
|
libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 \
|
||||||
libdbus-1-3 libdrm2 libxkbcommon0 libatspi2.0-0 libxcomposite1 \
|
libdbus-1-3 libdrm2 libxkbcommon0 libatspi2.0-0 libxcomposite1 \
|
||||||
@@ -9,17 +9,38 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
libxcb1 libxext6 libxshmfence1 \
|
libxcb1 libxext6 libxshmfence1 \
|
||||||
libglib2.0-0 libgtk-3-0 libpangocairo-1.0-0 libcairo-gobject2 \
|
libglib2.0-0 libgtk-3-0 libpangocairo-1.0-0 libcairo-gobject2 \
|
||||||
libgdk-pixbuf-2.0-0 libxss1 libxtst6 fonts-liberation \
|
libgdk-pixbuf-2.0-0 libxss1 libxtst6 fonts-liberation \
|
||||||
|
xvfb xdotool \
|
||||||
|
curl ca-certificates \
|
||||||
|
&& curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
|
||||||
|
&& apt-get install -y --no-install-recommends nodejs \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Python wrapper
|
||||||
COPY pyproject.toml README.md LICENSE BINARY-LICENSE.md CHANGELOG.md ./
|
COPY pyproject.toml README.md LICENSE BINARY-LICENSE.md CHANGELOG.md ./
|
||||||
COPY cloakbrowser/ cloakbrowser/
|
COPY cloakbrowser/ cloakbrowser/
|
||||||
COPY examples/ examples/
|
|
||||||
|
|
||||||
RUN pip install --no-cache-dir .
|
RUN pip install --no-cache-dir .
|
||||||
|
|
||||||
|
# JS wrapper
|
||||||
|
COPY js/ js/
|
||||||
|
RUN cd js && npm install && npm run build
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
COPY examples/ examples/
|
||||||
|
|
||||||
# Pre-download stealth Chromium binary during build (not at runtime)
|
# Pre-download stealth Chromium binary during build (not at runtime)
|
||||||
RUN python -c "from cloakbrowser import ensure_binary; ensure_binary()"
|
RUN python -c "from cloakbrowser import ensure_binary; ensure_binary()"
|
||||||
|
|
||||||
|
# CLI shortcuts
|
||||||
|
COPY bin/cloaktest /usr/local/bin/cloaktest
|
||||||
|
RUN chmod +x /usr/local/bin/cloaktest
|
||||||
|
|
||||||
|
# Xvfb entrypoint for headed mode support
|
||||||
|
COPY bin/docker-entrypoint.sh /entrypoint.sh
|
||||||
|
RUN chmod +x /entrypoint.sh
|
||||||
|
|
||||||
|
ENV DISPLAY=:99
|
||||||
|
|
||||||
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
CMD ["python"]
|
CMD ["python"]
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://pypi.org/project/cloakbrowser/"><img src="https://img.shields.io/pypi/v/cloakbrowser" alt="PyPI"></a>
|
<a href="https://pypi.org/project/cloakbrowser/"><img src="https://img.shields.io/pypi/v/cloakbrowser" alt="PyPI"></a>
|
||||||
<a href="https://www.npmjs.com/package/cloakbrowser"><img src="https://img.shields.io/npm/v/cloakbrowser" alt="npm"></a>
|
<a href="https://www.npmjs.com/package/cloakbrowser"><img src="https://img.shields.io/npm/v/cloakbrowser" alt="npm"></a>
|
||||||
<a href="LICENSE"><img src="https://img.shields.io/github/license/CloakHQ/CloakBrowser" alt="License"></a>
|
<a href="LICENSE"><img src="https://img.shields.io/github/license/cloakhq/cloakbrowser?v=1" alt="License"></a>
|
||||||
<a href="https://github.com/CloakHQ/CloakBrowser"><img src="https://img.shields.io/github/last-commit/CloakHQ/CloakBrowser" alt="Last Commit"></a>
|
<a href="https://github.com/CloakHQ/CloakBrowser"><img src="https://img.shields.io/github/last-commit/cloakhq/cloakbrowser" alt="Last Commit"></a>
|
||||||
<br>
|
<br>
|
||||||
<a href="https://github.com/CloakHQ/CloakBrowser"><img src="https://img.shields.io/github/stars/CloakHQ/CloakBrowser" alt="Stars"></a>
|
<a href="https://github.com/CloakHQ/CloakBrowser"><img src="https://img.shields.io/github/stars/cloakhq/cloakbrowser" alt="Stars"></a>
|
||||||
<a href="https://pepy.tech/projects/cloakbrowser"><img src="https://img.shields.io/pepy/dt/cloakbrowser?label=pypi&logo=pypi&logoColor=white" alt="PyPI Downloads"></a>
|
<a href="https://pepy.tech/projects/cloakbrowser"><img src="https://img.shields.io/pepy/dt/cloakbrowser?label=pypi&logo=pypi&logoColor=white" alt="PyPI Downloads"></a>
|
||||||
<a href="https://www.npmjs.com/package/cloakbrowser"><img src="https://img.shields.io/npm/dt/cloakbrowser?label=npm&logo=npm&logoColor=white" alt="npm Downloads"></a>
|
<a href="https://www.npmjs.com/package/cloakbrowser"><img src="https://img.shields.io/npm/dt/cloakbrowser?label=npm&logo=npm&logoColor=white" alt="npm Downloads"></a>
|
||||||
</p>
|
</p>
|
||||||
@@ -42,6 +42,11 @@ Same API, same code — just swap the import. <strong>3 lines of code, 30 second
|
|||||||
- **`pip install cloakbrowser`** or **`npm install cloakbrowser`** — binary auto-downloads, zero config
|
- **`pip install cloakbrowser`** or **`npm install cloakbrowser`** — binary auto-downloads, zero config
|
||||||
- **Free and open source** — no subscriptions, no usage limits
|
- **Free and open source** — no subscriptions, no usage limits
|
||||||
|
|
||||||
|
**Try it now** — no install needed:
|
||||||
|
```bash
|
||||||
|
docker run --rm cloakhq/cloakbrowser cloaktest
|
||||||
|
```
|
||||||
|
|
||||||
**Python:**
|
**Python:**
|
||||||
```python
|
```python
|
||||||
from cloakbrowser import launch
|
from cloakbrowser import launch
|
||||||
@@ -413,9 +418,6 @@ The binary detects its platform at compile time — a macOS binary reports as ma
|
|||||||
> const browser = await launch({ args: ['--fingerprint=12345'] });
|
> const browser = await launch({ args: ['--fingerprint=12345'] });
|
||||||
> ```
|
> ```
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><strong>Default Fingerprint & All Flags</strong> (click to expand)</summary>
|
|
||||||
|
|
||||||
### Default Fingerprint
|
### Default Fingerprint
|
||||||
|
|
||||||
Every `launch()` call sets these automatically. The **wrapper** applies platform-aware defaults — on Linux it spoofs as Windows for a more common fingerprint, on macOS it runs as a native Mac browser:
|
Every `launch()` call sets these automatically. The **wrapper** applies platform-aware defaults — on Linux it spoofs as Windows for a more common fingerprint, on macOS it runs as a native Mac browser:
|
||||||
@@ -475,8 +477,6 @@ browser = launch(args=[
|
|||||||
])
|
])
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
**Python** — see [`examples/`](examples/):
|
**Python** — see [`examples/`](examples/):
|
||||||
@@ -506,30 +506,24 @@ The wrapper auto-downloads the correct binary for your platform.
|
|||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
|
|
||||||
A ready-to-use [`Dockerfile`](Dockerfile) is included. It installs system deps, the package, and pre-downloads the stealth binary during build:
|
Pre-built image on Docker Hub — no install, no setup:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker build -t cloakbrowser .
|
# Run the stealth test suite
|
||||||
docker run --rm cloakbrowser python examples/basic.py
|
docker run --rm cloakhq/cloakbrowser cloaktest
|
||||||
```
|
|
||||||
|
|
||||||
The key steps in the Dockerfile:
|
# Run your own script
|
||||||
1. **System deps** — Chromium requires ~15 shared libraries (`libnss3`, `libgbm1`, etc.)
|
docker run --rm cloakhq/cloakbrowser python -c "
|
||||||
2. **`pip install .`** — installs CloakBrowser + Playwright
|
from cloakbrowser import launch
|
||||||
3. **`ensure_binary()`** — downloads the stealth Chromium binary at build time (~200MB), so containers start instantly
|
browser = launch()
|
||||||
|
page = browser.new_page()
|
||||||
|
page.goto('https://example.com')
|
||||||
|
print(page.title())
|
||||||
|
browser.close()
|
||||||
|
"
|
||||||
|
|
||||||
To extend with your own script, just add a `COPY` + `CMD`:
|
# With a proxy
|
||||||
|
docker run --rm cloakhq/cloakbrowser python -c "
|
||||||
```dockerfile
|
|
||||||
FROM cloakbrowser
|
|
||||||
COPY your_script.py /app/
|
|
||||||
CMD ["python", "your_script.py"]
|
|
||||||
```
|
|
||||||
|
|
||||||
**With a proxy** (the most common production setup):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run --rm cloakbrowser python -c "
|
|
||||||
from cloakbrowser import launch
|
from cloakbrowser import launch
|
||||||
browser = launch(proxy='http://user:pass@proxy:8080')
|
browser = launch(proxy='http://user:pass@proxy:8080')
|
||||||
page = browser.new_page()
|
page = browser.new_page()
|
||||||
@@ -539,6 +533,20 @@ browser.close()
|
|||||||
"
|
"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To extend with your own script:
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
FROM cloakhq/cloakbrowser
|
||||||
|
COPY your_script.py /app/
|
||||||
|
CMD ["python", "your_script.py"]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Building from source** — a [`Dockerfile`](Dockerfile) is also included if you prefer to build your own image:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t cloakbrowser .
|
||||||
|
```
|
||||||
|
|
||||||
CloakBrowser works identically local, in Docker, and on VPS. No environment-specific config needed.
|
CloakBrowser works identically local, in Docker, and on VPS. No environment-specific config needed.
|
||||||
|
|
||||||
**Note:** If you run CloakBrowser inside a web server with uvloop (e.g., `uvicorn[standard]`), use `--loop asyncio` to avoid subprocess pipe hangs.
|
**Note:** If you run CloakBrowser inside a web server with uvloop (e.g., `uvicorn[standard]`), use `--loop asyncio` to avoid subprocess pipe hangs.
|
||||||
@@ -584,11 +592,12 @@ const browser = await launch({ args: ['--disable-http2'] });
|
|||||||
|
|
||||||
Only use this flag for sites that require it — most sites work fine with HTTP/2.
|
Only use this flag for sites that require it — most sites work fine with HTTP/2.
|
||||||
|
|
||||||
**Something not working? Make sure you're on the latest wrapper**
|
**Something not working? Make sure you're on the latest version**
|
||||||
Older versions may use outdated stealth args or download an older binary:
|
Older versions may use outdated stealth args or download an older binary:
|
||||||
```bash
|
```bash
|
||||||
pip install -U cloakbrowser # Python
|
pip install -U cloakbrowser # Python
|
||||||
npm install cloakbrowser@latest # JavaScript
|
npm install cloakbrowser@latest # JavaScript
|
||||||
|
docker pull cloakhq/cloakbrowser:latest # Docker
|
||||||
```
|
```
|
||||||
|
|
||||||
**Binary download fails / timeout**
|
**Binary download fails / timeout**
|
||||||
|
|||||||
Executable
+3
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Run CloakBrowser stealth test suite
|
||||||
|
exec python -u /app/examples/stealth_test.py --no-screenshots "$@"
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Start Xvfb for headed mode (Turnstile, CAPTCHAs), then run user command
|
||||||
|
Xvfb :99 -screen 0 1920x1080x24 -nolisten tcp &
|
||||||
|
sleep 1
|
||||||
|
exec "$@"
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from cloakbrowser import launch
|
from cloakbrowser import launch
|
||||||
|
|
||||||
|
print("Launching stealth browser...", flush=True)
|
||||||
browser = launch(headless=False)
|
browser = launch(headless=False)
|
||||||
page = browser.new_page()
|
page = browser.new_page()
|
||||||
|
|
||||||
|
|||||||
@@ -185,6 +185,7 @@ def main():
|
|||||||
print(f"Proxy: {PROXY or 'none'}")
|
print(f"Proxy: {PROXY or 'none'}")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
print("Launching stealth browser...", flush=True)
|
||||||
context = launch_context(
|
context = launch_context(
|
||||||
headless=HEADLESS,
|
headless=HEADLESS,
|
||||||
proxy=PROXY,
|
proxy=PROXY,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ PROFILE_DIR = "./my-profile"
|
|||||||
|
|
||||||
# Session 1 — set some state
|
# Session 1 — set some state
|
||||||
print("=== Session 1: Setting state ===")
|
print("=== Session 1: Setting state ===")
|
||||||
|
print("Launching stealth browser...", flush=True)
|
||||||
ctx = launch_persistent_context(PROFILE_DIR, headless=False)
|
ctx = launch_persistent_context(PROFILE_DIR, headless=False)
|
||||||
page = ctx.new_page()
|
page = ctx.new_page()
|
||||||
page.goto("https://example.com")
|
page.goto("https://example.com")
|
||||||
@@ -18,6 +19,7 @@ ctx.close()
|
|||||||
|
|
||||||
# Session 2 — state is restored
|
# Session 2 — state is restored
|
||||||
print("\n=== Session 2: Verifying persistence ===")
|
print("\n=== Session 2: Verifying persistence ===")
|
||||||
|
print("Launching stealth browser...", flush=True)
|
||||||
ctx = launch_persistent_context(PROFILE_DIR, headless=False)
|
ctx = launch_persistent_context(PROFILE_DIR, headless=False)
|
||||||
page = ctx.new_page()
|
page = ctx.new_page()
|
||||||
page.goto("https://example.com")
|
page.goto("https://example.com")
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import time
|
|||||||
|
|
||||||
from cloakbrowser import launch
|
from cloakbrowser import launch
|
||||||
|
|
||||||
|
print("Launching stealth browser...", flush=True)
|
||||||
browser = launch(headless=True)
|
browser = launch(headless=True)
|
||||||
page = browser.new_page()
|
page = browser.new_page()
|
||||||
|
|
||||||
|
|||||||
+80
-32
@@ -53,21 +53,27 @@ def test_bot_sannysoft(page):
|
|||||||
def test_bot_incolumitas(page):
|
def test_bot_incolumitas(page):
|
||||||
"""bot.incolumitas.com — comprehensive 30+ check bot detection."""
|
"""bot.incolumitas.com — comprehensive 30+ check bot detection."""
|
||||||
page.goto("https://bot.incolumitas.com", wait_until="networkidle", timeout=30000)
|
page.goto("https://bot.incolumitas.com", wait_until="networkidle", timeout=30000)
|
||||||
time.sleep(12) # needs time to run all detection tests
|
|
||||||
|
|
||||||
# Site outputs JSON blocks in page text, not HTML tables
|
# Poll until test count stabilizes (site runs tests progressively)
|
||||||
results = page.evaluate("""() => {
|
last_total = 0
|
||||||
const text = document.body.innerText;
|
for _ in range(15):
|
||||||
const okMatches = text.match(/"\\w+":\\s*"OK"/g) || [];
|
time.sleep(2)
|
||||||
const failMatches = text.match(/"\\w+":\\s*"FAIL"/g) || [];
|
results = page.evaluate("""() => {
|
||||||
const failedTests = failMatches.map(m => m.match(/"(\\w+)"/)[1]);
|
const text = document.body.innerText;
|
||||||
return {
|
const okMatches = text.match(/"\\w+":\\s*"OK"/g) || [];
|
||||||
passed: okMatches.length,
|
const failMatches = text.match(/"\\w+":\\s*"FAIL"/g) || [];
|
||||||
failed: failMatches.length,
|
const failedTests = failMatches.map(m => m.match(/"(\\w+)"/)[1]);
|
||||||
failedTests,
|
return {
|
||||||
total: okMatches.length + failMatches.length
|
passed: okMatches.length,
|
||||||
};
|
failed: failMatches.length,
|
||||||
}""")
|
failedTests,
|
||||||
|
total: okMatches.length + failMatches.length
|
||||||
|
};
|
||||||
|
}""")
|
||||||
|
if results["total"] >= 30 and results["total"] == last_total:
|
||||||
|
break
|
||||||
|
last_total = results["total"]
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
@@ -146,23 +152,18 @@ def test_recaptcha(page):
|
|||||||
wait_until="domcontentloaded",
|
wait_until="domcontentloaded",
|
||||||
timeout=30000,
|
timeout=30000,
|
||||||
)
|
)
|
||||||
# Wait for backend response (step3 element appears when score arrives)
|
# Wait for score to appear (polls up to 30s)
|
||||||
try:
|
for _ in range(15):
|
||||||
page.wait_for_selector("li.step3", timeout=20000)
|
time.sleep(2)
|
||||||
time.sleep(1)
|
score = page.evaluate("""() => {
|
||||||
except Exception:
|
const text = document.body.innerText;
|
||||||
time.sleep(10) # fallback
|
const match = text.match(/"score":\\s*(\\d+\\.\\d+)/);
|
||||||
|
return match ? parseFloat(match[1]) : null;
|
||||||
|
}""")
|
||||||
|
if score is not None:
|
||||||
|
break
|
||||||
|
|
||||||
results = page.evaluate("""() => {
|
return {"score": score}
|
||||||
const text = document.body.innerText;
|
|
||||||
// Score appears in JSON response block: "score": 0.9
|
|
||||||
const scoreMatch = text.match(/"score":\\s*(\\d+\\.\\d+)/);
|
|
||||||
return {
|
|
||||||
score: scoreMatch ? parseFloat(scoreMatch[1]) : null,
|
|
||||||
pageText: text.substring(0, 500)
|
|
||||||
};
|
|
||||||
}""")
|
|
||||||
return results
|
|
||||||
|
|
||||||
|
|
||||||
TESTS = [
|
TESTS = [
|
||||||
@@ -179,8 +180,11 @@ TESTS = [
|
|||||||
"url": "https://bot.incolumitas.com",
|
"url": "https://bot.incolumitas.com",
|
||||||
"runner": test_bot_incolumitas,
|
"runner": test_bot_incolumitas,
|
||||||
"verdict": lambda r: f"{r['passed']}/{r['total']} passed"
|
"verdict": lambda r: f"{r['passed']}/{r['total']} passed"
|
||||||
+ (f" (FAILED: {', '.join(r.get('failedTests', []))})" if r.get("failed", 0) > 0 else " — ALL GREEN"),
|
+ (" — ALL GREEN" if r.get("failed", 0) == 0
|
||||||
"pass": lambda r: r.get("failed", 0) <= 1, # fpscanner.WEBDRIVER false positive expected (all builds)
|
else f" (FAILED: {', '.join(r.get('failedTests', []))} — known false positives)"
|
||||||
|
if set(r.get("failedTests", [])) <= {"WEBDRIVER", "connectionRTT"}
|
||||||
|
else f" (FAILED: {', '.join(r.get('failedTests', []))})"),
|
||||||
|
"pass": lambda r: set(r.get("failedTests", [])) <= {"WEBDRIVER", "connectionRTT"}, # known false positives
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "BrowserScan",
|
"name": "BrowserScan",
|
||||||
@@ -222,10 +226,54 @@ def main():
|
|||||||
print(f"Screenshots: {'on' if SCREENSHOTS else 'off'}")
|
print(f"Screenshots: {'on' if SCREENSHOTS else 'off'}")
|
||||||
print(f"Proxy: {PROXY or 'none'}")
|
print(f"Proxy: {PROXY or 'none'}")
|
||||||
print()
|
print()
|
||||||
|
print("Launching stealth browser...", flush=True)
|
||||||
|
|
||||||
browser = launch(headless=not HEADED, proxy=PROXY)
|
browser = launch(headless=not HEADED, proxy=PROXY)
|
||||||
page = browser.new_page()
|
page = browser.new_page()
|
||||||
|
|
||||||
|
# Show browser fingerprint details
|
||||||
|
try:
|
||||||
|
import re
|
||||||
|
info = page.evaluate("""async () => {
|
||||||
|
const ua = navigator.userAgent;
|
||||||
|
let fullVersion = null;
|
||||||
|
try {
|
||||||
|
const data = await navigator.userAgentData.getHighEntropyValues(['fullVersionList', 'platform', 'platformVersion']);
|
||||||
|
const chrome = data.fullVersionList.find(b => b.brand === 'Chromium' || b.brand === 'Google Chrome');
|
||||||
|
fullVersion = chrome ? chrome.version : null;
|
||||||
|
} catch {}
|
||||||
|
const gl = document.createElement('canvas').getContext('webgl');
|
||||||
|
const dbg = gl ? gl.getExtension('WEBGL_debug_renderer_info') : null;
|
||||||
|
return {
|
||||||
|
ua,
|
||||||
|
fullVersion,
|
||||||
|
platform: navigator.platform,
|
||||||
|
cores: navigator.hardwareConcurrency,
|
||||||
|
gpu: dbg ? gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : 'N/A',
|
||||||
|
gpuVendor: dbg ? gl.getParameter(dbg.UNMASKED_VENDOR_WEBGL) : 'N/A',
|
||||||
|
screen: screen.width + 'x' + screen.height,
|
||||||
|
languages: navigator.languages.join(', '),
|
||||||
|
};
|
||||||
|
}""")
|
||||||
|
# Condensed UA
|
||||||
|
ua_short = re.sub(r'^Mozilla/5\.0 \(', '', info["ua"])
|
||||||
|
ua_short = re.sub(r'\) AppleWebKit/[\d.]+ \(KHTML, like Gecko\) ', ' | ', ua_short)
|
||||||
|
print(f"UA: {ua_short}", flush=True)
|
||||||
|
print(f"Platform: {info['platform']} | Cores: {info['cores']} | Screen: {info['screen']}", flush=True)
|
||||||
|
print(f"GPU: {info['gpuVendor']} — {info['gpu']}", flush=True)
|
||||||
|
except Exception:
|
||||||
|
print("Chrome: could not detect", flush=True)
|
||||||
|
|
||||||
|
# Show IP address
|
||||||
|
try:
|
||||||
|
page.goto("https://httpbin.org/ip", timeout=10000)
|
||||||
|
ip = page.evaluate("JSON.parse(document.body.innerText).origin")
|
||||||
|
print(f"IP: {ip}", flush=True)
|
||||||
|
except Exception:
|
||||||
|
print("IP: could not detect", flush=True)
|
||||||
|
|
||||||
|
print(f"Running {len(TESTS)} tests (this takes ~2 minutes)...\n", flush=True)
|
||||||
|
|
||||||
results_summary = []
|
results_summary = []
|
||||||
|
|
||||||
for test in TESTS:
|
for test in TESTS:
|
||||||
|
|||||||
Reference in New Issue
Block a user