unrealon 1.1.6__py3-none-any.whl → 2.0.4__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.
- {unrealon-1.1.6.dist-info/licenses → unrealon-2.0.4.dist-info}/LICENSE +1 -1
- unrealon-2.0.4.dist-info/METADATA +491 -0
- unrealon-2.0.4.dist-info/RECORD +129 -0
- {unrealon-1.1.6.dist-info → unrealon-2.0.4.dist-info}/WHEEL +2 -1
- unrealon-2.0.4.dist-info/entry_points.txt +3 -0
- unrealon-2.0.4.dist-info/top_level.txt +3 -0
- unrealon_browser/__init__.py +5 -6
- unrealon_browser/cli/browser_cli.py +18 -9
- unrealon_browser/cli/interactive_mode.py +13 -4
- unrealon_browser/core/browser_manager.py +29 -16
- unrealon_browser/dto/__init__.py +21 -0
- unrealon_browser/dto/bot_detection.py +175 -0
- unrealon_browser/dto/models/config.py +9 -3
- unrealon_browser/managers/__init__.py +1 -1
- unrealon_browser/managers/logger_bridge.py +1 -4
- unrealon_browser/stealth/__init__.py +27 -0
- unrealon_browser/stealth/bypass_techniques.pyc +0 -0
- unrealon_browser/stealth/manager.pyc +0 -0
- unrealon_browser/stealth/nodriver_stealth.pyc +0 -0
- unrealon_browser/stealth/playwright_stealth.pyc +0 -0
- unrealon_browser/stealth/scanner_tester.pyc +0 -0
- unrealon_browser/stealth/undetected_chrome.pyc +0 -0
- unrealon_core/__init__.py +160 -0
- unrealon_core/config/__init__.py +16 -0
- unrealon_core/config/environment.py +98 -0
- unrealon_core/config/urls.py +93 -0
- unrealon_core/enums/__init__.py +24 -0
- unrealon_core/enums/status.py +216 -0
- unrealon_core/enums/types.py +240 -0
- unrealon_core/error_handling/__init__.py +45 -0
- unrealon_core/error_handling/circuit_breaker.py +292 -0
- unrealon_core/error_handling/error_context.py +324 -0
- unrealon_core/error_handling/recovery.py +371 -0
- unrealon_core/error_handling/retry.py +268 -0
- unrealon_core/exceptions/__init__.py +46 -0
- unrealon_core/exceptions/base.py +292 -0
- unrealon_core/exceptions/communication.py +22 -0
- unrealon_core/exceptions/driver.py +11 -0
- unrealon_core/exceptions/proxy.py +11 -0
- unrealon_core/exceptions/task.py +12 -0
- unrealon_core/exceptions/validation.py +17 -0
- unrealon_core/models/__init__.py +98 -0
- unrealon_core/models/arq_context.py +252 -0
- unrealon_core/models/arq_responses.py +125 -0
- unrealon_core/models/base.py +291 -0
- unrealon_core/models/bridge_stats.py +58 -0
- unrealon_core/models/communication.py +39 -0
- unrealon_core/models/config.py +47 -0
- unrealon_core/models/connection_stats.py +47 -0
- unrealon_core/models/driver.py +30 -0
- unrealon_core/models/driver_details.py +98 -0
- unrealon_core/models/logging.py +28 -0
- unrealon_core/models/task.py +21 -0
- unrealon_core/models/typed_responses.py +210 -0
- unrealon_core/models/websocket/__init__.py +91 -0
- unrealon_core/models/websocket/base.py +49 -0
- unrealon_core/models/websocket/config.py +200 -0
- unrealon_core/models/websocket/driver.py +215 -0
- unrealon_core/models/websocket/errors.py +138 -0
- unrealon_core/models/websocket/heartbeat.py +100 -0
- unrealon_core/models/websocket/logging.py +261 -0
- unrealon_core/models/websocket/proxy.py +496 -0
- unrealon_core/models/websocket/tasks.py +275 -0
- unrealon_core/models/websocket/utils.py +153 -0
- unrealon_core/models/websocket_session.py +144 -0
- unrealon_core/monitoring/__init__.py +43 -0
- unrealon_core/monitoring/alerts.py +398 -0
- unrealon_core/monitoring/dashboard.py +307 -0
- unrealon_core/monitoring/health_check.py +354 -0
- unrealon_core/monitoring/metrics.py +352 -0
- unrealon_core/utils/__init__.py +11 -0
- unrealon_core/utils/time.py +61 -0
- unrealon_core/version.py +219 -0
- unrealon_driver/__init__.py +90 -51
- unrealon_driver/core_module/__init__.py +34 -0
- unrealon_driver/core_module/base.py +184 -0
- unrealon_driver/core_module/config.py +30 -0
- unrealon_driver/core_module/event_manager.py +127 -0
- unrealon_driver/core_module/protocols.py +98 -0
- unrealon_driver/core_module/registry.py +146 -0
- unrealon_driver/decorators/__init__.py +15 -0
- unrealon_driver/decorators/retry.py +117 -0
- unrealon_driver/decorators/schedule.py +137 -0
- unrealon_driver/decorators/task.py +61 -0
- unrealon_driver/decorators/timing.py +132 -0
- unrealon_driver/driver/__init__.py +20 -0
- unrealon_driver/driver/communication/__init__.py +10 -0
- unrealon_driver/driver/communication/session.py +203 -0
- unrealon_driver/driver/communication/websocket_client.py +197 -0
- unrealon_driver/driver/core/__init__.py +10 -0
- unrealon_driver/driver/core/config.py +85 -0
- unrealon_driver/driver/core/driver.py +221 -0
- unrealon_driver/driver/factory/__init__.py +9 -0
- unrealon_driver/driver/factory/manager_factory.py +130 -0
- unrealon_driver/driver/lifecycle/__init__.py +11 -0
- unrealon_driver/driver/lifecycle/daemon.py +76 -0
- unrealon_driver/driver/lifecycle/initialization.py +97 -0
- unrealon_driver/driver/lifecycle/shutdown.py +48 -0
- unrealon_driver/driver/monitoring/__init__.py +9 -0
- unrealon_driver/driver/monitoring/health.py +63 -0
- unrealon_driver/driver/utilities/__init__.py +10 -0
- unrealon_driver/driver/utilities/logging.py +51 -0
- unrealon_driver/driver/utilities/serialization.py +61 -0
- unrealon_driver/managers/__init__.py +32 -0
- unrealon_driver/managers/base.py +174 -0
- unrealon_driver/managers/browser.py +98 -0
- unrealon_driver/managers/cache.py +116 -0
- unrealon_driver/managers/http.py +107 -0
- unrealon_driver/managers/logger.py +286 -0
- unrealon_driver/managers/proxy.py +99 -0
- unrealon_driver/managers/registry.py +87 -0
- unrealon_driver/managers/threading.py +54 -0
- unrealon_driver/managers/update.py +107 -0
- unrealon_driver/utils/__init__.py +9 -0
- unrealon_driver/utils/time.py +10 -0
- unrealon-1.1.6.dist-info/METADATA +0 -625
- unrealon-1.1.6.dist-info/RECORD +0 -55
- unrealon-1.1.6.dist-info/entry_points.txt +0 -9
- unrealon_browser/managers/stealth.py +0 -388
- unrealon_driver/README.md +0 -0
- unrealon_driver/exceptions.py +0 -33
- unrealon_driver/html_analyzer/__init__.py +0 -32
- unrealon_driver/html_analyzer/cleaner.py +0 -657
- unrealon_driver/html_analyzer/config.py +0 -64
- unrealon_driver/html_analyzer/manager.py +0 -247
- unrealon_driver/html_analyzer/models.py +0 -115
- unrealon_driver/html_analyzer/websocket_analyzer.py +0 -157
- unrealon_driver/models/__init__.py +0 -31
- unrealon_driver/models/websocket.py +0 -98
- unrealon_driver/parser/__init__.py +0 -36
- unrealon_driver/parser/cli_manager.py +0 -142
- unrealon_driver/parser/daemon_manager.py +0 -403
- unrealon_driver/parser/managers/__init__.py +0 -25
- unrealon_driver/parser/managers/config.py +0 -293
- unrealon_driver/parser/managers/error.py +0 -412
- unrealon_driver/parser/managers/result.py +0 -321
- unrealon_driver/parser/parser_manager.py +0 -458
- unrealon_driver/smart_logging/__init__.py +0 -24
- unrealon_driver/smart_logging/models.py +0 -44
- unrealon_driver/smart_logging/smart_logger.py +0 -406
- unrealon_driver/smart_logging/unified_logger.py +0 -525
- unrealon_driver/websocket/__init__.py +0 -31
- unrealon_driver/websocket/client.py +0 -249
- unrealon_driver/websocket/config.py +0 -188
- unrealon_driver/websocket/manager.py +0 -90
|
@@ -13,11 +13,19 @@ from rich.panel import Panel
|
|
|
13
13
|
from rich.table import Table
|
|
14
14
|
|
|
15
15
|
# Use existing unrealon_browser API
|
|
16
|
-
from unrealon_browser import BrowserManager
|
|
16
|
+
from unrealon_browser.core.browser_manager import BrowserManager
|
|
17
|
+
from unrealon_browser.dto.models.config import BrowserConfig, BrowserType, BrowserMode
|
|
18
|
+
|
|
19
|
+
from unrealon_core.config import get_url_config
|
|
17
20
|
|
|
18
21
|
console = Console()
|
|
19
22
|
|
|
20
23
|
|
|
24
|
+
def _get_default_test_url() -> str:
|
|
25
|
+
"""Get default test URL from configuration."""
|
|
26
|
+
return get_url_config().stealth_test_url
|
|
27
|
+
|
|
28
|
+
|
|
21
29
|
@click.group()
|
|
22
30
|
def browser():
|
|
23
31
|
"""🌐 Browser automation commands."""
|
|
@@ -46,9 +54,10 @@ def launch(parser, browser_type, headless, stealth, url):
|
|
|
46
54
|
|
|
47
55
|
# Interactive mode if no URL provided
|
|
48
56
|
if not url:
|
|
49
|
-
|
|
57
|
+
default_url = _get_default_test_url()
|
|
58
|
+
url = questionary.text("Enter URL to navigate:", default=default_url).ask()
|
|
50
59
|
if not url: # If user just pressed Enter with empty input
|
|
51
|
-
url =
|
|
60
|
+
url = default_url
|
|
52
61
|
|
|
53
62
|
# Run browser session
|
|
54
63
|
asyncio.run(_run_browser_session(parser, browser_type, headless, stealth, url))
|
|
@@ -56,7 +65,7 @@ def launch(parser, browser_type, headless, stealth, url):
|
|
|
56
65
|
|
|
57
66
|
@browser.command()
|
|
58
67
|
@click.option("--parser", default="default_parser", help="Parser name")
|
|
59
|
-
@click.option("--url", default=
|
|
68
|
+
@click.option("--url", default=None, help="Test URL")
|
|
60
69
|
def stealth_test(parser, url):
|
|
61
70
|
"""🕵️ Test stealth effectiveness."""
|
|
62
71
|
console.print("[bold blue]🕵️ Testing stealth capabilities...[/bold blue]")
|
|
@@ -109,9 +118,10 @@ def _interactive_launch():
|
|
|
109
118
|
browser_type = questionary.select("Browser type:", choices=["chromium", "firefox", "webkit"]).ask()
|
|
110
119
|
headless = questionary.confirm("Headless mode?", default=False).ask()
|
|
111
120
|
stealth = questionary.select("Stealth level:", choices=["disabled", "basic", "advanced"]).ask()
|
|
112
|
-
|
|
121
|
+
default_url = _get_default_test_url()
|
|
122
|
+
url = questionary.text("URL to navigate:", default=default_url).ask()
|
|
113
123
|
if not url:
|
|
114
|
-
url =
|
|
124
|
+
url = default_url
|
|
115
125
|
|
|
116
126
|
asyncio.run(_run_browser_session(parser, browser_type, headless, stealth, url))
|
|
117
127
|
|
|
@@ -119,7 +129,8 @@ def _interactive_launch():
|
|
|
119
129
|
def _interactive_stealth_test():
|
|
120
130
|
"""Interactive stealth test."""
|
|
121
131
|
parser = questionary.text("Parser name:", default="default_parser").ask()
|
|
122
|
-
|
|
132
|
+
default_url = _get_default_test_url()
|
|
133
|
+
url = questionary.text("Test URL:", default=default_url).ask()
|
|
123
134
|
|
|
124
135
|
asyncio.run(_run_stealth_test(parser, url))
|
|
125
136
|
|
|
@@ -157,7 +168,6 @@ async def _run_browser_session(parser: str, browser_type: str, headless: bool, s
|
|
|
157
168
|
parser_name=parser,
|
|
158
169
|
browser_type=BrowserType(browser_type.lower()),
|
|
159
170
|
mode=BrowserMode.HEADLESS if headless else BrowserMode.HEADED,
|
|
160
|
-
# stealth_level removed - STEALTH ALWAYS ON!
|
|
161
171
|
)
|
|
162
172
|
|
|
163
173
|
# Use existing BrowserManager - no duplication!
|
|
@@ -194,7 +204,6 @@ async def _run_stealth_test(parser: str, url: str):
|
|
|
194
204
|
parser_name=parser,
|
|
195
205
|
browser_type=BrowserType.CHROMIUM,
|
|
196
206
|
mode=BrowserMode.HEADLESS,
|
|
197
|
-
# stealth_level removed - STEALTH ALWAYS ON!
|
|
198
207
|
)
|
|
199
208
|
|
|
200
209
|
browser_manager = BrowserManager(config)
|
|
@@ -11,9 +11,16 @@ from rich.panel import Panel
|
|
|
11
11
|
from rich.table import Table
|
|
12
12
|
from typing import Dict, Any
|
|
13
13
|
|
|
14
|
+
from unrealon_core.config import get_url_config
|
|
15
|
+
|
|
14
16
|
console = Console()
|
|
15
17
|
|
|
16
18
|
|
|
19
|
+
def _get_default_test_url() -> str:
|
|
20
|
+
"""Get default test URL from configuration."""
|
|
21
|
+
return get_url_config().stealth_test_url
|
|
22
|
+
|
|
23
|
+
|
|
17
24
|
async def run_interactive_mode(parser: str, verbose: bool = False) -> None:
|
|
18
25
|
"""
|
|
19
26
|
Run fully interactive browser management mode.
|
|
@@ -100,12 +107,13 @@ async def _interactive_browser_launch(parser: str, verbose: bool) -> None:
|
|
|
100
107
|
# 🔥 STEALTH ALWAYS ON - NO CONFIG NEEDED!
|
|
101
108
|
stealth_info = questionary.select(
|
|
102
109
|
"Stealth is always enabled. Select stealth verification:",
|
|
103
|
-
choices=["None", "Test on
|
|
110
|
+
choices=["None", "Test on detection service", "Test on fingerprint.com"]
|
|
104
111
|
).ask()
|
|
105
112
|
|
|
113
|
+
default_url = _get_default_test_url()
|
|
106
114
|
url = questionary.text(
|
|
107
115
|
"Enter URL to navigate:",
|
|
108
|
-
default=
|
|
116
|
+
default=default_url
|
|
109
117
|
).ask()
|
|
110
118
|
|
|
111
119
|
# Show configuration summary
|
|
@@ -136,9 +144,10 @@ async def _interactive_stealth_test(parser: str, verbose: bool) -> None:
|
|
|
136
144
|
"""Interactive stealth testing."""
|
|
137
145
|
console.print("\n[bold blue]🕵️ Stealth Testing[/bold blue]")
|
|
138
146
|
|
|
147
|
+
default_url = _get_default_test_url()
|
|
139
148
|
test_url = questionary.text(
|
|
140
149
|
"Enter test URL:",
|
|
141
|
-
default=
|
|
150
|
+
default=default_url
|
|
142
151
|
).ask()
|
|
143
152
|
|
|
144
153
|
test_levels = questionary.checkbox(
|
|
@@ -330,7 +339,7 @@ def _show_help_information() -> None:
|
|
|
330
339
|
|
|
331
340
|
[yellow]Examples:[/yellow]
|
|
332
341
|
[dim]cli-browser launch --parser my_parser --headless --url https://example.com
|
|
333
|
-
cli-browser stealth-test --url
|
|
342
|
+
cli-browser stealth-test --url {_get_default_test_url()}
|
|
334
343
|
cli-browser workflow --parser scraper --url https://target-site.com[/dim]
|
|
335
344
|
|
|
336
345
|
[yellow]For more information:[/yellow]
|
|
@@ -55,14 +55,16 @@ class BrowserManager:
|
|
|
55
55
|
self._initialized = False
|
|
56
56
|
self._statistics = BrowserManagerStatistics()
|
|
57
57
|
|
|
58
|
+
# Initialize logger bridge first
|
|
59
|
+
self.logger_bridge = create_browser_logger_bridge(session_id=self._generate_session_id(), parser_id=self.parser_id, enable_console=True) # Use resolved parser_id
|
|
60
|
+
|
|
58
61
|
# Initialize managers
|
|
59
|
-
self.stealth_manager = StealthManager()
|
|
62
|
+
self.stealth_manager = StealthManager(logger_bridge=self.logger_bridge)
|
|
60
63
|
# ✅ FIX: Don't create default managers here - they will be set by Browser service
|
|
61
64
|
# This prevents duplicate directory creation with wrong paths
|
|
62
65
|
self.profile_manager = None
|
|
63
66
|
self.cookie_manager = None
|
|
64
67
|
self.captcha_manager = CaptchaDetector()
|
|
65
|
-
self.logger_bridge = create_browser_logger_bridge(session_id=self._generate_session_id(), parser_id=self.parser_id, enable_console=True) # Use resolved parser_id
|
|
66
68
|
self.page_wait = PageWaitManager(None, self.logger_bridge)
|
|
67
69
|
|
|
68
70
|
# Signal handlers for graceful shutdown
|
|
@@ -226,13 +228,13 @@ class BrowserManager:
|
|
|
226
228
|
|
|
227
229
|
# Create page
|
|
228
230
|
self._page = await self._context.new_page()
|
|
229
|
-
|
|
231
|
+
|
|
230
232
|
# Update page wait manager with new page
|
|
231
233
|
self.page_wait.update_page(self._page)
|
|
232
234
|
|
|
233
235
|
# 🔥 STEALTH ALWAYS APPLIED TO EVERY PAGE!
|
|
234
236
|
stealth_success = await self.stealth_manager.apply_stealth(self._page)
|
|
235
|
-
self.logger_bridge.log_stealth_applied(
|
|
237
|
+
self.logger_bridge.log_stealth_applied(stealth_success)
|
|
236
238
|
|
|
237
239
|
# 🔥 CRITICAL: If stealth fails, CLOSE BROWSER WITH ERROR!
|
|
238
240
|
if not stealth_success:
|
|
@@ -302,34 +304,34 @@ class BrowserManager:
|
|
|
302
304
|
async def _navigate_with_stealth_retry(self, url: str, wait_for: Optional[str] = None) -> Dict[str, Any]:
|
|
303
305
|
"""Navigate with stealth retry logic - universal for all sites"""
|
|
304
306
|
self.logger_bridge.log_info(f"🥷 Stealth navigation to: {url}")
|
|
305
|
-
|
|
307
|
+
|
|
306
308
|
# Stealth warmup if enabled
|
|
307
309
|
if self.config.stealth_warmup_enabled:
|
|
308
310
|
self.logger_bridge.log_info(f"🧪 First visiting stealth test page: {self.config.stealth_test_url}")
|
|
309
311
|
await self._navigate_basic(self.config.stealth_test_url, None)
|
|
310
312
|
self.logger_bridge.log_info(f"⏳ Waiting {self.config.stealth_warmup_delay} seconds for stealth establishment...")
|
|
311
313
|
await asyncio.sleep(self.config.stealth_warmup_delay)
|
|
312
|
-
|
|
314
|
+
|
|
313
315
|
# Now navigate to the actual target URL with retry logic
|
|
314
316
|
max_retries = self.config.stealth_retry_attempts
|
|
315
317
|
for attempt in range(max_retries + 1):
|
|
316
318
|
if attempt > 0:
|
|
317
319
|
self.logger_bridge.log_info(f"🔄 Stealth retry attempt {attempt}/{max_retries}")
|
|
318
320
|
await asyncio.sleep(self.config.stealth_retry_delay)
|
|
319
|
-
|
|
321
|
+
|
|
320
322
|
result = await self._navigate_basic(url, wait_for)
|
|
321
|
-
|
|
323
|
+
|
|
322
324
|
# If successful, return result
|
|
323
325
|
if result["success"]:
|
|
324
326
|
if attempt > 0:
|
|
325
327
|
self.logger_bridge.log_info(f"✅ Stealth retry successful on attempt {attempt + 1}")
|
|
326
328
|
return result
|
|
327
|
-
|
|
329
|
+
|
|
328
330
|
# If failed and we have retries left, continue
|
|
329
331
|
if attempt < max_retries:
|
|
330
332
|
self.logger_bridge.log_info(f"⚠️ Navigation failed, will retry in {self.config.stealth_retry_delay} seconds...")
|
|
331
333
|
continue
|
|
332
|
-
|
|
334
|
+
|
|
333
335
|
# All retries failed
|
|
334
336
|
self.logger_bridge.log_warning(f"❌ Stealth navigation failed after {max_retries + 1} attempts")
|
|
335
337
|
return result
|
|
@@ -411,10 +413,6 @@ class BrowserManager:
|
|
|
411
413
|
"error": str(e),
|
|
412
414
|
}
|
|
413
415
|
|
|
414
|
-
async def wait_for_page_ready_async(self, wait_type: str = "networkidle", timeout: int = 10000) -> bool:
|
|
415
|
-
"""Wait for page to be ready for parsing (legacy method - use page_wait.* methods instead)"""
|
|
416
|
-
return await self.page_wait.wait_custom(wait_type, timeout)
|
|
417
|
-
|
|
418
416
|
async def get_page_content_async(self) -> Optional[str]:
|
|
419
417
|
"""Get current page content"""
|
|
420
418
|
if not self._page:
|
|
@@ -483,8 +481,23 @@ class BrowserManager:
|
|
|
483
481
|
self.logger_bridge.print_statistics()
|
|
484
482
|
|
|
485
483
|
async def test_stealth_async(self) -> Dict[str, Any]:
|
|
486
|
-
"""Test stealth effectiveness on
|
|
487
|
-
|
|
484
|
+
"""Test stealth effectiveness on detection service"""
|
|
485
|
+
self.logger_bridge.log_info("🔍 DEBUG: test_stealth_async called")
|
|
486
|
+
self.logger_bridge.log_info(f"🔍 DEBUG: stealth_manager={self.stealth_manager}")
|
|
487
|
+
try:
|
|
488
|
+
self.logger_bridge.log_info("🔍 DEBUG: About to call stealth_manager.test_stealth_effectiveness")
|
|
489
|
+
result = await self.stealth_manager.test_stealth_effectiveness(self)
|
|
490
|
+
self.logger_bridge.log_info(f"🔍 DEBUG: stealth_manager.test_stealth_effectiveness returned: {result}")
|
|
491
|
+
return result
|
|
492
|
+
except Exception as e:
|
|
493
|
+
self.logger_bridge.log_error(f"❌ DEBUG: Exception in test_stealth_async: {e}")
|
|
494
|
+
import traceback
|
|
495
|
+
self.logger_bridge.log_error(f"❌ DEBUG: Traceback: {traceback.format_exc()}")
|
|
496
|
+
return {
|
|
497
|
+
"success": False,
|
|
498
|
+
"error": str(e),
|
|
499
|
+
"skipped": False,
|
|
500
|
+
}
|
|
488
501
|
|
|
489
502
|
async def detect_captcha_async(self) -> Dict[str, Any]:
|
|
490
503
|
"""Detect captcha on current page"""
|
unrealon_browser/dto/__init__.py
CHANGED
|
@@ -39,6 +39,18 @@ from .models.detection import (
|
|
|
39
39
|
CookieMetadata,
|
|
40
40
|
)
|
|
41
41
|
|
|
42
|
+
# Bot detection models
|
|
43
|
+
from .bot_detection import (
|
|
44
|
+
TestResult,
|
|
45
|
+
BotDetectionSummary,
|
|
46
|
+
BotDetectionResults,
|
|
47
|
+
BotDetectionError,
|
|
48
|
+
BotDetectionResponse,
|
|
49
|
+
BotTestResult,
|
|
50
|
+
BotSummary,
|
|
51
|
+
BotResults,
|
|
52
|
+
)
|
|
53
|
+
|
|
42
54
|
|
|
43
55
|
# Exports
|
|
44
56
|
__all__ = [
|
|
@@ -62,4 +74,13 @@ __all__ = [
|
|
|
62
74
|
"CaptchaDetection",
|
|
63
75
|
"CaptchaDetectionResult",
|
|
64
76
|
"CookieMetadata",
|
|
77
|
+
# Bot detection models
|
|
78
|
+
"TestResult",
|
|
79
|
+
"BotDetectionSummary",
|
|
80
|
+
"BotDetectionResults",
|
|
81
|
+
"BotDetectionError",
|
|
82
|
+
"BotDetectionResponse",
|
|
83
|
+
"BotTestResult",
|
|
84
|
+
"BotSummary",
|
|
85
|
+
"BotResults",
|
|
65
86
|
]
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Bot Detection Results DTOs
|
|
3
|
+
|
|
4
|
+
Pydantic models for bot detection results from the frontend scanner.
|
|
5
|
+
Based on TypeScript interfaces from botDetection.ts
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Dict, List, Literal, Optional, Union
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestResult(BaseModel):
|
|
13
|
+
"""Individual test result from bot detection"""
|
|
14
|
+
|
|
15
|
+
name: str = Field(..., description="Name of the test")
|
|
16
|
+
status: Literal["passed", "failed", "warn"] = Field(..., description="Test status")
|
|
17
|
+
value: Any = Field(..., description="Test result value (can be any type)")
|
|
18
|
+
description: str = Field(..., description="Human-readable test description")
|
|
19
|
+
score: int = Field(..., ge=0, le=100, description="Suspicion score (0-100, higher = more suspicious)")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class BotDetectionSummary(BaseModel):
|
|
23
|
+
"""Summary statistics for bot detection tests"""
|
|
24
|
+
|
|
25
|
+
passed: int = Field(..., ge=0, description="Number of tests that passed")
|
|
26
|
+
failed: int = Field(..., ge=0, description="Number of tests that failed")
|
|
27
|
+
warnings: int = Field(..., ge=0, description="Number of tests with warnings")
|
|
28
|
+
total: int = Field(..., ge=0, description="Total number of tests run")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class BotDetectionResults(BaseModel):
|
|
32
|
+
"""Complete bot detection results from frontend scanner"""
|
|
33
|
+
|
|
34
|
+
tests: List[TestResult] = Field(..., description="List of individual test results")
|
|
35
|
+
overall_score: int = Field(..., ge=0, le=100, description="Overall suspicion score (0-100)", alias="overallScore")
|
|
36
|
+
is_bot: bool = Field(..., description="Whether the browser is detected as a bot", alias="isBot")
|
|
37
|
+
confidence: Literal["low", "medium", "high"] = Field(..., description="Confidence level of detection")
|
|
38
|
+
summary: BotDetectionSummary = Field(..., description="Summary statistics")
|
|
39
|
+
|
|
40
|
+
model_config = {
|
|
41
|
+
"json_encoders": {
|
|
42
|
+
# Handle Any type serialization
|
|
43
|
+
Any: lambda v: v
|
|
44
|
+
},
|
|
45
|
+
# Allow population by field name or alias
|
|
46
|
+
"populate_by_name": True
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def failed_tests(self) -> List[TestResult]:
|
|
51
|
+
"""Get list of failed tests"""
|
|
52
|
+
return [test for test in self.tests if test.status == "failed"]
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def critical_failures(self) -> List[TestResult]:
|
|
56
|
+
"""Get list of critical failures that strongly indicate bot detection"""
|
|
57
|
+
critical_test_names = [
|
|
58
|
+
"BotD Detection",
|
|
59
|
+
"WebDriver Property",
|
|
60
|
+
"Headless Chrome",
|
|
61
|
+
"Chrome Object Consistency",
|
|
62
|
+
"Navigator WebDriver",
|
|
63
|
+
"Advanced Automation Detection"
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
return [
|
|
67
|
+
test for test in self.failed_tests
|
|
68
|
+
if any(critical in test.name for critical in critical_test_names)
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def warning_tests(self) -> List[TestResult]:
|
|
73
|
+
"""Get list of tests with warnings"""
|
|
74
|
+
return [test for test in self.tests if test.status == "warn"]
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def passed_tests(self) -> List[TestResult]:
|
|
78
|
+
"""Get list of passed tests"""
|
|
79
|
+
return [test for test in self.tests if test.status == "passed"]
|
|
80
|
+
|
|
81
|
+
def get_effectiveness_rating(self) -> str:
|
|
82
|
+
"""Get stealth effectiveness rating based on results"""
|
|
83
|
+
if self.overall_score <= 10 and not self.is_bot:
|
|
84
|
+
return "excellent"
|
|
85
|
+
elif self.overall_score <= 25 and not self.is_bot:
|
|
86
|
+
return "good"
|
|
87
|
+
elif self.overall_score <= 50:
|
|
88
|
+
return "moderate"
|
|
89
|
+
else:
|
|
90
|
+
return "poor"
|
|
91
|
+
|
|
92
|
+
def get_recommendations(self) -> List[str]:
|
|
93
|
+
"""Generate recommendations based on test results"""
|
|
94
|
+
recommendations = []
|
|
95
|
+
|
|
96
|
+
failed_test_names = [test.name for test in self.failed_tests]
|
|
97
|
+
|
|
98
|
+
if any("BotD" in name for name in failed_test_names):
|
|
99
|
+
recommendations.append("Consider using headed mode instead of headless for better BotD bypass")
|
|
100
|
+
|
|
101
|
+
if any("WebDriver" in name for name in failed_test_names):
|
|
102
|
+
recommendations.append("Enhance webdriver property removal techniques")
|
|
103
|
+
|
|
104
|
+
if any("Chrome Object" in name for name in failed_test_names):
|
|
105
|
+
recommendations.append("Improve window.chrome object spoofing")
|
|
106
|
+
|
|
107
|
+
if any("Plugin" in name for name in failed_test_names):
|
|
108
|
+
recommendations.append("Add more realistic browser plugin simulation")
|
|
109
|
+
|
|
110
|
+
if any("WebGL" in name for name in failed_test_names):
|
|
111
|
+
recommendations.append("Enhance WebGL vendor/renderer spoofing")
|
|
112
|
+
|
|
113
|
+
if self.overall_score > 30:
|
|
114
|
+
recommendations.append("Consider using undetected-chromedriver or NoDriver for better results")
|
|
115
|
+
|
|
116
|
+
if len(self.critical_failures) > 2:
|
|
117
|
+
recommendations.append("Multiple critical failures detected - review stealth configuration")
|
|
118
|
+
|
|
119
|
+
return recommendations
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class BotDetectionError(BaseModel):
|
|
123
|
+
"""Error information when bot detection fails"""
|
|
124
|
+
|
|
125
|
+
error: str = Field(..., description="Error message")
|
|
126
|
+
error_type: str = Field(default="detection_error", description="Type of error")
|
|
127
|
+
timestamp: Optional[str] = Field(None, description="When the error occurred")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class BotDetectionResponse(BaseModel):
|
|
131
|
+
"""Response wrapper for bot detection results"""
|
|
132
|
+
|
|
133
|
+
success: bool = Field(..., description="Whether detection was successful")
|
|
134
|
+
results: Optional[BotDetectionResults] = Field(None, description="Detection results if successful")
|
|
135
|
+
error: Optional[BotDetectionError] = Field(None, description="Error information if failed")
|
|
136
|
+
scanner_url: Optional[str] = Field(None, description="URL of the scanner used")
|
|
137
|
+
method: Optional[str] = Field(None, description="Stealth method used")
|
|
138
|
+
timestamp: Optional[str] = Field(None, description="When the detection was performed")
|
|
139
|
+
|
|
140
|
+
@classmethod
|
|
141
|
+
def success_response(
|
|
142
|
+
cls,
|
|
143
|
+
results: BotDetectionResults,
|
|
144
|
+
scanner_url: Optional[str] = None,
|
|
145
|
+
method: Optional[str] = None
|
|
146
|
+
) -> "BotDetectionResponse":
|
|
147
|
+
"""Create a successful response"""
|
|
148
|
+
return cls(
|
|
149
|
+
success=True,
|
|
150
|
+
results=results,
|
|
151
|
+
scanner_url=scanner_url,
|
|
152
|
+
method=method
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
@classmethod
|
|
156
|
+
def error_response(
|
|
157
|
+
cls,
|
|
158
|
+
error_message: str,
|
|
159
|
+
error_type: str = "detection_error",
|
|
160
|
+
scanner_url: Optional[str] = None,
|
|
161
|
+
method: Optional[str] = None
|
|
162
|
+
) -> "BotDetectionResponse":
|
|
163
|
+
"""Create an error response"""
|
|
164
|
+
return cls(
|
|
165
|
+
success=False,
|
|
166
|
+
error=BotDetectionError(error=error_message, error_type=error_type),
|
|
167
|
+
scanner_url=scanner_url,
|
|
168
|
+
method=method
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
# Type aliases for convenience
|
|
173
|
+
BotTestResult = TestResult
|
|
174
|
+
BotSummary = BotDetectionSummary
|
|
175
|
+
BotResults = BotDetectionResults
|
|
@@ -8,6 +8,13 @@ from typing import Optional
|
|
|
8
8
|
from pydantic import BaseModel, Field, ConfigDict
|
|
9
9
|
from .enums import BrowserType, BrowserMode
|
|
10
10
|
|
|
11
|
+
from unrealon_core.config import get_url_config
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _get_default_stealth_url() -> str:
|
|
15
|
+
"""Get default stealth test URL based on environment configuration."""
|
|
16
|
+
return get_url_config().stealth_test_url
|
|
17
|
+
|
|
11
18
|
|
|
12
19
|
class BrowserConfig(BaseModel):
|
|
13
20
|
"""Simplified browser configuration."""
|
|
@@ -30,11 +37,10 @@ class BrowserConfig(BaseModel):
|
|
|
30
37
|
|
|
31
38
|
# Performance
|
|
32
39
|
disable_images: bool = Field(default=False)
|
|
33
|
-
enable_stealth_check: bool = Field(default=False)
|
|
34
40
|
|
|
35
41
|
# Stealth settings
|
|
36
42
|
stealth_warmup_enabled: bool = Field(default=True, description="Enable stealth warmup before target navigation")
|
|
37
|
-
stealth_test_url: str = Field(
|
|
43
|
+
stealth_test_url: str = Field(default_factory=_get_default_stealth_url, description="URL for stealth warmup")
|
|
38
44
|
stealth_warmup_delay: float = Field(default=3.0, description="Delay in seconds after stealth warmup")
|
|
39
45
|
stealth_retry_attempts: int = Field(default=2, description="Maximum retry attempts for failed navigation")
|
|
40
|
-
stealth_retry_delay: float = Field(default=3.0, description="Delay between retry attempts")
|
|
46
|
+
stealth_retry_delay: float = Field(default=3.0, description="Delay between retry attempts")
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Browser Managers Package - Specialized management components
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from ..stealth import StealthManager
|
|
6
6
|
from .profile import ProfileManager
|
|
7
7
|
from .logger_bridge import BrowserLoggerBridge, create_browser_logger_bridge
|
|
8
8
|
from .cookies import CookieManager
|
|
@@ -141,7 +141,6 @@ class BrowserLoggerBridge:
|
|
|
141
141
|
session_id=metadata.session_id,
|
|
142
142
|
parser_name=metadata.parser_name,
|
|
143
143
|
browser_type=metadata.browser_type or "unknown",
|
|
144
|
-
stealth_level="ALWAYS_ON", # 🔥 STEALTH ALWAYS ON!
|
|
145
144
|
proxy_host=getattr(metadata.proxy, "host", None) if metadata.proxy else None,
|
|
146
145
|
proxy_port=getattr(metadata.proxy, "port", None) if metadata.proxy else None,
|
|
147
146
|
)
|
|
@@ -168,20 +167,18 @@ class BrowserLoggerBridge:
|
|
|
168
167
|
navigation_type="browser_navigation",
|
|
169
168
|
)
|
|
170
169
|
|
|
171
|
-
def log_stealth_applied(self,
|
|
170
|
+
def log_stealth_applied(self, success: bool = True) -> None:
|
|
172
171
|
"""Log stealth application - 🔥 STEALTH ALWAYS ON!"""
|
|
173
172
|
self._browser_events["stealth_applied"] += 1
|
|
174
173
|
|
|
175
174
|
if success:
|
|
176
175
|
self._log_info(
|
|
177
176
|
"Stealth measures applied: ALWAYS_ON",
|
|
178
|
-
stealth_level="ALWAYS_ON",
|
|
179
177
|
stealth_success=True,
|
|
180
178
|
)
|
|
181
179
|
else:
|
|
182
180
|
self._log_warning(
|
|
183
181
|
"Stealth application failed: ALWAYS_ON",
|
|
184
|
-
stealth_level="ALWAYS_ON",
|
|
185
182
|
stealth_success=False,
|
|
186
183
|
)
|
|
187
184
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
UnrealOn Stealth Package - Advanced anti-detection system
|
|
3
|
+
|
|
4
|
+
Modular bot detection bypass system:
|
|
5
|
+
- PlaywrightStealth: playwright-stealth integration
|
|
6
|
+
- UndetectedChrome: undetected-chromedriver support
|
|
7
|
+
- NoDriverStealth: NoDriver integration
|
|
8
|
+
- BypassTechniques: advanced BotD bypass techniques
|
|
9
|
+
- StealthManager: main coordinator for all techniques
|
|
10
|
+
- ScannerTester: real-world testing through UnrealOn scanner
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .manager import StealthManager
|
|
14
|
+
from .playwright_stealth import PlaywrightStealth
|
|
15
|
+
from .undetected_chrome import UndetectedChrome
|
|
16
|
+
from .nodriver_stealth import NoDriverStealth
|
|
17
|
+
from .bypass_techniques import BypassTechniques
|
|
18
|
+
from .scanner_tester import ScannerTester
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"StealthManager",
|
|
22
|
+
"PlaywrightStealth",
|
|
23
|
+
"UndetectedChrome",
|
|
24
|
+
"NoDriverStealth",
|
|
25
|
+
"BypassTechniques",
|
|
26
|
+
"ScannerTester"
|
|
27
|
+
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|