chromix 152.0.7977.82__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.
- chromix-152.0.7977.82/LICENSE +29 -0
- chromix-152.0.7977.82/PKG-INFO +157 -0
- chromix-152.0.7977.82/README.md +108 -0
- chromix-152.0.7977.82/chromix/__init__.py +46 -0
- chromix-152.0.7977.82/chromix/__main__.py +40 -0
- chromix-152.0.7977.82/chromix/_binary.py +107 -0
- chromix-152.0.7977.82/chromix/_fonts.py +46 -0
- chromix-152.0.7977.82/chromix/api.py +705 -0
- chromix-152.0.7977.82/chromix/humanize.py +304 -0
- chromix-152.0.7977.82/chromix/widevine.py +189 -0
- chromix-152.0.7977.82/chromix.egg-info/PKG-INFO +157 -0
- chromix-152.0.7977.82/chromix.egg-info/SOURCES.txt +17 -0
- chromix-152.0.7977.82/chromix.egg-info/dependency_links.txt +1 -0
- chromix-152.0.7977.82/chromix.egg-info/entry_points.txt +2 -0
- chromix-152.0.7977.82/chromix.egg-info/requires.txt +3 -0
- chromix-152.0.7977.82/chromix.egg-info/top_level.txt +1 -0
- chromix-152.0.7977.82/pyproject.toml +32 -0
- chromix-152.0.7977.82/setup.cfg +4 -0
- chromix-152.0.7977.82/tests/test_api.py +176 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, xiaozhou26
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: chromix
|
|
3
|
+
Version: 152.0.7977.82
|
|
4
|
+
Summary: Drive the Chromix stealth Chromium engine with a CloakBrowser-compatible API.
|
|
5
|
+
Author: xiaozhou26
|
|
6
|
+
License: BSD 3-Clause License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026, xiaozhou26
|
|
9
|
+
All rights reserved.
|
|
10
|
+
|
|
11
|
+
Redistribution and use in source and binary forms, with or without
|
|
12
|
+
modification, are permitted provided that the following conditions are met:
|
|
13
|
+
|
|
14
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
15
|
+
list of conditions and the following disclaimer.
|
|
16
|
+
|
|
17
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
18
|
+
this list of conditions and the following disclaimer in the documentation
|
|
19
|
+
and/or other materials provided with the distribution.
|
|
20
|
+
|
|
21
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
22
|
+
contributors may be used to endorse or promote products derived from
|
|
23
|
+
this software without specific prior written permission.
|
|
24
|
+
|
|
25
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
26
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
27
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
28
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
29
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
30
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
31
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
32
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
33
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
34
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
35
|
+
|
|
36
|
+
Project-URL: Homepage, https://github.com/xiaozhou26/Chromix
|
|
37
|
+
Project-URL: Source, https://github.com/xiaozhou26/Chromix
|
|
38
|
+
Project-URL: Issues, https://github.com/xiaozhou26/Chromix/issues
|
|
39
|
+
Keywords: chromium,stealth,browser,automation,anti-bot,cloakbrowser
|
|
40
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
41
|
+
Classifier: Programming Language :: Python :: 3
|
|
42
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
43
|
+
Requires-Python: >=3.8
|
|
44
|
+
Description-Content-Type: text/markdown
|
|
45
|
+
License-File: LICENSE
|
|
46
|
+
Provides-Extra: playwright
|
|
47
|
+
Requires-Dist: playwright; extra == "playwright"
|
|
48
|
+
Dynamic: license-file
|
|
49
|
+
|
|
50
|
+
# chromix (Python)
|
|
51
|
+
|
|
52
|
+
Drive the Chromix stealth Chromium engine with a **CloakBrowser-compatible API** —
|
|
53
|
+
function names, keyword arguments, return types (Playwright `Browser` / `BrowserContext`)
|
|
54
|
+
and `CLOAKBROWSER_*` env-var names all match the [`cloakbrowser`](https://github.com/CloakHQ/CloakBrowser)
|
|
55
|
+
wrapper, so existing CloakBrowser scripts run on Chromix by changing only the import:
|
|
56
|
+
|
|
57
|
+
```diff
|
|
58
|
+
- from cloakbrowser import launch
|
|
59
|
+
+ from chromix import launch
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from chromix import launch
|
|
64
|
+
|
|
65
|
+
browser = launch(proxy="http://user:pass@proxy:8080", geoip=True, humanize=True)
|
|
66
|
+
page = browser.new_page()
|
|
67
|
+
page.goto("https://example.com")
|
|
68
|
+
browser.close()
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Install
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install chromix playwright
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The distribution and import package are both named `chromix`. To install the
|
|
78
|
+
SDK directly from a repository checkout instead, run:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pip install ./sdk/python playwright
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
On first launch the stealth Chromium binary is downloaded from this repo's GitHub
|
|
85
|
+
Release, SHA256-verified, and cached under `~/.cache/chromix`. Point
|
|
86
|
+
`CLOAKBROWSER_BINARY_PATH` at a local build (e.g. your own `chrome.exe`) to skip
|
|
87
|
+
the download.
|
|
88
|
+
|
|
89
|
+
## API
|
|
90
|
+
|
|
91
|
+
| Function | Description |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `launch(**opts)` | Returns a Playwright `Browser` |
|
|
94
|
+
| `launch_async(**opts)` | Async variant |
|
|
95
|
+
| `launch_context(**opts)` | Returns a `BrowserContext` (viewport/locale/color_scheme pre-set) |
|
|
96
|
+
| `launch_context_async(**opts)` | Async variant |
|
|
97
|
+
| `launch_persistent_context(user_data_dir, **opts)` | Persistent profile |
|
|
98
|
+
| `launch_persistent_context_async(user_data_dir, **opts)` | Async variant |
|
|
99
|
+
| `build_args` / `get_default_stealth_args` | Arg assembly (32-bit random seed + platform claim) |
|
|
100
|
+
| `maybe_resolve_geoip(geoip, proxy, tz, locale, args)` | Egress IP → (tz, locale, exit_ip) |
|
|
101
|
+
| `ensure_binary` / `clear_cache` / `binary_info` / `check_for_update` | Binary management |
|
|
102
|
+
| `HumanConfig` / `resolve_human_config` | Behavioral-layer config (`default` / `careful` presets) |
|
|
103
|
+
| `ProxySettings` | Playwright-shaped proxy TypedDict |
|
|
104
|
+
|
|
105
|
+
Options (`headless, proxy, args, stealth_args, timezone, locale, geoip, humanize,
|
|
106
|
+
human_preset, human_config, extension_paths, license_key, browser_version,
|
|
107
|
+
release_channel, user_agent, viewport, color_scheme`) match CloakBrowser
|
|
108
|
+
name-for-name; `**kwargs` passes through to `playwright.chromium.launch()` /
|
|
109
|
+
`browser.new_context()`.
|
|
110
|
+
|
|
111
|
+
## Env vars
|
|
112
|
+
|
|
113
|
+
- `CLOAKBROWSER_BINARY_PATH` — use a local chrome binary instead of downloading
|
|
114
|
+
- `CLOAKBROWSER_VERSION` / `CLOAKBROWSER_RELEASE_CHANNEL` — pin a version/channel
|
|
115
|
+
- `CLOAKBROWSER_GEOIP_TIMEOUT_SECONDS` — geoip lookup timeout
|
|
116
|
+
- `CLOAKBROWSER_WIDEVINE_CDM` — explicit Widevine CDM dir (DRM); `CLOAKBROWSER_WIDEVINE=0` disables DRM
|
|
117
|
+
- `CHROMIX_CACHE_DIR` / `CHROMIX_DOWNLOAD_HOST` — cache location / release host override
|
|
118
|
+
|
|
119
|
+
High-risk engine ports are available only through explicit browser `args`:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
browser = launch(args=[
|
|
123
|
+
"--fingerprint-devtools-runtime-suppression",
|
|
124
|
+
"--fingerprint-canvas-bridge=127.0.0.1:9228",
|
|
125
|
+
"--fingerprint-canvas-bridge-unsafe",
|
|
126
|
+
"--fingerprint-webrtc-fake-srflx=203.0.113.20",
|
|
127
|
+
"--fingerprint-webrtc-fake-srflx-allow-udp",
|
|
128
|
+
])
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Runtime suppression can break console/binding-based automation. Canvas Bridge
|
|
132
|
+
removes the sandbox from bridge renderer processes and forwards canvas/WebGL
|
|
133
|
+
operations to the configured endpoint. Fake srflx does not enable non-proxied
|
|
134
|
+
UDP unless the separate `allow-udp` flag is supplied.
|
|
135
|
+
|
|
136
|
+
## CLI
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
python -m chromix install # pre-download the binary
|
|
140
|
+
python -m chromix info # binary / cache info
|
|
141
|
+
python -m chromix widevine # fetch the Widevine CDM (Linux x64)
|
|
142
|
+
python -m chromix clear-cache
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Intentional differences from CloakBrowser
|
|
146
|
+
|
|
147
|
+
1. `license_key` is accepted and ignored (one open tier).
|
|
148
|
+
2. `geoip` queries ip-api.com over HTTP instead of a local GeoLite2 database;
|
|
149
|
+
explicit `timezone=` / `locale=` always win.
|
|
150
|
+
3. No `cloakbrowser/puppeteer` subpath — use the Playwright surface.
|
|
151
|
+
4. Widevine is enabled automatically when a CDM is present (installed Chrome,
|
|
152
|
+
`CLOAKBROWSER_WIDEVINE_CDM`, or `python -m chromix widevine`).
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
The Python SDK is available under the BSD 3-Clause License. See
|
|
157
|
+
[`LICENSE`](LICENSE).
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# chromix (Python)
|
|
2
|
+
|
|
3
|
+
Drive the Chromix stealth Chromium engine with a **CloakBrowser-compatible API** —
|
|
4
|
+
function names, keyword arguments, return types (Playwright `Browser` / `BrowserContext`)
|
|
5
|
+
and `CLOAKBROWSER_*` env-var names all match the [`cloakbrowser`](https://github.com/CloakHQ/CloakBrowser)
|
|
6
|
+
wrapper, so existing CloakBrowser scripts run on Chromix by changing only the import:
|
|
7
|
+
|
|
8
|
+
```diff
|
|
9
|
+
- from cloakbrowser import launch
|
|
10
|
+
+ from chromix import launch
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from chromix import launch
|
|
15
|
+
|
|
16
|
+
browser = launch(proxy="http://user:pass@proxy:8080", geoip=True, humanize=True)
|
|
17
|
+
page = browser.new_page()
|
|
18
|
+
page.goto("https://example.com")
|
|
19
|
+
browser.close()
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install chromix playwright
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The distribution and import package are both named `chromix`. To install the
|
|
29
|
+
SDK directly from a repository checkout instead, run:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install ./sdk/python playwright
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
On first launch the stealth Chromium binary is downloaded from this repo's GitHub
|
|
36
|
+
Release, SHA256-verified, and cached under `~/.cache/chromix`. Point
|
|
37
|
+
`CLOAKBROWSER_BINARY_PATH` at a local build (e.g. your own `chrome.exe`) to skip
|
|
38
|
+
the download.
|
|
39
|
+
|
|
40
|
+
## API
|
|
41
|
+
|
|
42
|
+
| Function | Description |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `launch(**opts)` | Returns a Playwright `Browser` |
|
|
45
|
+
| `launch_async(**opts)` | Async variant |
|
|
46
|
+
| `launch_context(**opts)` | Returns a `BrowserContext` (viewport/locale/color_scheme pre-set) |
|
|
47
|
+
| `launch_context_async(**opts)` | Async variant |
|
|
48
|
+
| `launch_persistent_context(user_data_dir, **opts)` | Persistent profile |
|
|
49
|
+
| `launch_persistent_context_async(user_data_dir, **opts)` | Async variant |
|
|
50
|
+
| `build_args` / `get_default_stealth_args` | Arg assembly (32-bit random seed + platform claim) |
|
|
51
|
+
| `maybe_resolve_geoip(geoip, proxy, tz, locale, args)` | Egress IP → (tz, locale, exit_ip) |
|
|
52
|
+
| `ensure_binary` / `clear_cache` / `binary_info` / `check_for_update` | Binary management |
|
|
53
|
+
| `HumanConfig` / `resolve_human_config` | Behavioral-layer config (`default` / `careful` presets) |
|
|
54
|
+
| `ProxySettings` | Playwright-shaped proxy TypedDict |
|
|
55
|
+
|
|
56
|
+
Options (`headless, proxy, args, stealth_args, timezone, locale, geoip, humanize,
|
|
57
|
+
human_preset, human_config, extension_paths, license_key, browser_version,
|
|
58
|
+
release_channel, user_agent, viewport, color_scheme`) match CloakBrowser
|
|
59
|
+
name-for-name; `**kwargs` passes through to `playwright.chromium.launch()` /
|
|
60
|
+
`browser.new_context()`.
|
|
61
|
+
|
|
62
|
+
## Env vars
|
|
63
|
+
|
|
64
|
+
- `CLOAKBROWSER_BINARY_PATH` — use a local chrome binary instead of downloading
|
|
65
|
+
- `CLOAKBROWSER_VERSION` / `CLOAKBROWSER_RELEASE_CHANNEL` — pin a version/channel
|
|
66
|
+
- `CLOAKBROWSER_GEOIP_TIMEOUT_SECONDS` — geoip lookup timeout
|
|
67
|
+
- `CLOAKBROWSER_WIDEVINE_CDM` — explicit Widevine CDM dir (DRM); `CLOAKBROWSER_WIDEVINE=0` disables DRM
|
|
68
|
+
- `CHROMIX_CACHE_DIR` / `CHROMIX_DOWNLOAD_HOST` — cache location / release host override
|
|
69
|
+
|
|
70
|
+
High-risk engine ports are available only through explicit browser `args`:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
browser = launch(args=[
|
|
74
|
+
"--fingerprint-devtools-runtime-suppression",
|
|
75
|
+
"--fingerprint-canvas-bridge=127.0.0.1:9228",
|
|
76
|
+
"--fingerprint-canvas-bridge-unsafe",
|
|
77
|
+
"--fingerprint-webrtc-fake-srflx=203.0.113.20",
|
|
78
|
+
"--fingerprint-webrtc-fake-srflx-allow-udp",
|
|
79
|
+
])
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Runtime suppression can break console/binding-based automation. Canvas Bridge
|
|
83
|
+
removes the sandbox from bridge renderer processes and forwards canvas/WebGL
|
|
84
|
+
operations to the configured endpoint. Fake srflx does not enable non-proxied
|
|
85
|
+
UDP unless the separate `allow-udp` flag is supplied.
|
|
86
|
+
|
|
87
|
+
## CLI
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
python -m chromix install # pre-download the binary
|
|
91
|
+
python -m chromix info # binary / cache info
|
|
92
|
+
python -m chromix widevine # fetch the Widevine CDM (Linux x64)
|
|
93
|
+
python -m chromix clear-cache
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Intentional differences from CloakBrowser
|
|
97
|
+
|
|
98
|
+
1. `license_key` is accepted and ignored (one open tier).
|
|
99
|
+
2. `geoip` queries ip-api.com over HTTP instead of a local GeoLite2 database;
|
|
100
|
+
explicit `timezone=` / `locale=` always win.
|
|
101
|
+
3. No `cloakbrowser/puppeteer` subpath — use the Playwright surface.
|
|
102
|
+
4. Widevine is enabled automatically when a CDM is present (installed Chrome,
|
|
103
|
+
`CLOAKBROWSER_WIDEVINE_CDM`, or `python -m chromix widevine`).
|
|
104
|
+
|
|
105
|
+
## License
|
|
106
|
+
|
|
107
|
+
The Python SDK is available under the BSD 3-Clause License. See
|
|
108
|
+
[`LICENSE`](LICENSE).
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""chromix — drive the Chromix stealth Chromium engine with a CloakBrowser-compatible API.
|
|
2
|
+
|
|
3
|
+
Function names, keyword arguments, return types (Playwright ``Browser`` /
|
|
4
|
+
``BrowserContext``) and ``CLOAKBROWSER_*`` env-var names all match the
|
|
5
|
+
``cloakbrowser`` wrapper (github.com/CloakHQ/CloakBrowser), so existing
|
|
6
|
+
CloakBrowser scripts run on Chromix by changing only the import:
|
|
7
|
+
|
|
8
|
+
- from cloakbrowser import launch
|
|
9
|
+
+ from chromix import launch
|
|
10
|
+
|
|
11
|
+
from chromix import launch
|
|
12
|
+
|
|
13
|
+
browser = launch(proxy="http://user:pass@proxy:8080", geoip=True, humanize=True)
|
|
14
|
+
page = browser.new_page()
|
|
15
|
+
page.goto("https://example.com")
|
|
16
|
+
browser.close()
|
|
17
|
+
"""
|
|
18
|
+
from .api import (
|
|
19
|
+
HumanConfig,
|
|
20
|
+
ProxySettings,
|
|
21
|
+
binary_info,
|
|
22
|
+
build_args,
|
|
23
|
+
check_for_update,
|
|
24
|
+
clear_cache,
|
|
25
|
+
ensure_binary,
|
|
26
|
+
get_default_stealth_args,
|
|
27
|
+
launch,
|
|
28
|
+
launch_async,
|
|
29
|
+
launch_context,
|
|
30
|
+
launch_context_async,
|
|
31
|
+
launch_persistent_context,
|
|
32
|
+
launch_persistent_context_async,
|
|
33
|
+
maybe_resolve_geoip,
|
|
34
|
+
resolve_human_config,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
__version__ = "152.0.7977.82"
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"launch", "launch_async", "launch_context", "launch_context_async",
|
|
41
|
+
"launch_persistent_context", "launch_persistent_context_async",
|
|
42
|
+
"ProxySettings", "build_args", "maybe_resolve_geoip",
|
|
43
|
+
"get_default_stealth_args", "ensure_binary", "clear_cache",
|
|
44
|
+
"binary_info", "check_for_update", "HumanConfig", "resolve_human_config",
|
|
45
|
+
"__version__",
|
|
46
|
+
]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""CLI: ``python -m chromix`` — pre-download / inspect / manage the binary."""
|
|
2
|
+
import argparse
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from . import __version__, binary_info, clear_cache, ensure_binary
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main():
|
|
10
|
+
ap = argparse.ArgumentParser(
|
|
11
|
+
prog="chromix",
|
|
12
|
+
description="Manage the Chromix stealth Chromium binary.")
|
|
13
|
+
sub = ap.add_subparsers(dest="cmd")
|
|
14
|
+
sub.add_parser("install", help="download the stealth binary now")
|
|
15
|
+
sub.add_parser("info", help="show binary / cache info as JSON")
|
|
16
|
+
sub.add_parser("clear-cache", help="delete all cached binaries")
|
|
17
|
+
sub.add_parser("widevine", help="fetch the Widevine CDM (Linux x64) for DRM playback")
|
|
18
|
+
ap.add_argument("--version", action="version", version=f"chromix {__version__}")
|
|
19
|
+
args = ap.parse_args()
|
|
20
|
+
|
|
21
|
+
if args.cmd == "install":
|
|
22
|
+
print(ensure_binary())
|
|
23
|
+
elif args.cmd == "info":
|
|
24
|
+
print(json.dumps(binary_info(), indent=2))
|
|
25
|
+
elif args.cmd == "clear-cache":
|
|
26
|
+
clear_cache()
|
|
27
|
+
print("cache cleared")
|
|
28
|
+
elif args.cmd == "widevine":
|
|
29
|
+
from .widevine import ensure_widevine
|
|
30
|
+
cdm = ensure_widevine()
|
|
31
|
+
if cdm:
|
|
32
|
+
print(f"CDM ready: {cdm}")
|
|
33
|
+
else:
|
|
34
|
+
sys.exit("no Widevine CDM source available on this platform")
|
|
35
|
+
else:
|
|
36
|
+
ap.print_help()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
if __name__ == "__main__":
|
|
40
|
+
main()
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Binary management for chromix: download, verify, cache the stealth Chromium bundle.
|
|
2
|
+
|
|
3
|
+
Detects the platform, downloads the matching bundle from the GitHub Release,
|
|
4
|
+
verifies it against SHA256SUMS, and caches it under ``~/.cache/chromix``.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
import hashlib
|
|
8
|
+
import os
|
|
9
|
+
import platform
|
|
10
|
+
import sys
|
|
11
|
+
import tarfile
|
|
12
|
+
import urllib.request
|
|
13
|
+
import zipfile
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
_REPO = "xiaozhou26/Chromix"
|
|
17
|
+
# Release channels track the latest verified binary for each supported major.
|
|
18
|
+
# Source/package versions may move ahead while the staged Chromium build runs.
|
|
19
|
+
_CHANNELS = {
|
|
20
|
+
"stable": {"tag": "v151.0.7922.173"},
|
|
21
|
+
"latest": {"tag": "v152.0.7977.75"},
|
|
22
|
+
}
|
|
23
|
+
_CACHE = Path(os.environ.get("CHROMIX_CACHE_DIR",
|
|
24
|
+
Path.home() / ".cache" / "chromix"))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _host(tag: str) -> str:
|
|
28
|
+
return os.environ.get("CHROMIX_DOWNLOAD_HOST",
|
|
29
|
+
f"https://github.com/{_REPO}/releases/download/{tag}")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# platform key -> (release asset, archive kind, launcher relative path)
|
|
33
|
+
_ASSETS = {
|
|
34
|
+
"linux-x64": ("chromix-linux-x64.tar.gz", "tar", "chromix/chromix"),
|
|
35
|
+
"win-x64": ("chromix-win-x64.zip", "zip", "chromix/chromix.cmd"),
|
|
36
|
+
"mac-arm64": ("chromix-mac-arm64.tar.gz", "tar", "chromix/chromix"),
|
|
37
|
+
"mac-x64": ("chromix-mac-x64.tar.gz", "tar", "chromix/chromix"),
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def resolve_platform() -> str | None:
|
|
42
|
+
sysname, mach = platform.system(), platform.machine().lower()
|
|
43
|
+
if sysname == "Linux" and mach in ("x86_64", "amd64"):
|
|
44
|
+
return "linux-x64"
|
|
45
|
+
if sysname == "Windows" and mach in ("amd64", "x86_64"):
|
|
46
|
+
return "win-x64"
|
|
47
|
+
if sysname == "Darwin":
|
|
48
|
+
return "mac-arm64" if mach in ("arm64", "aarch64") else "mac-x64"
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _sha256(path: Path) -> str:
|
|
53
|
+
h = hashlib.sha256()
|
|
54
|
+
with open(path, "rb") as fh:
|
|
55
|
+
for chunk in iter(lambda: fh.read(1 << 20), b""):
|
|
56
|
+
h.update(chunk)
|
|
57
|
+
return h.hexdigest()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _expected_sha(asset: str, host: str) -> str | None:
|
|
61
|
+
"""Fetch SHA256SUMS from the release and return the hash for `asset`."""
|
|
62
|
+
try:
|
|
63
|
+
with urllib.request.urlopen(f"{host}/SHA256SUMS", timeout=30) as r:
|
|
64
|
+
for line in r.read().decode().splitlines():
|
|
65
|
+
parts = line.split()
|
|
66
|
+
if len(parts) == 2 and parts[1].lstrip("*") == asset:
|
|
67
|
+
return parts[0].lower()
|
|
68
|
+
except Exception:
|
|
69
|
+
return None
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _download(plat: str, host: str, tag: str) -> Path:
|
|
74
|
+
"""Ensure the bundle for `plat` is present + verified; return the launcher path."""
|
|
75
|
+
asset, kind, launcher_rel = _ASSETS[plat]
|
|
76
|
+
root = _CACHE / tag / plat # cache per release tag so channels don't collide
|
|
77
|
+
launcher = root / launcher_rel
|
|
78
|
+
if launcher.exists():
|
|
79
|
+
return launcher
|
|
80
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
81
|
+
archive = root / asset
|
|
82
|
+
url = f"{host}/{asset}"
|
|
83
|
+
sys.stderr.write(f"[chromix] downloading {url} ...\n")
|
|
84
|
+
urllib.request.urlretrieve(url, archive)
|
|
85
|
+
|
|
86
|
+
expected = _expected_sha(asset, host)
|
|
87
|
+
if expected:
|
|
88
|
+
actual = _sha256(archive)
|
|
89
|
+
if actual != expected:
|
|
90
|
+
archive.unlink(missing_ok=True)
|
|
91
|
+
raise RuntimeError(f"SHA256 mismatch for {asset}: expected {expected}, got {actual}")
|
|
92
|
+
sys.stderr.write("[chromix] SHA256 verified\n")
|
|
93
|
+
else:
|
|
94
|
+
sys.stderr.write("[chromix] WARNING: no SHA256SUMS published; skipping verification\n")
|
|
95
|
+
|
|
96
|
+
if kind == "tar":
|
|
97
|
+
with tarfile.open(archive) as t:
|
|
98
|
+
t.extractall(root)
|
|
99
|
+
else:
|
|
100
|
+
with zipfile.ZipFile(archive) as z:
|
|
101
|
+
z.extractall(root)
|
|
102
|
+
archive.unlink(missing_ok=True)
|
|
103
|
+
if launcher.exists() and not launcher.name.endswith(".cmd"):
|
|
104
|
+
launcher.chmod(0o755)
|
|
105
|
+
if not launcher.exists():
|
|
106
|
+
raise RuntimeError(f"bundle extracted but launcher missing: {launcher}")
|
|
107
|
+
return launcher
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Linux Fontconfig wiring for the bundled Windows font assets."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import tempfile
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def linux_font_env(executable: str | os.PathLike) -> dict[str, str]:
|
|
12
|
+
"""Return ``FONTCONFIG_FILE`` when a Linux bundle contains ``fonts/``."""
|
|
13
|
+
if sys.platform != "linux":
|
|
14
|
+
return {}
|
|
15
|
+
|
|
16
|
+
fonts_dir = Path(executable).resolve().parent / "fonts"
|
|
17
|
+
template = fonts_dir / "fonts.conf.template"
|
|
18
|
+
if not template.is_file():
|
|
19
|
+
return {}
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
cache_dir = Path(os.environ.get("XDG_CACHE_HOME", Path.home() / ".cache")) \
|
|
23
|
+
/ "chromix" / "fontconfig"
|
|
24
|
+
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
25
|
+
config = template.read_text(encoding="utf-8")
|
|
26
|
+
config = config.replace("@FONTS_DIR@", str(fonts_dir))
|
|
27
|
+
config = config.replace("@CACHE_DIR@", str(cache_dir))
|
|
28
|
+
config_path = Path(tempfile.gettempdir()) / f"chromix-fontconfig-{os.getuid()}.conf"
|
|
29
|
+
config_path.write_text(config, encoding="utf-8")
|
|
30
|
+
return {"FONTCONFIG_FILE": str(config_path)}
|
|
31
|
+
except OSError:
|
|
32
|
+
return {}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def apply_font_env(executable: str | os.PathLike,
|
|
36
|
+
launch_kwargs: dict[str, Any]) -> None:
|
|
37
|
+
"""Merge the bundled-font environment into Playwright launch options."""
|
|
38
|
+
font_env = linux_font_env(executable)
|
|
39
|
+
user_env = launch_kwargs.get("env")
|
|
40
|
+
if not font_env and user_env is None:
|
|
41
|
+
return
|
|
42
|
+
merged = dict(os.environ)
|
|
43
|
+
merged.update(font_env)
|
|
44
|
+
if user_env:
|
|
45
|
+
merged.update(user_env)
|
|
46
|
+
launch_kwargs["env"] = merged
|