llzai commited on
Commit
38a5f07
·
verified ·
1 Parent(s): 3ec4c3c

Upload 6 files

Browse files
Files changed (6) hide show
  1. README.md +257 -10
  2. api_solver.py +956 -0
  3. browser_configs.py +172 -0
  4. db_results.py +134 -0
  5. proxies.txt +0 -0
  6. requirements.txt +8 -0
README.md CHANGED
@@ -1,10 +1,257 @@
1
- ---
2
- title: Turnstile
3
- emoji: 🏆
4
- colorFrom: red
5
- colorTo: yellow
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Cloudflare - Turnstile Solver NEW
2
+
3
+ ## 📢 Connect with Us
4
+
5
+ - **📢 Channel**: [https://t.me/D3_vin](https://t.me/D3_vin) - Latest updates and releases
6
+ - **💬 Chat**: [https://t.me/D3vin_chat](https://t.me/D3vin_chat) - Community support and discussions
7
+ - **📁 GitHub**: [https://github.com/D3-vin](https://github.com/D3-vin) - Source code and development
8
+
9
+ ![Python](https://img.shields.io/badge/Python-3.6+-blue)
10
+ ![Platform](https://img.shields.io/badge/Platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey)
11
+ ![License](https://img.shields.io/badge/License-Educational%20Use-green)
12
+
13
+
14
+ ❤️ Support the Project
15
+ If you find this collection valuable and appreciate the effort involved in obtaining and sharing these insights, please consider supporting the project. Your contribution helps keep this resource updated and allows for further exploration.
16
+
17
+ You can show your support via:
18
+
19
+ Cryptocurrency:
20
+ - **EVM:** 0xeba21af63e707ce84b76a87d0ba82140048c057e (ETH,BNB,etc)
21
+ - **TRON:** TEfECnyz5G1EkFrUqnbFcWLVdLvAgW9Raa
22
+ - **TON:** UQCJ7KC2zxV_zKwLahaHf9jxy0vsWRcvQFie_FUBJW-9LcEW
23
+ - **BTC:** bc1qdag98y5yahs6wf7rsfeh4cadsjfzmn5ngpjrcf
24
+ - **SOL:** EwXXR4VqmWSNz1sjhZ8qcQ882i4URwAwhixSPEbDzyv6
25
+ - **SUI:** 0x76da9b74c61508fbbd0b3e1989446e036b0622f252dd8d07c3fce759b239b47d
26
+
27
+
28
+ 🙏 Thank you for your support!
29
+
30
+ A Python-based Turnstile solver using the patchright and camoufox libraries, featuring multi-threaded execution, API integration, and support for different browsers. It solves CAPTCHAs quickly and efficiently, with customizable configurations and detailed logging.
31
+
32
+ ## 🚀 Features
33
+
34
+ - **Multi-threaded execution** - Solve multiple CAPTCHAs simultaneously
35
+ - **Multiple browser support** - Chromium, Chrome, Edge, and Camoufox
36
+ - **Proxy support** - Use proxies from proxies.txt file
37
+ - **Random browser configurations** - Rotate User-Agent and Sec-CH-UA headers
38
+ - **Detailed logging** - Comprehensive debug information
39
+ - **REST API** - Easy integration with other applications
40
+ - **Database storage** - SQLite database for result persistence
41
+ - **Automatic cleanup** - Old results are automatically cleaned up
42
+ - **Image blocking** - Optimized performance by blocking unnecessary images
43
+
44
+ ## 🔧 Configuration
45
+
46
+ ### Browser Configurations
47
+
48
+ The solver supports various browser configurations with realistic User-Agent strings and Sec-CH-UA headers:
49
+
50
+ - **Chrome** (versions 136-139)
51
+ - **Edge** (versions 137-139)
52
+ - **Avast** (versions 137-138)
53
+ - **Brave** (versions 137-139)
54
+
55
+ ### Proxy Format
56
+
57
+ Add proxies to `proxies.txt` in the following formats:
58
+
59
+ ```
60
+ ip:port
61
+ ip:port:username:password
62
+ scheme://ip:port
63
+ scheme://username:password@ip:port
64
+ ```
65
+
66
+ ## ❗ Disclaimers
67
+
68
+ I am not responsible for anything that may happen, such as API Blocking, IP ban, etc.
69
+ This was a quick project that was made for fun and personal use if you want to see further updates, star the repo & create an "issue" here
70
+
71
+ ## ⚙️ Installation Instructions
72
+
73
+ Ensure Python 3.8+ is installed on your system.
74
+
75
+ ### 1. Create a Python virtual environment:
76
+
77
+ ```bash
78
+ python -m venv venv
79
+ ```
80
+
81
+ ### 2. Activate the virtual environment:
82
+
83
+ **On Windows:**
84
+ ```bash
85
+ venv\Scripts\activate
86
+ ```
87
+
88
+ **On macOS/Linux:**
89
+ ```bash
90
+ source venv/bin/activate
91
+ ```
92
+
93
+ ### 3. Install required dependencies:
94
+
95
+ ```bash
96
+ pip install -r requirements.txt
97
+ ```
98
+
99
+ ### 4. Select the browser to install:
100
+
101
+ You can choose between Chromium, Chrome, Edge or Camoufox:
102
+
103
+ **To install Chromium:**
104
+ ```bash
105
+ python -m patchright install chromium
106
+ ```
107
+
108
+ **To install Chrome:**
109
+ - **On macOS/Windows:** [Click here](https://www.google.com/chrome/)
110
+ - **On Linux (Debian/Ubuntu-based):**
111
+ ```bash
112
+ apt update
113
+ wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
114
+ apt install -y ./google-chrome-stable_current_amd64.deb
115
+ apt -f install -y # Fix dependencies if needed
116
+ rm ./google-chrome-stable_current_amd64.deb
117
+ ```
118
+
119
+ **To install Edge:**
120
+ ```bash
121
+ python -m patchright install msedge
122
+ ```
123
+
124
+ **To install Camoufox:**
125
+ ```bash
126
+ python -m camoufox fetch
127
+ ```
128
+
129
+ ### 5. Start testing:
130
+
131
+ Run the script (Check [🔧 Command line arguments](#-command-line-arguments) for better setup):
132
+
133
+ ```bash
134
+ python api_solver.py
135
+ ```
136
+
137
+ ## 🔧 Command line arguments
138
+
139
+ | Parameter | Default | Type | Description |
140
+ |-----------|---------|------|-------------|
141
+ | `--no-headless` | False | boolean | Runs the browser with GUI (disable headless mode). By default, headless mode is enabled. |
142
+ | `--useragent` | None | string | Specifies a custom User-Agent string for the browser. (No need to set if camoufox used) |
143
+ | `--debug` | False | boolean | Enables or disables debug mode for additional logging and troubleshooting. |
144
+ | `--browser_type` | chromium | string | Specify the browser type for the solver. Supported options: chromium, chrome, msedge, camoufox |
145
+ | `--thread` | 4 | integer | Sets the number of browser threads to use in multi-threaded mode. |
146
+ | `--host` | 0.0.0.0 | string | Specifies the IP address the API solver runs on. |
147
+ | `--port` | 6080 | integer | Sets the port the API solver listens on. |
148
+ | `--proxy` | False | boolean | Select a random proxy from proxies.txt for solving captchas |
149
+ | `--random` | False | boolean | Use random User-Agent and Sec-CH-UA configuration from pool |
150
+ | `--browser` | None | string | Specify browser name to use (e.g., chrome, firefox) |
151
+ | `--version` | None | string | Specify browser version to use (e.g., 139, 141) |
152
+
153
+ ## 📡 API Documentation
154
+
155
+ ### Solve turnstile
156
+
157
+ ```
158
+ GET /turnstile?url=https://example.com&sitekey=0x4AAAAAAA
159
+ ```
160
+
161
+ **Request Parameters:**
162
+
163
+ | Parameter | Type | Description | Required |
164
+ |-----------|------|-------------|----------|
165
+ | `url` | string | The target URL containing the CAPTCHA. (e.g., https://example.com) | Yes |
166
+ | `sitekey` | string | The site key for the CAPTCHA to be solved. (e.g., 0x4AAAAAAA) | Yes |
167
+ | `action` | string | Action to trigger during CAPTCHA solving, e.g., login | No |
168
+ | `cdata` | string | Custom data that can be used for additional CAPTCHA parameters. | No |
169
+
170
+ **Response:**
171
+
172
+ If the request is successfully received, the server will respond with a task_id for the CAPTCHA solving task:
173
+
174
+ ```json
175
+ {
176
+ "task_id": "d2cbb257-9c37-4f9c-9bc7-1eaee72d96a8"
177
+ }
178
+ ```
179
+
180
+ ### Get Result
181
+
182
+ ```
183
+ GET /result?id=f0dbe75b-fa76-41ad-89aa-4d3a392040af
184
+ ```
185
+
186
+ **Request Parameters:**
187
+
188
+ | Parameter | Type | Description | Required |
189
+ |-----------|------|-------------|----------|
190
+ | `id` | string | The unique task ID returned from the /turnstile request. | Yes |
191
+
192
+ **Response:**
193
+
194
+ If the CAPTCHA is solved successfully, the server will respond with the following information:
195
+
196
+ ```json
197
+ {
198
+ "status": "ready",
199
+ "value": "0.KBtT-r",
200
+ "elapsed_time": 7.625
201
+ }
202
+ ```
203
+
204
+ **Error Responses:**
205
+
206
+ ```json
207
+ {
208
+ "status": "processing"
209
+ }
210
+ ```
211
+
212
+ ```json
213
+ {
214
+ "status": "fail",
215
+ "value": "CAPTCHA_FAIL",
216
+ "elapsed_time": 30.0
217
+ }
218
+ ```
219
+
220
+
221
+
222
+ ## 🐛 Troubleshooting
223
+
224
+ ### Common Issues
225
+
226
+ 1. **Browser not found**: Make sure you've installed the required browser using the installation instructions
227
+ 2. **Permission denied**: Run with appropriate permissions or check file permissions
228
+ 3. **Port already in use**: Change the port using `--port` argument
229
+ 4. **Proxy connection failed**: Check proxy format and availability
230
+
231
+ ### Debug Mode
232
+
233
+ Enable debug mode for detailed logging:
234
+
235
+ ```bash
236
+ python api_solver.py --debug
237
+ ```
238
+
239
+ ## 📊 Performance
240
+
241
+ - **Average solving time**: 5-15 seconds
242
+ - **Success rate**: 95%+ (depending on site complexity)
243
+ - **Memory usage**: ~50-100MB per browser thread
244
+ - **CPU usage**: Moderate (depends on thread count)
245
+
246
+ ## 🤝 Contributing
247
+
248
+ 1. Fork the repository
249
+ 2. Create a feature branch
250
+ 3. Make your changes
251
+ 4. Add tests if applicable
252
+ 5. Submit a pull request
253
+
254
+ ## 📄 License
255
+
256
+ This project is for educational purposes only. Use at your own risk.
257
+
api_solver.py ADDED
@@ -0,0 +1,956 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import time
4
+ import uuid
5
+ import random
6
+ import logging
7
+ import asyncio
8
+ from typing import Optional, Union
9
+ import argparse
10
+ from quart import Quart, request, jsonify
11
+ from camoufox.async_api import AsyncCamoufox
12
+ from patchright.async_api import async_playwright
13
+ from db_results import init_db, save_result, load_result, cleanup_old_results
14
+ from browser_configs import browser_config
15
+ from rich.console import Console
16
+ from rich.panel import Panel
17
+ from rich.text import Text
18
+ from rich.align import Align
19
+ from rich import box
20
+
21
+
22
+
23
+ COLORS = {
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
+
33
+ class 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
+
55
+ logging.setLoggerClass(CustomLogger)
56
+ logger = logging.getLogger("TurnstileAPIServer")
57
+ logger.setLevel(logging.DEBUG)
58
+ handler = logging.StreamHandler(sys.stdout)
59
+ logger.addHandler(handler)
60
+
61
+
62
+ class 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.2b", 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
+ if config['useragent']:
206
+ browser_args.append(f"--user-agent={config['useragent']}")
207
+
208
+ browser = None
209
+ if self.browser_type in ['chromium', 'chrome', 'msedge'] and playwright:
210
+ browser = await playwright.chromium.launch(
211
+ channel=self.browser_type,
212
+ headless=self.headless,
213
+ args=browser_args
214
+ )
215
+ elif self.browser_type == "camoufox" and camoufox:
216
+ browser = await camoufox.start()
217
+
218
+ if browser:
219
+ await self.browser_pool.put((i+1, browser, config))
220
+
221
+ if self.debug:
222
+ logger.info(f"Browser {i + 1} initialized successfully with {config['browser_name']} {config['browser_version']}")
223
+
224
+ logger.info(f"Browser pool initialized with {self.browser_pool.qsize()} browsers")
225
+
226
+ if self.use_random_config:
227
+ logger.info(f"Each browser in pool received random configuration")
228
+ elif self.browser_name and self.browser_version:
229
+ logger.info(f"All browsers using configuration: {self.browser_name} {self.browser_version}")
230
+ else:
231
+ logger.info("Using custom configuration")
232
+
233
+ if self.debug:
234
+ for i, config in enumerate(browser_configs):
235
+ logger.debug(f"Browser {i+1} config: {config['browser_name']} {config['browser_version']}")
236
+ logger.debug(f"Browser {i+1} User-Agent: {config['useragent']}")
237
+ logger.debug(f"Browser {i+1} Sec-CH-UA: {config['sec_ch_ua']}")
238
+
239
+ async def _periodic_cleanup(self):
240
+ """Periodic cleanup of old results every hour"""
241
+ while True:
242
+ try:
243
+ await asyncio.sleep(3600)
244
+ deleted_count = await cleanup_old_results(days_old=7)
245
+ if deleted_count > 0:
246
+ logger.info(f"Cleaned up {deleted_count} old results")
247
+ except Exception as e:
248
+ logger.error(f"Error during periodic cleanup: {e}")
249
+
250
+ async def _antishadow_inject(self, page):
251
+ await page.add_init_script("""
252
+ (function() {
253
+ const originalAttachShadow = Element.prototype.attachShadow;
254
+ Element.prototype.attachShadow = function(init) {
255
+ const shadow = originalAttachShadow.call(this, init);
256
+ if (init.mode === 'closed') {
257
+ window.__lastClosedShadowRoot = shadow;
258
+ }
259
+ return shadow;
260
+ };
261
+ })();
262
+ """)
263
+
264
+
265
+
266
+ async def _optimized_route_handler(self, route):
267
+ """Оптимизированный обработчик маршрутов для экономии ресурсов."""
268
+ url = route.request.url
269
+ resource_type = route.request.resource_type
270
+
271
+ allowed_types = {'document', 'script', 'xhr', 'fetch'}
272
+
273
+ allowed_domains = [
274
+ 'challenges.cloudflare.com',
275
+ 'static.cloudflareinsights.com',
276
+ 'cloudflare.com'
277
+ ]
278
+
279
+ if resource_type in allowed_types:
280
+ await route.continue_()
281
+ elif any(domain in url for domain in allowed_domains):
282
+ await route.continue_()
283
+ else:
284
+ await route.abort()
285
+
286
+ async def _block_rendering(self, page):
287
+ """Блокировка рендеринга для экономии ресурсов"""
288
+ await page.route("**/*", self._optimized_route_handler)
289
+
290
+ async def _unblock_rendering(self, page):
291
+ """Разблокировка рендеринга"""
292
+ await page.unroute("**/*", self._optimized_route_handler)
293
+
294
+ async def _find_turnstile_elements(self, page, index: int):
295
+ """Умная проверка всех возможных Turnstile элементов"""
296
+ selectors = [
297
+ '.cf-turnstile',
298
+ '[data-sitekey]',
299
+ 'iframe[src*="turnstile"]',
300
+ 'iframe[title*="widget"]',
301
+ 'div[id*="turnstile"]',
302
+ 'div[class*="turnstile"]'
303
+ ]
304
+
305
+ elements = []
306
+ for selector in selectors:
307
+ try:
308
+ # Безопасная проверка count()
309
+ try:
310
+ count = await page.locator(selector).count()
311
+ except Exception:
312
+ # Если count() дает ошибку, пропускаем этот селектор
313
+ continue
314
+
315
+ if count > 0:
316
+ elements.append((selector, count))
317
+ if self.debug:
318
+ logger.debug(f"Browser {index}: Found {count} elements with selector '{selector}'")
319
+ except Exception as e:
320
+ if self.debug:
321
+ logger.debug(f"Browser {index}: Selector '{selector}' failed: {str(e)}")
322
+ continue
323
+
324
+ return elements
325
+
326
+ async def _find_and_click_checkbox(self, page, index: int):
327
+ """Найти и кликнуть по чекбоксу Turnstile CAPTCHA внутри iframe"""
328
+ try:
329
+ # Пробуем разные селекторы iframe с защитой от ошибок
330
+ iframe_selectors = [
331
+ 'iframe[src*="challenges.cloudflare.com"]',
332
+ 'iframe[src*="turnstile"]',
333
+ 'iframe[title*="widget"]'
334
+ ]
335
+
336
+ iframe_locator = None
337
+ for selector in iframe_selectors:
338
+ try:
339
+ test_locator = page.locator(selector).first
340
+ # Безопасная проверка count для iframe
341
+ try:
342
+ iframe_count = await test_locator.count()
343
+ except Exception:
344
+ iframe_count = 0
345
+
346
+ if iframe_count > 0:
347
+ iframe_locator = test_locator
348
+ if self.debug:
349
+ logger.debug(f"Browser {index}: Found Turnstile iframe with selector: {selector}")
350
+ break
351
+ except Exception as e:
352
+ if self.debug:
353
+ logger.debug(f"Browser {index}: Iframe selector '{selector}' failed: {str(e)}")
354
+ continue
355
+
356
+ if iframe_locator:
357
+ try:
358
+ # Получаем frame из iframe
359
+ iframe_element = await iframe_locator.element_handle()
360
+ frame = await iframe_element.content_frame()
361
+
362
+ if frame:
363
+ # Ищем чекбокс внутри iframe
364
+ checkbox_selectors = [
365
+ 'input[type="checkbox"]',
366
+ '.cb-lb input[type="checkbox"]',
367
+ 'label input[type="checkbox"]'
368
+ ]
369
+
370
+ for selector in checkbox_selectors:
371
+ try:
372
+ # Полностью избегаем locator.count() в iframe - используем альтернативный подход
373
+ try:
374
+ # Пробуем кликнуть напрямую без count проверки
375
+ checkbox = frame.locator(selector).first
376
+ await checkbox.click(timeout=2000)
377
+ if self.debug:
378
+ logger.debug(f"Browser {index}: Successfully clicked checkbox in iframe with selector '{selector}'")
379
+ return True
380
+ except Exception as click_e:
381
+ # Если прямой клик не сработал, записываем в debug но не падаем
382
+ if self.debug:
383
+ logger.debug(f"Browser {index}: Direct checkbox click failed for '{selector}': {str(click_e)}")
384
+ continue
385
+ except Exception as e:
386
+ if self.debug:
387
+ logger.debug(f"Browser {index}: Iframe checkbox selector '{selector}' failed: {str(e)}")
388
+ continue
389
+
390
+ # Если нашли iframe, но не смогли кликнуть чекбокс, пробуем клик по iframe
391
+ try:
392
+ if self.debug:
393
+ logger.debug(f"Browser {index}: Trying to click iframe directly as fallback")
394
+ await iframe_locator.click(timeout=1000)
395
+ return True
396
+ except Exception as e:
397
+ if self.debug:
398
+ logger.debug(f"Browser {index}: Iframe direct click failed: {str(e)}")
399
+
400
+ except Exception as e:
401
+ if self.debug:
402
+ logger.debug(f"Browser {index}: Failed to access iframe content: {str(e)}")
403
+
404
+ except Exception as e:
405
+ if self.debug:
406
+ logger.debug(f"Browser {index}: General iframe search failed: {str(e)}")
407
+
408
+ return False
409
+
410
+ async def _try_click_strategies(self, page, index: int):
411
+ strategies = [
412
+ ('checkbox_click', lambda: self._find_and_click_checkbox(page, index)),
413
+ ('direct_widget', lambda: self._safe_click(page, '.cf-turnstile', index)),
414
+ ('iframe_click', lambda: self._safe_click(page, 'iframe[src*="turnstile"]', index)),
415
+ ('js_click', lambda: page.evaluate("document.querySelector('.cf-turnstile')?.click()")),
416
+ ('sitekey_attr', lambda: self._safe_click(page, '[data-sitekey]', index)),
417
+ ('any_turnstile', lambda: self._safe_click(page, '*[class*="turnstile"]', index)),
418
+ ('xpath_click', lambda: self._safe_click(page, "//div[@class='cf-turnstile']", index))
419
+ ]
420
+
421
+ for strategy_name, strategy_func in strategies:
422
+ try:
423
+ result = await strategy_func()
424
+ if result is True or result is None: # None означает успех для большинства стратегий
425
+ if self.debug:
426
+ logger.debug(f"Browser {index}: Click strategy '{strategy_name}' succeeded")
427
+ return True
428
+ except Exception as e:
429
+ if self.debug:
430
+ logger.debug(f"Browser {index}: Click strategy '{strategy_name}' failed: {str(e)}")
431
+ continue
432
+
433
+ return False
434
+
435
+ async def _safe_click(self, page, selector: str, index: int):
436
+ """Полностью безопасный клик с максимальной защитой от ошибок"""
437
+ try:
438
+ # Пробуем кликнуть напрямую без count() проверки
439
+ locator = page.locator(selector).first
440
+ await locator.click(timeout=1000)
441
+ return True
442
+ except Exception as e:
443
+ # Логируем ошибку только в debug режиме
444
+ if self.debug and "Can't query n-th element" not in str(e):
445
+ logger.debug(f"Browser {index}: Safe click failed for '{selector}': {str(e)}")
446
+ return False
447
+
448
+ async def _load_captcha_overlay(self, page, websiteKey: str, action: str = '', index: int = 0):
449
+ script = f"""
450
+ const existing = document.querySelector('#captcha-overlay');
451
+ if (existing) existing.remove();
452
+
453
+ const overlay = document.createElement('div');
454
+ overlay.id = 'captcha-overlay';
455
+ overlay.style.position = 'absolute';
456
+ overlay.style.top = '0';
457
+ overlay.style.left = '0';
458
+ overlay.style.width = '100vw';
459
+ overlay.style.height = '100vh';
460
+ overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
461
+ overlay.style.display = 'block';
462
+ overlay.style.justifyContent = 'center';
463
+ overlay.style.alignItems = 'center';
464
+ overlay.style.zIndex = '1000';
465
+
466
+ const captchaDiv = document.createElement('div');
467
+ captchaDiv.className = 'cf-turnstile';
468
+ captchaDiv.setAttribute('data-sitekey', '{websiteKey}');
469
+ captchaDiv.setAttribute('data-callback', 'onCaptchaSuccess');
470
+ captchaDiv.setAttribute('data-action', '{action}');
471
+
472
+ overlay.appendChild(captchaDiv);
473
+ document.body.appendChild(overlay);
474
+
475
+ const script = document.createElement('script');
476
+ script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js';
477
+ script.async = true;
478
+ script.defer = true;
479
+ document.head.appendChild(script);
480
+ """
481
+
482
+ await page.evaluate(script)
483
+ if self.debug:
484
+ logger.debug(f"Browser {index}: Created CAPTCHA overlay with sitekey: {websiteKey}")
485
+
486
+ async def _solve_turnstile(self, task_id: str, url: str, sitekey: str, action: Optional[str] = None, cdata: Optional[str] = None):
487
+ """Solve the Turnstile challenge."""
488
+ proxy = None
489
+
490
+ index, browser, browser_config = await self.browser_pool.get()
491
+
492
+ try:
493
+ if hasattr(browser, 'is_connected') and not browser.is_connected():
494
+ if self.debug:
495
+ logger.warning(f"Browser {index}: Browser disconnected, skipping")
496
+ await self.browser_pool.put((index, browser, browser_config))
497
+ await save_result(task_id, "turnstile", {"value": "CAPTCHA_FAIL", "elapsed_time": 0})
498
+ return
499
+ except Exception as e:
500
+ if self.debug:
501
+ logger.warning(f"Browser {index}: Cannot check browser state: {str(e)}")
502
+
503
+ if self.proxy_support:
504
+ proxy_file_path = os.path.join(os.getcwd(), "proxies.txt")
505
+
506
+ try:
507
+ with open(proxy_file_path) as proxy_file:
508
+ proxies = [line.strip() for line in proxy_file if line.strip()]
509
+
510
+ proxy = random.choice(proxies) if proxies else None
511
+
512
+ if self.debug and proxy:
513
+ logger.debug(f"Browser {index}: Selected proxy: {proxy}")
514
+ elif self.debug and not proxy:
515
+ logger.debug(f"Browser {index}: No proxies available")
516
+
517
+ except FileNotFoundError:
518
+ logger.warning(f"Proxy file not found: {proxy_file_path}")
519
+ proxy = None
520
+ except Exception as e:
521
+ logger.error(f"Error reading proxy file: {str(e)}")
522
+ proxy = None
523
+
524
+ if proxy:
525
+ if '@' in proxy:
526
+ try:
527
+ scheme_part, auth_part = proxy.split('://')
528
+ auth, address = auth_part.split('@')
529
+ username, password = auth.split(':')
530
+ ip, port = address.split(':')
531
+ if self.debug:
532
+ logger.debug(f"Browser {index}: Creating context with proxy {scheme_part}://{ip}:{port} (auth: {username}:***)")
533
+ context_options = {
534
+ "proxy": {
535
+ "server": f"{scheme_part}://{ip}:{port}",
536
+ "username": username,
537
+ "password": password
538
+ },
539
+ "user_agent": browser_config['useragent']
540
+ }
541
+
542
+ if browser_config['sec_ch_ua'] and browser_config['sec_ch_ua'].strip():
543
+ context_options['extra_http_headers'] = {
544
+ 'sec-ch-ua': browser_config['sec_ch_ua']
545
+ }
546
+
547
+ context = await browser.new_context(**context_options)
548
+ except ValueError:
549
+ raise ValueError(f"Invalid proxy format: {proxy}")
550
+ else:
551
+ parts = proxy.split(':')
552
+ if len(parts) == 5:
553
+ proxy_scheme, proxy_ip, proxy_port, proxy_user, proxy_pass = parts
554
+ if self.debug:
555
+ logger.debug(f"Browser {index}: Creating context with proxy {proxy_scheme}://{proxy_ip}:{proxy_port} (auth: {proxy_user}:***)")
556
+ context_options = {
557
+ "proxy": {
558
+ "server": f"{proxy_scheme}://{proxy_ip}:{proxy_port}",
559
+ "username": proxy_user,
560
+ "password": proxy_pass
561
+ },
562
+ "user_agent": browser_config['useragent']
563
+ }
564
+
565
+ if browser_config['sec_ch_ua'] and browser_config['sec_ch_ua'].strip():
566
+ context_options['extra_http_headers'] = {
567
+ 'sec-ch-ua': browser_config['sec_ch_ua']
568
+ }
569
+
570
+ context = await browser.new_context(**context_options)
571
+ elif len(parts) == 3:
572
+ if self.debug:
573
+ logger.debug(f"Browser {index}: Creating context with proxy {proxy}")
574
+ context_options = {
575
+ "proxy": {"server": f"{proxy}"},
576
+ "user_agent": browser_config['useragent']
577
+ }
578
+
579
+ if browser_config['sec_ch_ua'] and browser_config['sec_ch_ua'].strip():
580
+ context_options['extra_http_headers'] = {
581
+ 'sec-ch-ua': browser_config['sec_ch_ua']
582
+ }
583
+
584
+ context = await browser.new_context(**context_options)
585
+ else:
586
+ raise ValueError(f"Invalid proxy format: {proxy}")
587
+ else:
588
+ if self.debug:
589
+ logger.debug(f"Browser {index}: Creating context without proxy")
590
+ context_options = {"user_agent": browser_config['useragent']}
591
+
592
+ if browser_config['sec_ch_ua'] and browser_config['sec_ch_ua'].strip():
593
+ context_options['extra_http_headers'] = {
594
+ 'sec-ch-ua': browser_config['sec_ch_ua']
595
+ }
596
+
597
+ context = await browser.new_context(**context_options)
598
+ else:
599
+ context_options = {"user_agent": browser_config['useragent']}
600
+
601
+ if browser_config['sec_ch_ua'] and browser_config['sec_ch_ua'].strip():
602
+ context_options['extra_http_headers'] = {
603
+ 'sec-ch-ua': browser_config['sec_ch_ua']
604
+ }
605
+
606
+ context = await browser.new_context(**context_options)
607
+
608
+ page = await context.new_page()
609
+
610
+ #await self._antishadow_inject(page)
611
+
612
+ await self._block_rendering(page)
613
+
614
+ #await page.add_init_script("""
615
+ #Object.defineProperty(navigator, 'webdriver', {
616
+ # get: () => undefined,
617
+ #});
618
+
619
+ #window.chrome = {
620
+ # runtime: {},
621
+ # loadTimes: function() {},
622
+ # csi: function() {},
623
+ #};
624
+ ##""")
625
+
626
+ if self.browser_type in ['chromium', 'chrome', 'msedge']:
627
+ await page.set_viewport_size({"width": 500, "height": 100})
628
+ if self.debug:
629
+ logger.debug(f"Browser {index}: Set viewport size to 500x240")
630
+
631
+ start_time = time.time()
632
+
633
+ try:
634
+ if self.debug:
635
+ logger.debug(f"Browser {index}: Starting Turnstile solve for URL: {url} with Sitekey: {sitekey} | Action: {action} | Cdata: {cdata} | Proxy: {proxy}")
636
+ logger.debug(f"Browser {index}: Setting up optimized page loading with resource blocking")
637
+
638
+ if self.debug:
639
+ logger.debug(f"Browser {index}: Loading real website directly: {url}")
640
+
641
+ await page.goto(url, wait_until='domcontentloaded', timeout=30000)
642
+
643
+ await self._unblock_rendering(page)
644
+
645
+ # Ждем немного времени для загрузки CAPTCHA
646
+ await asyncio.sleep(3)
647
+
648
+ locator = page.locator('input[name="cf-turnstile-response"]')
649
+ max_attempts = 20
650
+
651
+ for attempt in range(max_attempts):
652
+ try:
653
+ # Безопасная проверка количества элементов с токеном
654
+ try:
655
+ count = await locator.count()
656
+ except Exception as e:
657
+ if self.debug:
658
+ logger.debug(f"Browser {index}: Locator count failed on attempt {attempt + 1}: {str(e)}")
659
+ count = 0
660
+
661
+ if count == 0:
662
+ if self.debug:
663
+ logger.debug(f"Browser {index}: No token elements found on attempt {attempt + 1}")
664
+ elif count == 1:
665
+ # Если только один элемент, проверяем его токен
666
+ try:
667
+ token = await locator.input_value(timeout=500)
668
+ if token:
669
+ elapsed_time = round(time.time() - start_time, 3)
670
+ 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")
671
+ await save_result(task_id, "turnstile", {"value": token, "elapsed_time": elapsed_time})
672
+ return
673
+ except Exception as e:
674
+ if self.debug:
675
+ logger.debug(f"Browser {index}: Single token element check failed: {str(e)}")
676
+ else:
677
+ # Если несколько элементов, проверяем все по очереди
678
+ if self.debug:
679
+ logger.debug(f"Browser {index}: Found {count} token elements, checking all")
680
+
681
+ for i in range(count):
682
+ try:
683
+ element_token = await locator.nth(i).input_value(timeout=500)
684
+ if element_token:
685
+ elapsed_time = round(time.time() - start_time, 3)
686
+ 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")
687
+ await save_result(task_id, "turnstile", {"value": element_token, "elapsed_time": elapsed_time})
688
+ return
689
+ except Exception as e:
690
+ if self.debug:
691
+ logger.debug(f"Browser {index}: Token element {i} check failed: {str(e)}")
692
+ continue
693
+
694
+ # Клик стратегии только каждые 3 попытки и не сразу
695
+ if attempt > 2 and attempt % 3 == 0:
696
+ click_success = await self._try_click_strategies(page, index)
697
+ if not click_success and self.debug:
698
+ logger.debug(f"Browser {index}: All click strategies failed on attempt {attempt + 1}")
699
+
700
+ # Fallback overlay на 10 попытке если токена все еще нет
701
+ if attempt == 10:
702
+ try:
703
+ # Безопасная проверка count для overlay
704
+ try:
705
+ current_count = await locator.count()
706
+ except Exception:
707
+ current_count = 0
708
+
709
+ if current_count == 0:
710
+ if self.debug:
711
+ logger.debug(f"Browser {index}: Creating overlay as fallback strategy")
712
+ await self._load_captcha_overlay(page, sitekey, action or '', index)
713
+ await asyncio.sleep(2)
714
+ except Exception as e:
715
+ if self.debug:
716
+ logger.debug(f"Browser {index}: Fallback overlay creation failed: {str(e)}")
717
+
718
+ # Адаптивное ожидание
719
+ wait_time = min(0.5 + (attempt * 0.05), 2.0)
720
+ await asyncio.sleep(wait_time)
721
+
722
+ if self.debug and attempt % 5 == 0:
723
+ logger.debug(f"Browser {index}: Attempt {attempt + 1}/{max_attempts} - No valid token yet")
724
+
725
+ except Exception as e:
726
+ if self.debug:
727
+ logger.debug(f"Browser {index}: Attempt {attempt + 1} error: {str(e)}")
728
+ continue
729
+
730
+ elapsed_time = round(time.time() - start_time, 3)
731
+ await save_result(task_id, "turnstile", {"value": "CAPTCHA_FAIL", "elapsed_time": elapsed_time})
732
+ if self.debug:
733
+ logger.error(f"Browser {index}: Error solving Turnstile in {COLORS.get('RED')}{elapsed_time}{COLORS.get('RESET')} Seconds")
734
+ except Exception as e:
735
+ elapsed_time = round(time.time() - start_time, 3)
736
+ await save_result(task_id, "turnstile", {"value": "CAPTCHA_FAIL", "elapsed_time": elapsed_time})
737
+ if self.debug:
738
+ logger.error(f"Browser {index}: Error solving Turnstile: {str(e)}")
739
+ finally:
740
+ if self.debug:
741
+ logger.debug(f"Browser {index}: Closing browser context and cleaning up")
742
+
743
+ try:
744
+ await context.close()
745
+ if self.debug:
746
+ logger.debug(f"Browser {index}: Context closed successfully")
747
+ except Exception as e:
748
+ if self.debug:
749
+ logger.warning(f"Browser {index}: Error closing context: {str(e)}")
750
+
751
+ try:
752
+ if hasattr(browser, 'is_connected') and browser.is_connected():
753
+ await self.browser_pool.put((index, browser, browser_config))
754
+ if self.debug:
755
+ logger.debug(f"Browser {index}: Browser returned to pool")
756
+ else:
757
+ if self.debug:
758
+ logger.warning(f"Browser {index}: Browser disconnected, not returning to pool")
759
+ except Exception as e:
760
+ if self.debug:
761
+ logger.warning(f"Browser {index}: Error returning browser to pool: {str(e)}")
762
+
763
+
764
+
765
+
766
+
767
+
768
+ async def process_turnstile(self):
769
+ """Handle the /turnstile endpoint requests."""
770
+ url = request.args.get('url')
771
+ sitekey = request.args.get('sitekey')
772
+ action = request.args.get('action')
773
+ cdata = request.args.get('cdata')
774
+
775
+ if not url or not sitekey:
776
+ return jsonify({
777
+ "errorId": 1,
778
+ "errorCode": "ERROR_WRONG_PAGEURL",
779
+ "errorDescription": "Both 'url' and 'sitekey' are required"
780
+ }), 200
781
+
782
+ task_id = str(uuid.uuid4())
783
+ await save_result(task_id, "turnstile", {
784
+ "status": "CAPTCHA_NOT_READY",
785
+ "createTime": int(time.time()),
786
+ "url": url,
787
+ "sitekey": sitekey,
788
+ "action": action,
789
+ "cdata": cdata
790
+ })
791
+
792
+ try:
793
+ asyncio.create_task(self._solve_turnstile(task_id=task_id, url=url, sitekey=sitekey, action=action, cdata=cdata))
794
+
795
+ if self.debug:
796
+ logger.debug(f"Request completed with taskid {task_id}.")
797
+ return jsonify({
798
+ "errorId": 0,
799
+ "taskId": task_id
800
+ }), 200
801
+ except Exception as e:
802
+ logger.error(f"Unexpected error processing request: {str(e)}")
803
+ return jsonify({
804
+ "errorId": 1,
805
+ "errorCode": "ERROR_UNKNOWN",
806
+ "errorDescription": str(e)
807
+ }), 200
808
+
809
+ async def get_result(self):
810
+ """Return solved data"""
811
+ task_id = request.args.get('id')
812
+
813
+ if not task_id:
814
+ return jsonify({
815
+ "errorId": 1,
816
+ "errorCode": "ERROR_WRONG_CAPTCHA_ID",
817
+ "errorDescription": "Invalid task ID/Request parameter"
818
+ }), 200
819
+
820
+ result = await load_result(task_id)
821
+ if not result:
822
+ return jsonify({
823
+ "errorId": 1,
824
+ "errorCode": "ERROR_CAPTCHA_UNSOLVABLE",
825
+ "errorDescription": "Task not found"
826
+ }), 200
827
+
828
+ if result == "CAPTCHA_NOT_READY" or (isinstance(result, dict) and result.get("status") == "CAPTCHA_NOT_READY"):
829
+ return jsonify({"status": "processing"}), 200
830
+
831
+ if isinstance(result, dict) and result.get("value") == "CAPTCHA_FAIL":
832
+ return jsonify({
833
+ "errorId": 1,
834
+ "errorCode": "ERROR_CAPTCHA_UNSOLVABLE",
835
+ "errorDescription": "Workers could not solve the Captcha"
836
+ }), 200
837
+
838
+ if isinstance(result, dict) and result.get("value") and result.get("value") != "CAPTCHA_FAIL":
839
+ return jsonify({
840
+ "errorId": 0,
841
+ "status": "ready",
842
+ "solution": {
843
+ "token": result["value"]
844
+ }
845
+ }), 200
846
+ else:
847
+ return jsonify({
848
+ "errorId": 1,
849
+ "errorCode": "ERROR_CAPTCHA_UNSOLVABLE",
850
+ "errorDescription": "Workers could not solve the Captcha"
851
+ }), 200
852
+
853
+
854
+
855
+ @staticmethod
856
+ async def index():
857
+ """Serve the API documentation page."""
858
+ return """
859
+ <!DOCTYPE html>
860
+ <html lang="en">
861
+ <head>
862
+ <meta charset="UTF-8">
863
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
864
+ <title>Turnstile Solver API</title>
865
+ <script src="https://cdn.tailwindcss.com"></script>
866
+ </head>
867
+ <body class="bg-gray-900 text-gray-200 min-h-screen flex items-center justify-center">
868
+ <div class="bg-gray-800 p-8 rounded-lg shadow-md max-w-2xl w-full border border-red-500">
869
+ <h1 class="text-3xl font-bold mb-6 text-center text-red-500">Welcome to Turnstile Solver API</h1>
870
+
871
+ <p class="mb-4 text-gray-300">To use the turnstile service, send a GET request to
872
+ <code class="bg-red-700 text-white px-2 py-1 rounded">/turnstile</code> with the following query parameters:</p>
873
+
874
+ <ul class="list-disc pl-6 mb-6 text-gray-300">
875
+ <li><strong>url</strong>: The URL where Turnstile is to be validated</li>
876
+ <li><strong>sitekey</strong>: The site key for Turnstile</li>
877
+ </ul>
878
+
879
+ <div class="bg-gray-700 p-4 rounded-lg mb-6 border border-red-500">
880
+ <p class="font-semibold mb-2 text-red-400">Example usage:</p>
881
+ <code class="text-sm break-all text-red-300">/turnstile?url=https://example.com&sitekey=sitekey</code>
882
+ </div>
883
+
884
+
885
+ <div class="bg-gray-700 p-4 rounded-lg mb-6">
886
+ <p class="text-gray-200 font-semibold mb-3">📢 Connect with Us</p>
887
+ <div class="space-y-2 text-sm">
888
+ <p class="text-gray-300">
889
+ 📢 <strong>Channel:</strong>
890
+ <a href="https://t.me/D3_vin" class="text-red-300 hover:underline">https://t.me/D3_vin</a>
891
+ - Latest updates and releases
892
+ </p>
893
+ <p class="text-gray-300">
894
+ 💬 <strong>Chat:</strong>
895
+ <a href="https://t.me/D3vin_chat" class="text-red-300 hover:underline">https://t.me/D3vin_chat</a>
896
+ - Community support and discussions
897
+ </p>
898
+ <p class="text-gray-300">
899
+ 📁 <strong>GitHub:</strong>
900
+ <a href="https://github.com/D3-vin" class="text-red-300 hover:underline">https://github.com/D3-vin</a>
901
+ - Source code and development
902
+ </p>
903
+ </div>
904
+ </div>
905
+ </div>
906
+ </body>
907
+ </html>
908
+ """
909
+
910
+
911
+ def parse_args():
912
+ """Parse command-line arguments."""
913
+ parser = argparse.ArgumentParser(description="Turnstile API Server")
914
+
915
+ parser.add_argument('--no-headless', action='store_true', help='Run the browser with GUI (disable headless mode). By default, headless mode is enabled.')
916
+ parser.add_argument('--useragent', type=str, help='User-Agent string (if not specified, random configuration is used)')
917
+ parser.add_argument('--debug', action='store_true', help='Enable or disable debug mode for additional logging and troubleshooting information (default: False)')
918
+ 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)')
919
+ 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)')
920
+ parser.add_argument('--proxy', action='store_true', help='Enable proxy support for the solver (Default: False)')
921
+ parser.add_argument('--random', action='store_true', help='Use random User-Agent and Sec-CH-UA configuration from pool')
922
+ parser.add_argument('--browser', type=str, help='Specify browser name to use (e.g., chrome, firefox)')
923
+ parser.add_argument('--version', type=str, help='Specify browser version to use (e.g., 139, 141)')
924
+ 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)')
925
+ parser.add_argument('--port', type=str, default='5072', help='Set the port for the API solver to listen on. (Default: 5072)')
926
+ return parser.parse_args()
927
+
928
+
929
+ def 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:
930
+ 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)
931
+ return server.app
932
+
933
+
934
+ if __name__ == '__main__':
935
+ args = parse_args()
936
+ browser_types = [
937
+ 'chromium',
938
+ 'chrome',
939
+ 'msedge',
940
+ 'camoufox',
941
+ ]
942
+ if args.browser_type not in browser_types:
943
+ logger.error(f"Unknown browser type: {COLORS.get('RED')}{args.browser_type}{COLORS.get('RESET')} Available browser types: {browser_types}")
944
+ else:
945
+ app = create_app(
946
+ headless=not args.no_headless,
947
+ debug=args.debug,
948
+ useragent=args.useragent,
949
+ browser_type=args.browser_type,
950
+ thread=args.thread,
951
+ proxy_support=args.proxy,
952
+ use_random_config=args.random,
953
+ browser_name=args.browser,
954
+ browser_version=args.version
955
+ )
956
+ app.run(host=args.host, port=int(args.port))
browser_configs.py ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Browser Configurations
4
+ Browser configuration with User-Agent and Sec-CH-UA data for TLS fingerprinting
5
+ """
6
+
7
+ import random
8
+ from typing import Dict, List, Tuple, Optional
9
+
10
+
11
+ class BrowserConfig:
12
+ """Class for working with browser configurations"""
13
+
14
+ SEC_CH_UA_CONFIGS = {
15
+ "chrome": {
16
+ "139": "\"Not;A=Brand\";v=\"99\", \"Google Chrome\";v=\"139\", \"Chromium\";v=\"139\"",
17
+ "138": "\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\", \"Google Chrome\";v=\"138\"",
18
+ "137": "\"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\"",
19
+ "136": "\"Chromium\";v=\"136\", \"Google Chrome\";v=\"136\", \"Not.A/Brand\";v=\"99\""
20
+ },
21
+ "edge": {
22
+ "139": "\"Not;A=Brand\";v=\"99\", \"Microsoft Edge\";v=\"139\", \"Chromium\";v=\"139\"",
23
+ "138": "\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\", \"Microsoft Edge\";v=\"138\"",
24
+ "137": "\"Microsoft Edge\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\""
25
+ },
26
+ "avast": {
27
+ "138": "\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\", \"Avast Secure Browser\";v=\"138\"",
28
+ "137": "\"Avast Secure Browser\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\""
29
+ },
30
+ "brave": {
31
+ "139": "\"Not;A=Brand\";v=\"99\", \"Brave\";v=\"139\", \"Chromium\";v=\"139\"",
32
+ "138": "\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\", \"Brave\";v=\"138\"",
33
+ "137": "\"Brave\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\""
34
+ }
35
+ }
36
+
37
+ USER_AGENT_CONFIGS = {
38
+ "chrome": {
39
+ "139": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
40
+ "138": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
41
+ "137": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
42
+ "136": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"
43
+ },
44
+ "edge": {
45
+ "139": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0",
46
+ "138": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0",
47
+ "137": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0"
48
+ },
49
+ "avast": {
50
+ "138": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Avast/138.0.0.0",
51
+ "137": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Avast/137.0.0.0"
52
+ },
53
+ "brave": {
54
+ "139": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
55
+ "138": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
56
+ "137": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
57
+ }
58
+ }
59
+
60
+
61
+ def __init__(self):
62
+ self.available_browsers = list(self.USER_AGENT_CONFIGS.keys())
63
+
64
+ def get_random_browser_config(self, browser_type=None) -> Tuple[str, str, str, str]:
65
+ """
66
+ Get random browser configuration
67
+
68
+ Args:
69
+ browser_type: Browser type for filtering (chrome, chromium, camoufox)
70
+
71
+ Returns:
72
+ Tuple[str, str, str, str]: (browser_name, version, user_agent, sec_ch_ua)
73
+ """
74
+ if browser_type in ['chrome', 'chromium', 'msedge', 'avast']:
75
+ chromium_browsers = ['chrome', 'edge', 'avast', 'brave']
76
+ browser = random.choice(chromium_browsers)
77
+ elif browser_type == 'camoufox':
78
+ return 'firefox', 'custom', '', ''
79
+ else:
80
+ browser = random.choice(self.available_browsers)
81
+
82
+ versions = list(self.USER_AGENT_CONFIGS[browser].keys())
83
+ version = random.choice(versions)
84
+
85
+ user_agent = self.USER_AGENT_CONFIGS[browser][version]
86
+
87
+ if version in self.SEC_CH_UA_CONFIGS.get(browser, {}):
88
+ sec_ch_ua = self.SEC_CH_UA_CONFIGS[browser][version]
89
+ else:
90
+ sec_ch_ua = ""
91
+
92
+ return browser, version, user_agent, sec_ch_ua
93
+
94
+ def get_browser_config(self, browser: str, version: str) -> Optional[Tuple[str, str]]:
95
+
96
+ try:
97
+ user_agent = self.USER_AGENT_CONFIGS[browser][version]
98
+
99
+ if version in self.SEC_CH_UA_CONFIGS.get(browser, {}):
100
+ sec_ch_ua = self.SEC_CH_UA_CONFIGS[browser][version]
101
+ else:
102
+ sec_ch_ua = ""
103
+
104
+ return user_agent, sec_ch_ua
105
+ except KeyError:
106
+ return None
107
+
108
+ def get_all_configs(self) -> List[Tuple[str, str, str, str]]:
109
+
110
+ configs = []
111
+ for browser in self.available_browsers:
112
+ for version in self.USER_AGENT_CONFIGS[browser].keys():
113
+ user_agent = self.USER_AGENT_CONFIGS[browser][version]
114
+
115
+ if version in self.SEC_CH_UA_CONFIGS.get(browser, {}):
116
+ sec_ch_ua = self.SEC_CH_UA_CONFIGS[browser][version]
117
+ else:
118
+ sec_ch_ua = ""
119
+
120
+ configs.append((browser, version, user_agent, sec_ch_ua))
121
+
122
+ return configs
123
+
124
+ def get_browser_versions(self, browser: str) -> List[str]:
125
+
126
+ return list(self.USER_AGENT_CONFIGS.get(browser, {}).keys())
127
+
128
+ def get_available_browsers(self) -> List[str]:
129
+
130
+ return self.available_browsers.copy()
131
+
132
+ def print_all_configs(self):
133
+ """Print all available configurations to console"""
134
+ print("=== AVAILABLE BROWSER CONFIGURATIONS ===\n")
135
+
136
+ for browser in self.available_browsers:
137
+ print(f"🌐 {browser.upper()}:")
138
+ for version in self.USER_AGENT_CONFIGS[browser].keys():
139
+ user_agent = self.USER_AGENT_CONFIGS[browser][version]
140
+
141
+ if version in self.SEC_CH_UA_CONFIGS.get(browser, {}):
142
+ sec_ch_ua = self.SEC_CH_UA_CONFIGS[browser][version]
143
+ else:
144
+ sec_ch_ua = "NOT SUPPORTED"
145
+
146
+ print(f" 📱 Version {version}:")
147
+ print(f" User-Agent: {user_agent}")
148
+ print(f" Sec-CH-UA: {sec_ch_ua}")
149
+ print()
150
+ print("-" * 50)
151
+
152
+
153
+ browser_config = BrowserConfig()
154
+
155
+
156
+ if __name__ == '__main__':
157
+ config = BrowserConfig()
158
+
159
+ print("🎯 Random configuration:")
160
+ browser, version, ua, sec_ua = config.get_random_browser_config()
161
+ print(f"Browser: {browser} {version}")
162
+ print(f"User-Agent: {ua}")
163
+ print(f"Sec-CH-UA: {sec_ua}")
164
+ print()
165
+
166
+ print("📋 All available browsers:")
167
+ for browser in config.get_available_browsers():
168
+ versions = config.get_browser_versions(browser)
169
+ print(f" {browser}: {', '.join(versions)}")
170
+ print()
171
+
172
+ config.print_all_configs()
db_results.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import aiosqlite
2
+ import json
3
+ import logging
4
+ from typing import Dict, Any, Optional, Union
5
+
6
+ DB_PATH = "results.db"
7
+
8
+ # PRAGMA настройки для оптимизации БД
9
+ PRAGMA_SETTINGS = [
10
+ "PRAGMA journal_mode=WAL",
11
+ "PRAGMA synchronous=NORMAL",
12
+ "PRAGMA cache_size=10000",
13
+ "PRAGMA temp_store=MEMORY",
14
+ "PRAGMA busy_timeout=30000"
15
+ ]
16
+
17
+ async def _apply_pragma_settings(db):
18
+ """Применить PRAGMA настройки к подключению БД"""
19
+ for pragma in PRAGMA_SETTINGS:
20
+ await db.execute(pragma)
21
+
22
+ async def init_db():
23
+ """Initialize database with results table in WAL mode"""
24
+ try:
25
+ async with aiosqlite.connect(DB_PATH) as db:
26
+ await _apply_pragma_settings(db)
27
+
28
+ await db.execute("""
29
+ CREATE TABLE IF NOT EXISTS results (
30
+ task_id TEXT PRIMARY KEY,
31
+ type TEXT NOT NULL,
32
+ data TEXT NOT NULL,
33
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
34
+ )
35
+ """)
36
+ await db.commit()
37
+ logging.getLogger("TurnstileAPIServer").info(f"Database initialized in WAL mode: {DB_PATH}")
38
+ except Exception as e:
39
+ logging.getLogger("TurnstileAPIServer").error(f"Database initialization error: {e}")
40
+ raise
41
+
42
+ async def save_result(task_id: str, task_type: str, data: Union[Dict[str, Any], str]) -> None:
43
+ """Save result to database"""
44
+ try:
45
+ async with aiosqlite.connect(DB_PATH) as db:
46
+ await _apply_pragma_settings(db)
47
+
48
+ data_json = json.dumps(data) if isinstance(data, dict) else data
49
+
50
+ await db.execute(
51
+ "REPLACE INTO results (task_id, type, data) VALUES (?, ?, ?)",
52
+ (task_id, task_type, data_json)
53
+ )
54
+ await db.commit()
55
+ except Exception as e:
56
+ logging.getLogger("TurnstileAPIServer").error(f"Error saving result {task_id}: {e}")
57
+ raise
58
+
59
+ async def load_result(task_id: str) -> Optional[Union[Dict[str, Any], str]]:
60
+ """Load result from database"""
61
+ try:
62
+ async with aiosqlite.connect(DB_PATH) as db:
63
+ await _apply_pragma_settings(db)
64
+
65
+ async with db.execute("SELECT data FROM results WHERE task_id = ?", (task_id,)) as cursor:
66
+ row = await cursor.fetchone()
67
+ if row:
68
+ try:
69
+ return json.loads(row[0])
70
+ except json.JSONDecodeError:
71
+ return row[0]
72
+ return None
73
+ except Exception as e:
74
+ logging.getLogger("TurnstileAPIServer").error(f"Error loading result {task_id}: {e}")
75
+ return None
76
+
77
+ async def load_all_results() -> Dict[str, Any]:
78
+ """Load all results from database"""
79
+ try:
80
+ async with aiosqlite.connect(DB_PATH) as db:
81
+ await _apply_pragma_settings(db)
82
+
83
+ results = {}
84
+ async with db.execute("SELECT task_id, data FROM results") as cursor:
85
+ async for row in cursor:
86
+ try:
87
+ results[row[0]] = json.loads(row[1])
88
+ except json.JSONDecodeError:
89
+ results[row[0]] = row[1]
90
+ return results
91
+ except Exception as e:
92
+ logging.getLogger("TurnstileAPIServer").error(f"Error loading all results: {e}")
93
+ return {}
94
+
95
+ async def delete_result(task_id: str) -> None:
96
+ """Delete result from database"""
97
+ try:
98
+ async with aiosqlite.connect(DB_PATH) as db:
99
+ await _apply_pragma_settings(db)
100
+
101
+ await db.execute("DELETE FROM results WHERE task_id = ?", (task_id,))
102
+ await db.commit()
103
+ except Exception as e:
104
+ logging.getLogger("TurnstileAPIServer").error(f"Error deleting result {task_id}: {e}")
105
+
106
+ async def get_pending_count() -> int:
107
+ """Get count of pending tasks"""
108
+ try:
109
+ async with aiosqlite.connect(DB_PATH) as db:
110
+ await _apply_pragma_settings(db)
111
+
112
+ async with db.execute("SELECT COUNT(*) FROM results WHERE data LIKE '%CAPTCHA_NOT_READY%'") as cursor:
113
+ row = await cursor.fetchone()
114
+ return row[0] if row else 0
115
+ except Exception as e:
116
+ logging.getLogger("TurnstileAPIServer").error(f"Error getting pending count: {e}")
117
+ return 0
118
+
119
+ async def cleanup_old_results(days_old: int = 1) -> int:
120
+ """Clean up results older than specified days"""
121
+ try:
122
+ async with aiosqlite.connect(DB_PATH) as db:
123
+ await _apply_pragma_settings(db)
124
+
125
+ async with db.execute(
126
+ "DELETE FROM results WHERE created_at < datetime('now', '-{} days')".format(days_old)
127
+ ) as cursor:
128
+ deleted_count = cursor.rowcount
129
+ await db.commit()
130
+ logging.getLogger("TurnstileAPIServer").info(f"Cleaned up {deleted_count} old results")
131
+ return deleted_count
132
+ except Exception as e:
133
+ logging.getLogger("TurnstileAPIServer").error(f"Error cleaning up old results: {e}")
134
+ return 0
proxies.txt ADDED
File without changes
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ quart
2
+ asyncio
3
+ argparse
4
+ patchright
5
+ camoufox[geoip]
6
+ requests
7
+ aiosqlite
8
+ rich