Upload 6 files
Browse files- README.md +257 -10
- api_solver.py +956 -0
- browser_configs.py +172 -0
- db_results.py +134 -0
- proxies.txt +0 -0
- requirements.txt +8 -0
README.md
CHANGED
|
@@ -1,10 +1,257 @@
|
|
| 1 |
-
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+

|
| 10 |
+

|
| 11 |
+

|
| 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
|