spfnas/grok
0
1import os
2import sys
3import time
4import uuid
5import random
6import logging
7import asyncio
8from typing import Optional, Union
9import argparse
10from quart import Quart, request, jsonify
11from camoufox.async_api import AsyncCamoufox
12from patchright.async_api import async_playwright
13from db_results import init_db, save_result, load_result, cleanup_old_results
14from browser_configs import browser_config
15from rich.console import Console
16from rich.panel import Panel
17from rich.text import Text
18from rich.align import Align
19from rich import box
20
21
22
23COLORS = {
24 'MAGENTA': '\033[35m',
25 'BLUE': '\033[34m',
26 'GREEN': '\033[32m',
27 'YELLOW': '\033[33m',
28 'RED': '\033[31m',
29 'RESET': '\033[0m',
30}
31
32
33class CustomLogger(logging.Logger):
34 @staticmethod
35 def format_message(level, color, message):
36 timestamp = time.strftime('%H:%M:%S')
37 return f"[{timestamp}] [{COLORS.get(color)}{level}{COLORS.get('RESET')}] -> {message}"
38
39 def debug(self, message, *args, **kwargs):
40 super().debug(self.format_message('DEBUG', 'MAGENTA', message), *args, **kwargs)
41
42 def info(self, message, *args, **kwargs):
43 super().info(self.format_message('INFO', 'BLUE', message), *args, **kwargs)
44
45 def success(self, message, *args, **kwargs):
46 super().info(self.format_message('SUCCESS', 'GREEN', message), *args, **kwargs)
47
48 def warning(self, message, *args, **kwargs):
49 super().warning(self.format_message('WARNING', 'YELLOW', message), *args, **kwargs)
50
51 def error(self, message, *args, **kwargs):
52 super().error(self.format_message('ERROR', 'RED', message), *args, **kwargs)
53
54
55logging.setLoggerClass(CustomLogger)
56logger: CustomLogger = logging.getLogger("TurnstileAPIServer") # type: ignore
57logger.setLevel(logging.DEBUG)
58handler = logging.StreamHandler(sys.stdout)
59logger.addHandler(handler)
60
61
62class TurnstileAPIServer:
63
64 def __init__(self, headless: bool, useragent: Optional[str], debug: bool, browser_type: str, thread: int, proxy_support: bool, use_random_config: bool = False, browser_name: Optional[str] = None, browser_version: Optional[str] = None):
65 self.app = Quart(__name__)
66 self.debug = debug
67 self.browser_type = browser_type
68 self.headless = headless
69 self.thread_count = thread
70 self.proxy_support = proxy_support
71 self.browser_pool = asyncio.Queue()
72 self.use_random_config = use_random_config
73 self.browser_name = browser_name
74 self.browser_version = browser_version
75 self.console = Console()
76
77 # Initialize useragent and sec_ch_ua attributes
78 self.useragent = useragent
79 self.sec_ch_ua = None
80
81
82 if self.browser_type in ['chromium', 'chrome', 'msedge']:
83 if browser_name and browser_version:
84 config = browser_config.get_browser_config(browser_name, browser_version)
85 if config:
86 useragent, sec_ch_ua = config
87 self.useragent = useragent
88 self.sec_ch_ua = sec_ch_ua
89 elif useragent:
90 self.useragent = useragent
91 else:
92 browser, version, useragent, sec_ch_ua = browser_config.get_random_browser_config(self.browser_type)
93 self.browser_name = browser
94 self.browser_version = version
95 self.useragent = useragent
96 self.sec_ch_ua = sec_ch_ua
97
98 self.browser_args = []
99 if self.useragent:
100 self.browser_args.append(f"--user-agent={self.useragent}")
101
102 self._setup_routes()
103
104 def display_welcome(self):
105 """Displays welcome screen with logo."""
106 self.console.clear()
107
108 combined_text = Text()
109 combined_text.append("\n📢 Channel: ", style="bold white")
110 combined_text.append("https://t.me/D3_vin", style="cyan")
111 combined_text.append("\n💬 Chat: ", style="bold white")
112 combined_text.append("https://t.me/D3vin_chat", style="cyan")
113 combined_text.append("\n📁 GitHub: ", style="bold white")
114 combined_text.append("https://github.com/D3-vin", style="cyan")
115 combined_text.append("\n📁 Version: ", style="bold white")
116 combined_text.append("1.2a", style="green")
117 combined_text.append("\n")
118
119 info_panel = Panel(
120 Align.left(combined_text),
121 title="[bold blue]Turnstile Solver[/bold blue]",
122 subtitle="[bold magenta]Dev by D3vin[/bold magenta]",
123 box=box.ROUNDED,
124 border_style="bright_blue",
125 padding=(0, 1),
126 width=50
127 )
128
129 self.console.print(info_panel)
130 self.console.print()
131
132
133
134
135 def _setup_routes(self) -> None:
136 """Set up the application routes."""
137 self.app.before_serving(self._startup)
138 self.app.route('/turnstile', methods=['GET'])(self.process_turnstile)
139 self.app.route('/result', methods=['GET'])(self.get_result)
140 self.app.route('/')(self.index)
141
142
143 async def _startup(self) -> None:
144 """Initialize the browser and page pool on startup."""
145 self.display_welcome()
146 logger.info("Starting browser initialization")
147 try:
148 await init_db()
149 await self._initialize_browser()
150
151 # Запускаем периодическую очистку старых результатов
152 asyncio.create_task(self._periodic_cleanup())
153
154 except Exception as e:
155 logger.error(f"Failed to initialize browser: {str(e)}")
156 raise
157
158 async def _initialize_browser(self) -> None:
159 """Initialize the browser and create the page pool."""
160 playwright = None
161 camoufox = None
162
163 if self.browser_type in ['chromium', 'chrome', 'msedge']:
164 playwright = await async_playwright().start()
165 elif self.browser_type == "camoufox":
166 camoufox = AsyncCamoufox(headless=self.headless)
167
168 browser_configs = []
169 for _ in range(self.thread_count):
170 if self.browser_type in ['chromium', 'chrome', 'msedge']:
171 if self.use_random_config:
172 browser, version, useragent, sec_ch_ua = browser_config.get_random_browser_config(self.browser_type)
173 elif self.browser_name and self.browser_version:
174 config = browser_config.get_browser_config(self.browser_name, self.browser_version)
175 if config:
176 useragent, sec_ch_ua = config
177 browser = self.browser_name
178 version = self.browser_version
179 else:
180 browser, version, useragent, sec_ch_ua = browser_config.get_random_browser_config(self.browser_type)
181 else:
182 browser = getattr(self, 'browser_name', 'custom')
183 version = getattr(self, 'browser_version', 'custom')
184 useragent = self.useragent
185 sec_ch_ua = getattr(self, 'sec_ch_ua', '')
186 else:
187 # Для camoufox и других браузеров используем значения по умолчанию
188 browser = self.browser_type
189 version = 'custom'
190 useragent = self.useragent
191 sec_ch_ua = getattr(self, 'sec_ch_ua', '')
192
193
194 browser_configs.append({
195 'browser_name': browser,
196 'browser_version': version,
197 'useragent': useragent,
198 'sec_ch_ua': sec_ch_ua
199 })
200
201 for i in range(self.thread_count):
202 config = browser_configs[i]
203
204 browser_args = [
205 "--window-position=0,0",
206 "--force-device-scale-factor=1"
207 ]
208 if config['useragent']:
209 browser_args.append(f"--user-agent={config['useragent']}")
210
211 browser = None
212 if self.browser_type in ['chromium', 'chrome', 'msedge'] and playwright:
213 browser = await playwright.chromium.launch(
214 channel=self.browser_type,
215 headless=self.headless,
216 args=browser_args
217 )
218 elif self.browser_type == "camoufox" and camoufox:
219 browser = await camoufox.start()
220
221 if browser:
222 await self.browser_pool.put((i+1, browser, config))
223
224 if self.debug:
225 logger.info(f"Browser {i + 1} initialized successfully with {config['browser_name']} {config['browser_version']}")
226
227 logger.info(f"Browser pool initialized with {self.browser_pool.qsize()} browsers")
228
229 if self.use_random_config:
230 logger.info(f"Each browser in pool received random configuration")
231 elif self.browser_name and self.browser_version:
232 logger.info(f"All browsers using configuration: {self.browser_name} {self.browser_version}")
233 else:
234 logger.info("Using custom configuration")
235
236 if self.debug:
237 for i, config in enumerate(browser_configs):
238 logger.debug(f"Browser {i+1} config: {config['browser_name']} {config['browser_version']}")
239 logger.debug(f"Browser {i+1} User-Agent: {config['useragent']}")
240 logger.debug(f"Browser {i+1} Sec-CH-UA: {config['sec_ch_ua']}")
241
242 async def _periodic_cleanup(self):
243 """Periodic cleanup of old results every hour"""
244 while True:
245 try:
246 await asyncio.sleep(3600)
247 deleted_count = await cleanup_old_results(days_old=7)
248 if deleted_count > 0:
249 logger.info(f"Cleaned up {deleted_count} old results")
250 except Exception as e:
251 logger.error(f"Error during periodic cleanup: {e}")
252
253 async def _antishadow_inject(self, page):
254 await page.add_init_script("""
255 (function() {
256 const originalAttachShadow = Element.prototype.attachShadow;
257 Element.prototype.attachShadow = function(init) {
258 const shadow = originalAttachShadow.call(this, init);
259 if (init.mode === 'closed') {
260 window.__lastClosedShadowRoot = shadow;
261 }
262 return shadow;
263 };
264 })();
265 """)
266
267
268
269 async def _optimized_route_handler(self, route):
270 """Оптимизированный обработчик маршрутов для экономии ресурсов."""
271 url = route.request.url
272 resource_type = route.request.resource_type
273
274 allowed_types = {'document', 'script', 'xhr', 'fetch'}
275
276 allowed_domains = [
277 'challenges.cloudflare.com',
278 'static.cloudflareinsights.com',
279 'cloudflare.com'
280 ]
281
282 if resource_type in allowed_types:
283 await route.continue_()
284 elif any(domain in url for domain in allowed_domains):
285 await route.continue_()
286 else:
287 await route.abort()
288
289 async def _block_rendering(self, page):
290 """Блокировка рендеринга для экономии ресурсов"""
291 await page.route("**/*", self._optimized_route_handler)
292
293 async def _unblock_rendering(self, page):
294 """Разблокировка рендеринга"""
295 await page.unroute("**/*", self._optimized_route_handler)
296
297 async def _find_turnstile_elements(self, page, index: int):
298 """Умная проверка всех возможных Turnstile элементов"""
299 selectors = [
300 '.cf-turnstile',
301 '[data-sitekey]',
302 'iframe[src*="turnstile"]',
303 'iframe[title*="widget"]',
304 'div[id*="turnstile"]',
305 'div[class*="turnstile"]'
306 ]
307
308 elements = []
309 for selector in selectors:
310 try:
311 # Безопасная проверка count()
312 try:
313 count = await page.locator(selector).count()
314 except Exception:
315 # Если count() дает ошибку, пропускаем этот селектор
316 continue
317
318 if count > 0:
319 elements.append((selector, count))
320 if self.debug:
321 logger.debug(f"Browser {index}: Found {count} elements with selector '{selector}'")
322 except Exception as e:
323 if self.debug:
324 logger.debug(f"Browser {index}: Selector '{selector}' failed: {str(e)}")
325 continue
326
327 return elements
328
329 async def _find_and_click_checkbox(self, page, index: int):
330 """Найти и кликнуть по чекбоксу Turnstile CAPTCHA внутри iframe"""
331 try:
332 # Пробуем разные селекторы iframe с защитой от ошибок
333 iframe_selectors = [
334 'iframe[src*="challenges.cloudflare.com"]',
335 'iframe[src*="turnstile"]',
336 'iframe[title*="widget"]'
337 ]
338
339 iframe_locator = None
340 for selector in iframe_selectors:
341 try:
342 test_locator = page.locator(selector).first
343 # Безопасная проверка count для iframe
344 try:
345 iframe_count = await test_locator.count()
346 except Exception:
347 iframe_count = 0
348
349 if iframe_count > 0:
350 iframe_locator = test_locator
351 if self.debug:
352 logger.debug(f"Browser {index}: Found Turnstile iframe with selector: {selector}")
353 break
354 except Exception as e:
355 if self.debug:
356 logger.debug(f"Browser {index}: Iframe selector '{selector}' failed: {str(e)}")
357 continue
358
359 if iframe_locator:
360 try:
361 # Получаем frame из iframe
362 iframe_element = await iframe_locator.element_handle()
363 frame = await iframe_element.content_frame()
364
365 if frame:
366 # Ищем чекбокс внутри iframe
367 checkbox_selectors = [
368 'input[type="checkbox"]',
369 '.cb-lb input[type="checkbox"]',
370 'label input[type="checkbox"]'
371 ]
372
373 for selector in checkbox_selectors:
374 try:
375 # Полностью избегаем locator.count() в iframe - используем альтернативный подход
376 try:
377 # Пробуем кликнуть напрямую без count проверки
378 checkbox = frame.locator(selector).first
379 await checkbox.click(timeout=2000)
380 if self.debug:
381 logger.debug(f"Browser {index}: Successfully clicked checkbox in iframe with selector '{selector}'")
382 return True
383 except Exception as click_e:
384 # Если прямой клик не сработал, записываем в debug но не падаем
385 if self.debug:
386 logger.debug(f"Browser {index}: Direct checkbox click failed for '{selector}': {str(click_e)}")
387 continue
388 except Exception as e:
389 if self.debug:
390 logger.debug(f"Browser {index}: Iframe checkbox selector '{selector}' failed: {str(e)}")
391 continue
392
393 # Если нашли iframe, но не смогли кликнуть чекбокс, пробуем клик по iframe
394 try:
395 if self.debug:
396 logger.debug(f"Browser {index}: Trying to click iframe directly as fallback")
397 await iframe_locator.click(timeout=1000)
398 return True
399 except Exception as e:
400 if self.debug:
401 logger.debug(f"Browser {index}: Iframe direct click failed: {str(e)}")
402
403 except Exception as e:
404 if self.debug:
405 logger.debug(f"Browser {index}: Failed to access iframe content: {str(e)}")
406
407 except Exception as e:
408 if self.debug:
409 logger.debug(f"Browser {index}: General iframe search failed: {str(e)}")
410
411 return False
412
413 async def _try_click_strategies(self, page, index: int):
414 strategies = [
415 ('checkbox_click', lambda: self._find_and_click_checkbox(page, index)),
416 ('direct_widget', lambda: self._safe_click(page, '.cf-turnstile', index)),
417 ('iframe_click', lambda: self._safe_click(page, 'iframe[src*="turnstile"]', index)),
418 ('js_click', lambda: page.evaluate("document.querySelector('.cf-turnstile')?.click()")),
419 ('sitekey_attr', lambda: self._safe_click(page, '[data-sitekey]', index)),
420 ('any_turnstile', lambda: self._safe_click(page, '*[class*="turnstile"]', index)),
421 ('xpath_click', lambda: self._safe_click(page, "//div[@class='cf-turnstile']", index))
422 ]
423
424 for strategy_name, strategy_func in strategies:
425 try:
426 result = await strategy_func()
427 if result is True or result is None: # None означает успех для большинства стратегий
428 if self.debug:
429 logger.debug(f"Browser {index}: Click strategy '{strategy_name}' succeeded")
430 return True
431 except Exception as e:
432 if self.debug:
433 logger.debug(f"Browser {index}: Click strategy '{strategy_name}' failed: {str(e)}")
434 continue
435
436 return False
437
438 async def _safe_click(self, page, selector: str, index: int):
439 """Полностью безопасный клик с максимальной защитой от ошибок"""
440 try:
441 # Пробуем кликнуть напрямую без count() проверки
442 locator = page.locator(selector).first
443 await locator.click(timeout=1000)
444 return True
445 except Exception as e:
446 # Логируем ошибку только в debug режиме
447 if self.debug and "Can't query n-th element" not in str(e):
448 logger.debug(f"Browser {index}: Safe click failed for '{selector}': {str(e)}")
449 return False
450
451 async def _inject_captcha_directly(self, page, websiteKey: str, action: str = '', cdata: str = '', index: int = 0):
452 """Inject CAPTCHA directly into the target website"""
453 script = f"""
454 // Remove any existing turnstile widgets first
455 document.querySelectorAll('.cf-turnstile').forEach(el => el.remove());
456 document.querySelectorAll('[data-sitekey]').forEach(el => el.remove());
457
458 // Create turnstile widget directly on the page
459 const captchaDiv = document.createElement('div');
460 captchaDiv.className = 'cf-turnstile';
461 captchaDiv.setAttribute('data-sitekey', '{websiteKey}');
462 captchaDiv.setAttribute('data-callback', 'onTurnstileCallback');
463 {f'captchaDiv.setAttribute("data-action", "{action}");' if action else ''}
464 {f'captchaDiv.setAttribute("data-cdata", "{cdata}");' if cdata else ''}
465 captchaDiv.style.position = 'fixed';
466 captchaDiv.style.top = '20px';
467 captchaDiv.style.left = '20px';
468 captchaDiv.style.zIndex = '9999';
469 captchaDiv.style.backgroundColor = 'white';
470 captchaDiv.style.padding = '15px';
471 captchaDiv.style.border = '2px solid #0f79af';
472 captchaDiv.style.borderRadius = '8px';
473 captchaDiv.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.3)';
474
475 // Add to body immediately
476 document.body.appendChild(captchaDiv);
477
478 // Load Turnstile script and render widget
479 const loadTurnstile = () => {{
480 const script = document.createElement('script');
481 script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js';
482 script.async = true;
483 script.defer = true;
484 script.onload = function() {{
485 console.log('Turnstile script loaded');
486 // Wait a bit for script to initialize
487 setTimeout(() => {{
488 if (window.turnstile && window.turnstile.render) {{
489 try {{
490 window.turnstile.render(captchaDiv, {{
491 sitekey: '{websiteKey}',
492 {f'action: "{action}",' if action else ''}
493 {f'cdata: "{cdata}",' if cdata else ''}
494 callback: function(token) {{
495 console.log('Turnstile solved with token:', token);
496 // Create hidden input for token
497 let tokenInput = document.querySelector('input[name="cf-turnstile-response"]');
498 if (!tokenInput) {{
499 tokenInput = document.createElement('input');
500 tokenInput.type = 'hidden';
501 tokenInput.name = 'cf-turnstile-response';
502 document.body.appendChild(tokenInput);
503 }}
504 tokenInput.value = token;
505 }},
506 'error-callback': function(error) {{
507 console.log('Turnstile error:', error);
508 }}
509 }});
510 }} catch (e) {{
511 console.log('Turnstile render error:', e);
512 }}
513 }} else {{
514 console.log('Turnstile API not available');
515 }}
516 }}, 1000);
517 }};
518 script.onerror = function() {{
519 console.log('Failed to load Turnstile script');
520 }};
521 document.head.appendChild(script);
522 }};
523
524 // Check if Turnstile is already loaded
525 if (window.turnstile) {{
526 console.log('Turnstile already loaded, rendering immediately');
527 try {{
528 window.turnstile.render(captchaDiv, {{
529 sitekey: '{websiteKey}',
530 {f'action: "{action}",' if action else ''}
531 {f'cdata: "{cdata}",' if cdata else ''}
532 callback: function(token) {{
533 console.log('Turnstile solved with token:', token);
534 let tokenInput = document.querySelector('input[name="cf-turnstile-response"]');
535 if (!tokenInput) {{
536 tokenInput = document.createElement('input');
537 tokenInput.type = 'hidden';
538 tokenInput.name = 'cf-turnstile-response';
539 document.body.appendChild(tokenInput);
540 }}
541 tokenInput.value = token;
542 }},
543 'error-callback': function(error) {{
544 console.log('Turnstile error:', error);
545 }}
546 }});
547 }} catch (e) {{
548 console.log('Immediate render error:', e);
549 loadTurnstile();
550 }}
551 }} else {{
552 loadTurnstile();
553 }}
554
555 // Setup global callback
556 window.onTurnstileCallback = function(token) {{
557 console.log('Global turnstile callback executed:', token);
558 }};
559 """
560
561 await page.evaluate(script)
562 if self.debug:
563 logger.debug(f"Browser {index}: Injected CAPTCHA directly into website with sitekey: {websiteKey}")
564
565 async def _solve_turnstile(self, task_id: str, url: str, sitekey: str, action: Optional[str] = None, cdata: Optional[str] = None):
566 """Solve the Turnstile challenge."""
567 proxy = None
568
569 index, browser, browser_config = await self.browser_pool.get()
570
571 try:
572 if hasattr(browser, 'is_connected') and not browser.is_connected():
573 if self.debug:
574 logger.warning(f"Browser {index}: Browser disconnected, skipping")
575 await self.browser_pool.put((index, browser, browser_config))
576 await save_result(task_id, "turnstile", {"value": "CAPTCHA_FAIL", "elapsed_time": 0})
577 return
578 except Exception as e:
579 if self.debug:
580 logger.warning(f"Browser {index}: Cannot check browser state: {str(e)}")
581
582 if self.proxy_support:
583 proxy_file_path = os.path.join(os.getcwd(), "proxies.txt")
584
585 try:
586 with open(proxy_file_path) as proxy_file:
587 proxies = [line.strip() for line in proxy_file if line.strip()]
588
589 proxy = random.choice(proxies) if proxies else None
590
591 if self.debug and proxy:
592 logger.debug(f"Browser {index}: Selected proxy: {proxy}")
593 elif self.debug and not proxy:
594 logger.debug(f"Browser {index}: No proxies available")
595
596 except FileNotFoundError:
597 logger.warning(f"Proxy file not found: {proxy_file_path}")
598 proxy = None
599 except Exception as e:
600 logger.error(f"Error reading proxy file: {str(e)}")
601 proxy = None
602
603 if proxy:
604 if '@' in proxy:
605 try:
606 scheme_part, auth_part = proxy.split('://')
607 auth, address = auth_part.split('@')
608 username, password = auth.split(':')
609 ip, port = address.split(':')
610 if self.debug:
611 logger.debug(f"Browser {index}: Creating context with proxy {scheme_part}://{ip}:{port} (auth: {username}:***)")
612 context_options = {
613 "proxy": {
614 "server": f"{scheme_part}://{ip}:{port}",
615 "username": username,
616 "password": password
617 },
618 "user_agent": browser_config['useragent']
619 }
620
621 if browser_config['sec_ch_ua'] and browser_config['sec_ch_ua'].strip():
622 context_options['extra_http_headers'] = {
623 'sec-ch-ua': browser_config['sec_ch_ua']
624 }
625
626 context = await browser.new_context(**context_options)
627 except ValueError:
628 raise ValueError(f"Invalid proxy format: {proxy}")
629 else:
630 parts = proxy.split(':')
631 if len(parts) == 5:
632 proxy_scheme, proxy_ip, proxy_port, proxy_user, proxy_pass = parts
633 if self.debug:
634 logger.debug(f"Browser {index}: Creating context with proxy {proxy_scheme}://{proxy_ip}:{proxy_port} (auth: {proxy_user}:***)")
635 context_options = {
636 "proxy": {
637 "server": f"{proxy_scheme}://{proxy_ip}:{proxy_port}",
638 "username": proxy_user,
639 "password": proxy_pass
640 },
641 "user_agent": browser_config['useragent']
642 }
643
644 if browser_config['sec_ch_ua'] and browser_config['sec_ch_ua'].strip():
645 context_options['extra_http_headers'] = {
646 'sec-ch-ua': browser_config['sec_ch_ua']
647 }
648
649 context = await browser.new_context(**context_options)
650 elif len(parts) == 3:
651 if self.debug:
652 logger.debug(f"Browser {index}: Creating context with proxy {proxy}")
653 context_options = {
654 "proxy": {"server": f"{proxy}"},
655 "user_agent": browser_config['useragent']
656 }
657
658 if browser_config['sec_ch_ua'] and browser_config['sec_ch_ua'].strip():
659 context_options['extra_http_headers'] = {
660 'sec-ch-ua': browser_config['sec_ch_ua']
661 }
662
663 context = await browser.new_context(**context_options)
664 else:
665 raise ValueError(f"Invalid proxy format: {proxy}")
666 else:
667 if self.debug:
668 logger.debug(f"Browser {index}: Creating context without proxy")
669 context_options = {"user_agent": browser_config['useragent']}
670
671 if browser_config['sec_ch_ua'] and browser_config['sec_ch_ua'].strip():
672 context_options['extra_http_headers'] = {
673 'sec-ch-ua': browser_config['sec_ch_ua']
674 }
675
676 context = await browser.new_context(**context_options)
677 else:
678 context_options = {"user_agent": browser_config['useragent']}
679
680 if browser_config['sec_ch_ua'] and browser_config['sec_ch_ua'].strip():
681 context_options['extra_http_headers'] = {
682 'sec-ch-ua': browser_config['sec_ch_ua']
683 }
684
685 context = await browser.new_context(**context_options)
686
687 page = await context.new_page()
688
689 await self._antishadow_inject(page)
690
691 await self._block_rendering(page)
692
693 await page.add_init_script("""
694 Object.defineProperty(navigator, 'webdriver', {
695 get: () => undefined,
696 });
697
698 window.chrome = {
699 runtime: {},
700 loadTimes: function() {},
701 csi: function() {},
702 };
703 """)
704
705 if self.browser_type in ['chromium', 'chrome', 'msedge']:
706 await page.set_viewport_size({"width": 500, "height": 100})
707 if self.debug:
708 logger.debug(f"Browser {index}: Set viewport size to 500x240")
709
710 start_time = time.time()
711
712 try:
713 if self.debug:
714 logger.debug(f"Browser {index}: Starting Turnstile solve for URL: {url} with Sitekey: {sitekey} | Action: {action} | Cdata: {cdata} | Proxy: {proxy}")
715 logger.debug(f"Browser {index}: Setting up optimized page loading with resource blocking")
716
717 if self.debug:
718 logger.debug(f"Browser {index}: Loading real website directly: {url}")
719
720 await page.goto(url, wait_until='domcontentloaded', timeout=30000)
721
722 await self._unblock_rendering(page)
723
724 # Сразу инъектируем виджет Turnstile на целевой сайт
725 if self.debug:
726 logger.debug(f"Browser {index}: Injecting Turnstile widget directly into target site")
727
728 await self._inject_captcha_directly(page, sitekey, action or '', cdata or '', index)
729
730 # Ждем время для загрузки и рендеринга виджета
731 await asyncio.sleep(3)
732
733 locator = page.locator('input[name="cf-turnstile-response"]')
734 max_attempts = 30
735 click_count = 0
736 max_clicks = 10
737
738 for attempt in range(max_attempts):
739 try:
740 # Безопасная проверка количества элементов с токеном
741 try:
742 count = await locator.count()
743 except Exception as e:
744 if self.debug:
745 logger.debug(f"Browser {index}: Locator count failed on attempt {attempt + 1}: {str(e)}")
746 count = 0
747
748 if count == 0:
749 if self.debug and attempt % 5 == 0:
750 logger.debug(f"Browser {index}: No token elements found on attempt {attempt + 1}")
751 elif count == 1:
752 # Если только один элемент, проверяем его токен
753 try:
754 token = await locator.input_value(timeout=500)
755 if token:
756 elapsed_time = round(time.time() - start_time, 3)
757 logger.success(f"Browser {index}: Successfully solved captcha - {COLORS.get('MAGENTA')}{token[:10]}{COLORS.get('RESET')} in {COLORS.get('GREEN')}{elapsed_time}{COLORS.get('RESET')} Seconds")
758 await save_result(task_id, "turnstile", {"value": token, "elapsed_time": elapsed_time})
759 return
760 except Exception as e:
761 if self.debug:
762 logger.debug(f"Browser {index}: Single token element check failed: {str(e)}")
763 else:
764 # Если несколько элементов, проверяем все по очереди
765 if self.debug:
766 logger.debug(f"Browser {index}: Found {count} token elements, checking all")
767
768 for i in range(count):
769 try:
770 element_token = await locator.nth(i).input_value(timeout=500)
771 if element_token:
772 elapsed_time = round(time.time() - start_time, 3)
773 logger.success(f"Browser {index}: Successfully solved captcha - {COLORS.get('MAGENTA')}{element_token[:10]}{COLORS.get('RESET')} in {COLORS.get('GREEN')}{elapsed_time}{COLORS.get('RESET')} Seconds")
774 await save_result(task_id, "turnstile", {"value": element_token, "elapsed_time": elapsed_time})
775 return
776 except Exception as e:
777 if self.debug:
778 logger.debug(f"Browser {index}: Token element {i} check failed: {str(e)}")
779 continue
780
781 if attempt > 2 and attempt % 3 == 0 and click_count < max_clicks:
782 click_success = await self._try_click_strategies(page, index)
783 click_count += 1
784 if click_success and self.debug:
785 logger.debug(f"Browser {index}: Click successful (click #{click_count}/{max_clicks})")
786 elif not click_success and self.debug:
787 logger.debug(f"Browser {index}: All click strategies failed on attempt {attempt + 1} (click #{click_count}/{max_clicks})")
788
789 # Адаптивное ожидание
790 wait_time = min(0.5 + (attempt * 0.05), 2.0)
791 await asyncio.sleep(wait_time)
792
793 if self.debug and attempt % 5 == 0:
794 logger.debug(f"Browser {index}: Attempt {attempt + 1}/{max_attempts} - Waiting for token (clicks: {click_count}/{max_clicks})")
795
796 except Exception as e:
797 if self.debug:
798 logger.debug(f"Browser {index}: Attempt {attempt + 1} error: {str(e)}")
799 continue
800
801 elapsed_time = round(time.time() - start_time, 3)
802 await save_result(task_id, "turnstile", {"value": "CAPTCHA_FAIL", "elapsed_time": elapsed_time})
803 if self.debug:
804 logger.error(f"Browser {index}: Error solving Turnstile in {COLORS.get('RED')}{elapsed_time}{COLORS.get('RESET')} Seconds")
805 except Exception as e:
806 elapsed_time = round(time.time() - start_time, 3)
807 await save_result(task_id, "turnstile", {"value": "CAPTCHA_FAIL", "elapsed_time": elapsed_time})
808 if self.debug:
809 logger.error(f"Browser {index}: Error solving Turnstile: {str(e)}")
810 finally:
811 if self.debug:
812 logger.debug(f"Browser {index}: Closing browser context and cleaning up")
813
814 try:
815 await context.close()
816 if self.debug:
817 logger.debug(f"Browser {index}: Context closed successfully")
818 except Exception as e:
819 if self.debug:
820 logger.warning(f"Browser {index}: Error closing context: {str(e)}")
821
822 try:
823 if hasattr(browser, 'is_connected') and browser.is_connected():
824 await self.browser_pool.put((index, browser, browser_config))
825 if self.debug:
826 logger.debug(f"Browser {index}: Browser returned to pool")
827 else:
828 if self.debug:
829 logger.warning(f"Browser {index}: Browser disconnected, not returning to pool")
830 except Exception as e:
831 if self.debug:
832 logger.warning(f"Browser {index}: Error returning browser to pool: {str(e)}")
833
834
835
836
837
838
839 async def process_turnstile(self):
840 """Handle the /turnstile endpoint requests."""
841 url = request.args.get('url')
842 sitekey = request.args.get('sitekey')
843 action = request.args.get('action')
844 cdata = request.args.get('cdata')
845
846 if not url or not sitekey:
847 return jsonify({
848 "errorId": 1,
849 "errorCode": "ERROR_WRONG_PAGEURL",
850 "errorDescription": "Both 'url' and 'sitekey' are required"
851 }), 200
852
853 task_id = str(uuid.uuid4())
854 await save_result(task_id, "turnstile", {
855 "status": "CAPTCHA_NOT_READY",
856 "createTime": int(time.time()),
857 "url": url,
858 "sitekey": sitekey,
859 "action": action,
860 "cdata": cdata
861 })
862
863 try:
864 asyncio.create_task(self._solve_turnstile(task_id=task_id, url=url, sitekey=sitekey, action=action, cdata=cdata))
865
866 if self.debug:
867 logger.debug(f"Request completed with taskid {task_id}.")
868 return jsonify({
869 "errorId": 0,
870 "taskId": task_id
871 }), 200
872 except Exception as e:
873 logger.error(f"Unexpected error processing request: {str(e)}")
874 return jsonify({
875 "errorId": 1,
876 "errorCode": "ERROR_UNKNOWN",
877 "errorDescription": str(e)
878 }), 200
879
880 async def get_result(self):
881 """Return solved data"""
882 task_id = request.args.get('id')
883
884 if not task_id:
885 return jsonify({
886 "errorId": 1,
887 "errorCode": "ERROR_WRONG_CAPTCHA_ID",
888 "errorDescription": "Invalid task ID/Request parameter"
889 }), 200
890
891 result = await load_result(task_id)
892 if not result:
893 return jsonify({
894 "errorId": 1,
895 "errorCode": "ERROR_CAPTCHA_UNSOLVABLE",
896 "errorDescription": "Task not found"
897 }), 200
898
899 if result == "CAPTCHA_NOT_READY" or (isinstance(result, dict) and result.get("status") == "CAPTCHA_NOT_READY"):
900 return jsonify({"status": "processing"}), 200
901
902 if isinstance(result, dict) and result.get("value") == "CAPTCHA_FAIL":
903 return jsonify({
904 "errorId": 1,
905 "errorCode": "ERROR_CAPTCHA_UNSOLVABLE",
906 "errorDescription": "Workers could not solve the Captcha"
907 }), 200
908
909 if isinstance(result, dict) and result.get("value") and result.get("value") != "CAPTCHA_FAIL":
910 return jsonify({
911 "errorId": 0,
912 "status": "ready",
913 "solution": {
914 "token": result["value"]
915 }
916 }), 200
917 else:
918 return jsonify({
919 "errorId": 1,
920 "errorCode": "ERROR_CAPTCHA_UNSOLVABLE",
921 "errorDescription": "Workers could not solve the Captcha"
922 }), 200
923
924
925
926 @staticmethod
927 async def index():
928 """Serve the API documentation page."""
929 return """
930 <!DOCTYPE html>
931 <html lang="en">
932 <head>
933 <meta charset="UTF-8">
934 <meta name="viewport" content="width=device-width, initial-scale=1.0">
935 <title>Turnstile Solver API</title>
936 <script src="https://cdn.tailwindcss.com"></script>
937 </head>
938 <body class="bg-gray-900 text-gray-200 min-h-screen flex items-center justify-center">
939 <div class="bg-gray-800 p-8 rounded-lg shadow-md max-w-2xl w-full border border-red-500">
940 <h1 class="text-3xl font-bold mb-6 text-center text-red-500">Welcome to Turnstile Solver API</h1>
941
942 <p class="mb-4 text-gray-300">To use the turnstile service, send a GET request to
943 <code class="bg-red-700 text-white px-2 py-1 rounded">/turnstile</code> with the following query parameters:</p>
944
945 <ul class="list-disc pl-6 mb-6 text-gray-300">
946 <li><strong>url</strong>: The URL where Turnstile is to be validated</li>
947 <li><strong>sitekey</strong>: The site key for Turnstile</li>
948 </ul>
949
950 <div class="bg-gray-700 p-4 rounded-lg mb-6 border border-red-500">
951 <p class="font-semibold mb-2 text-red-400">Example usage:</p>
952 <code class="text-sm break-all text-red-300">/turnstile?url=https://example.com&sitekey=sitekey</code>
953 </div>
954
955
956 <div class="bg-gray-700 p-4 rounded-lg mb-6">
957 <p class="text-gray-200 font-semibold mb-3">📢 Connect with Us</p>
958 <div class="space-y-2 text-sm">
959 <p class="text-gray-300">
960 📢 <strong>Channel:</strong>
961 <a href="https://t.me/D3_vin" class="text-red-300 hover:underline">https://t.me/D3_vin</a>
962 - Latest updates and releases
963 </p>
964 <p class="text-gray-300">
965 💬 <strong>Chat:</strong>
966 <a href="https://t.me/D3vin_chat" class="text-red-300 hover:underline">https://t.me/D3vin_chat</a>
967 - Community support and discussions
968 </p>
969 <p class="text-gray-300">
970 📁 <strong>GitHub:</strong>
971 <a href="https://github.com/D3-vin" class="text-red-300 hover:underline">https://github.com/D3-vin</a>
972 - Source code and development
973 </p>
974 </div>
975 </div>
976 </div>
977 </body>
978 </html>
979 """
980
981
982def parse_args():
983 """Parse command-line arguments."""
984 parser = argparse.ArgumentParser(description="Turnstile API Server")
985
986 parser.add_argument('--no-headless', action='store_true', help='Run the browser with GUI (disable headless mode). By default, headless mode is enabled.')
987 parser.add_argument('--useragent', type=str, help='User-Agent string (if not specified, random configuration is used)')
988 parser.add_argument('--debug', action='store_true', help='Enable or disable debug mode for additional logging and troubleshooting information (default: False)')
989 parser.add_argument('--browser_type', type=str, default='chromium', help='Specify the browser type for the solver. Supported options: chromium, chrome, msedge, camoufox (default: chromium)')
990 parser.add_argument('--thread', type=int, default=4, help='Set the number of browser threads to use for multi-threaded mode. Increasing this will speed up execution but requires more resources (default: 1)')
991 parser.add_argument('--proxy', action='store_true', help='Enable proxy support for the solver (Default: False)')
992 parser.add_argument('--random', action='store_true', help='Use random User-Agent and Sec-CH-UA configuration from pool')
993 parser.add_argument('--browser', type=str, help='Specify browser name to use (e.g., chrome, firefox)')
994 parser.add_argument('--version', type=str, help='Specify browser version to use (e.g., 139, 141)')
995 parser.add_argument('--host', type=str, default='0.0.0.0', help='Specify the IP address where the API solver runs. (Default: 127.0.0.1)')
996 parser.add_argument('--port', type=str, default='5072', help='Set the port for the API solver to listen on. (Default: 5072)')
997 return parser.parse_args()
998
999
1000def create_app(headless: bool, useragent: str, debug: bool, browser_type: str, thread: int, proxy_support: bool, use_random_config: bool, browser_name: str, browser_version: str) -> Quart:
1001 server = TurnstileAPIServer(headless=headless, useragent=useragent, debug=debug, browser_type=browser_type, thread=thread, proxy_support=proxy_support, use_random_config=use_random_config, browser_name=browser_name, browser_version=browser_version)
1002 return server.app
1003
1004
1005if __name__ == '__main__':
1006 args = parse_args()
1007 browser_types = [
1008 'chromium',
1009 'chrome',
1010 'msedge',
1011 'camoufox',
1012 ]
1013 if args.browser_type not in browser_types:
1014 logger.error(f"Unknown browser type: {COLORS.get('RED')}{args.browser_type}{COLORS.get('RESET')} Available browser types: {browser_types}")
1015 else:
1016 app = create_app(
1017 headless=not args.no_headless,
1018 debug=args.debug,
1019 useragent=args.useragent,
1020 browser_type=args.browser_type,
1021 thread=args.thread,
1022 proxy_support=args.proxy,
1023 use_random_config=args.random,
1024 browser_name=args.browser,
1025 browser_version=args.version
1026 )
1027 app.run(host=args.host, port=int(args.port))
1028 