cloudsneak 1.0.0__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.
- cloudsneak-1.0.0/PKG-INFO +74 -0
- cloudsneak-1.0.0/README.md +59 -0
- cloudsneak-1.0.0/cloudsneak/__init__.py +16 -0
- cloudsneak-1.0.0/cloudsneak/browser.py +196 -0
- cloudsneak-1.0.0/cloudsneak/cli.py +33 -0
- cloudsneak-1.0.0/cloudsneak/iuam.py +117 -0
- cloudsneak-1.0.0/cloudsneak/js/api.js +2018 -0
- cloudsneak-1.0.0/cloudsneak/server.py +180 -0
- cloudsneak-1.0.0/cloudsneak/turnstile.py +134 -0
- cloudsneak-1.0.0/cloudsneak.egg-info/PKG-INFO +74 -0
- cloudsneak-1.0.0/cloudsneak.egg-info/SOURCES.txt +16 -0
- cloudsneak-1.0.0/cloudsneak.egg-info/dependency_links.txt +1 -0
- cloudsneak-1.0.0/cloudsneak.egg-info/entry_points.txt +2 -0
- cloudsneak-1.0.0/cloudsneak.egg-info/requires.txt +9 -0
- cloudsneak-1.0.0/cloudsneak.egg-info/top_level.txt +1 -0
- cloudsneak-1.0.0/pyproject.toml +34 -0
- cloudsneak-1.0.0/setup.cfg +4 -0
- cloudsneak-1.0.0/tests/test_cloudsneak.py +20 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cloudsneak
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Cloudflare clearance cookie and Turnstile token solver service
|
|
5
|
+
Requires-Python: >=3.8
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: fastapi>=0.100.0
|
|
8
|
+
Requires-Dist: uvicorn[standard]>=0.20.0
|
|
9
|
+
Requires-Dist: playwright>=1.35.0
|
|
10
|
+
Requires-Dist: pydantic>=2.0.0
|
|
11
|
+
Requires-Dist: py-ghost-cursor>=0.1.1
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: pytest; extra == "dev"
|
|
14
|
+
Requires-Dist: httpx; extra == "dev"
|
|
15
|
+
|
|
16
|
+
# cloudsneak
|
|
17
|
+
|
|
18
|
+
Cloudflare clearance cookie (`cf_clearance`) and Turnstile token solver service ported to Python.
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- **Turnstile Token Solver**: Render and solve Cloudflare Turnstile challenge widgets using synthetic injection and local script replacement.
|
|
23
|
+
- **IUAM clearance cookie Extraction**: Intercept responses to extract `cf_clearance` cookies and corresponding `User-Agent`.
|
|
24
|
+
- **Anti-Detect Browser Integration**: Custom Chrome launch configurations with Playwright.
|
|
25
|
+
- **FastAPI REST API**: Server with TTL caching, authentication token checks, and concurrency throttling.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install .
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Ensure Playwright browsers are installed:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
playwright install chromium
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Running the Server
|
|
40
|
+
|
|
41
|
+
Start the REST service:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
cloudsneak --host 0.0.0.0 --port 8742
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Or via python:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
python -m cloudsneak.cli
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## API Usage
|
|
54
|
+
|
|
55
|
+
### `POST /cloudflare`
|
|
56
|
+
|
|
57
|
+
#### Turnstile Mode
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"mode": "turnstile",
|
|
62
|
+
"domain": "https://example.com",
|
|
63
|
+
"siteKey": "0x4AAAAAAACk534..."
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
#### IUAM Mode
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"mode": "iuam",
|
|
72
|
+
"domain": "https://example.com"
|
|
73
|
+
}
|
|
74
|
+
```
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# cloudsneak
|
|
2
|
+
|
|
3
|
+
Cloudflare clearance cookie (`cf_clearance`) and Turnstile token solver service ported to Python.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Turnstile Token Solver**: Render and solve Cloudflare Turnstile challenge widgets using synthetic injection and local script replacement.
|
|
8
|
+
- **IUAM clearance cookie Extraction**: Intercept responses to extract `cf_clearance` cookies and corresponding `User-Agent`.
|
|
9
|
+
- **Anti-Detect Browser Integration**: Custom Chrome launch configurations with Playwright.
|
|
10
|
+
- **FastAPI REST API**: Server with TTL caching, authentication token checks, and concurrency throttling.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install .
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Ensure Playwright browsers are installed:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
playwright install chromium
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Running the Server
|
|
25
|
+
|
|
26
|
+
Start the REST service:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
cloudsneak --host 0.0.0.0 --port 8742
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Or via python:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
python -m cloudsneak.cli
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## API Usage
|
|
39
|
+
|
|
40
|
+
### `POST /cloudflare`
|
|
41
|
+
|
|
42
|
+
#### Turnstile Mode
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"mode": "turnstile",
|
|
47
|
+
"domain": "https://example.com",
|
|
48
|
+
"siteKey": "0x4AAAAAAACk534..."
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
#### IUAM Mode
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"mode": "iuam",
|
|
57
|
+
"domain": "https://example.com"
|
|
58
|
+
}
|
|
59
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
cloudsneak package initialization.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__version__ = "1.0.0"
|
|
6
|
+
|
|
7
|
+
from cloudsneak.browser import connect, check_turnstile
|
|
8
|
+
from cloudsneak.iuam import solve_iuam
|
|
9
|
+
from cloudsneak.turnstile import solve_turnstile
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"connect",
|
|
13
|
+
"check_turnstile",
|
|
14
|
+
"solve_iuam",
|
|
15
|
+
"solve_turnstile",
|
|
16
|
+
]
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Browser management module for cloudsneak.
|
|
3
|
+
Handles anti-detect Playwright browser context initialization, mouse movements,
|
|
4
|
+
and Turnstile iframe / widget click detection.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
10
|
+
from playwright.async_api import async_playwright, Browser, BrowserContext, Page
|
|
11
|
+
from py_ghost_cursor import path
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger("cloudsneak.browser")
|
|
14
|
+
|
|
15
|
+
DEFAULT_CHROME_FLAGS = [
|
|
16
|
+
"--no-sandbox",
|
|
17
|
+
"--disable-dev-shm-usage",
|
|
18
|
+
"--disable-blink-features=AutomationControlled",
|
|
19
|
+
"--window-size=1920,1080",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
async def check_turnstile(page: Page) -> bool:
|
|
24
|
+
"""
|
|
25
|
+
Checks for Turnstile challenge response inputs or bounding box coordinates
|
|
26
|
+
and simulates a click if found.
|
|
27
|
+
"""
|
|
28
|
+
try:
|
|
29
|
+
# Check for explicit turnstile response elements
|
|
30
|
+
elements = await page.query_selector_all('[name="cf-turnstile-response"]')
|
|
31
|
+
if elements:
|
|
32
|
+
for element in elements:
|
|
33
|
+
try:
|
|
34
|
+
parent = await element.evaluate_handle("el => el.parentElement")
|
|
35
|
+
element_handle = parent.as_element()
|
|
36
|
+
if element_handle:
|
|
37
|
+
box = await element_handle.bounding_box()
|
|
38
|
+
if box:
|
|
39
|
+
click_x = box["x"] + 30
|
|
40
|
+
click_y = box["y"] + box["height"] / 2
|
|
41
|
+
await page.mouse.click(click_x, click_y)
|
|
42
|
+
except Exception as err:
|
|
43
|
+
logger.debug(f"Error clicking turnstile response parent: {err}")
|
|
44
|
+
return True
|
|
45
|
+
|
|
46
|
+
# Fallback: search for div elements with width between 290px and 310px
|
|
47
|
+
js_find_coords = """
|
|
48
|
+
() => {
|
|
49
|
+
let coordinates = [];
|
|
50
|
+
document.querySelectorAll('div').forEach(item => {
|
|
51
|
+
try {
|
|
52
|
+
let rect = item.getBoundingClientRect();
|
|
53
|
+
let css = window.getComputedStyle(item);
|
|
54
|
+
if (css.margin === "0px" && css.padding === "0px" && rect.width > 290 && rect.width <= 310 && !item.querySelector('*')) {
|
|
55
|
+
coordinates.push({ x: rect.x, y: rect.y, w: rect.width, h: rect.height });
|
|
56
|
+
}
|
|
57
|
+
} catch (e) {}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (coordinates.length === 0) {
|
|
61
|
+
document.querySelectorAll('div').forEach(item => {
|
|
62
|
+
try {
|
|
63
|
+
let rect = item.getBoundingClientRect();
|
|
64
|
+
if (rect.width > 290 && rect.width <= 310 && !item.querySelector('*')) {
|
|
65
|
+
coordinates.push({ x: rect.x, y: rect.y, w: rect.width, h: rect.height });
|
|
66
|
+
}
|
|
67
|
+
} catch (e) {}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return coordinates;
|
|
72
|
+
}
|
|
73
|
+
"""
|
|
74
|
+
coords: List[Dict[str, float]] = await page.evaluate(js_find_coords)
|
|
75
|
+
for item in coords:
|
|
76
|
+
try:
|
|
77
|
+
click_x = item["x"] + 30
|
|
78
|
+
click_y = item["y"] + item["h"] / 2
|
|
79
|
+
await page.mouse.click(click_x, click_y)
|
|
80
|
+
except Exception as err:
|
|
81
|
+
logger.debug(f"Error clicking candidate div: {err}")
|
|
82
|
+
return True
|
|
83
|
+
except Exception as e:
|
|
84
|
+
logger.debug(f"check_turnstile exception: {e}")
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
async def start_turnstile_loop(page: Page, stop_event: asyncio.Event) -> None:
|
|
89
|
+
"""
|
|
90
|
+
Runs a loop to check and click turnstile widgets until stop_event is set or page closes.
|
|
91
|
+
"""
|
|
92
|
+
while not stop_event.is_set():
|
|
93
|
+
if page.is_closed():
|
|
94
|
+
break
|
|
95
|
+
try:
|
|
96
|
+
await check_turnstile(page)
|
|
97
|
+
except Exception:
|
|
98
|
+
pass
|
|
99
|
+
await asyncio.sleep(1.0)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class PlaywrightManager:
|
|
103
|
+
"""
|
|
104
|
+
Manager for Playwright lifecycle and anti-detect page instances.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
def __init__(self) -> None:
|
|
108
|
+
self.pw = None
|
|
109
|
+
self.browser: Optional[Browser] = None
|
|
110
|
+
self.context: Optional[BrowserContext] = None
|
|
111
|
+
|
|
112
|
+
async def initialize(
|
|
113
|
+
self,
|
|
114
|
+
headless: bool = False,
|
|
115
|
+
args: Optional[List[str]] = None,
|
|
116
|
+
proxy: Optional[Dict[str, str]] = None,
|
|
117
|
+
) -> Tuple[Browser, BrowserContext]:
|
|
118
|
+
if self.browser and self.browser.is_connected():
|
|
119
|
+
return self.browser, self.context
|
|
120
|
+
|
|
121
|
+
self.pw = await async_playwright().start()
|
|
122
|
+
|
|
123
|
+
launch_args = list(DEFAULT_CHROME_FLAGS)
|
|
124
|
+
if args:
|
|
125
|
+
launch_args.extend(args)
|
|
126
|
+
|
|
127
|
+
proxy_config = None
|
|
128
|
+
if proxy and proxy.get("host") and proxy.get("port"):
|
|
129
|
+
server_str = f"{proxy['host']}:{proxy['port']}"
|
|
130
|
+
if not server_str.startswith("http://") and not server_str.startswith("https://") and not server_str.startswith("socks5://"):
|
|
131
|
+
server_str = f"http://{server_str}"
|
|
132
|
+
proxy_config = {"server": server_str}
|
|
133
|
+
if proxy.get("username") and proxy.get("password"):
|
|
134
|
+
proxy_config["username"] = proxy["username"]
|
|
135
|
+
proxy_config["password"] = proxy["password"]
|
|
136
|
+
|
|
137
|
+
self.browser = await self.pw.chromium.launch(
|
|
138
|
+
headless=headless,
|
|
139
|
+
args=launch_args,
|
|
140
|
+
proxy=proxy_config,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
self.context = await self.browser.new_context(
|
|
144
|
+
viewport={"width": 1920, "height": 1080},
|
|
145
|
+
user_agent=(
|
|
146
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
|
147
|
+
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
|
148
|
+
"Chrome/120.0.0.0 Safari/537.36"
|
|
149
|
+
),
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Anti-detection script injection
|
|
153
|
+
await self.context.add_init_script(
|
|
154
|
+
"Object.defineProperty(navigator, 'webdriver', {get: () => undefined});"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
return self.browser, self.context
|
|
158
|
+
|
|
159
|
+
async def get_page(self, turnstile: bool = False) -> Tuple[Page, Optional[asyncio.Event]]:
|
|
160
|
+
if not self.context:
|
|
161
|
+
await self.initialize()
|
|
162
|
+
|
|
163
|
+
page = await self.context.new_page()
|
|
164
|
+
|
|
165
|
+
stop_event = None
|
|
166
|
+
if turnstile:
|
|
167
|
+
stop_event = asyncio.Event()
|
|
168
|
+
asyncio.create_task(start_turnstile_loop(page, stop_event))
|
|
169
|
+
|
|
170
|
+
return page, stop_event
|
|
171
|
+
|
|
172
|
+
async def close(self) -> None:
|
|
173
|
+
if self.context:
|
|
174
|
+
await self.context.close()
|
|
175
|
+
self.context = None
|
|
176
|
+
if self.browser:
|
|
177
|
+
await self.browser.close()
|
|
178
|
+
self.browser = None
|
|
179
|
+
if self.pw:
|
|
180
|
+
await self.pw.stop()
|
|
181
|
+
self.pw = None
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
async def connect(
|
|
185
|
+
headless: bool = False,
|
|
186
|
+
args: Optional[List[str]] = None,
|
|
187
|
+
proxy: Optional[Dict[str, str]] = None,
|
|
188
|
+
turnstile: bool = False,
|
|
189
|
+
) -> Tuple[PlaywrightManager, Page]:
|
|
190
|
+
"""
|
|
191
|
+
Connect function matching the reference API. Returns (manager, page).
|
|
192
|
+
"""
|
|
193
|
+
manager = PlaywrightManager()
|
|
194
|
+
await manager.initialize(headless=headless, args=args, proxy=proxy)
|
|
195
|
+
page, _ = await manager.get_page(turnstile=turnstile)
|
|
196
|
+
return manager, page
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI entry point for cloudsneak.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
from cloudsneak.server import start_server, PORT
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main() -> None:
|
|
11
|
+
parser = argparse.ArgumentParser(
|
|
12
|
+
description="cloudsneak - Cloudflare clearance cookie and Turnstile token solver service"
|
|
13
|
+
)
|
|
14
|
+
parser.add_argument(
|
|
15
|
+
"--host",
|
|
16
|
+
type=str,
|
|
17
|
+
default="0.0.0.0",
|
|
18
|
+
help="Host address to bind the server (default: 0.0.0.0)",
|
|
19
|
+
)
|
|
20
|
+
parser.add_argument(
|
|
21
|
+
"--port",
|
|
22
|
+
type=int,
|
|
23
|
+
default=PORT,
|
|
24
|
+
help=f"Port to run the server on (default: {PORT})",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
args = parser.parse_args()
|
|
28
|
+
print(f"Starting cloudsneak server on {args.host}:{args.port}...")
|
|
29
|
+
start_server(host=args.host, port=args.port)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
if __name__ == "__main__":
|
|
33
|
+
main()
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cloudflare Under Attack Mode (IUAM) clearance solver.
|
|
3
|
+
Extracts cf_clearance cookie and associated User-Agent.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import re
|
|
8
|
+
import time
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Dict, Optional
|
|
11
|
+
from playwright.async_api import Page, Request, Response
|
|
12
|
+
|
|
13
|
+
API_JS_PATH = Path(__file__).parent / "js" / "api.js"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def solve_iuam(
|
|
17
|
+
data: Dict[str, Any],
|
|
18
|
+
page: Page,
|
|
19
|
+
timeout_ms: int = 60000,
|
|
20
|
+
) -> Dict[str, Any]:
|
|
21
|
+
"""
|
|
22
|
+
Solves Cloudflare IUAM challenge on target domain and returns cf_clearance cookie info.
|
|
23
|
+
|
|
24
|
+
:param data: Dictionary containing 'domain' and optional 'proxy' info.
|
|
25
|
+
:param page: Playwright Page instance.
|
|
26
|
+
:param timeout_ms: Maximum time to wait for solution in milliseconds.
|
|
27
|
+
:return: Dict containing cf_clearance, user_agent, and elapsed time.
|
|
28
|
+
"""
|
|
29
|
+
domain = data.get("domain")
|
|
30
|
+
if not domain:
|
|
31
|
+
raise ValueError("Missing domain parameter")
|
|
32
|
+
|
|
33
|
+
proxy = data.get("proxy", {})
|
|
34
|
+
if proxy.get("username") and proxy.get("password"):
|
|
35
|
+
# Credentials are set at context launch or routed in playwright context
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
api_js_content: Optional[bytes] = None
|
|
39
|
+
if API_JS_PATH.exists():
|
|
40
|
+
try:
|
|
41
|
+
api_js_content = API_JS_PATH.read_bytes()
|
|
42
|
+
except Exception as e:
|
|
43
|
+
print(f"Failed to read local api.js: {e}")
|
|
44
|
+
|
|
45
|
+
start_time = time.time()
|
|
46
|
+
solved_future: asyncio.Future = asyncio.get_running_loop().create_future()
|
|
47
|
+
|
|
48
|
+
async def handle_route(route: Any, request: Request):
|
|
49
|
+
url = request.url
|
|
50
|
+
try:
|
|
51
|
+
if "challenges.cloudflare.com/turnstile/v0/b/88d68f5d5ea3/api.js" in url:
|
|
52
|
+
if api_js_content:
|
|
53
|
+
await route.fulfill(
|
|
54
|
+
status=200,
|
|
55
|
+
content_type="application/javascript",
|
|
56
|
+
body=api_js_content,
|
|
57
|
+
headers={"Access-Control-Allow-Origin": "*"},
|
|
58
|
+
)
|
|
59
|
+
else:
|
|
60
|
+
await route.continue_()
|
|
61
|
+
elif (
|
|
62
|
+
url == domain
|
|
63
|
+
or url == domain.rstrip("/") + "/"
|
|
64
|
+
or "challenges.cloudflare.com" in url
|
|
65
|
+
or "/cdn-cgi/challenge-platform/" in url
|
|
66
|
+
):
|
|
67
|
+
await route.continue_()
|
|
68
|
+
else:
|
|
69
|
+
await route.abort()
|
|
70
|
+
except Exception:
|
|
71
|
+
try:
|
|
72
|
+
await route.continue_()
|
|
73
|
+
except Exception:
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
async def handle_response(response: Response):
|
|
77
|
+
url = response.url
|
|
78
|
+
if "/cdn-cgi/challenge-platform/" in url:
|
|
79
|
+
try:
|
|
80
|
+
headers = await response.headers_array()
|
|
81
|
+
set_cookie_headers = [
|
|
82
|
+
h["value"] for h in headers if h["name"].lower() == "set-cookie"
|
|
83
|
+
]
|
|
84
|
+
combined_cookies = "\n".join(set_cookie_headers)
|
|
85
|
+
match = re.search(r"cf_clearance=([^;]+)", combined_cookies)
|
|
86
|
+
if match and not solved_future.done():
|
|
87
|
+
cf_clearance = match.group(1)
|
|
88
|
+
req_headers = await response.request.all_headers()
|
|
89
|
+
user_agent = req_headers.get("user-agent", "")
|
|
90
|
+
elapsed = f"{time.time() - start_time:.2f}s"
|
|
91
|
+
solved_future.set_result(
|
|
92
|
+
{
|
|
93
|
+
"cf_clearance": cf_clearance,
|
|
94
|
+
"user_agent": user_agent,
|
|
95
|
+
"elapsed": elapsed,
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
except Exception:
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
# Register route interception and response listener
|
|
102
|
+
await page.route("**/*", handle_route)
|
|
103
|
+
page.on("response", handle_response)
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
await page.goto(domain, wait_until="domcontentloaded")
|
|
107
|
+
|
|
108
|
+
result = await asyncio.wait_for(solved_future, timeout=timeout_ms / 1000.0)
|
|
109
|
+
return result
|
|
110
|
+
except asyncio.TimeoutError:
|
|
111
|
+
raise TimeoutError("Timeout Error resolving IUAM challenge")
|
|
112
|
+
finally:
|
|
113
|
+
try:
|
|
114
|
+
await page.unroute("**/*", handle_route)
|
|
115
|
+
except Exception:
|
|
116
|
+
pass
|
|
117
|
+
page.remove_listener("response", handle_response)
|