unrealon 2.0.10__py3-none-any.whl → 2.0.11__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,355 @@
1
+ """
2
+ ScannerTester - Clean stealth testing through UnrealOn scanner
3
+
4
+ Provides streamlined stealth testing using our detection service with Pydantic models.
5
+ """
6
+
7
+ import logging
8
+ import asyncio
9
+ from typing import Optional, List
10
+ from playwright.async_api import Page
11
+
12
+ from unrealon_core.config.urls import get_url_config
13
+ from unrealon_browser.dto import BotDetectionResults, BotDetectionResponse
14
+
15
+ logger = logging.getLogger(__name__)
16
+ logger.setLevel(logging.DEBUG)
17
+
18
+ class ScannerTester:
19
+ """Clean stealth testing through UnrealOn scanner"""
20
+
21
+ def __init__(self, logger_bridge=None):
22
+ """Initialize ScannerTester"""
23
+ self.logger_bridge = logger_bridge
24
+
25
+ def _log(self, message: str, level: str = "info") -> None:
26
+ """Log message with bridge support"""
27
+ if self.logger_bridge:
28
+ getattr(self.logger_bridge, f"log_{level}", self.logger_bridge.log_info)(message)
29
+ else:
30
+ getattr(logger, level, logger.info)(message)
31
+
32
+ def _get_scanner_url(self) -> str:
33
+ """Get scanner URL from configuration"""
34
+ return get_url_config().stealth_test_url
35
+
36
+ async def test_stealth(self, browser_manager, stealth_manager, method: str = "comprehensive") -> BotDetectionResponse:
37
+ """
38
+ Test stealth effectiveness with specified method
39
+
40
+ Args:
41
+ browser_manager: Browser manager instance
42
+ stealth_manager: Stealth manager instance
43
+ method: Stealth method to use
44
+
45
+ Returns:
46
+ BotDetectionResponse with results
47
+ """
48
+ if not browser_manager or not browser_manager.page:
49
+ return BotDetectionResponse.error_response(
50
+ "Browser manager or page not available",
51
+ "browser_error",
52
+ self._get_scanner_url()
53
+ )
54
+
55
+ try:
56
+ scanner_url = self._get_scanner_url()
57
+ self._log(f"🔬 Testing stealth with method: {method}")
58
+
59
+ # Apply stealth
60
+ stealth_applied = await stealth_manager.apply_stealth(browser_manager.page, method=method)
61
+ if not stealth_applied:
62
+ return BotDetectionResponse.error_response(
63
+ f"Failed to apply stealth method: {method}",
64
+ "stealth_error",
65
+ scanner_url,
66
+ method
67
+ )
68
+
69
+ # Navigate to scanner
70
+ nav_result = await browser_manager.navigate_async(scanner_url)
71
+ if not nav_result["success"]:
72
+ return BotDetectionResponse.error_response(
73
+ f"Navigation failed: {nav_result.get('error', 'Unknown error')}",
74
+ "navigation_error",
75
+ scanner_url,
76
+ method
77
+ )
78
+
79
+ # Setup console logging to catch JavaScript errors
80
+ await self._setup_console_logging(browser_manager.page)
81
+
82
+ # Wait for scanner to complete
83
+ await self._wait_for_scanner(browser_manager)
84
+
85
+ # Extract and parse results
86
+ bot_results = await self._extract_bot_results(browser_manager.page)
87
+ if bot_results:
88
+ return BotDetectionResponse.success_response(bot_results, scanner_url, method)
89
+ else:
90
+ return BotDetectionResponse.error_response(
91
+ "Scanner results not available or invalid",
92
+ "extraction_error",
93
+ scanner_url,
94
+ method
95
+ )
96
+
97
+ except Exception as e:
98
+ self._log(f"❌ Stealth test failed: {e}", "error")
99
+ return BotDetectionResponse.error_response(
100
+ str(e),
101
+ "test_error",
102
+ self._get_scanner_url(),
103
+ method
104
+ )
105
+
106
+ async def compare_methods(self, browser_manager, stealth_manager, methods: Optional[List[str]] = None) -> List[BotDetectionResponse]:
107
+ """
108
+ Compare multiple stealth methods
109
+
110
+ Args:
111
+ browser_manager: Browser manager instance
112
+ stealth_manager: Stealth manager instance
113
+ methods: List of methods to test (default: ["playwright", "comprehensive", "aggressive"])
114
+
115
+ Returns:
116
+ List of BotDetectionResponse for each method
117
+ """
118
+ if methods is None:
119
+ methods = ["playwright", "comprehensive", "aggressive"]
120
+
121
+ self._log(f"🔬 Comparing {len(methods)} stealth methods")
122
+
123
+ results = []
124
+ for method in methods:
125
+ self._log(f"🧪 Testing method: {method}")
126
+ result = await self.test_stealth(browser_manager, stealth_manager, method)
127
+ results.append(result)
128
+
129
+ # Brief pause between tests
130
+ await asyncio.sleep(1)
131
+
132
+ return results
133
+
134
+ async def _setup_console_logging(self, page) -> None:
135
+ """Setup console error logging to debug React issues"""
136
+ def handle_console_message(msg):
137
+ if msg.type in ['error', 'warning']:
138
+ self._log(f"🔍 CONSOLE {msg.type.upper()}: {msg.text}", "warning")
139
+ elif msg.type == 'log' and ('error' in msg.text.lower() or 'failed' in msg.text.lower()):
140
+ self._log(f"🔍 CONSOLE LOG: {msg.text}", "info")
141
+
142
+ page.on("console", handle_console_message)
143
+ self._log("🔍 Console logging enabled for JavaScript error detection", "info")
144
+
145
+ async def _wait_for_scanner(self, browser_manager) -> None:
146
+ """Wait for scanner to complete tests using proper page wait methods"""
147
+ self._log("⏳ Waiting for React SPA and bot detection tests...")
148
+
149
+ # Use proper SPA wait method
150
+ if hasattr(browser_manager, 'page_wait') and browser_manager.page_wait:
151
+ self._log("🚀 Using SPA wait method...")
152
+ spa_ready = await browser_manager.page_wait.wait_spa()
153
+ if not spa_ready:
154
+ self._log("⚠️ SPA wait timeout, trying full load method", "warning")
155
+ await browser_manager.page_wait.wait_full_load()
156
+ else:
157
+ # Fallback to basic wait
158
+ try:
159
+ await browser_manager.page.wait_for_load_state("networkidle", timeout=15000)
160
+ except Exception as e:
161
+ self._log(f"⚠️ Basic load timeout: {e}", "warning")
162
+
163
+ # Now wait for bot detection results to appear
164
+ self._log("⏳ Waiting for bot detection results...")
165
+
166
+ max_attempts = 45 # 45 seconds for bot detection
167
+ attempt = 0
168
+
169
+ while attempt < max_attempts:
170
+ try:
171
+ check_result = await browser_manager.page.evaluate("""
172
+ () => {
173
+ // Check if results are available
174
+ if (window.botDetectionResults && typeof window.botDetectionResults === 'object') {
175
+ return {
176
+ hasResults: true,
177
+ isComplete: window.botDetectionResults.tests && window.botDetectionResults.tests.length > 0,
178
+ testsCount: window.botDetectionResults.tests ? window.botDetectionResults.tests.length : 0
179
+ };
180
+ }
181
+
182
+ // Check scanner status and page content
183
+ const bodyText = document.body.innerText || '';
184
+ const windowKeys = Object.keys(window).filter(k => k.includes('bot') || k.includes('detection') || k.includes('scan'));
185
+
186
+ return {
187
+ hasResults: false,
188
+ isComplete: false,
189
+ testsCount: 0,
190
+ isInitializing: bodyText.includes('Initializing'),
191
+ isScanning: bodyText.includes('Scanning') || bodyText.includes('Running'),
192
+ bodyPreview: bodyText.substring(0, 200),
193
+ windowKeys: windowKeys,
194
+ botDetectionType: typeof window.botDetectionResults,
195
+ url: window.location.href,
196
+ title: document.title
197
+ };
198
+ }
199
+ """)
200
+
201
+ if check_result['hasResults'] and check_result['isComplete']:
202
+ self._log(f"✅ Bot detection completed! Found {check_result['testsCount']} tests")
203
+ await asyncio.sleep(1) # Final wait for completion
204
+ return
205
+
206
+ # Log detailed progress every 5 seconds
207
+ if attempt % 5 == 0:
208
+ status = "initializing" if check_result.get('isInitializing') else ("scanning" if check_result.get('isScanning') else "unknown")
209
+ self._log(f"🔍 DEBUG Wait Status ({attempt}s):")
210
+ self._log(f" URL: {check_result.get('url')}")
211
+ self._log(f" Title: {check_result.get('title')}")
212
+ self._log(f" Status: {status}")
213
+ self._log(f" botDetectionResults type: {check_result.get('botDetectionType')}")
214
+ self._log(f" Window keys: {check_result.get('windowKeys')}")
215
+ self._log(f" Body preview: {check_result.get('bodyPreview')}")
216
+
217
+ await asyncio.sleep(1)
218
+ attempt += 1
219
+
220
+ except Exception as e:
221
+ self._log(f"⚠️ Error checking results: {e}", "warning")
222
+ await asyncio.sleep(1)
223
+ attempt += 1
224
+
225
+ self._log("⚠️ Bot detection results not available after 45s timeout", "warning")
226
+
227
+ async def _extract_bot_results(self, page: Page) -> Optional[BotDetectionResults]:
228
+ """Extract and parse bot detection results"""
229
+ try:
230
+ # First, let's debug what's on the page
231
+ debug_info = await page.evaluate("""
232
+ () => {
233
+ return {
234
+ title: document.title,
235
+ url: window.location.href,
236
+ bodyText: document.body.innerText.substring(0, 200),
237
+ hasBotDetectionResults: typeof window.botDetectionResults !== 'undefined',
238
+ botDetectionResultsType: typeof window.botDetectionResults,
239
+ botDetectionResultsValue: window.botDetectionResults,
240
+ windowKeys: Object.keys(window).filter(k => k.includes('bot') || k.includes('detection')).slice(0, 10)
241
+ };
242
+ }
243
+ """)
244
+
245
+ self._log(f"🔍 DEBUG Page Info:", "info")
246
+ self._log(f" Title: {debug_info.get('title')}", "info")
247
+ self._log(f" URL: {debug_info.get('url')}", "info")
248
+ self._log(f" Body preview: {debug_info.get('bodyText')}", "info")
249
+ self._log(f" Has botDetectionResults: {debug_info.get('hasBotDetectionResults')}", "info")
250
+ self._log(f" Type: {debug_info.get('botDetectionResultsType')}", "info")
251
+ self._log(f" Value: {debug_info.get('botDetectionResultsValue')}", "info")
252
+ self._log(f" Window keys: {debug_info.get('windowKeys')}", "info")
253
+
254
+ # Extract results from window.botDetectionResults
255
+ raw_results = await page.evaluate("""
256
+ () => {
257
+ if (window.botDetectionResults) {
258
+ return window.botDetectionResults;
259
+ }
260
+ return null;
261
+ }
262
+ """)
263
+
264
+ if not raw_results:
265
+ self._log("⚠️ No botDetectionResults found", "warning")
266
+ return None
267
+
268
+ self._log(f"🔍 Raw results type: {type(raw_results)}, value: {raw_results}", "info")
269
+
270
+ # Parse with Pydantic
271
+ return BotDetectionResults(**raw_results)
272
+
273
+ except Exception as e:
274
+ self._log(f"❌ Failed to extract results: {e}", "error")
275
+ return None
276
+
277
+ def print_results(self, response: BotDetectionResponse) -> None:
278
+ """Print formatted test results"""
279
+ self._log("\n🔬 STEALTH TEST RESULTS", "info")
280
+ self._log("=" * 50, "info")
281
+
282
+ if not response.success:
283
+ self._log(f"❌ Test failed: {response.error.error if response.error else 'Unknown error'}", "error")
284
+ if response.scanner_url:
285
+ self._log(f"🌐 Scanner: {response.scanner_url}", "info")
286
+ if response.method:
287
+ self._log(f"🥷 Method: {response.method}", "info")
288
+ return
289
+
290
+ if not response.results:
291
+ self._log("❌ No results available", "error")
292
+ return
293
+
294
+ results = response.results
295
+
296
+ # Basic info
297
+ self._log(f"🌐 Scanner: {response.scanner_url or 'Unknown'}", "info")
298
+ self._log(f"🥷 Method: {response.method or 'Unknown'}", "info")
299
+ self._log(f"📊 Score: {results.overall_score}% | Bot: {results.is_bot} | Confidence: {results.confidence}", "info")
300
+ self._log(f"📈 Tests: {results.summary.passed}✅ {results.summary.failed}❌ {results.summary.warnings}⚠️ ({results.summary.total} total)", "info")
301
+ self._log(f"🏆 Effectiveness: {results.get_effectiveness_rating()}", "info")
302
+
303
+ # Failed tests
304
+ if results.failed_tests:
305
+ self._log(f"\n❌ Failed Tests ({len(results.failed_tests)}):", "warning")
306
+ for test in results.failed_tests[:3]: # Show top 3
307
+ self._log(f" • {test.name}: {test.description} (Score: {test.score})", "warning")
308
+
309
+ # Critical failures
310
+ if results.critical_failures:
311
+ self._log(f"\n🚨 Critical Failures ({len(results.critical_failures)}):", "error")
312
+ for failure in results.critical_failures:
313
+ self._log(f" • {failure.name}: {failure.description}", "error")
314
+
315
+ # Recommendations
316
+ recommendations = results.get_recommendations()
317
+ if recommendations:
318
+ self._log(f"\n💡 Recommendations:", "info")
319
+ for rec in recommendations[:3]: # Show top 3
320
+ self._log(f" • {rec}", "info")
321
+
322
+ def print_comparison(self, results: List[BotDetectionResponse]) -> None:
323
+ """Print comparison results"""
324
+ self._log("\n🔬 STEALTH METHOD COMPARISON", "info")
325
+ self._log("=" * 60, "info")
326
+
327
+ successful_results = [r for r in results if r.success and r.results]
328
+
329
+ if not successful_results:
330
+ self._log("❌ No successful tests to compare", "error")
331
+ return
332
+
333
+ # Sort by score (best first)
334
+ successful_results.sort(key=lambda x: x.results.overall_score)
335
+ best = successful_results[0]
336
+
337
+ self._log(f"🏆 WINNER: {best.method} (Score: {best.results.overall_score}%)", "info")
338
+
339
+ # Show all results
340
+ for i, result in enumerate(results, 1):
341
+ if result.success and result.results:
342
+ r = result.results
343
+ status = "🏆" if result == best else f"{i}."
344
+ self._log(f"{status} {result.method}: {r.overall_score}% | Bot: {r.is_bot} | {r.get_effectiveness_rating()}", "info")
345
+ else:
346
+ self._log(f"{i}. {result.method}: ❌ Failed - {result.error.error if result.error else 'Unknown'}", "error")
347
+
348
+ def get_best_method(self, results: List[BotDetectionResponse]) -> Optional[str]:
349
+ """Get the best performing method from comparison results"""
350
+ successful_results = [r for r in results if r.success and r.results]
351
+ if not successful_results:
352
+ return None
353
+
354
+ best = min(successful_results, key=lambda x: x.results.overall_score)
355
+ return best.method