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.
@@ -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())