capiumbrowser 1.2.4__tar.gz → 1.3.1__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.
- {capiumbrowser-1.2.4/capiumbrowser.egg-info → capiumbrowser-1.3.1}/PKG-INFO +2 -2
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/README.md +1 -1
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/_version.py +1 -1
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/android-phones.json +64 -10
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/browser.py +36 -10
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/browser_async.py +16 -10
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/channels.json +13 -13
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/config.py +7 -6
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/mobile.py +1 -1
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1/capiumbrowser.egg-info}/PKG-INFO +2 -2
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/pyproject.toml +1 -1
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/tests/test_geoip_flags.py +17 -6
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/LICENSE +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/MANIFEST.in +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/__init__.py +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/__main__.py +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/_driver.py +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/browser-brands.json +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/desktop.py +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/errors.py +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/human/__init__.py +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/human/behavior.py +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/ios-phones.json +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/licensing/__init__.py +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/licensing/client.py +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/licensing/download.py +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/licensing/readiness.py +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/network/__init__.py +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/network/proxy.py +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser.egg-info/SOURCES.txt +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser.egg-info/dependency_links.txt +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser.egg-info/entry_points.txt +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser.egg-info/requires.txt +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser.egg-info/top_level.txt +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/setup.cfg +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/tests/test_channels_manifest.py +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/tests/test_config_args.py +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/tests/test_desktop_brands.py +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/tests/test_download_contract.py +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/tests/test_ios_profiles.py +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/tests/test_launch_viewport.py +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/tests/test_license_readiness.py +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/tests/test_mobile_profiles.py +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/tests/test_proxy.py +0 -0
- {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/tests/test_version_tables_consistent.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: capiumbrowser
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.1
|
|
4
4
|
Summary: Drop-in stealth browser SDK on top of Playwright (Capium Chromium).
|
|
5
5
|
Author: Capzy
|
|
6
6
|
License: Capzy capiumbrowser SDK License
|
|
@@ -408,7 +408,7 @@ and `launch_persistent_context` forward everything to `launch`.
|
|
|
408
408
|
| Parameter | Type | Default | Description |
|
|
409
409
|
|---|---|---|---|
|
|
410
410
|
| `proxy` | `str` \| `dict` \| `"env"`/`True` \| `None` | `None` | HTTP/HTTPS/SOCKS5 URL with inline creds, a `{"server","username","password"}` dict, `"env"`/`True` (reads `CAPIUM_PROXY`), or `None`/`"none"`/`""`. |
|
|
411
|
-
| `geoip` | `bool` \| `None` | `None` | Geo coherence, resolved inside the binary. **`None` (default) — OFF**:
|
|
411
|
+
| `geoip` | `bool` \| `None` | `None` | Geo coherence, resolved inside the binary. **`None` (default) — ON whenever a proxy is set, OFF otherwise**: an uncorrected host timezone under a proxied identity (UTC on a server) is a far stronger detection signal than the lookup's one-off launch latency. **`True`** — ON even proxyless (pins timezone/geo/WebRTC/`navigator.languages` to the host IP). **`False`** — explicit opt-out. No SDK-side probe. |
|
|
412
412
|
| `timezone` | `str` \| `None` | `None` | IANA tz (e.g. `"America/New_York"`) → `--timezone`. **Overrides** `geoip`. |
|
|
413
413
|
| `locale` | `str` \| `None` | `None` | BCP-47 (e.g. `"en-US"`) → `--lang` + `--accept-lang`. **Overrides** `geoip`. |
|
|
414
414
|
|
|
@@ -345,7 +345,7 @@ and `launch_persistent_context` forward everything to `launch`.
|
|
|
345
345
|
| Parameter | Type | Default | Description |
|
|
346
346
|
|---|---|---|---|
|
|
347
347
|
| `proxy` | `str` \| `dict` \| `"env"`/`True` \| `None` | `None` | HTTP/HTTPS/SOCKS5 URL with inline creds, a `{"server","username","password"}` dict, `"env"`/`True` (reads `CAPIUM_PROXY`), or `None`/`"none"`/`""`. |
|
|
348
|
-
| `geoip` | `bool` \| `None` | `None` | Geo coherence, resolved inside the binary. **`None` (default) — OFF**:
|
|
348
|
+
| `geoip` | `bool` \| `None` | `None` | Geo coherence, resolved inside the binary. **`None` (default) — ON whenever a proxy is set, OFF otherwise**: an uncorrected host timezone under a proxied identity (UTC on a server) is a far stronger detection signal than the lookup's one-off launch latency. **`True`** — ON even proxyless (pins timezone/geo/WebRTC/`navigator.languages` to the host IP). **`False`** — explicit opt-out. No SDK-side probe. |
|
|
349
349
|
| `timezone` | `str` \| `None` | `None` | IANA tz (e.g. `"America/New_York"`) → `--timezone`. **Overrides** `geoip`. |
|
|
350
350
|
| `locale` | `str` \| `None` | `None` | BCP-47 (e.g. `"en-US"`) → `--lang` + `--accept-lang`. **Overrides** `geoip`. |
|
|
351
351
|
|
|
@@ -17,7 +17,7 @@ import json
|
|
|
17
17
|
import os
|
|
18
18
|
import platform as _platform
|
|
19
19
|
|
|
20
|
-
__version__ = "1.
|
|
20
|
+
__version__ = "1.3.1"
|
|
21
21
|
|
|
22
22
|
# Used only if channels.json can't be read (corrupt/removed install). ensure_binary() raises a
|
|
23
23
|
# clear error long before the "unsupported platform" case matters; this keeps lookups total.
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"schema_version": 2,
|
|
3
3
|
"minimum_release_year": 2022,
|
|
4
4
|
"browser": "chrome",
|
|
5
|
-
"status": "
|
|
6
|
-
"gpu_policy": "
|
|
7
|
-
"hardware_policy": "
|
|
5
|
+
"status": "catalog-emulation",
|
|
6
|
+
"gpu_policy": "catalog",
|
|
7
|
+
"hardware_policy": "catalog",
|
|
8
8
|
"physical_device_validation": "pending",
|
|
9
9
|
"default_profile_ids": [
|
|
10
10
|
"pixel-7",
|
|
@@ -13,6 +13,20 @@
|
|
|
13
13
|
"pixel-8a",
|
|
14
14
|
"pixel-9"
|
|
15
15
|
],
|
|
16
|
+
"font_core": [
|
|
17
|
+
"Roboto",
|
|
18
|
+
"Noto Sans",
|
|
19
|
+
"Noto Serif",
|
|
20
|
+
"Noto Sans Mono",
|
|
21
|
+
"Noto Color Emoji",
|
|
22
|
+
"Noto Sans JP",
|
|
23
|
+
"Noto Sans KR",
|
|
24
|
+
"Noto Sans SC",
|
|
25
|
+
"Noto Sans TC",
|
|
26
|
+
"Noto Naskh Arabic",
|
|
27
|
+
"Noto Sans Devanagari",
|
|
28
|
+
"Noto Sans Thai"
|
|
29
|
+
],
|
|
16
30
|
"sources": [
|
|
17
31
|
"https://support.google.com/pixelphone/answer/7158570?hl=en",
|
|
18
32
|
"https://news.samsung.com/global/samsung-galaxy-a55-5g-and-galaxy-a35-5g-awesome-innovations-and-security-engineered-for-everyone",
|
|
@@ -27,7 +41,12 @@
|
|
|
27
41
|
"platform_version": "13",
|
|
28
42
|
"width": 412,
|
|
29
43
|
"height": 915,
|
|
30
|
-
"dpr": 2.625
|
|
44
|
+
"dpr": 2.625,
|
|
45
|
+
"soc": "Tensor G2",
|
|
46
|
+
"cores": 8,
|
|
47
|
+
"memory_gb": 8,
|
|
48
|
+
"gpu_vendor": "Google Inc. (ARM)",
|
|
49
|
+
"gpu_renderer": "ANGLE (ARM, Mali-G710, OpenGL ES 3.2)"
|
|
31
50
|
},
|
|
32
51
|
{
|
|
33
52
|
"id": "pixel-8",
|
|
@@ -36,7 +55,12 @@
|
|
|
36
55
|
"platform_version": "14",
|
|
37
56
|
"width": 412,
|
|
38
57
|
"height": 915,
|
|
39
|
-
"dpr": 2.625
|
|
58
|
+
"dpr": 2.625,
|
|
59
|
+
"soc": "Tensor G3",
|
|
60
|
+
"cores": 9,
|
|
61
|
+
"memory_gb": 8,
|
|
62
|
+
"gpu_vendor": "Google Inc. (ARM)",
|
|
63
|
+
"gpu_renderer": "ANGLE (ARM, Mali-G715, OpenGL ES 3.2)"
|
|
40
64
|
},
|
|
41
65
|
{
|
|
42
66
|
"id": "pixel-8-pro",
|
|
@@ -45,7 +69,12 @@
|
|
|
45
69
|
"platform_version": "14",
|
|
46
70
|
"width": 448,
|
|
47
71
|
"height": 997,
|
|
48
|
-
"dpr": 3.0
|
|
72
|
+
"dpr": 3.0,
|
|
73
|
+
"soc": "Tensor G3",
|
|
74
|
+
"cores": 9,
|
|
75
|
+
"memory_gb": 12,
|
|
76
|
+
"gpu_vendor": "Google Inc. (ARM)",
|
|
77
|
+
"gpu_renderer": "ANGLE (ARM, Mali-G715, OpenGL ES 3.2)"
|
|
49
78
|
},
|
|
50
79
|
{
|
|
51
80
|
"id": "pixel-8a",
|
|
@@ -54,7 +83,12 @@
|
|
|
54
83
|
"platform_version": "14",
|
|
55
84
|
"width": 412,
|
|
56
85
|
"height": 915,
|
|
57
|
-
"dpr": 2.625
|
|
86
|
+
"dpr": 2.625,
|
|
87
|
+
"soc": "Tensor G3",
|
|
88
|
+
"cores": 9,
|
|
89
|
+
"memory_gb": 8,
|
|
90
|
+
"gpu_vendor": "Google Inc. (ARM)",
|
|
91
|
+
"gpu_renderer": "ANGLE (ARM, Mali-G715, OpenGL ES 3.2)"
|
|
58
92
|
},
|
|
59
93
|
{
|
|
60
94
|
"id": "pixel-9",
|
|
@@ -63,7 +97,12 @@
|
|
|
63
97
|
"platform_version": "14",
|
|
64
98
|
"width": 412,
|
|
65
99
|
"height": 924,
|
|
66
|
-
"dpr": 2.625
|
|
100
|
+
"dpr": 2.625,
|
|
101
|
+
"soc": "Tensor G4",
|
|
102
|
+
"cores": 8,
|
|
103
|
+
"memory_gb": 12,
|
|
104
|
+
"gpu_vendor": "Google Inc. (ARM)",
|
|
105
|
+
"gpu_renderer": "ANGLE (ARM, Mali-G715, OpenGL ES 3.2)"
|
|
67
106
|
},
|
|
68
107
|
{
|
|
69
108
|
"id": "samsung-galaxy-a55",
|
|
@@ -74,6 +113,11 @@
|
|
|
74
113
|
"width": 360,
|
|
75
114
|
"height": 800,
|
|
76
115
|
"dpr": 2.25,
|
|
116
|
+
"soc": "Exynos 1480",
|
|
117
|
+
"cores": 8,
|
|
118
|
+
"memory_gb": 8,
|
|
119
|
+
"gpu_vendor": "Google Inc. (Samsung Electronics Co., Ltd.)",
|
|
120
|
+
"gpu_renderer": "ANGLE (Samsung Electronics Co., Ltd., Xclipse 530, OpenGL ES 3.2)",
|
|
77
121
|
"geometry_status": "devtools-emulation-only",
|
|
78
122
|
"geometry_note": "These Chromium preset dimensions/DPR do not directly map to the published 1080x2340 panel; physical Chrome capture required."
|
|
79
123
|
},
|
|
@@ -84,7 +128,12 @@
|
|
|
84
128
|
"platform_version": "14",
|
|
85
129
|
"width": 427,
|
|
86
130
|
"height": 952,
|
|
87
|
-
"dpr": 3.0
|
|
131
|
+
"dpr": 3.0,
|
|
132
|
+
"soc": "Tensor G4",
|
|
133
|
+
"cores": 8,
|
|
134
|
+
"memory_gb": 16,
|
|
135
|
+
"gpu_vendor": "Google Inc. (ARM)",
|
|
136
|
+
"gpu_renderer": "ANGLE (ARM, Mali-G715, OpenGL ES 3.2)"
|
|
88
137
|
},
|
|
89
138
|
{
|
|
90
139
|
"id": "pixel-9-pro-xl",
|
|
@@ -93,7 +142,12 @@
|
|
|
93
142
|
"platform_version": "14",
|
|
94
143
|
"width": 448,
|
|
95
144
|
"height": 997,
|
|
96
|
-
"dpr": 3.0
|
|
145
|
+
"dpr": 3.0,
|
|
146
|
+
"soc": "Tensor G4",
|
|
147
|
+
"cores": 8,
|
|
148
|
+
"memory_gb": 16,
|
|
149
|
+
"gpu_vendor": "Google Inc. (ARM)",
|
|
150
|
+
"gpu_renderer": "ANGLE (ARM, Mali-G715, OpenGL ES 3.2)"
|
|
97
151
|
}
|
|
98
152
|
]
|
|
99
153
|
}
|
|
@@ -90,19 +90,22 @@ def _proxy_and_geo_args(spec, binpath, geoip_on, display):
|
|
|
90
90
|
would otherwise add up to 2.5s to every proxied launch. When it does run it resolves the
|
|
91
91
|
egress IP (proxy exit, else the host's real public IP) and pins WebRTC / timezone /
|
|
92
92
|
geolocation / language -- so the SDK adds no probe of its own. geoip_on is tri-state:
|
|
93
|
-
* None -> default:
|
|
94
|
-
|
|
93
|
+
* None -> default: ON whenever a proxy is set, OFF otherwise. An uncorrected host
|
|
94
|
+
timezone under a proxied identity (UTC on a server, or the operator's home
|
|
95
|
+
zone against a foreign exit) is a far stronger detection signal than the
|
|
96
|
+
one-off launch-latency cost of the lookup. Pass geoip=False to opt out.
|
|
95
97
|
* True -> ON with --geoip (resolves the egress -- proxy exit, or the host IP if proxyless).
|
|
96
98
|
* False -> OFF with --geoip=false, which the binary honors FIRST (explicit opt-out).
|
|
97
99
|
Returns (proxy_kwargs, [flags]).
|
|
98
100
|
"""
|
|
99
101
|
proxy_kwargs, out = _proxy.resolve_proxy_config(spec)
|
|
100
102
|
out = list(out)
|
|
103
|
+
explicit_off = geoip_on is False
|
|
101
104
|
if geoip_on is None:
|
|
102
|
-
|
|
103
|
-
|
|
105
|
+
geoip_on = bool(spec)
|
|
106
|
+
if geoip_on:
|
|
104
107
|
out.append("--geoip")
|
|
105
|
-
|
|
108
|
+
elif explicit_off:
|
|
106
109
|
out.append("--geoip=false")
|
|
107
110
|
return proxy_kwargs, out
|
|
108
111
|
|
|
@@ -163,14 +166,34 @@ def fit_window(browser, context, page):
|
|
|
163
166
|
pass
|
|
164
167
|
|
|
165
168
|
|
|
169
|
+
def _window_bounds_for_seed(seed, aw, ah):
|
|
170
|
+
"""Deterministic per-seed window geometry inside the seed's available screen area.
|
|
171
|
+
|
|
172
|
+
Every profile previously got the identical maximized-like window (exactly availWidth x
|
|
173
|
+
availHeight at 0,0), so all profiles sharing a pool screen were geometry-identical -- a
|
|
174
|
+
clusterable signature. Real users are a mix: most keep the browser maximized, a minority
|
|
175
|
+
run a windowed browser at an arbitrary size/position. Mirror that mix deterministically
|
|
176
|
+
per seed (the geometry is part of the profile's stable identity, like its screen)."""
|
|
177
|
+
import hashlib
|
|
178
|
+
d = hashlib.sha256(b"capium-window:%d" % int(seed)).digest()
|
|
179
|
+
if d[0] % 10 < 6: # most seeds: maximized-like (previous behavior)
|
|
180
|
+
return 0, 0, aw, ah
|
|
181
|
+
w = max(800, int(aw * (0.78 + (d[1] / 255.0) * 0.16))) # 78-94% of avail width
|
|
182
|
+
h = max(600, int(ah * (0.80 + (d[2] / 255.0) * 0.15))) # 80-95% of avail height
|
|
183
|
+
left = (aw - w) * d[3] // 255
|
|
184
|
+
top = (ah - h) * d[4] // 255
|
|
185
|
+
return int(left), int(top), int(w), int(h)
|
|
186
|
+
|
|
187
|
+
|
|
166
188
|
def _fit_window_to_screen(context):
|
|
167
189
|
"""Size the window to the seed's (coherent, per-seed) spoofed screen so viewport ~= screen.
|
|
168
190
|
|
|
169
191
|
With screen=None (the default), each persona uses its device-pool screen/dpr. Here we size the
|
|
170
|
-
window
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
192
|
+
window within that screen's available area -- maximized-like for most seeds, realistically
|
|
193
|
+
windowed for the rest (see _window_bounds_for_seed) -- via the page's CDP session
|
|
194
|
+
(works for persistent contexts, which have no browser() object). This keeps viewport<=screen
|
|
195
|
+
coherent in headless/container (avoids the browserscan 'screen != viewport' VM tell) AND stays
|
|
196
|
+
coherent in headed (Chrome clamps to the real display). Best-effort."""
|
|
174
197
|
s = None
|
|
175
198
|
try:
|
|
176
199
|
page = context.pages[0] if context.pages else None
|
|
@@ -180,10 +203,12 @@ def _fit_window_to_screen(context):
|
|
|
180
203
|
if not (scr and scr.get("w") and scr.get("h")):
|
|
181
204
|
return
|
|
182
205
|
aw, ah = int(scr["w"]), int(scr["h"])
|
|
206
|
+
left, top, w, h = _window_bounds_for_seed(
|
|
207
|
+
getattr(context, "_capium_seed", 0) or 0, aw, ah)
|
|
183
208
|
s = context.new_cdp_session(page)
|
|
184
209
|
wid = s.send("Browser.getWindowForTarget")["windowId"]
|
|
185
210
|
s.send("Browser.setWindowBounds", {"windowId": wid, "bounds": {
|
|
186
|
-
"left":
|
|
211
|
+
"left": left, "top": top, "width": w, "height": h, "windowState": "normal"}})
|
|
187
212
|
except Exception:
|
|
188
213
|
pass
|
|
189
214
|
finally:
|
|
@@ -470,6 +495,7 @@ def launch_context(seed=None, platform="windows", headless=False, url=None,
|
|
|
470
495
|
_human.humanize(context, preset=human_preset)
|
|
471
496
|
page = context.pages[0] if context.pages else context.new_page()
|
|
472
497
|
if not getattr(browser, "_capium_mobile", False):
|
|
498
|
+
context._capium_seed = getattr(browser, "_capium_seed", 0)
|
|
473
499
|
if headless:
|
|
474
500
|
_fit_window_to_screen(context) # per-seed screen fit (coherent viewport, not the tiny startup window)
|
|
475
501
|
else:
|
|
@@ -33,17 +33,18 @@ async def _license_preflight_async(license_key, license_server, do_preflight):
|
|
|
33
33
|
async def _proxy_and_geo_args_async(spec, binpath, geoip_on, display):
|
|
34
34
|
"""Return (proxy_kwargs, chrome_args). proxy_kwargs for Playwright auth; chrome_args for
|
|
35
35
|
native --proxy-server + geo flags. The binary's CapiumGeoipResolver runs at PreBrowserStart
|
|
36
|
-
ONLY when --geoip is present
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
(--geoip=false, honored first)."""
|
|
36
|
+
ONLY when --geoip is present. geoip_on is tri-state: None = default ON whenever a proxy is
|
|
37
|
+
set and OFF otherwise (an uncorrected host timezone under a proxied identity -- UTC on a
|
|
38
|
+
server -- is a far stronger detection signal than the lookup's launch latency), True = ON
|
|
39
|
+
(--geoip), False = explicit opt-out (--geoip=false, honored first)."""
|
|
40
40
|
proxy_kwargs, out = _proxy.resolve_proxy_config(spec)
|
|
41
41
|
out = list(out)
|
|
42
|
+
explicit_off = geoip_on is False
|
|
42
43
|
if geoip_on is None:
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
geoip_on = bool(spec)
|
|
45
|
+
if geoip_on:
|
|
45
46
|
out.append("--geoip")
|
|
46
|
-
|
|
47
|
+
elif explicit_off:
|
|
47
48
|
out.append("--geoip=false")
|
|
48
49
|
return proxy_kwargs, out
|
|
49
50
|
|
|
@@ -164,18 +165,22 @@ async def _require_mobile_engine_async(context, mobile_device=None, seed=0, plat
|
|
|
164
165
|
|
|
165
166
|
|
|
166
167
|
async def _fit_window_to_screen_async(context):
|
|
167
|
-
"""Fit a
|
|
168
|
+
"""Fit a desktop window within its available screen (per-seed geometry, no emulation)."""
|
|
169
|
+
from .browser import _window_bounds_for_seed
|
|
168
170
|
session = None
|
|
169
171
|
try:
|
|
170
172
|
page = context.pages[0] if context.pages else await context.new_page()
|
|
171
173
|
screen = await page.evaluate("()=>({w:screen.availWidth,h:screen.availHeight})")
|
|
172
174
|
if not (screen and screen.get("w") and screen.get("h")):
|
|
173
175
|
return
|
|
176
|
+
left, top, w, h = _window_bounds_for_seed(
|
|
177
|
+
getattr(context, "_capium_seed", 0) or 0,
|
|
178
|
+
int(screen["w"]), int(screen["h"]))
|
|
174
179
|
session = await context.new_cdp_session(page)
|
|
175
180
|
window_id = (await session.send("Browser.getWindowForTarget"))["windowId"]
|
|
176
181
|
await session.send("Browser.setWindowBounds", {"windowId": window_id, "bounds": {
|
|
177
|
-
"left":
|
|
178
|
-
"height":
|
|
182
|
+
"left": left, "top": top, "width": w,
|
|
183
|
+
"height": h, "windowState": "normal"}})
|
|
179
184
|
except Exception:
|
|
180
185
|
pass
|
|
181
186
|
finally:
|
|
@@ -281,6 +286,7 @@ async def launch_context_async(seed=None, platform="windows", headless=False, ur
|
|
|
281
286
|
_human.humanize(context, preset=human_preset)
|
|
282
287
|
page = context.pages[0] if context.pages else await context.new_page()
|
|
283
288
|
if not getattr(browser, "_capium_mobile", False):
|
|
289
|
+
context._capium_seed = getattr(browser, "_capium_seed", 0)
|
|
284
290
|
await _fit_window_to_screen_async(context)
|
|
285
291
|
if url:
|
|
286
292
|
await page.goto(url, wait_until="domcontentloaded", timeout=60000)
|
|
@@ -1,36 +1,36 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_comment": "SINGLE SOURCE OF TRUTH for the current stable Capium browser build per OS/arch. Each OS reaches stable independently -- edit one entry without touching the others. This file ships inside the wheel (the SDK reads it to pick each host's stable build) AND is published to the download server by the release pipeline. `version` is the reported Chromium version (what the browser reports to the customer, names the R2 folder); `engine_version` is the actual built Chromium engine (box_package verifies the binary against it) -- it differs from `version` when a platform reports a spoofed patch (Windows
|
|
2
|
+
"_comment": "SINGLE SOURCE OF TRUTH for the current stable Capium browser build per OS/arch. Each OS reaches stable independently -- edit one entry without touching the others. This file ships inside the wheel (the SDK reads it to pick each host's stable build) AND is published to the download server by the release pipeline. `version` is the reported Chromium version (what the browser reports to the customer, names the R2 folder); `engine_version` is the actual built Chromium engine (box_package verifies the binary against it) -- it differs from `version` when a platform reports a spoofed patch (Windows/macOS report .48 on the .47 engine). `revision` is our packaging counter -- the served R2 folder is chromium-v<version>.<revision>/, ALWAYS dotted-suffixed starting at 1. A Chromium version bump resets revision to 1. `published: false` means the tag is declared but no artifact exists yet.",
|
|
3
3
|
"schema": 1,
|
|
4
|
-
"updated": "2026-10-
|
|
4
|
+
"updated": "2026-10-24",
|
|
5
5
|
"channels": {
|
|
6
6
|
"stable": {
|
|
7
7
|
"windows-x64": {
|
|
8
|
-
"version": "
|
|
9
|
-
"engine_version": "
|
|
8
|
+
"version": "156.0.8078.48",
|
|
9
|
+
"engine_version": "156.0.8078.47",
|
|
10
10
|
"revision": 2,
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "76fa0d609f3d3fcac05436681884b3150405aaa470730d9a9ad1453ff6ad8276",
|
|
12
12
|
"published": true,
|
|
13
13
|
"license_status_protocol": 1
|
|
14
14
|
},
|
|
15
15
|
"macos-arm64": {
|
|
16
|
-
"version": "
|
|
17
|
-
"engine_version": "
|
|
16
|
+
"version": "156.0.8078.48",
|
|
17
|
+
"engine_version": "156.0.8078.47",
|
|
18
18
|
"revision": 2,
|
|
19
|
-
"sha256": "
|
|
19
|
+
"sha256": "3b883146c0f41fcb409113d3f43764b7e7061bb6c64d58cddb26b2619887275f",
|
|
20
20
|
"published": true,
|
|
21
21
|
"license_status_protocol": 1
|
|
22
22
|
},
|
|
23
23
|
"linux-x64": {
|
|
24
|
-
"version": "
|
|
25
|
-
"engine_version": "
|
|
24
|
+
"version": "156.0.8078.47",
|
|
25
|
+
"engine_version": "156.0.8078.47",
|
|
26
26
|
"revision": 2,
|
|
27
|
-
"sha256": "
|
|
27
|
+
"sha256": "5fc9954366d8052e09d8f528d1cdbcedcf6d0cb1ae43c36441ce975a1e60c749",
|
|
28
28
|
"published": true,
|
|
29
29
|
"license_status_protocol": 1
|
|
30
30
|
},
|
|
31
31
|
"linux-arm64": {
|
|
32
|
-
"version": "
|
|
33
|
-
"engine_version": "
|
|
32
|
+
"version": "156.0.8078.47",
|
|
33
|
+
"engine_version": "156.0.8078.47",
|
|
34
34
|
"revision": 1,
|
|
35
35
|
"sha256": null,
|
|
36
36
|
"published": false
|
|
@@ -141,12 +141,13 @@ def get_default_stealth_args(seed=None, platform="windows", screen=None, mobile_
|
|
|
141
141
|
"primaryPointerType=4,availablePointerTypes=4",
|
|
142
142
|
"--no-first-run",
|
|
143
143
|
"--no-default-browser-check",
|
|
144
|
-
#
|
|
145
|
-
#
|
|
146
|
-
#
|
|
147
|
-
# --capium-
|
|
148
|
-
#
|
|
149
|
-
#
|
|
144
|
+
# CDP stealth defaults (engine 156): inspector-frame scrub ON (hides the
|
|
145
|
+
# UtilityScript/eval source-URL leak from Error.stack -- Patchright-equivalent,
|
|
146
|
+
# does not trip anti-detect ML); console mask OFF (ON makes developer_tools=false
|
|
147
|
+
# -> FPJS anti_detect/tampering) -- opt in with --capium-enable-console-mask; main-
|
|
148
|
+
# world evaluate isolation OFF (ON breaks page-global access for solvers) -- opt in
|
|
149
|
+
# with --capium-isolate-cdp-eval. --capium-disable-cdp-stealth turns everything off.
|
|
150
|
+
# See docs/STEALTH-FLAGS.md.
|
|
150
151
|
]
|
|
151
152
|
# Per-profile render farbling is ON (the engine default) so canvas/WebGL/audio are unique AND
|
|
152
153
|
# stable per seed. Without it, every profile on a host renders identically -- a cross-instance
|
|
@@ -15,7 +15,7 @@ _IOS_PROFILES = {profile["id"]: profile for profile in _IOS_CATALOG["profiles"]}
|
|
|
15
15
|
# No network request is sent. A secure origin exposes native UA Client Hints.
|
|
16
16
|
ENGINE_PROBE = r"""async expected => {
|
|
17
17
|
const match = navigator.userAgent.match(/Chrome\/(\d+)\.0\.0\.0 Mobile Safari\//);
|
|
18
|
-
if (!expected || navigator.platform !== 'Linux
|
|
18
|
+
if (!expected || navigator.platform !== 'Linux armv8l' || !match ||
|
|
19
19
|
!navigator.userAgent.includes('(Linux; Android 10; K)') ||
|
|
20
20
|
navigator.userAgent.includes('Version/4.0') || !navigator.userAgentData) return false;
|
|
21
21
|
const ch = await navigator.userAgentData.getHighEntropyValues([
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: capiumbrowser
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.1
|
|
4
4
|
Summary: Drop-in stealth browser SDK on top of Playwright (Capium Chromium).
|
|
5
5
|
Author: Capzy
|
|
6
6
|
License: Capzy capiumbrowser SDK License
|
|
@@ -408,7 +408,7 @@ and `launch_persistent_context` forward everything to `launch`.
|
|
|
408
408
|
| Parameter | Type | Default | Description |
|
|
409
409
|
|---|---|---|---|
|
|
410
410
|
| `proxy` | `str` \| `dict` \| `"env"`/`True` \| `None` | `None` | HTTP/HTTPS/SOCKS5 URL with inline creds, a `{"server","username","password"}` dict, `"env"`/`True` (reads `CAPIUM_PROXY`), or `None`/`"none"`/`""`. |
|
|
411
|
-
| `geoip` | `bool` \| `None` | `None` | Geo coherence, resolved inside the binary. **`None` (default) — OFF**:
|
|
411
|
+
| `geoip` | `bool` \| `None` | `None` | Geo coherence, resolved inside the binary. **`None` (default) — ON whenever a proxy is set, OFF otherwise**: an uncorrected host timezone under a proxied identity (UTC on a server) is a far stronger detection signal than the lookup's one-off launch latency. **`True`** — ON even proxyless (pins timezone/geo/WebRTC/`navigator.languages` to the host IP). **`False`** — explicit opt-out. No SDK-side probe. |
|
|
412
412
|
| `timezone` | `str` \| `None` | `None` | IANA tz (e.g. `"America/New_York"`) → `--timezone`. **Overrides** `geoip`. |
|
|
413
413
|
| `locale` | `str` \| `None` | `None` | BCP-47 (e.g. `"en-US"`) → `--lang` + `--accept-lang`. **Overrides** `geoip`. |
|
|
414
414
|
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
"""Unit tests for the SDK geoip flag tri-state (capiumbrowser.browser._proxy_and_geo_args).
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Contract as of 1.3.1: geoip=None (the default) enables --geoip WHENEVER A PROXY IS SET and
|
|
4
|
+
stays off otherwise. An uncorrected host timezone under a proxied identity (UTC on a server
|
|
5
|
+
against a residential exit) is a far stronger detection signal than the lookup's one-off
|
|
6
|
+
launch latency, which is why the 2026-09 "default always off" contract was reversed.
|
|
7
|
+
Explicit False still opts out with --geoip=false (honored first by the binary).
|
|
5
8
|
"""
|
|
6
9
|
import asyncio
|
|
7
10
|
|
|
@@ -14,11 +17,12 @@ def _flags(spec, geoip):
|
|
|
14
17
|
return out
|
|
15
18
|
|
|
16
19
|
|
|
17
|
-
def
|
|
20
|
+
def test_default_none_enables_geoip_with_proxy():
|
|
18
21
|
out = _flags("http://u:p@h:1", geoip=None)
|
|
19
22
|
assert any(a.startswith("--proxy-server=") for a in out), "proxy flag expected"
|
|
20
|
-
assert
|
|
21
|
-
"a proxy
|
|
23
|
+
assert "--geoip" in out, \
|
|
24
|
+
"a proxy must enable geoip by default (exit-coherent timezone/geo/WebRTC)"
|
|
25
|
+
assert not any(a.startswith("--geoip=") for a in out)
|
|
22
26
|
|
|
23
27
|
|
|
24
28
|
def test_true_adds_geoip():
|
|
@@ -26,9 +30,15 @@ def test_true_adds_geoip():
|
|
|
26
30
|
assert "--geoip" in out
|
|
27
31
|
|
|
28
32
|
|
|
33
|
+
def test_true_without_proxy_adds_geoip():
|
|
34
|
+
out = _flags(None, geoip=True)
|
|
35
|
+
assert "--geoip" in out
|
|
36
|
+
|
|
37
|
+
|
|
29
38
|
def test_false_adds_explicit_optout():
|
|
30
39
|
out = _flags("http://u:p@h:1", geoip=False)
|
|
31
40
|
assert "--geoip=false" in out
|
|
41
|
+
assert "--geoip" not in out
|
|
32
42
|
|
|
33
43
|
|
|
34
44
|
def test_no_proxy_no_geoip_by_default():
|
|
@@ -42,5 +52,6 @@ def test_async_mirror_matches_sync():
|
|
|
42
52
|
geoip_on=geoip, display=None)
|
|
43
53
|
return out
|
|
44
54
|
assert "--geoip" in asyncio.run(_run("http://u:p@h:1", True))
|
|
45
|
-
assert
|
|
55
|
+
assert "--geoip" in asyncio.run(_run("http://u:p@h:1", None))
|
|
56
|
+
assert not any(a.startswith("--geoip") for a in asyncio.run(_run(None, None)))
|
|
46
57
|
assert "--geoip=false" in asyncio.run(_run(None, False))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|