clearcote 0.1.0__py3-none-any.whl
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.
- clearcote/__init__.py +120 -0
- clearcote/_fingerprint.py +50 -0
- clearcote/download.py +138 -0
- clearcote/release.py +23 -0
- clearcote-0.1.0.dist-info/METADATA +130 -0
- clearcote-0.1.0.dist-info/RECORD +8 -0
- clearcote-0.1.0.dist-info/WHEEL +4 -0
- clearcote-0.1.0.dist-info/licenses/LICENSE +34 -0
clearcote/__init__.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Clearcote — Playwright drop-in (Python).
|
|
2
|
+
|
|
3
|
+
from clearcote import launch
|
|
4
|
+
|
|
5
|
+
browser = launch(fingerprint="seed-123", platform="windows")
|
|
6
|
+
page = browser.new_page()
|
|
7
|
+
page.goto("https://abrahamjuliot.github.io/creepjs/")
|
|
8
|
+
browser.close()
|
|
9
|
+
|
|
10
|
+
launch() returns a standard Playwright sync ``Browser`` backed by the verified Clearcote
|
|
11
|
+
binary (auto-downloaded + SHA-256 checked on first use, then cached). Every Playwright launch
|
|
12
|
+
option (headless, proxy, args, timeout, ...) passes through; the fingerprint kwargs map to the
|
|
13
|
+
engine switches.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import atexit
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
|
|
20
|
+
from ._fingerprint import FINGERPRINT_KEYS, fingerprint_args
|
|
21
|
+
from .download import ensure_binary
|
|
22
|
+
from .release import RELEASE
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"launch",
|
|
26
|
+
"launch_persistent_context",
|
|
27
|
+
"executable_path",
|
|
28
|
+
"download",
|
|
29
|
+
"RELEASE",
|
|
30
|
+
"__version__",
|
|
31
|
+
]
|
|
32
|
+
__version__ = "0.1.0"
|
|
33
|
+
|
|
34
|
+
_pw = None # the shared, lazily-started Playwright driver (one per process)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _stop_quietly(pw):
|
|
38
|
+
try:
|
|
39
|
+
pw.stop()
|
|
40
|
+
except Exception: # noqa: BLE001
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _playwright():
|
|
45
|
+
global _pw
|
|
46
|
+
if _pw is None:
|
|
47
|
+
try:
|
|
48
|
+
from playwright.sync_api import sync_playwright
|
|
49
|
+
except ImportError as exc:
|
|
50
|
+
raise RuntimeError(
|
|
51
|
+
"clearcote requires Playwright. Install it with:\n pip install playwright\n"
|
|
52
|
+
"(You do NOT need 'playwright install' — Clearcote uses its own browser binary.)"
|
|
53
|
+
) from exc
|
|
54
|
+
_pw = sync_playwright().start()
|
|
55
|
+
atexit.register(_stop_quietly, _pw)
|
|
56
|
+
return _pw
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _resolve_binary(executable_path=None, cache_dir=None, quiet=False):
|
|
60
|
+
if executable_path:
|
|
61
|
+
return executable_path
|
|
62
|
+
env = os.environ.get("CLEARCOTE_BINARY")
|
|
63
|
+
if env:
|
|
64
|
+
return env
|
|
65
|
+
return ensure_binary(cache_dir=cache_dir, quiet=quiet)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def executable_path(executable_path=None, cache_dir=None, quiet=False):
|
|
69
|
+
"""Resolve the Clearcote chrome.exe path, downloading + verifying it if needed.
|
|
70
|
+
|
|
71
|
+
Order: explicit ``executable_path`` > ``CLEARCOTE_BINARY`` env > auto-download.
|
|
72
|
+
"""
|
|
73
|
+
return _resolve_binary(executable_path, cache_dir, quiet)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def download(cache_dir=None, quiet=False):
|
|
77
|
+
"""Pre-fetch + verify the Clearcote binary without launching. Returns the chrome.exe path."""
|
|
78
|
+
return ensure_binary(cache_dir=cache_dir, quiet=quiet)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _guard(exe):
|
|
82
|
+
if sys.platform != "win32":
|
|
83
|
+
raise RuntimeError(
|
|
84
|
+
f"Clearcote {RELEASE['version']} ships a Windows x64 binary only — it cannot launch "
|
|
85
|
+
f"on {sys.platform!r}.\nRun on Windows, or pass executable_path=... to a compatible "
|
|
86
|
+
f"binary.\n(The binary downloaded and verified fine; it is cached at: {exe})"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _prepare(kwargs):
|
|
91
|
+
fp = {k: kwargs.pop(k) for k in list(kwargs) if k in FINGERPRINT_KEYS}
|
|
92
|
+
exe_path = kwargs.pop("executable_path", None)
|
|
93
|
+
extra_args = kwargs.pop("args", None)
|
|
94
|
+
cache_dir = kwargs.pop("cache_dir", None)
|
|
95
|
+
quiet = kwargs.pop("quiet", False)
|
|
96
|
+
exe = _resolve_binary(exe_path, cache_dir, quiet)
|
|
97
|
+
_guard(exe)
|
|
98
|
+
args = fingerprint_args(fp) + list(extra_args or [])
|
|
99
|
+
return exe, args, kwargs
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def launch(**kwargs):
|
|
103
|
+
"""Launch Clearcote and return a standard Playwright sync ``Browser``.
|
|
104
|
+
|
|
105
|
+
Fingerprint kwargs: fingerprint, platform, platform_version, brand, brand_version,
|
|
106
|
+
gpu_vendor, gpu_renderer, hardware_concurrency, location, timezone, webrtc_ip,
|
|
107
|
+
disable_gpu_fingerprint. All other kwargs (headless, proxy, args, timeout, ...) pass
|
|
108
|
+
through to Playwright's chromium.launch().
|
|
109
|
+
"""
|
|
110
|
+
exe, args, pw_kwargs = _prepare(kwargs)
|
|
111
|
+
return _playwright().chromium.launch(executable_path=exe, args=args, **pw_kwargs)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def launch_persistent_context(user_data_dir, **kwargs):
|
|
115
|
+
"""Launch Clearcote with a persistent profile directory; returns a Playwright
|
|
116
|
+
``BrowserContext`` (cookies/storage persist in ``user_data_dir``)."""
|
|
117
|
+
exe, args, pw_kwargs = _prepare(kwargs)
|
|
118
|
+
return _playwright().chromium.launch_persistent_context(
|
|
119
|
+
user_data_dir, executable_path=exe, args=args, **pw_kwargs
|
|
120
|
+
)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Map Clearcote fingerprint kwargs to Chromium command-line switches.
|
|
2
|
+
|
|
3
|
+
Switch names mirror components/ungoogled/ungoogled_switches.cc
|
|
4
|
+
(see patches/000-fingerprint-switches.patch).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# kwargs accepted by launch()/launch_persistent_context() that are fingerprint options
|
|
8
|
+
# (everything else is passed straight through to Playwright).
|
|
9
|
+
FINGERPRINT_KEYS = (
|
|
10
|
+
"fingerprint",
|
|
11
|
+
"platform",
|
|
12
|
+
"platform_version",
|
|
13
|
+
"brand",
|
|
14
|
+
"brand_version",
|
|
15
|
+
"gpu_vendor",
|
|
16
|
+
"gpu_renderer",
|
|
17
|
+
"hardware_concurrency",
|
|
18
|
+
"location",
|
|
19
|
+
"timezone",
|
|
20
|
+
"webrtc_ip",
|
|
21
|
+
"disable_gpu_fingerprint",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# kwarg -> switch name (without leading "--"). disable_gpu_fingerprint is a boolean flag,
|
|
25
|
+
# handled separately below.
|
|
26
|
+
_FLAGS = {
|
|
27
|
+
"fingerprint": "fingerprint",
|
|
28
|
+
"platform": "fingerprint-platform",
|
|
29
|
+
"platform_version": "fingerprint-platform-version",
|
|
30
|
+
"brand": "fingerprint-brand",
|
|
31
|
+
"brand_version": "fingerprint-brand-version",
|
|
32
|
+
"gpu_vendor": "fingerprint-gpu-vendor",
|
|
33
|
+
"gpu_renderer": "fingerprint-gpu-renderer",
|
|
34
|
+
"hardware_concurrency": "fingerprint-hardware-concurrency",
|
|
35
|
+
"location": "fingerprint-location",
|
|
36
|
+
"timezone": "timezone",
|
|
37
|
+
"webrtc_ip": "webrtc-ip",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def fingerprint_args(opts):
|
|
42
|
+
"""Build the Chromium switches for a dict of fingerprint options."""
|
|
43
|
+
args = []
|
|
44
|
+
for key, flag in _FLAGS.items():
|
|
45
|
+
value = opts.get(key)
|
|
46
|
+
if value is not None and value != "":
|
|
47
|
+
args.append(f"--{flag}={value}")
|
|
48
|
+
if opts.get("disable_gpu_fingerprint"):
|
|
49
|
+
args.append("--disable-gpu-fingerprint")
|
|
50
|
+
return args
|
clearcote/download.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Resolve the Clearcote browser binary: download the pinned release, verify its SHA-256
|
|
2
|
+
against the value baked into the SDK, extract it to a per-version cache, and return the path
|
|
3
|
+
to chrome.exe. The hash check is mandatory — a mismatch raises and the partial file is removed.
|
|
4
|
+
|
|
5
|
+
Uses only the standard library (urllib + hashlib + zipfile) — no extra dependencies.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import hashlib
|
|
9
|
+
import os
|
|
10
|
+
import shutil
|
|
11
|
+
import sys
|
|
12
|
+
import urllib.request
|
|
13
|
+
import zipfile
|
|
14
|
+
|
|
15
|
+
from .release import RELEASE
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _log(quiet, msg):
|
|
19
|
+
if not quiet:
|
|
20
|
+
sys.stderr.write(f"[clearcote] {msg}\n")
|
|
21
|
+
sys.stderr.flush()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _cache_root():
|
|
25
|
+
env = os.environ.get("CLEARCOTE_CACHE")
|
|
26
|
+
if env:
|
|
27
|
+
return env
|
|
28
|
+
if sys.platform == "win32":
|
|
29
|
+
base = os.environ.get("LOCALAPPDATA") or os.path.expanduser(r"~\AppData\Local")
|
|
30
|
+
return os.path.join(base, "clearcote", "Cache")
|
|
31
|
+
if sys.platform == "darwin":
|
|
32
|
+
return os.path.join(os.path.expanduser("~/Library/Caches"), "clearcote")
|
|
33
|
+
base = os.environ.get("XDG_CACHE_HOME") or os.path.expanduser("~/.cache")
|
|
34
|
+
return os.path.join(base, "clearcote")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _find(dirpath, name):
|
|
38
|
+
name = name.lower()
|
|
39
|
+
for root, _dirs, files in os.walk(dirpath):
|
|
40
|
+
for f in files:
|
|
41
|
+
if f.lower() == name:
|
|
42
|
+
return os.path.join(root, f)
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _sha256_file(path):
|
|
47
|
+
h = hashlib.sha256()
|
|
48
|
+
with open(path, "rb") as f:
|
|
49
|
+
for chunk in iter(lambda: f.read(1 << 20), b""):
|
|
50
|
+
h.update(chunk)
|
|
51
|
+
return h.hexdigest()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _download(url, dest, quiet):
|
|
55
|
+
"""Stream the download to dest, hashing as we go; return the hex digest."""
|
|
56
|
+
h = hashlib.sha256()
|
|
57
|
+
req = urllib.request.Request(url, headers={"User-Agent": "clearcote-sdk"})
|
|
58
|
+
with urllib.request.urlopen(req) as resp, open(dest, "wb") as out: # noqa: S310
|
|
59
|
+
total = int(resp.headers.get("Content-Length") or RELEASE["size"])
|
|
60
|
+
seen = 0
|
|
61
|
+
last = -1
|
|
62
|
+
while True:
|
|
63
|
+
chunk = resp.read(1 << 20)
|
|
64
|
+
if not chunk:
|
|
65
|
+
break
|
|
66
|
+
out.write(chunk)
|
|
67
|
+
h.update(chunk)
|
|
68
|
+
seen += len(chunk)
|
|
69
|
+
if not quiet:
|
|
70
|
+
pct = int(seen * 100 / total) if total else 0
|
|
71
|
+
if pct != last and pct % 5 == 0:
|
|
72
|
+
last = pct
|
|
73
|
+
sys.stderr.write(
|
|
74
|
+
f"\r[clearcote] downloading {pct}% "
|
|
75
|
+
f"({seen // 10**6}/{total // 10**6} MB)"
|
|
76
|
+
)
|
|
77
|
+
sys.stderr.flush()
|
|
78
|
+
if not quiet:
|
|
79
|
+
sys.stderr.write("\n")
|
|
80
|
+
sys.stderr.flush()
|
|
81
|
+
return h.hexdigest()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def ensure_binary(cache_dir=None, quiet=False):
|
|
85
|
+
"""Ensure the Clearcote binary is present and verified; return the chrome.exe path.
|
|
86
|
+
Cached per release tag, so later calls are instant."""
|
|
87
|
+
base = os.path.join(cache_dir or _cache_root(), RELEASE["tag"])
|
|
88
|
+
browser_dir = os.path.join(base, "browser")
|
|
89
|
+
marker = os.path.join(base, ".verified")
|
|
90
|
+
|
|
91
|
+
if os.path.exists(marker):
|
|
92
|
+
cached = _find(browser_dir, "chrome.exe")
|
|
93
|
+
if cached:
|
|
94
|
+
return cached
|
|
95
|
+
|
|
96
|
+
os.makedirs(base, exist_ok=True)
|
|
97
|
+
zip_path = os.path.join(base, RELEASE["asset"])
|
|
98
|
+
|
|
99
|
+
_log(quiet, f"fetching Clearcote {RELEASE['version']} ({RELEASE['tag']}, "
|
|
100
|
+
f"~{RELEASE['size'] // 10**6} MB)")
|
|
101
|
+
got = _download(RELEASE["url"], zip_path, quiet)
|
|
102
|
+
|
|
103
|
+
_log(quiet, "verifying SHA-256")
|
|
104
|
+
if got.lower() != RELEASE["sha256"].lower():
|
|
105
|
+
try:
|
|
106
|
+
os.remove(zip_path)
|
|
107
|
+
except OSError:
|
|
108
|
+
pass
|
|
109
|
+
raise RuntimeError(
|
|
110
|
+
"Clearcote archive SHA-256 mismatch — refusing to use it.\n"
|
|
111
|
+
f" expected {RELEASE['sha256']}\n got {got}"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
_log(quiet, "extracting")
|
|
115
|
+
if os.path.isdir(browser_dir):
|
|
116
|
+
shutil.rmtree(browser_dir, ignore_errors=True)
|
|
117
|
+
with zipfile.ZipFile(zip_path) as z:
|
|
118
|
+
z.extractall(browser_dir)
|
|
119
|
+
|
|
120
|
+
exe = _find(browser_dir, "chrome.exe")
|
|
121
|
+
if not exe:
|
|
122
|
+
raise RuntimeError("Clearcote archive verified but chrome.exe was not found inside it.")
|
|
123
|
+
|
|
124
|
+
exe_hash = _sha256_file(exe)
|
|
125
|
+
if exe_hash.lower() != RELEASE["exe_sha256"].lower():
|
|
126
|
+
raise RuntimeError(
|
|
127
|
+
"Clearcote chrome.exe SHA-256 mismatch — refusing to use it.\n"
|
|
128
|
+
f" expected {RELEASE['exe_sha256']}\n got {exe_hash}"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
with open(marker, "w", encoding="utf-8") as f:
|
|
132
|
+
f.write(RELEASE["sha256"] + "\n")
|
|
133
|
+
try:
|
|
134
|
+
os.remove(zip_path) # reclaim ~250 MB; keep only the extracted tree
|
|
135
|
+
except OSError:
|
|
136
|
+
pass
|
|
137
|
+
_log(quiet, f"ready: {exe}")
|
|
138
|
+
return exe
|
clearcote/release.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Pinned Clearcote release the SDK downloads and verifies.
|
|
2
|
+
|
|
3
|
+
Bumping to a new browser build = updating these values (and the version in pyproject.toml).
|
|
4
|
+
The sha256 is the single trust anchor for the auto-download: if you trust this package, the
|
|
5
|
+
hash check guarantees you run exactly the published, signed binary. Published checksums + GPG
|
|
6
|
+
signatures live on the release page.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
RELEASE = {
|
|
10
|
+
"tag": "v0.1.0-pre.2",
|
|
11
|
+
"version": "149.0.7827.114",
|
|
12
|
+
"asset": "clearcote-149.0.7827.114-windows-x64.zip",
|
|
13
|
+
"url": (
|
|
14
|
+
"https://github.com/clearcotelabs/clearcote-browser/releases/download/"
|
|
15
|
+
"v0.1.0-pre.2/clearcote-149.0.7827.114-windows-x64.zip"
|
|
16
|
+
),
|
|
17
|
+
# SHA-256 of the zip — verified after download; a mismatch is a hard failure.
|
|
18
|
+
"sha256": "4071aa06add252caa274c4f52dfe2e8eaede4eb37cf55be6fb6f8e3c28bcf256",
|
|
19
|
+
# SHA-256 of chrome.exe inside the zip — verified after extraction (defense in depth).
|
|
20
|
+
"exe_sha256": "5743595256c89c6874804bf3315acce592fc7f1883760c8d380c010151a73b23",
|
|
21
|
+
"size": 253015969,
|
|
22
|
+
"os": "win32",
|
|
23
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: clearcote
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Playwright drop-in for the Clearcote anti-fingerprint Chromium build. launch() returns a standard Playwright Browser backed by the verified Clearcote binary, which is auto-downloaded and SHA-256 checked.
|
|
5
|
+
Project-URL: Homepage, https://github.com/clearcotelabs/clearcote-browser
|
|
6
|
+
Project-URL: Repository, https://github.com/clearcotelabs/clearcote-browser
|
|
7
|
+
Project-URL: Issues, https://github.com/clearcotelabs/clearcote-browser/issues
|
|
8
|
+
License: BSD-3-Clause
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: anti-detect,anti-fingerprint,automation,chromium,clearcote,farbling,fingerprint,playwright,stealth,ungoogled
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Requires-Dist: playwright>=1.40
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# clearcote (Python SDK)
|
|
16
|
+
|
|
17
|
+
A **Playwright drop-in** for [Clearcote](https://github.com/clearcotelabs/clearcote-browser) — the
|
|
18
|
+
open, reproducible, anti-fingerprint Chromium build. `launch()` returns a standard Playwright
|
|
19
|
+
`Browser`, so migrating is a one-line import change.
|
|
20
|
+
|
|
21
|
+
The verified Clearcote binary is **auto-downloaded and SHA-256 checked** on first use, then cached —
|
|
22
|
+
no zips or paths to manage.
|
|
23
|
+
|
|
24
|
+
> **Platform:** Clearcote currently ships a **Windows x64** binary, so `launch()` runs on Windows.
|
|
25
|
+
> (The SDK will download + verify the binary on any OS — handy for packaging — but only launches it
|
|
26
|
+
> on Windows. Linux/macOS builds are on the [roadmap](../../ROADMAP.md).)
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install clearcote
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`playwright` is pulled in as a dependency. You do **not** need to run `playwright install`
|
|
35
|
+
(Clearcote uses its own browser binary, not Playwright's bundled Chromium).
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from clearcote import launch
|
|
41
|
+
|
|
42
|
+
browser = launch(
|
|
43
|
+
fingerprint="user-7423", # per-eTLD+1 seed: same seed => same identity, different => unlinkable
|
|
44
|
+
platform="windows",
|
|
45
|
+
timezone="America/New_York",
|
|
46
|
+
headless=False,
|
|
47
|
+
)
|
|
48
|
+
page = browser.new_page()
|
|
49
|
+
page.goto("https://abrahamjuliot.github.io/creepjs/")
|
|
50
|
+
# ... standard Playwright (sync API) from here ...
|
|
51
|
+
browser.close()
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Already using Playwright? Swap `p.chromium.launch(...)` for `launch(...)` from `clearcote` — the
|
|
55
|
+
returned object is a normal Playwright `Browser`. (One shared Playwright driver is started lazily
|
|
56
|
+
and stopped at interpreter exit.)
|
|
57
|
+
|
|
58
|
+
### Through a proxy (report the proxy's IP, not your host's)
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
browser = launch(
|
|
62
|
+
fingerprint="user-7423",
|
|
63
|
+
proxy={"server": "http://host:8080", "username": "u", "password": "p"}, # standard Playwright option
|
|
64
|
+
timezone="America/New_York",
|
|
65
|
+
webrtc_ip="203.0.113.10", # make WebRTC report the proxy egress IP
|
|
66
|
+
)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Persistent profile
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from clearcote import launch_persistent_context
|
|
73
|
+
|
|
74
|
+
context = launch_persistent_context(
|
|
75
|
+
"./profile-7423",
|
|
76
|
+
fingerprint="user-7423",
|
|
77
|
+
platform="windows",
|
|
78
|
+
)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Fingerprint options
|
|
82
|
+
|
|
83
|
+
All optional. Anything not listed here is passed straight through to Playwright
|
|
84
|
+
(`headless`, `proxy`, `args`, `timeout`, `slow_mo`, …).
|
|
85
|
+
|
|
86
|
+
| Kwarg | Switch | Meaning |
|
|
87
|
+
|---|---|---|
|
|
88
|
+
| `fingerprint` | `--fingerprint` | Master seed (per-eTLD+1 farbling root). `str` or `int`. |
|
|
89
|
+
| `platform` | `--fingerprint-platform` | `"windows"` \| `"linux"` \| `"macos"`. |
|
|
90
|
+
| `platform_version` | `--fingerprint-platform-version` | UA-CH platform version. |
|
|
91
|
+
| `brand` | `--fingerprint-brand` | `"Chrome"` \| `"Edge"` \| `"Opera"` \| `"Vivaldi"`. |
|
|
92
|
+
| `brand_version` | `--fingerprint-brand-version` | Brand version. |
|
|
93
|
+
| `gpu_vendor` | `--fingerprint-gpu-vendor` | WebGL UNMASKED vendor. |
|
|
94
|
+
| `gpu_renderer` | `--fingerprint-gpu-renderer` | WebGL UNMASKED renderer. |
|
|
95
|
+
| `hardware_concurrency` | `--fingerprint-hardware-concurrency` | `navigator.hardwareConcurrency`. |
|
|
96
|
+
| `location` | `--fingerprint-location` | `"lat,lng"` (only when geo permission is granted). |
|
|
97
|
+
| `timezone` | `--timezone` | IANA timezone, e.g. `"America/New_York"`. |
|
|
98
|
+
| `webrtc_ip` | `--webrtc-ip` | WebRTC egress IP to report (your proxy IP). |
|
|
99
|
+
| `disable_gpu_fingerprint` | `--disable-gpu-fingerprint` | Turn off GPU/WebGL spoofing. |
|
|
100
|
+
|
|
101
|
+
## API
|
|
102
|
+
|
|
103
|
+
- `launch(**options)` → Playwright `Browser`.
|
|
104
|
+
- `launch_persistent_context(user_data_dir, **options)` → Playwright `BrowserContext`.
|
|
105
|
+
- `executable_path(executable_path=None, cache_dir=None, quiet=False)` → `str` — resolve (download/verify if needed) the chrome.exe path.
|
|
106
|
+
- `download(cache_dir=None, quiet=False)` → `str` — pre-fetch + verify without launching.
|
|
107
|
+
- `RELEASE` — the pinned release metadata (tag, version, sha256).
|
|
108
|
+
|
|
109
|
+
## Binary resolution & verification
|
|
110
|
+
|
|
111
|
+
`launch()` resolves the browser in this order:
|
|
112
|
+
|
|
113
|
+
1. `executable_path=` argument, if given;
|
|
114
|
+
2. `CLEARCOTE_BINARY` environment variable, if set;
|
|
115
|
+
3. otherwise **download** the pinned release, **verify its SHA-256** (the hash is baked into this
|
|
116
|
+
package — it's the trust anchor), extract to a per-version cache, and verify the extracted
|
|
117
|
+
`chrome.exe` hash too.
|
|
118
|
+
|
|
119
|
+
Cache location (override with `CLEARCOTE_CACHE`):
|
|
120
|
+
- Windows: `%LOCALAPPDATA%\clearcote\Cache\<tag>`
|
|
121
|
+
- macOS: `~/Library/Caches/clearcote/<tag>`
|
|
122
|
+
- Linux: `${XDG_CACHE_HOME:-~/.cache}/clearcote/<tag>`
|
|
123
|
+
|
|
124
|
+
A SHA-256 mismatch is a hard error — the SDK refuses to run an unverified binary. You can
|
|
125
|
+
independently confirm the published checksums and GPG signatures on the
|
|
126
|
+
[release page](https://github.com/clearcotelabs/clearcote-browser/releases).
|
|
127
|
+
|
|
128
|
+
## License
|
|
129
|
+
|
|
130
|
+
BSD-3-Clause. See [LICENSE](../../LICENSE).
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
clearcote/__init__.py,sha256=NHQnCFxQ6BVjwJ7PvSQ4YwjEabNXD0XpXpUds5x165c,4094
|
|
2
|
+
clearcote/_fingerprint.py,sha256=6WUeXKzR8vtFotZlvZ3PvgmsrhQmqDOrpQ_JHSU4tSI,1581
|
|
3
|
+
clearcote/download.py,sha256=OB_4AVAU1Vo1qxPB-B71CfQz0_pW2soUnZA7ZuAWaVQ,4635
|
|
4
|
+
clearcote/release.py,sha256=oQ2krf2Udz48MZ77geCYM8HGR5rIvNpK1uSQ_Lgmp-E,1063
|
|
5
|
+
clearcote-0.1.0.dist-info/METADATA,sha256=2f8gngGNuQp1Fx5mhEWg9OCR_wJDbk-wSErXXG-gbzU,5398
|
|
6
|
+
clearcote-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
7
|
+
clearcote-0.1.0.dist-info/licenses/LICENSE,sha256=onkDgSIh3yfBM40kQLPu2Mg_Ph1Ym8D9yYMaIkYjbEk,1731
|
|
8
|
+
clearcote-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Clearcote Labs and the Clearcote contributors
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
This license applies to Clearcote's own source code and patches. Upstream
|
|
33
|
+
components (Chromium, ungoogled-chromium, and others) are distributed under
|
|
34
|
+
their respective licenses; see CREDITS.md.
|