clearcote 0.1.0__tar.gz

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,6 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.egg-info/
4
+ .eggs/
5
+ build/
6
+ dist/
@@ -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.
@@ -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,116 @@
1
+ # clearcote (Python SDK)
2
+
3
+ A **Playwright drop-in** for [Clearcote](https://github.com/clearcotelabs/clearcote-browser) — the
4
+ open, reproducible, anti-fingerprint Chromium build. `launch()` returns a standard Playwright
5
+ `Browser`, so migrating is a one-line import change.
6
+
7
+ The verified Clearcote binary is **auto-downloaded and SHA-256 checked** on first use, then cached —
8
+ no zips or paths to manage.
9
+
10
+ > **Platform:** Clearcote currently ships a **Windows x64** binary, so `launch()` runs on Windows.
11
+ > (The SDK will download + verify the binary on any OS — handy for packaging — but only launches it
12
+ > on Windows. Linux/macOS builds are on the [roadmap](../../ROADMAP.md).)
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pip install clearcote
18
+ ```
19
+
20
+ `playwright` is pulled in as a dependency. You do **not** need to run `playwright install`
21
+ (Clearcote uses its own browser binary, not Playwright's bundled Chromium).
22
+
23
+ ## Usage
24
+
25
+ ```python
26
+ from clearcote import launch
27
+
28
+ browser = launch(
29
+ fingerprint="user-7423", # per-eTLD+1 seed: same seed => same identity, different => unlinkable
30
+ platform="windows",
31
+ timezone="America/New_York",
32
+ headless=False,
33
+ )
34
+ page = browser.new_page()
35
+ page.goto("https://abrahamjuliot.github.io/creepjs/")
36
+ # ... standard Playwright (sync API) from here ...
37
+ browser.close()
38
+ ```
39
+
40
+ Already using Playwright? Swap `p.chromium.launch(...)` for `launch(...)` from `clearcote` — the
41
+ returned object is a normal Playwright `Browser`. (One shared Playwright driver is started lazily
42
+ and stopped at interpreter exit.)
43
+
44
+ ### Through a proxy (report the proxy's IP, not your host's)
45
+
46
+ ```python
47
+ browser = launch(
48
+ fingerprint="user-7423",
49
+ proxy={"server": "http://host:8080", "username": "u", "password": "p"}, # standard Playwright option
50
+ timezone="America/New_York",
51
+ webrtc_ip="203.0.113.10", # make WebRTC report the proxy egress IP
52
+ )
53
+ ```
54
+
55
+ ### Persistent profile
56
+
57
+ ```python
58
+ from clearcote import launch_persistent_context
59
+
60
+ context = launch_persistent_context(
61
+ "./profile-7423",
62
+ fingerprint="user-7423",
63
+ platform="windows",
64
+ )
65
+ ```
66
+
67
+ ## Fingerprint options
68
+
69
+ All optional. Anything not listed here is passed straight through to Playwright
70
+ (`headless`, `proxy`, `args`, `timeout`, `slow_mo`, …).
71
+
72
+ | Kwarg | Switch | Meaning |
73
+ |---|---|---|
74
+ | `fingerprint` | `--fingerprint` | Master seed (per-eTLD+1 farbling root). `str` or `int`. |
75
+ | `platform` | `--fingerprint-platform` | `"windows"` \| `"linux"` \| `"macos"`. |
76
+ | `platform_version` | `--fingerprint-platform-version` | UA-CH platform version. |
77
+ | `brand` | `--fingerprint-brand` | `"Chrome"` \| `"Edge"` \| `"Opera"` \| `"Vivaldi"`. |
78
+ | `brand_version` | `--fingerprint-brand-version` | Brand version. |
79
+ | `gpu_vendor` | `--fingerprint-gpu-vendor` | WebGL UNMASKED vendor. |
80
+ | `gpu_renderer` | `--fingerprint-gpu-renderer` | WebGL UNMASKED renderer. |
81
+ | `hardware_concurrency` | `--fingerprint-hardware-concurrency` | `navigator.hardwareConcurrency`. |
82
+ | `location` | `--fingerprint-location` | `"lat,lng"` (only when geo permission is granted). |
83
+ | `timezone` | `--timezone` | IANA timezone, e.g. `"America/New_York"`. |
84
+ | `webrtc_ip` | `--webrtc-ip` | WebRTC egress IP to report (your proxy IP). |
85
+ | `disable_gpu_fingerprint` | `--disable-gpu-fingerprint` | Turn off GPU/WebGL spoofing. |
86
+
87
+ ## API
88
+
89
+ - `launch(**options)` → Playwright `Browser`.
90
+ - `launch_persistent_context(user_data_dir, **options)` → Playwright `BrowserContext`.
91
+ - `executable_path(executable_path=None, cache_dir=None, quiet=False)` → `str` — resolve (download/verify if needed) the chrome.exe path.
92
+ - `download(cache_dir=None, quiet=False)` → `str` — pre-fetch + verify without launching.
93
+ - `RELEASE` — the pinned release metadata (tag, version, sha256).
94
+
95
+ ## Binary resolution & verification
96
+
97
+ `launch()` resolves the browser in this order:
98
+
99
+ 1. `executable_path=` argument, if given;
100
+ 2. `CLEARCOTE_BINARY` environment variable, if set;
101
+ 3. otherwise **download** the pinned release, **verify its SHA-256** (the hash is baked into this
102
+ package — it's the trust anchor), extract to a per-version cache, and verify the extracted
103
+ `chrome.exe` hash too.
104
+
105
+ Cache location (override with `CLEARCOTE_CACHE`):
106
+ - Windows: `%LOCALAPPDATA%\clearcote\Cache\<tag>`
107
+ - macOS: `~/Library/Caches/clearcote/<tag>`
108
+ - Linux: `${XDG_CACHE_HOME:-~/.cache}/clearcote/<tag>`
109
+
110
+ A SHA-256 mismatch is a hard error — the SDK refuses to run an unverified binary. You can
111
+ independently confirm the published checksums and GPG signatures on the
112
+ [release page](https://github.com/clearcotelabs/clearcote-browser/releases).
113
+
114
+ ## License
115
+
116
+ BSD-3-Clause. See [LICENSE](../../LICENSE).
@@ -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
@@ -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
@@ -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,37 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "clearcote"
7
+ version = "0.1.0"
8
+ description = "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."
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = { text = "BSD-3-Clause" }
12
+ keywords = [
13
+ "clearcote",
14
+ "playwright",
15
+ "chromium",
16
+ "anti-detect",
17
+ "anti-fingerprint",
18
+ "fingerprint",
19
+ "farbling",
20
+ "automation",
21
+ "stealth",
22
+ "ungoogled",
23
+ ]
24
+ dependencies = [
25
+ "playwright>=1.40",
26
+ ]
27
+
28
+ [project.urls]
29
+ Homepage = "https://github.com/clearcotelabs/clearcote-browser"
30
+ Repository = "https://github.com/clearcotelabs/clearcote-browser"
31
+ Issues = "https://github.com/clearcotelabs/clearcote-browser/issues"
32
+
33
+ [tool.hatch.build.targets.wheel]
34
+ packages = ["clearcote"]
35
+
36
+ [tool.hatch.build.targets.sdist]
37
+ include = ["clearcote", "README.md", "LICENSE"]