unrealon 2.0.10__py3-none-any.whl → 2.0.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,507 @@
1
+ """
2
+ StealthManager - Main manager for all detection bypass techniques
3
+
4
+ Coordinates work of all stealth modules:
5
+ - PlaywrightStealth for playwright-stealth integration
6
+ - UndetectedChrome for undetected-chromedriver
7
+ - NoDriverStealth for NoDriver
8
+ - BypassTechniques for advanced BotD bypass techniques
9
+ """
10
+
11
+ import logging
12
+ import asyncio
13
+ from typing import Dict, Any, Optional, List
14
+ from playwright.async_api import Page, BrowserContext
15
+
16
+ from unrealon_core.config.urls import get_url_config
17
+ from unrealon_browser.dto import BotDetectionResults, BotDetectionResponse
18
+
19
+ logger = logging.getLogger(__name__)
20
+ logger.setLevel(logging.DEBUG)
21
+
22
+ class StealthManager:
23
+ """
24
+ Main manager for all detection bypass techniques
25
+
26
+ Coordinates application of various stealth techniques:
27
+ - Playwright-stealth 2.0.0
28
+ - Undetected ChromeDriver integration
29
+ - NoDriver support
30
+ - Advanced BotD bypass techniques
31
+ """
32
+
33
+ def __init__(self, logger_bridge=None):
34
+ """Initialize stealth manager"""
35
+ self.stealth_applied = False
36
+ self.test_results: Optional[Dict[str, Any]] = None
37
+ self.logger_bridge = logger_bridge
38
+
39
+ # Lazy import stealth modules to avoid circular imports
40
+ self._playwright_stealth = None
41
+ self._undetected_chrome = None
42
+ self._nodriver_stealth = None
43
+ self._bypass_techniques = None
44
+ self._scanner_tester = None
45
+
46
+ def _get_stealth_test_url(self) -> str:
47
+ """Get stealth test URL from configuration."""
48
+ return get_url_config().stealth_test_url
49
+
50
+ def _logger(self, message: str, level: str = "info") -> None:
51
+ """Private wrapper for logger with bridge support"""
52
+ if self.logger_bridge:
53
+ if level == "info":
54
+ self.logger_bridge.log_info(message)
55
+ elif level == "error":
56
+ self.logger_bridge.log_error(message)
57
+ elif level == "warning":
58
+ self.logger_bridge.log_warning(message)
59
+ else:
60
+ self.logger_bridge.log_info(message)
61
+ else:
62
+ if level == "info":
63
+ logger.info(message)
64
+ elif level == "error":
65
+ logger.error(message)
66
+ elif level == "warning":
67
+ logger.warning(message)
68
+ else:
69
+ logger.info(message)
70
+
71
+ @property
72
+ def playwright_stealth(self):
73
+ """Lazy load PlaywrightStealth"""
74
+ if self._playwright_stealth is None:
75
+ from .playwright_stealth import PlaywrightStealth
76
+ self._playwright_stealth = PlaywrightStealth(self.logger_bridge)
77
+ return self._playwright_stealth
78
+
79
+ @property
80
+ def undetected_chrome(self):
81
+ """Lazy load UndetectedChrome"""
82
+ if self._undetected_chrome is None:
83
+ from .undetected_chrome import UndetectedChrome
84
+ self._undetected_chrome = UndetectedChrome(self.logger_bridge)
85
+ return self._undetected_chrome
86
+
87
+ @property
88
+ def nodriver_stealth(self):
89
+ """Lazy load NoDriverStealth"""
90
+ if self._nodriver_stealth is None:
91
+ from .nodriver_stealth import NoDriverStealth
92
+ self._nodriver_stealth = NoDriverStealth(self.logger_bridge)
93
+ return self._nodriver_stealth
94
+
95
+ @property
96
+ def bypass_techniques(self):
97
+ """Lazy load BypassTechniques"""
98
+ if self._bypass_techniques is None:
99
+ from .bypass_techniques import BypassTechniques
100
+ self._bypass_techniques = BypassTechniques(self.logger_bridge)
101
+ return self._bypass_techniques
102
+
103
+ @property
104
+ def scanner_tester(self):
105
+ """Lazy load ScannerTester"""
106
+ if not hasattr(self, '_scanner_tester') or self._scanner_tester is None:
107
+ from .scanner_tester import ScannerTester
108
+ self._scanner_tester = ScannerTester(self.logger_bridge)
109
+ return self._scanner_tester
110
+
111
+ def get_stealth_args(self) -> List[str]:
112
+ """
113
+ Get comprehensive stealth arguments for browser launch
114
+ Combines best practices from all stealth techniques
115
+ """
116
+ import platform
117
+
118
+ args = [
119
+ # 🔥 CRITICAL: Remove automation indicators
120
+ "--disable-blink-features=AutomationControlled",
121
+ "--disable-features=VizDisplayCompositor",
122
+
123
+ # 🥷 Security & Detection Bypass
124
+ "--disable-web-security",
125
+ "--disable-features=TranslateUI",
126
+ "--disable-ipc-flooding-protection",
127
+ "--disable-client-side-phishing-detection",
128
+ "--disable-component-extensions-with-background-pages",
129
+ "--disable-default-apps",
130
+ "--disable-domain-reliability",
131
+ "--disable-background-networking",
132
+
133
+ # 🎭 Mimic real user behavior
134
+ "--no-first-run",
135
+ "--no-default-browser-check",
136
+ "--no-sandbox",
137
+ "--disable-dev-shm-usage",
138
+ "--disable-translate",
139
+
140
+ # 📐 Window & Display - realistic settings
141
+ "--window-size=1366,768", # More common resolution
142
+ "--start-maximized",
143
+ "--hide-scrollbars",
144
+
145
+ # ⚡ Performance & Background
146
+ "--disable-background-timer-throttling",
147
+ "--disable-backgrounding-occluded-windows",
148
+ "--disable-renderer-backgrounding",
149
+ "--disable-field-trial-config",
150
+ "--disable-back-forward-cache",
151
+
152
+ # 🔧 System & Monitoring
153
+ "--disable-hang-monitor",
154
+ "--disable-prompt-on-repost",
155
+ "--disable-sync",
156
+ "--metrics-recording-only",
157
+ "--no-report-upload",
158
+ "--mute-audio",
159
+
160
+ # 🆕 Enhanced for BotD bypass (keep some extensions for realism)
161
+ "--disable-extensions-except=",
162
+ ]
163
+
164
+ # 🍎 macOS specific fixes for "Mach rendezvous failed" error
165
+ if platform.system() == "Darwin":
166
+ args.extend([
167
+ "--disable-gpu", # Disable GPU acceleration on macOS
168
+ "--disable-software-rasterizer", # Disable software rasterizer
169
+ "--disable-background-mode", # Disable background mode
170
+ "--disable-features=VizDisplayCompositor,VizHitTestSurfaceLayer", # Disable problematic features
171
+ "--use-gl=swiftshader", # Use software GL implementation
172
+ "--disable-ipc-flooding-protection", # Disable IPC flooding protection
173
+ "--single-process", # Use single process mode to avoid IPC issues
174
+ ])
175
+
176
+ return args
177
+
178
+ async def apply_stealth(self, page: Page, method: str = "comprehensive") -> bool:
179
+ """
180
+ Apply stealth measures to page
181
+
182
+ Args:
183
+ page: Playwright page instance
184
+ method: Stealth method ("playwright", "comprehensive", "aggressive")
185
+
186
+ Returns:
187
+ True if successful, False otherwise
188
+ """
189
+ try:
190
+ self._logger(f"🥷 Applying stealth measures (method: {method})...", "info")
191
+
192
+ success = True
193
+
194
+ if method in ["playwright", "comprehensive"]:
195
+ # 1. Apply playwright-stealth 2.0.0
196
+ playwright_success = await self.playwright_stealth.apply_stealth(page)
197
+ success = success and playwright_success
198
+
199
+ if method in ["comprehensive", "aggressive"]:
200
+ # 2. Apply advanced BotD bypass techniques
201
+ bypass_success = await self.bypass_techniques.apply_all_bypasses(page)
202
+ success = success and bypass_success
203
+
204
+ # 3. Always apply basic webdriver removal
205
+ await self.bypass_techniques.apply_webdriver_removal(page)
206
+
207
+ self.stealth_applied = success
208
+
209
+ if success:
210
+ self._logger("✅ Stealth measures applied successfully", "info")
211
+ else:
212
+ self._logger("⚠️ Some stealth measures failed, but continuing", "warning")
213
+
214
+ return success
215
+
216
+ except Exception as e:
217
+ self._logger(f"❌ Failed to apply stealth measures: {e}", "error")
218
+ self.stealth_applied = False
219
+ return False
220
+
221
+ async def apply_stealth_to_context(self, context: BrowserContext) -> bool:
222
+ """Apply stealth measures to entire browser context"""
223
+ try:
224
+ self._logger("🥷 Applying stealth to browser context...", "info")
225
+
226
+ # Apply context-level stealth scripts
227
+ success = await self.bypass_techniques.apply_context_stealth(context)
228
+
229
+ if success:
230
+ self._logger("✅ Context stealth applied successfully", "info")
231
+ else:
232
+ self._logger("⚠️ Context stealth partially failed", "warning")
233
+
234
+ return success
235
+
236
+ except Exception as e:
237
+ self._logger(f"❌ Failed to apply context stealth: {e}", "error")
238
+ return False
239
+
240
+ async def apply_webdriver_removal(self, context: BrowserContext) -> bool:
241
+ """
242
+ Apply webdriver removal to browser context
243
+
244
+ Args:
245
+ context: Playwright browser context
246
+
247
+ Returns:
248
+ True if successful
249
+ """
250
+ try:
251
+ self._logger("🛡️ Applying webdriver removal to context...", "info")
252
+
253
+ # Use bypass techniques for webdriver removal
254
+ success = await self.bypass_techniques.apply_context_stealth(context)
255
+
256
+ if success:
257
+ self._logger("✅ Webdriver removal applied successfully", "info")
258
+ else:
259
+ self._logger("⚠️ Webdriver removal partially failed", "warning")
260
+
261
+ return success
262
+
263
+ except Exception as e:
264
+ self._logger(f"❌ Failed to apply webdriver removal: {e}", "error")
265
+ return False
266
+
267
+ async def test_stealth_effectiveness(self, browser_manager) -> Dict[str, Any]:
268
+ """
269
+ Test stealth effectiveness using ScannerTester
270
+
271
+ Args:
272
+ browser_manager: Browser manager instance with page
273
+
274
+ Returns:
275
+ Dict with test results and detection metrics
276
+ """
277
+ self._logger("🔍 DEBUG: test_stealth_effectiveness called", "info")
278
+ self._logger(f"🔍 DEBUG: browser_manager={browser_manager}, page={browser_manager.page if browser_manager else None}", "info")
279
+
280
+ if not browser_manager or not browser_manager.page:
281
+ self._logger("❌ DEBUG: Browser manager or page not available", "error")
282
+ return {
283
+ "success": False,
284
+ "error": "Browser manager or page not available",
285
+ "skipped": True,
286
+ }
287
+
288
+ try:
289
+ self._logger("🧪 Testing stealth effectiveness using ScannerTester...", "info")
290
+ self._logger("🔍 DEBUG: About to call scanner_tester.test_stealth()", "info")
291
+
292
+ # Use ScannerTester for proper testing
293
+ response = await self.scanner_tester.test_stealth(browser_manager, self, "comprehensive")
294
+
295
+ self._logger(f"🔍 DEBUG: scanner_tester.test_stealth() returned: {response}", "info")
296
+
297
+ # Convert BotDetectionResponse to dict format for compatibility
298
+ if response.success and response.results:
299
+ results = response.results
300
+
301
+ # Store results for later access
302
+ self.test_results = results.model_dump()
303
+
304
+ return {
305
+ "success": True,
306
+ "results": results.model_dump(),
307
+ "detection_score": results.overall_score,
308
+ "is_bot": results.is_bot,
309
+ "confidence": results.confidence,
310
+ "tests_passed": results.summary.passed,
311
+ "tests_failed": results.summary.failed,
312
+ "total_tests": results.summary.total,
313
+ "error": None,
314
+ "skipped": False,
315
+ }
316
+ else:
317
+ error_msg = response.error.error if response.error else "Unknown scanner error"
318
+ return {
319
+ "success": False,
320
+ "error": error_msg,
321
+ "skipped": False,
322
+ }
323
+
324
+ except Exception as e:
325
+ self._logger(f"❌ Stealth test failed: {e}", "error")
326
+ return {
327
+ "success": False,
328
+ "error": str(e),
329
+ "skipped": False,
330
+ }
331
+
332
+ def get_stealth_status(self) -> Dict[str, Any]:
333
+ """Get current stealth status and test results"""
334
+ return {
335
+ "stealth_applied": self.stealth_applied,
336
+ "test_results": self.test_results,
337
+ }
338
+
339
+ def print_stealth_status(self) -> None:
340
+ """Print current stealth status"""
341
+ status = self.get_stealth_status()
342
+
343
+ self._logger("\n🥷 Stealth Status:", "info")
344
+ self._logger(f"Applied: {status['stealth_applied']}", "info")
345
+
346
+ if status["test_results"]:
347
+ results = status["test_results"]
348
+ self._logger(f" Last test score: {results.get('detection_score', 'Unknown')}", "info")
349
+ self._logger(f"Tests passed: {results.get('tests_passed', 0)}/{results.get('total_tests', 0)}")
350
+
351
+ async def test_with_scanner(self, browser_manager, method: str = "comprehensive") -> Dict[str, Any]:
352
+ """
353
+ Convenient method to test stealth effectiveness with our scanner
354
+
355
+ Args:
356
+ browser_manager: Browser manager instance
357
+ method: Stealth method to test ("playwright", "comprehensive", "aggressive")
358
+
359
+ Returns:
360
+ Dict with test results from our scanner
361
+ """
362
+ self._logger(f"🔬 Testing stealth with UnrealOn scanner (method: {method})", "info")
363
+
364
+ try:
365
+ # Apply stealth first
366
+ if browser_manager.page:
367
+ stealth_applied = await self.apply_stealth(browser_manager.page, method)
368
+ self._logger(f"🥷 Stealth applied: {stealth_applied}", "info")
369
+
370
+ # Use the existing test_stealth_effectiveness method
371
+ result = await self.test_stealth_effectiveness(browser_manager)
372
+
373
+ # Debug: Check result type (force print)
374
+ self._logger(f"🔍 test_stealth_effectiveness result type: {type(result)}, value: {result}", "info")
375
+
376
+ # Store results and add method info
377
+ if isinstance(result, dict) and result.get("success"):
378
+ result["method"] = method
379
+ result["test_type"] = "scanner_test"
380
+
381
+ # Log summary
382
+ self._logger(f"📊 Scanner Results ({method}):", "info")
383
+ self._logger(f" Detection Score: {result.get('detection_score', 'N/A')}%", "info")
384
+ self._logger(f" Bot Detected: {result.get('is_bot', 'N/A')}", "info")
385
+ self._logger(f" Confidence: {result.get('confidence', 'N/A')}", "info")
386
+ self._logger(f" Tests Passed: {result.get('tests_passed', 0)}/{result.get('total_tests', 0)}", "info")
387
+
388
+ # Show failed tests if any
389
+ if result.get("results") and "tests" in result["results"]:
390
+ failed_tests = [test for test in result["results"]["tests"] if test.get("status") == "failed"]
391
+ if failed_tests:
392
+ self._logger(f" ⚠️ Failed Tests: {len(failed_tests)}", "warning")
393
+ for test in failed_tests[:3]: # Show first 3
394
+ self._logger(f" • {test.get('name', 'Unknown')}", "warning")
395
+
396
+ return result
397
+
398
+ except Exception as e:
399
+ self._logger(f"❌ Scanner test failed: {e}", "error")
400
+ return {
401
+ "success": False,
402
+ "error": str(e),
403
+ "method": method
404
+ }
405
+
406
+ async def test_all_methods_with_scanner(self, browser_manager) -> Dict[str, Any]:
407
+ """
408
+ Test all stealth methods with our scanner and compare results
409
+
410
+ Args:
411
+ browser_manager: Browser manager instance
412
+
413
+ Returns:
414
+ Dict with comprehensive comparison results
415
+ """
416
+ self._logger("🔬 Testing all stealth methods with UnrealOn scanner", "info")
417
+
418
+ try:
419
+ # Use scanner tester for comprehensive test
420
+ results = await self.scanner_tester.test_stealth_comprehensive(browser_manager, self)
421
+
422
+ # Print formatted results
423
+ self.scanner_tester.print_test_results(results)
424
+
425
+ # Store best results
426
+ if results.get("best_config"):
427
+ self.test_results = results["best_config"].get("raw_results", results["best_config"])
428
+
429
+ return results
430
+
431
+ except Exception as e:
432
+ self._logger(f"❌ Comprehensive scanner test failed: {e}", "error")
433
+ return {
434
+ "success": False,
435
+ "error": str(e),
436
+ "test_type": "comprehensive"
437
+ }
438
+
439
+ async def compare_stealth_methods_with_scanner(self, browser_manager) -> Dict[str, Any]:
440
+ """
441
+ Compare different stealth methods using our scanner
442
+
443
+ Args:
444
+ browser_manager: Browser manager instance
445
+
446
+ Returns:
447
+ Dict with comparison results
448
+ """
449
+ self._logger("🔬 Comparing stealth methods with UnrealOn scanner", "info")
450
+
451
+ try:
452
+ # Use scanner tester for comparison
453
+ results = await self.scanner_tester.test_stealth_comparison(browser_manager, self)
454
+
455
+ # Print formatted results
456
+ self.scanner_tester.print_test_results(results)
457
+
458
+ # Store winner results
459
+ if results.get("winner"):
460
+ self.test_results = results["winner"].get("raw_results", results["winner"])
461
+
462
+ return results
463
+
464
+ except Exception as e:
465
+ self._logger(f"❌ Scanner comparison test failed: {e}", "error")
466
+ return {
467
+ "success": False,
468
+ "error": str(e),
469
+ "test_type": "comparison"
470
+ }
471
+
472
+ def get_scanner_recommendations(self) -> List[str]:
473
+ """
474
+ Get recommendations based on last scanner test results
475
+
476
+ Returns:
477
+ List of recommendations for improving stealth
478
+ """
479
+ if not self.test_results:
480
+ return ["Run scanner test first to get recommendations"]
481
+
482
+ # Extract recommendations from test results
483
+ if isinstance(self.test_results, dict):
484
+ # Check if it's a comprehensive result with analysis
485
+ if "analysis" in self.test_results:
486
+ return self.test_results["analysis"].get("recommendations", [])
487
+
488
+ # Check if it's raw bot detection results
489
+ if "tests" in self.test_results:
490
+ recommendations = []
491
+ failed_tests = [test for test in self.test_results.get("tests", []) if test.get("status") == "failed"]
492
+
493
+ # Generate basic recommendations based on failed tests
494
+ for test in failed_tests:
495
+ test_name = test.get("name", "")
496
+ if "BotD" in test_name:
497
+ recommendations.append("Consider using headed mode for better BotD bypass")
498
+ elif "WebDriver" in test_name:
499
+ recommendations.append("Enhance webdriver property removal")
500
+ elif "Chrome Object" in test_name:
501
+ recommendations.append("Improve window.chrome object spoofing")
502
+ elif "Plugin" in test_name:
503
+ recommendations.append("Add more realistic browser plugins")
504
+
505
+ return recommendations[:5] # Limit to 5 recommendations
506
+
507
+ return ["No specific recommendations available"]