capiumbrowser 0.1.1__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.
- capiumbrowser/__init__.py +51 -0
- capiumbrowser/__main__.py +328 -0
- capiumbrowser/_version.py +86 -0
- capiumbrowser/browser.py +271 -0
- capiumbrowser/browser_async.py +164 -0
- capiumbrowser/channels.json +13 -0
- capiumbrowser/config.py +196 -0
- capiumbrowser/errors.py +90 -0
- capiumbrowser/human/__init__.py +49 -0
- capiumbrowser/human/behavior.py +104 -0
- capiumbrowser/licensing/__init__.py +36 -0
- capiumbrowser/licensing/client.py +173 -0
- capiumbrowser/licensing/download.py +319 -0
- capiumbrowser/network/__init__.py +15 -0
- capiumbrowser/network/proxy.py +179 -0
- capiumbrowser-0.1.1.dist-info/METADATA +775 -0
- capiumbrowser-0.1.1.dist-info/RECORD +21 -0
- capiumbrowser-0.1.1.dist-info/WHEEL +5 -0
- capiumbrowser-0.1.1.dist-info/entry_points.txt +2 -0
- capiumbrowser-0.1.1.dist-info/licenses/LICENSE +38 -0
- capiumbrowser-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
capiumbrowser -- a drop-in stealth browser SDK on top of Playwright.
|
|
3
|
+
|
|
4
|
+
from capiumbrowser import launch, launch_context
|
|
5
|
+
browser = launch(seed=42, platform="windows", headless=False)
|
|
6
|
+
page = browser.new_context().new_page()
|
|
7
|
+
page.goto("https://fingerprint.com/demo/")
|
|
8
|
+
|
|
9
|
+
Playwright is a normal pip dependency, so you update the engine independently:
|
|
10
|
+
pip install -U playwright
|
|
11
|
+
|
|
12
|
+
The browser itself is the Capium stealth Chromium (driven via its `capium` wrapper). No
|
|
13
|
+
Patchright needed -- the binary's own 001/009 patches keep developer_tools/webdriver clean.
|
|
14
|
+
|
|
15
|
+
Docs: https://docs.capiumbrowser.com · Site: https://capiumbrowser.com
|
|
16
|
+
"""
|
|
17
|
+
from ._version import __version__, CAPIUM_BINARY_VERSION, CAPIUM_BINARY_VERSIONS
|
|
18
|
+
from . import config
|
|
19
|
+
from .network import proxy
|
|
20
|
+
from . import human
|
|
21
|
+
from .licensing import download
|
|
22
|
+
from .licensing import status
|
|
23
|
+
from .errors import (
|
|
24
|
+
CapiumError,
|
|
25
|
+
CapiumLicenseError,
|
|
26
|
+
CapiumConfigError,
|
|
27
|
+
CapiumSeatLimitError,
|
|
28
|
+
CapiumExpiredError,
|
|
29
|
+
CapiumServerDownError,
|
|
30
|
+
)
|
|
31
|
+
from .browser import (
|
|
32
|
+
launch,
|
|
33
|
+
launch_context,
|
|
34
|
+
launch_persistent_context,
|
|
35
|
+
)
|
|
36
|
+
from .browser_async import (
|
|
37
|
+
launch_async,
|
|
38
|
+
launch_context_async,
|
|
39
|
+
launch_persistent_context_async,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
"__version__",
|
|
44
|
+
"CAPIUM_BINARY_VERSION",
|
|
45
|
+
"CAPIUM_BINARY_VERSIONS",
|
|
46
|
+
"config", "proxy", "human", "download", "status",
|
|
47
|
+
"launch", "launch_context", "launch_persistent_context",
|
|
48
|
+
"launch_async", "launch_context_async", "launch_persistent_context_async",
|
|
49
|
+
"CapiumError", "CapiumLicenseError", "CapiumConfigError",
|
|
50
|
+
"CapiumSeatLimitError", "CapiumExpiredError", "CapiumServerDownError",
|
|
51
|
+
]
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"""capium CLI -- python -m capiumbrowser <command>
|
|
2
|
+
|
|
3
|
+
info environment check: license + binary + dependencies + system libraries
|
|
4
|
+
(i.e. "what do I still need installed?"). Exit 0 if ready, 1 if not.
|
|
5
|
+
status license entitlements from the server (plan, seats, live sessions, renewal)
|
|
6
|
+
version SDK version + the Chromium/Capium build it targets
|
|
7
|
+
install download the capium browser binary
|
|
8
|
+
run [URL] launch a stealth browser to URL (demo; Ctrl-C to exit)
|
|
9
|
+
|
|
10
|
+
info/status flags: --json --quick (skip the license-server call) --proxy URL
|
|
11
|
+
run env: SEED, PLATFORM (windows|macos|linux), HEADLESS=1, PROXY, GEOIP=1,
|
|
12
|
+
PROFILE=<dir> (persistent), HUMANIZE=1
|
|
13
|
+
"""
|
|
14
|
+
import argparse
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import platform
|
|
18
|
+
import re
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
21
|
+
import time
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ---- diagnostics helpers -----------------------------------------------------
|
|
25
|
+
|
|
26
|
+
def _playwright_version():
|
|
27
|
+
try:
|
|
28
|
+
import importlib.metadata as m
|
|
29
|
+
return m.version("playwright")
|
|
30
|
+
except Exception:
|
|
31
|
+
try:
|
|
32
|
+
import playwright # noqa: F401
|
|
33
|
+
return getattr(playwright, "__version__", "installed")
|
|
34
|
+
except Exception:
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _binary_version(binpath):
|
|
39
|
+
"""Ask the binary its version. `--version` prints and exits BEFORE the license gate,
|
|
40
|
+
so this works without a configured license. Returns None if it can't be read."""
|
|
41
|
+
try:
|
|
42
|
+
out = subprocess.run([binpath, "--version"], capture_output=True, text=True,
|
|
43
|
+
timeout=15)
|
|
44
|
+
return (out.stdout or out.stderr).strip() or None
|
|
45
|
+
except Exception:
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _missing_linux_libs(binpath):
|
|
50
|
+
"""Shared libraries the engine needs but that aren't installed (Linux only). ldd the
|
|
51
|
+
'chrome' engine the wrapper spawns and collect any 'not found' -- the usual cause of an
|
|
52
|
+
opaque launch failure on a fresh server."""
|
|
53
|
+
if platform.system() != "Linux":
|
|
54
|
+
return []
|
|
55
|
+
engine = os.path.join(os.path.dirname(binpath), "chrome")
|
|
56
|
+
target = engine if os.path.isfile(engine) else binpath
|
|
57
|
+
try:
|
|
58
|
+
out = subprocess.run(["ldd", target], capture_output=True, text=True, timeout=15)
|
|
59
|
+
except Exception:
|
|
60
|
+
return []
|
|
61
|
+
return [ln.strip().split()[0] for ln in out.stdout.splitlines() if "not found" in ln]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _license_source():
|
|
65
|
+
"""Where the key is configured (env / file), with NO server call. None if unset."""
|
|
66
|
+
from .licensing import client as _license
|
|
67
|
+
if os.environ.get("CAPIUM_LICENSE_KEY"):
|
|
68
|
+
return "env CAPIUM_LICENSE_KEY"
|
|
69
|
+
key, _ = _license._from_file() # ~/.capium/license
|
|
70
|
+
return "~/.capium/license" if key else None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _gather(quick, proxy):
|
|
74
|
+
from . import config
|
|
75
|
+
from .licensing import client as _license
|
|
76
|
+
from ._version import __version__, CAPIUM_BINARY_VERSION
|
|
77
|
+
info = {"issues": []}
|
|
78
|
+
|
|
79
|
+
info["sdk"] = {"version": __version__, "targets_chromium": CAPIUM_BINARY_VERSION}
|
|
80
|
+
info["python"] = {"version": platform.python_version(),
|
|
81
|
+
"os": platform.system(), "arch": platform.machine()}
|
|
82
|
+
|
|
83
|
+
pv = _playwright_version()
|
|
84
|
+
info["playwright"] = {"installed": pv is not None, "version": pv}
|
|
85
|
+
if pv is None:
|
|
86
|
+
info["issues"].append("Playwright not installed -> pip install playwright")
|
|
87
|
+
|
|
88
|
+
from .licensing import download as _dl
|
|
89
|
+
binary = {"found": False, "path": None, "executable": None, "version": None,
|
|
90
|
+
"installed_version": None, "target_version": CAPIUM_BINARY_VERSION,
|
|
91
|
+
"up_to_date": None, "platform_tag": None}
|
|
92
|
+
try:
|
|
93
|
+
binary["platform_tag"] = _dl.download_tag()
|
|
94
|
+
except Exception as e: # unsupported platform (macOS Intel, Windows ARM)
|
|
95
|
+
binary["platform_error"] = str(e)
|
|
96
|
+
info["issues"].append(str(e))
|
|
97
|
+
try:
|
|
98
|
+
path = config.find_binary()
|
|
99
|
+
vstr = _binary_version(path) # e.g. "Capium 151.0.7922.137"
|
|
100
|
+
m = re.search(r"\d+\.\d+\.\d+\.\d+", vstr or "")
|
|
101
|
+
iv = m.group(0) if m else _dl.installed_version(path) # marker fallback
|
|
102
|
+
binary.update(found=True, path=path,
|
|
103
|
+
executable=(os.name != "posix") or os.access(path, os.X_OK),
|
|
104
|
+
version=vstr, installed_version=iv,
|
|
105
|
+
up_to_date=(iv == CAPIUM_BINARY_VERSION) if iv else None)
|
|
106
|
+
if not binary["executable"]:
|
|
107
|
+
info["issues"].append("Browser binary is not executable -> chmod +x '%s'" % path)
|
|
108
|
+
if iv and iv != CAPIUM_BINARY_VERSION:
|
|
109
|
+
info["issues"].append(
|
|
110
|
+
"Browser binary is %s but the SDK targets %s -> it auto-updates on the next "
|
|
111
|
+
"launch (or run: python -m capiumbrowser install)" % (iv, CAPIUM_BINARY_VERSION))
|
|
112
|
+
except FileNotFoundError:
|
|
113
|
+
info["issues"].append("Browser binary not installed -> python -m capiumbrowser install")
|
|
114
|
+
info["binary"] = binary
|
|
115
|
+
|
|
116
|
+
src = _license_source()
|
|
117
|
+
lic = {"configured": src is not None, "source": src, "server_checked": False,
|
|
118
|
+
"plan": None, "sessions_cap": None, "live_sessions": None, "period_end": None}
|
|
119
|
+
if src is None:
|
|
120
|
+
info["issues"].append("No license key -> set CAPIUM_LICENSE_KEY or ~/.capium/license (KEY=...)")
|
|
121
|
+
elif not quick:
|
|
122
|
+
try:
|
|
123
|
+
st = _license.status()
|
|
124
|
+
lic.update(server_checked=True, plan=st.get("plan"),
|
|
125
|
+
sessions_cap=st.get("sessions_cap"),
|
|
126
|
+
live_sessions=st.get("live_sessions"),
|
|
127
|
+
period_end=st.get("period_end"))
|
|
128
|
+
except Exception as e:
|
|
129
|
+
lic["server_error"] = str(e)
|
|
130
|
+
info["issues"].append("License check failed -> %s" % e)
|
|
131
|
+
info["license"] = lic
|
|
132
|
+
|
|
133
|
+
if binary["found"]:
|
|
134
|
+
missing = _missing_linux_libs(binary["path"])
|
|
135
|
+
if missing:
|
|
136
|
+
info["missing_libs"] = missing
|
|
137
|
+
info["issues"].append("Missing system libraries: %s (install them via your package manager)"
|
|
138
|
+
% ", ".join(missing))
|
|
139
|
+
|
|
140
|
+
if proxy:
|
|
141
|
+
info["proxy"] = {"note": "exit IP + timezone resolved inside the browser at launch (--geoip flag)"}
|
|
142
|
+
return info
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _print_human(info):
|
|
146
|
+
def row(label, val, status=None):
|
|
147
|
+
tag = "" if status is None else " [%s]" % status
|
|
148
|
+
print(" %-13s %s%s" % (label, val, tag))
|
|
149
|
+
|
|
150
|
+
sdk = info["sdk"]
|
|
151
|
+
py = info["python"]
|
|
152
|
+
print("\nCapium browser -- environment check\n")
|
|
153
|
+
row("SDK", "capiumbrowser %s (targets Chromium %s)" % (sdk["version"], sdk["targets_chromium"]))
|
|
154
|
+
row("Python", "%s on %s/%s" % (py["version"], py["os"], py["arch"]))
|
|
155
|
+
pw = info["playwright"]
|
|
156
|
+
row("Playwright", pw["version"] or "not installed", "OK" if pw["installed"] else "MISSING")
|
|
157
|
+
|
|
158
|
+
b = info["binary"]
|
|
159
|
+
print()
|
|
160
|
+
if b.get("platform_tag"):
|
|
161
|
+
row("Platform", "%s (%s/%s)" % (b["platform_tag"], py["os"], py["arch"]))
|
|
162
|
+
elif b.get("platform_error"):
|
|
163
|
+
row("Platform", "unsupported: %s/%s" % (py["os"], py["arch"]), "UNSUPPORTED")
|
|
164
|
+
if b["found"]:
|
|
165
|
+
iv, tv = b.get("installed_version"), b.get("target_version")
|
|
166
|
+
if iv and b.get("up_to_date"):
|
|
167
|
+
row("Binary", "installed v%s (up to date)" % iv,
|
|
168
|
+
"OK" if b["executable"] else "not executable")
|
|
169
|
+
elif iv:
|
|
170
|
+
row("Binary", "installed v%s -> SDK targets v%s (auto-updates next launch)" % (iv, tv),
|
|
171
|
+
"UPDATE")
|
|
172
|
+
else:
|
|
173
|
+
row("Binary", "installed", "OK" if b["executable"] else "not executable")
|
|
174
|
+
row("", b["path"])
|
|
175
|
+
else:
|
|
176
|
+
row("Binary", "NOT INSTALLED", "MISSING")
|
|
177
|
+
row("", "target v%s -> python -m capiumbrowser install" % b.get("target_version"))
|
|
178
|
+
|
|
179
|
+
lic = info["license"]
|
|
180
|
+
print()
|
|
181
|
+
if lic["configured"]:
|
|
182
|
+
row("License", "key from %s" % lic["source"], "OK")
|
|
183
|
+
if lic.get("server_checked"):
|
|
184
|
+
row("", "plan=%s seats=%s (%s live) renews=%s"
|
|
185
|
+
% (lic["plan"], lic["sessions_cap"], lic["live_sessions"], lic["period_end"]))
|
|
186
|
+
elif lic.get("server_error"):
|
|
187
|
+
row("", "server check: %s" % lic["server_error"], "WARN")
|
|
188
|
+
else:
|
|
189
|
+
row("License", "not configured", "MISSING")
|
|
190
|
+
|
|
191
|
+
if info.get("missing_libs"):
|
|
192
|
+
print()
|
|
193
|
+
row("System libs", "missing: %s" % ", ".join(info["missing_libs"]), "MISSING")
|
|
194
|
+
|
|
195
|
+
if info.get("proxy"):
|
|
196
|
+
p = info["proxy"]
|
|
197
|
+
print()
|
|
198
|
+
row("Proxy", p.get("error") or p.get("note") or "configured")
|
|
199
|
+
|
|
200
|
+
print()
|
|
201
|
+
if info["issues"]:
|
|
202
|
+
print("%d thing(s) to fix:" % len(info["issues"]))
|
|
203
|
+
for i in info["issues"]:
|
|
204
|
+
print(" - %s" % i)
|
|
205
|
+
else:
|
|
206
|
+
print("Ready: everything needed is installed and configured.")
|
|
207
|
+
print()
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# ---- subcommands -------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
def _cmd_info(args):
|
|
213
|
+
info = _gather(quick=args.quick, proxy=args.proxy)
|
|
214
|
+
if args.json:
|
|
215
|
+
print(json.dumps(info, indent=2))
|
|
216
|
+
else:
|
|
217
|
+
_print_human(info)
|
|
218
|
+
return 1 if info["issues"] else 0
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _cmd_status(args):
|
|
222
|
+
from .licensing import client as _license
|
|
223
|
+
from .errors import CapiumError
|
|
224
|
+
try:
|
|
225
|
+
st = _license.status()
|
|
226
|
+
except CapiumError as e:
|
|
227
|
+
print("capium status: %s" % e, file=sys.stderr)
|
|
228
|
+
return 1
|
|
229
|
+
print(json.dumps(st, indent=2) if args.json
|
|
230
|
+
else "\n".join(" %-14s %s" % (k, v) for k, v in st.items()))
|
|
231
|
+
return 0
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _cmd_version(args):
|
|
235
|
+
from ._version import __version__, CAPIUM_BINARY_VERSION
|
|
236
|
+
from . import config
|
|
237
|
+
from .licensing import download as _dl
|
|
238
|
+
print("capiumbrowser %s (targets Chromium %s)" % (__version__, CAPIUM_BINARY_VERSION))
|
|
239
|
+
try:
|
|
240
|
+
path = config.find_binary()
|
|
241
|
+
vstr = _binary_version(path) or ""
|
|
242
|
+
m = re.search(r"\d+\.\d+\.\d+\.\d+", vstr)
|
|
243
|
+
iv = m.group(0) if m else (_dl.installed_version(path) or "unknown")
|
|
244
|
+
if iv == CAPIUM_BINARY_VERSION:
|
|
245
|
+
print("installed binary: v%s (up to date) at %s" % (iv, path))
|
|
246
|
+
else:
|
|
247
|
+
print("installed binary: v%s -> updates to v%s on next launch, at %s"
|
|
248
|
+
% (iv, CAPIUM_BINARY_VERSION, path))
|
|
249
|
+
except FileNotFoundError:
|
|
250
|
+
print("installed binary: NOT INSTALLED -> python -m capiumbrowser install")
|
|
251
|
+
return 0
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _cmd_install(args):
|
|
255
|
+
from .licensing import download as _download, client as _license
|
|
256
|
+
key, _ = _license.effective()
|
|
257
|
+
print("downloading the capium browser binary ...")
|
|
258
|
+
print("installed:", _download.ensure_binary(license_key=key))
|
|
259
|
+
return 0
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _cmd_run(args):
|
|
263
|
+
seed = os.environ.get("SEED")
|
|
264
|
+
_geoip = os.environ.get("GEOIP") # 1 = force on, 0 = force off, unset = auto (None)
|
|
265
|
+
opts = dict(
|
|
266
|
+
seed=int(seed) if seed else None,
|
|
267
|
+
platform=os.environ.get("PLATFORM", "windows"),
|
|
268
|
+
headless=os.environ.get("HEADLESS") == "1",
|
|
269
|
+
proxy=(os.environ.get("PROXY") or None),
|
|
270
|
+
geoip=True if _geoip == "1" else (False if _geoip == "0" else None),
|
|
271
|
+
)
|
|
272
|
+
from capiumbrowser import launch_persistent_context, launch_context
|
|
273
|
+
profile = os.environ.get("PROFILE")
|
|
274
|
+
if profile:
|
|
275
|
+
ctx = launch_persistent_context(profile, **opts)
|
|
276
|
+
page = ctx.pages[0] if ctx.pages else ctx.new_page()
|
|
277
|
+
page.goto(args.url, wait_until="domcontentloaded", timeout=60000)
|
|
278
|
+
closer = ctx
|
|
279
|
+
else:
|
|
280
|
+
browser, ctx, page = launch_context(
|
|
281
|
+
url=args.url, humanize=os.environ.get("HUMANIZE") == "1", **opts)
|
|
282
|
+
closer = browser
|
|
283
|
+
print("== Capium ==")
|
|
284
|
+
print(" platform :", page.evaluate("()=>navigator.platform"))
|
|
285
|
+
print(" userAgent:", page.evaluate("()=>navigator.userAgent"))
|
|
286
|
+
print(" url :", args.url)
|
|
287
|
+
print("Browser open. Ctrl-C to exit.")
|
|
288
|
+
try:
|
|
289
|
+
while True:
|
|
290
|
+
time.sleep(0.5)
|
|
291
|
+
except KeyboardInterrupt:
|
|
292
|
+
pass
|
|
293
|
+
finally:
|
|
294
|
+
closer.close()
|
|
295
|
+
return 0
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def main():
|
|
299
|
+
p = argparse.ArgumentParser(prog="python -m capiumbrowser",
|
|
300
|
+
description="Capium stealth browser CLI")
|
|
301
|
+
sub = p.add_subparsers(dest="cmd")
|
|
302
|
+
|
|
303
|
+
pi = sub.add_parser("info", help="environment check: license + binary + deps + system libs")
|
|
304
|
+
pi.add_argument("--json", action="store_true", help="machine-readable output")
|
|
305
|
+
pi.add_argument("--quick", action="store_true", help="skip the license-server call")
|
|
306
|
+
pi.add_argument("--proxy", help="note how exit IP + timezone are resolved for this proxy")
|
|
307
|
+
pi.set_defaults(fn=_cmd_info)
|
|
308
|
+
|
|
309
|
+
ps = sub.add_parser("status", help="license entitlements from the server")
|
|
310
|
+
ps.add_argument("--json", action="store_true")
|
|
311
|
+
ps.set_defaults(fn=_cmd_status)
|
|
312
|
+
|
|
313
|
+
sub.add_parser("version", help="SDK + targeted browser version").set_defaults(fn=_cmd_version)
|
|
314
|
+
sub.add_parser("install", help="download the capium browser binary").set_defaults(fn=_cmd_install)
|
|
315
|
+
|
|
316
|
+
pr = sub.add_parser("run", help="launch a stealth browser (demo)")
|
|
317
|
+
pr.add_argument("url", nargs="?", default="https://fingerprint.com/demo/")
|
|
318
|
+
pr.set_defaults(fn=_cmd_run)
|
|
319
|
+
|
|
320
|
+
args = p.parse_args()
|
|
321
|
+
if not getattr(args, "cmd", None):
|
|
322
|
+
# No subcommand -> the diagnostic, so a bare `python -m capiumbrowser` is useful.
|
|
323
|
+
return sys.exit(_cmd_info(argparse.Namespace(json=False, quick=False, proxy=None)))
|
|
324
|
+
sys.exit(args.fn(args))
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
if __name__ == "__main__":
|
|
328
|
+
main()
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Version of the capium Python SDK (independent of the browser binary version).
|
|
2
|
+
|
|
3
|
+
The SDK version and the Capium/Chromium binary version move on separate cadences: you update
|
|
4
|
+
the SDK with `pip install -U capiumbrowser`, and the SDK fetches the *default* binary build to
|
|
5
|
+
match. Crucially that default is **per platform** -- each OS/arch reaches a stable Capium build
|
|
6
|
+
independently (Windows can sit on 150 while macOS is on 151).
|
|
7
|
+
|
|
8
|
+
The stable build for each OS is not hardcoded here; it comes from `channels.json`, which ships
|
|
9
|
+
inside the wheel and is the single source of truth (the release pipeline edits that file to
|
|
10
|
+
promote an OS to a new stable, and the same file is published to the download server). This
|
|
11
|
+
module just loads it; `_FALLBACK_VERSIONS` is used only if the bundled manifest is missing or
|
|
12
|
+
corrupt in an install.
|
|
13
|
+
|
|
14
|
+
Override order at download time: CAPIUM_VERSION env > per-OS stable from channels.json.
|
|
15
|
+
"""
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import platform as _platform
|
|
19
|
+
|
|
20
|
+
__version__ = "0.1.1"
|
|
21
|
+
|
|
22
|
+
# Used only if channels.json can't be read (corrupt/removed install). ensure_binary() raises a
|
|
23
|
+
# clear error long before the "unsupported platform" case matters; this keeps lookups total.
|
|
24
|
+
_FALLBACK_VERSIONS = {
|
|
25
|
+
"windows-x64": "152.0.7977.65", # real Chrome 152 stable per OS (win/mac .65, linux .64)
|
|
26
|
+
"macos-arm64": "152.0.7977.65",
|
|
27
|
+
"linux-x64": "152.0.7977.64",
|
|
28
|
+
"linux-arm64": "152.0.7977.64", # declared; not yet published (see channels.json)
|
|
29
|
+
}
|
|
30
|
+
_DEFAULT_BINARY_VERSION = "152.0.7977.65"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _load_stable():
|
|
34
|
+
"""The `channels.stable` map from the bundled manifest, or None if unreadable."""
|
|
35
|
+
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "channels.json")
|
|
36
|
+
try:
|
|
37
|
+
with open(path, encoding="utf-8") as f:
|
|
38
|
+
return json.load(f)["channels"]["stable"]
|
|
39
|
+
except (OSError, ValueError, KeyError, TypeError):
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
_STABLE = _load_stable()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _versions_from_manifest():
|
|
47
|
+
if not _STABLE:
|
|
48
|
+
return dict(_FALLBACK_VERSIONS)
|
|
49
|
+
out = {tag: e["version"] for tag, e in _STABLE.items() if isinstance(e, dict) and e.get("version")}
|
|
50
|
+
return out or dict(_FALLBACK_VERSIONS)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# Default stable Capium binary build per distro tag, resolved from channels.json.
|
|
54
|
+
CAPIUM_BINARY_VERSIONS = _versions_from_manifest()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def binary_version_for(tag):
|
|
58
|
+
"""The current stable Capium binary version for a distro tag (e.g. 'windows-x64')."""
|
|
59
|
+
return CAPIUM_BINARY_VERSIONS.get(tag, _DEFAULT_BINARY_VERSION)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def is_published(tag):
|
|
63
|
+
"""False only when the manifest declares the tag but marks it not-yet-published. Unknown
|
|
64
|
+
tags / missing manifest return True so the download layer's own error/404 speaks instead."""
|
|
65
|
+
if not _STABLE:
|
|
66
|
+
return True
|
|
67
|
+
entry = _STABLE.get(tag)
|
|
68
|
+
if not isinstance(entry, dict):
|
|
69
|
+
return True
|
|
70
|
+
return bool(entry.get("published", True))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _host_tag():
|
|
74
|
+
"""Best-effort distro tag for the current host, mirroring licensing/download.py's mapping.
|
|
75
|
+
Kept dependency-free (no import of download.py) so _version stays importable in isolation."""
|
|
76
|
+
s = _platform.system().lower()
|
|
77
|
+
os_ = "macos" if s == "darwin" else ("windows" if s == "windows" else "linux")
|
|
78
|
+
m = _platform.machine().lower()
|
|
79
|
+
arch = "x64" if m in ("x86_64", "amd64", "x64") else ("arm64" if m in ("arm64", "aarch64") else m)
|
|
80
|
+
return "%s-%s" % (os_, arch)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# The stable build this SDK targets for THIS host. Kept as a module constant for backward
|
|
84
|
+
# compatibility (CLI display, examples). Code that fetches a build should prefer
|
|
85
|
+
# binary_version_for(tag) with the tag it already resolved.
|
|
86
|
+
CAPIUM_BINARY_VERSION = binary_version_for(_host_tag())
|