CoolFace
Apppublic

spfnas/grok

sourceHugging Facemitupdated 7mo agoView on Hugging Face
0likes
api_solver.py1028 linesDownload Raw Back to root
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