phantomwright 0.1.2__tar.gz → 0.1.4__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.
- {phantomwright-0.1.2 → phantomwright-0.1.4}/PKG-INFO +9 -3
- {phantomwright-0.1.2 → phantomwright-0.1.4}/README.md +7 -1
- phantomwright-0.1.4/phantomwright/_repo_version.py +1 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/captcha/cloudfare/solver.py +16 -45
- {phantomwright-0.1.2 → phantomwright-0.1.4}/pyproject.toml +2 -2
- phantomwright-0.1.2/phantomwright/_repo_version.py +0 -1
- {phantomwright-0.1.2 → phantomwright-0.1.4}/.gitignore +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/LICENSE +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/__init__.py +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/_impl/__init__.py +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/_impl/_core_debug_patch.py +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/_impl/_evaluate_patch.py +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/_impl/_inconsistency_patch.py +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/async_api/__init__.py +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/captcha/__init__.py +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/captcha/cloudfare/scripts/observer.js +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/captcha/cloudfare/scripts/shadow_root.js +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/captcha/cloudfare/utils/build_js.py +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/captcha/cloudfare/utils/consts.py +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/captcha/cloudfare/utils/detection.py +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/captcha/cloudfare/utils/dom_helpers.py +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/captcha/cloudfare/utils/shadow_root.py +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/py.typed +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/__init__.py +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/evasions/chrome.app.js +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/evasions/chrome.csi.js +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/evasions/chrome.hairline.js +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/evasions/chrome.load.times.js +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/evasions/chrome.runtime.js +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/evasions/error.prototype.js +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/evasions/iframe.contentWindow.js +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/evasions/media.codecs.js +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/evasions/navigator.hardwareConcurrency.js +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/evasions/navigator.languages.js +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/evasions/navigator.permissions.js +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/evasions/navigator.platform.js +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/evasions/navigator.plugins.js +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/evasions/navigator.userAgent.js +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/evasions/navigator.vendor.js +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/evasions/webgl.vendor.js +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/generate.magic.arrays.js +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/utils.js +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/stealth.py +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/sync_api/__init__.py +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/user_simulator/README.md +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/user_simulator/__init__.py +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/user_simulator/async_basic.py +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/user_simulator/async_simulator.py +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/user_simulator/script.py +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/user_simulator/sync_basic.py +0 -0
- {phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/user_simulator/sync_simulator.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: phantomwright
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Bridging playwright-core patch + extending playwright API for stealth injection & user simulation
|
|
5
5
|
Project-URL: homepage, https://github.com/ai-microsoft/phantom-wright
|
|
6
6
|
Project-URL: changelog, https://github.com/ai-microsoft/phantom-wright/blob/main/CHANGELOG.md
|
|
@@ -10,7 +10,7 @@ License-File: LICENSE
|
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Requires-Python: >=3.9
|
|
13
|
-
Requires-Dist: phantomwright-driver==1.57.
|
|
13
|
+
Requires-Dist: phantomwright-driver==1.57.7
|
|
14
14
|
Provides-Extra: black
|
|
15
15
|
Requires-Dist: black>=25.9.0; extra == 'black'
|
|
16
16
|
Provides-Extra: dev
|
|
@@ -183,6 +183,8 @@ async def main():
|
|
|
183
183
|
}
|
|
184
184
|
```
|
|
185
185
|
|
|
186
|
+
> ⚠️ **Note:** To prevent infinite detection loops caused by automatic page refreshes during captcha resolution, manual refreshes on the same URL will **not** trigger the resolve process again.
|
|
187
|
+
|
|
186
188
|
## Development
|
|
187
189
|
|
|
188
190
|
### Setup & Test
|
|
@@ -256,6 +258,10 @@ CDP does not provide endpoints to manipulate WebSocket. Supporting this would re
|
|
|
256
258
|
await page.route_web_socket("/ws", handler)
|
|
257
259
|
```
|
|
258
260
|
|
|
261
|
+
#### `add_init_script` Not Compatible with Edge New Tab Page Prerender
|
|
262
|
+
|
|
263
|
+
As Edge NTP's prerendered New Tab page won't install Playwright route, `add_init_script` won't work in a prerendered session, plz ensure you disabled Edge's prerender when launch browser.
|
|
264
|
+
|
|
259
265
|
#### `add_init_script` Timing Issue
|
|
260
266
|
|
|
261
267
|
`add_init_script` cannot directly call bindings exposed by `expose_function`/`expose_binding`. Init scripts are injected into the HTML document and execute before exposed APIs are available.
|
|
@@ -280,7 +286,7 @@ CDP does not provide endpoints to manipulate WebSocket. Supporting this would re
|
|
|
280
286
|
|
|
281
287
|
#### `add_init_script` Doesn't Affect Special URLs
|
|
282
288
|
|
|
283
|
-
Patchright init scripts use routing, which doesn't trigger for `about:blank`, Data-URIs,
|
|
289
|
+
Patchright init scripts use routing, which doesn't trigger for `about:blank`, Data-URIs, `file://` URLs, as well as privilege pages such as `edge://newtab`.
|
|
284
290
|
|
|
285
291
|
- ❌ **Data-URIs**
|
|
286
292
|
```python
|
|
@@ -163,6 +163,8 @@ async def main():
|
|
|
163
163
|
}
|
|
164
164
|
```
|
|
165
165
|
|
|
166
|
+
> ⚠️ **Note:** To prevent infinite detection loops caused by automatic page refreshes during captcha resolution, manual refreshes on the same URL will **not** trigger the resolve process again.
|
|
167
|
+
|
|
166
168
|
## Development
|
|
167
169
|
|
|
168
170
|
### Setup & Test
|
|
@@ -236,6 +238,10 @@ CDP does not provide endpoints to manipulate WebSocket. Supporting this would re
|
|
|
236
238
|
await page.route_web_socket("/ws", handler)
|
|
237
239
|
```
|
|
238
240
|
|
|
241
|
+
#### `add_init_script` Not Compatible with Edge New Tab Page Prerender
|
|
242
|
+
|
|
243
|
+
As Edge NTP's prerendered New Tab page won't install Playwright route, `add_init_script` won't work in a prerendered session, plz ensure you disabled Edge's prerender when launch browser.
|
|
244
|
+
|
|
239
245
|
#### `add_init_script` Timing Issue
|
|
240
246
|
|
|
241
247
|
`add_init_script` cannot directly call bindings exposed by `expose_function`/`expose_binding`. Init scripts are injected into the HTML document and execute before exposed APIs are available.
|
|
@@ -260,7 +266,7 @@ CDP does not provide endpoints to manipulate WebSocket. Supporting this would re
|
|
|
260
266
|
|
|
261
267
|
#### `add_init_script` Doesn't Affect Special URLs
|
|
262
268
|
|
|
263
|
-
Patchright init scripts use routing, which doesn't trigger for `about:blank`, Data-URIs,
|
|
269
|
+
Patchright init scripts use routing, which doesn't trigger for `about:blank`, Data-URIs, `file://` URLs, as well as privilege pages such as `edge://newtab`.
|
|
264
270
|
|
|
265
271
|
- ❌ **Data-URIs**
|
|
266
272
|
```python
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
version = 'v0.1.4'
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import json
|
|
3
3
|
import time
|
|
4
|
-
from typing import Callable, Optional
|
|
4
|
+
from typing import Callable, Optional
|
|
5
5
|
|
|
6
|
-
from phantomwright.async_api import Page,
|
|
6
|
+
from phantomwright.async_api import Page, BrowserContext
|
|
7
7
|
|
|
8
8
|
from .utils.consts import ChallengeType
|
|
9
9
|
from .utils.detection import (
|
|
@@ -46,7 +46,7 @@ class CloudflareSolverAsync:
|
|
|
46
46
|
|
|
47
47
|
def __init__(
|
|
48
48
|
self,
|
|
49
|
-
|
|
49
|
+
context: BrowserContext,
|
|
50
50
|
*,
|
|
51
51
|
max_attempts: int = 3,
|
|
52
52
|
attempt_delay: int = 5,
|
|
@@ -56,8 +56,7 @@ class CloudflareSolverAsync:
|
|
|
56
56
|
Initialize the Cloudflare solver.
|
|
57
57
|
|
|
58
58
|
Args:
|
|
59
|
-
|
|
60
|
-
If a Browser is provided, all its contexts will be monitored.
|
|
59
|
+
context: Playwright BrowserContext to monitor for challenges.
|
|
61
60
|
max_attempts: Maximum number of solve attempts per challenge.
|
|
62
61
|
attempt_delay: Delay in seconds between retry attempts.
|
|
63
62
|
log_callback: Optional callback function that receives a JSON string
|
|
@@ -71,9 +70,7 @@ class CloudflareSolverAsync:
|
|
|
71
70
|
- error: Error message if failed, None otherwise
|
|
72
71
|
- timestamp: Unix timestamp of the event
|
|
73
72
|
"""
|
|
74
|
-
self.
|
|
75
|
-
self._browser: Optional[Browser] = context_or_browser if self._is_browser else None
|
|
76
|
-
self._context: Optional[BrowserContext] = None if self._is_browser else context_or_browser
|
|
73
|
+
self.context = context
|
|
77
74
|
self.max_attempts = max_attempts
|
|
78
75
|
self.attempt_delay = attempt_delay
|
|
79
76
|
self.log = log_callback
|
|
@@ -143,7 +140,7 @@ class CloudflareSolverAsync:
|
|
|
143
140
|
report["challenge_type"] = challenge_type.name
|
|
144
141
|
|
|
145
142
|
if challenge_type is ChallengeType.TURNSTILE:
|
|
146
|
-
await page.locator("#cf-turnstile").wait_for()
|
|
143
|
+
await page.locator("#cf-turnstile").wait_for(10000)
|
|
147
144
|
|
|
148
145
|
cf_iframes = await search_shadow_root_iframes(
|
|
149
146
|
captcha_container=page,
|
|
@@ -203,6 +200,8 @@ class CloudflareSolverAsync:
|
|
|
203
200
|
|
|
204
201
|
finally:
|
|
205
202
|
report["duration"] = time.time() - report["start_time"]
|
|
203
|
+
if report["success"]:
|
|
204
|
+
report["url"] = None # Clear URL on success for privacy
|
|
206
205
|
self._log_final_report(report)
|
|
207
206
|
|
|
208
207
|
# ---------------- callback ----------------
|
|
@@ -237,48 +236,20 @@ class CloudflareSolverAsync:
|
|
|
237
236
|
await page.expose_function("__cf_callback", self._make_on_cf_detected(page))
|
|
238
237
|
page.on("load", lambda: asyncio.create_task(self._rebind(page)))
|
|
239
238
|
|
|
240
|
-
def _setup_context(self, context: BrowserContext) -> None:
|
|
241
|
-
"""Set up page monitoring for a single context."""
|
|
242
|
-
context.on(
|
|
243
|
-
"page",
|
|
244
|
-
lambda p: asyncio.create_task(self._setup_page(p)),
|
|
245
|
-
)
|
|
246
|
-
|
|
247
239
|
# ---------------- public api ----------------
|
|
248
240
|
def start(self) -> None:
|
|
249
241
|
"""
|
|
250
|
-
Start monitoring the browser
|
|
242
|
+
Start monitoring the browser context for Cloudflare challenges.
|
|
251
243
|
|
|
252
|
-
This method registers event listeners
|
|
253
|
-
Cloudflare challenges on any new page. Call this once
|
|
254
|
-
the solver instance.
|
|
255
|
-
|
|
256
|
-
If initialized with a Browser, monitors all existing and new contexts.
|
|
257
|
-
If initialized with a BrowserContext, monitors only that context.
|
|
244
|
+
This method registers event listeners on the context to automatically
|
|
245
|
+
detect and solve Cloudflare challenges on any new page. Call this once
|
|
246
|
+
after creating the solver instance.
|
|
258
247
|
|
|
259
248
|
Note:
|
|
260
249
|
This method is synchronous but sets up async handlers internally.
|
|
261
250
|
Challenges will be solved automatically in the background.
|
|
262
251
|
"""
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
def _check_and_setup_contexts():
|
|
268
|
-
for context in self._browser.contexts:
|
|
269
|
-
if context not in self._monitored_contexts:
|
|
270
|
-
self._monitored_contexts.add(context)
|
|
271
|
-
self._setup_context(context)
|
|
272
|
-
|
|
273
|
-
# Monitor all existing contexts
|
|
274
|
-
_check_and_setup_contexts()
|
|
275
|
-
|
|
276
|
-
# Start a background task to periodically check for new contexts
|
|
277
|
-
async def _monitor_contexts():
|
|
278
|
-
while self._browser.is_connected():
|
|
279
|
-
_check_and_setup_contexts()
|
|
280
|
-
await asyncio.sleep(0.1) # Check every 100ms for new contexts
|
|
281
|
-
|
|
282
|
-
self._monitor_task = asyncio.create_task(_monitor_contexts())
|
|
283
|
-
else:
|
|
284
|
-
self._setup_context(self._context)
|
|
252
|
+
self.context.on(
|
|
253
|
+
"page",
|
|
254
|
+
lambda p: asyncio.create_task(self._setup_page(p)),
|
|
255
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "phantomwright"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.4"
|
|
4
4
|
description = "Bridging playwright-core patch + extending playwright API for stealth injection & user simulation"
|
|
5
5
|
authors = [
|
|
6
6
|
{name="Hang Yin", email="hangyin@microsoft.com"},
|
|
@@ -11,7 +11,7 @@ readme = "README.md"
|
|
|
11
11
|
requires-python = ">=3.9"
|
|
12
12
|
|
|
13
13
|
dependencies = [
|
|
14
|
-
"phantomwright-driver==1.57.
|
|
14
|
+
"phantomwright-driver==1.57.7",
|
|
15
15
|
]
|
|
16
16
|
|
|
17
17
|
classifiers = [
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
version = 'v0.1.2'
|
|
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
|
{phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/captcha/cloudfare/scripts/observer.js
RENAMED
|
File without changes
|
{phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/captcha/cloudfare/scripts/shadow_root.js
RENAMED
|
File without changes
|
{phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/captcha/cloudfare/utils/build_js.py
RENAMED
|
File without changes
|
|
File without changes
|
{phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/captcha/cloudfare/utils/detection.py
RENAMED
|
File without changes
|
{phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/captcha/cloudfare/utils/dom_helpers.py
RENAMED
|
File without changes
|
{phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/captcha/cloudfare/utils/shadow_root.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/evasions/chrome.hairline.js
RENAMED
|
File without changes
|
{phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/evasions/chrome.load.times.js
RENAMED
|
File without changes
|
{phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/evasions/chrome.runtime.js
RENAMED
|
File without changes
|
{phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/evasions/error.prototype.js
RENAMED
|
File without changes
|
|
File without changes
|
{phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/evasions/media.codecs.js
RENAMED
|
File without changes
|
|
File without changes
|
{phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/evasions/navigator.languages.js
RENAMED
|
File without changes
|
|
File without changes
|
{phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/evasions/navigator.platform.js
RENAMED
|
File without changes
|
{phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/evasions/navigator.plugins.js
RENAMED
|
File without changes
|
{phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/evasions/navigator.userAgent.js
RENAMED
|
File without changes
|
{phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/evasions/navigator.vendor.js
RENAMED
|
File without changes
|
{phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/evasions/webgl.vendor.js
RENAMED
|
File without changes
|
{phantomwright-0.1.2 → phantomwright-0.1.4}/phantomwright/stealth/js/generate.magic.arrays.js
RENAMED
|
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
|