diff --git a/.docker/README.md b/.docker/README.md
new file mode 100644
index 0000000..139b396
--- /dev/null
+++ b/.docker/README.md
@@ -0,0 +1,41 @@
+# Docker
+
+The docker environment has been put together by [0x78f1935](https://github.com/0x78f1935), just like this readme file.
+
+## Overview
+
+Start the environment with `docker compose up --build -d`. Once the service is running the following ports are available by default:
+
+| Port | Internal Port | What is it? |
+| ---- | ------------- | ---------------- |
+| 8088 | 8088 | Turnstile Solver |
+| 5901 | 5901 | VNC Server |
+
+### Turnstile Solver
+
+Simply make your Python / Curl requests to this server as documented in the main readme.
+
+### VNC Server
+
+You can connect without a password to the VNC server, which will show you how the browser is being handled. In addition it allows you to manually manipulate the page if neceserry. Very nice for debugging.
+
+It also shows you that we run none-headless.
+
+## Internal connection
+
+To talk with the internal port, connect your docker environment to the `solver` network. When doing so, the dns name `captcha_resolver` should become available to you and resolves the turnstile docker IP addr.
+
+- Connect to `solver` network
+
+```sh
+docker network connect solver CONTAINER
+```
+
+Where `CONTAINER` is your container.
+
+## Recommendation
+
+I highly recommend in production to:
+
+- Disable VNC port. Only use this for development purposes
+- Disable Turnstile remote port, only use the internal docker network
diff --git a/.docker/services/turnstile/entrypoint.sh b/.docker/services/turnstile/entrypoint.sh
new file mode 100644
index 0000000..8e78b84
--- /dev/null
+++ b/.docker/services/turnstile/entrypoint.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+# Start Xvfb
+Xvfb :99 -screen 0 1024x768x16 &
+XVFB_PID=$!
+
+# Wait for Xvfb to become ready
+for i in {1..10}; do
+ if xdpyinfo -display :99 >/dev/null 2>&1; then
+ echo "Xvfb is ready!"
+ break
+ fi
+ echo "Waiting for Xvfb to start..."
+ sleep 1
+done
+
+# If it's not ready after 10s, bail
+if ! xdpyinfo -display :99 >/dev/null 2>&1; then
+ echo "Xvfb did not start!"
+ exit 1
+fi
+
+# Start window manager
+fluxbox &
+
+# Start VNC server
+x11vnc -display :99 -forever -nopw -shared -listen 0.0.0.0 -rfbport 5901 -bg
+
+# Run your app
+export DISPLAY=:99
+exec python main.py --browser-args "--no-sandbox --disable-gpu --disable-software-rasterizer --disable-dev-shm-usage --disable-setuid-sandbox --start-maximized --disable-blink-features=AutomationControlled --lang=en-US,en --window-size=1024,720 --enable-unsafe-webgpu --enable-webgl --use-gl=swiftshader --enable-features=SharedArrayBuffer,TrustTokens,PrivateNetworkAccessChecksBypassingPermissionPolicy"
diff --git a/.docker/services/turnstile/solver.Dockerfile b/.docker/services/turnstile/solver.Dockerfile
new file mode 100644
index 0000000..2ded5b5
--- /dev/null
+++ b/.docker/services/turnstile/solver.Dockerfile
@@ -0,0 +1,32 @@
+FROM python:3.13.3
+
+# Prevent interactive prompts during package installs
+ENV DEBIAN_FRONTEND=noninteractive
+ENV DISPLAY=:99
+
+# Install xvfb and VNC server
+RUN apt-get update && apt-get install -y \
+ xvfb \
+ fluxbox \
+ x11vnc \
+ supervisor \
+ x11-utils \
+ --no-install-recommends \
+ && apt-get clean && rm -rf /var/lib/apt/lists/*
+
+# Install requirements
+RUN pip install patchright && \
+ patchright install chromium --with-deps
+
+WORKDIR /workspaceFolder
+COPY . .
+RUN pip install -r requirements.txt
+RUN pip install .
+
+WORKDIR /workspaceFolder/src/turnstile_solver
+
+HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
+ CMD xvfb-run --auto-servernum echo "Xvfb is working!" || exit 1
+
+RUN chmod +x /workspaceFolder/.docker/services/turnstile/entrypoint.sh
+ENTRYPOINT [ "/workspaceFolder/.docker/services/turnstile/entrypoint.sh" ]
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..856f7d2
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,25 @@
+{
+ "configurations": [
+ {
+ "name": "Turnstile: Headless",
+ "type": "debugpy",
+ "request": "launch",
+ "module": "turnstile_solver",
+ "args": [
+ "--headless",
+ "--port",
+ "8088"
+ ]
+ },
+ {
+ "name": "Turnstile: None-Headless",
+ "type": "debugpy",
+ "request": "launch",
+ "module": "turnstile_solver",
+ "args": [
+ "--port",
+ "8088"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 6641cf4..ab8b484 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,23 @@ Python server to automatically solve Cloudflare Turnstile CAPTCHA with an averag
PD: This repository was initially created for personal use. I've adjusted it for sharing, but it might still be slightly disorganized. Feel free to contribute, open issues, and request new features.
+## Table of Content
+
+- [Screenshots](#screenshots)
+- [Install](#install)
+ - [Patchright](#install-patchright-patched-chromium-browser)
+- [How to use](#how-to-use)
+ - [Run server](#run-server)
+ - [Global proxy](#use-global-browser-proxy)
+ - [Proxy parameters](#load-proxy-parameters-from-environment-variables-all-caps)
+ - [Proxy file](#use-a-proxy-from-file-per-browser-context)
+- [Get Token](#get-token)
+ - [Curl](#curl)
+ - [Python](#python)
+- [Docker Micro Service](./.docker/README.md)
+- [Disclaimer](#disclaimer-️)
+- [Donate](#donate)
+
## Screenshots
TODO: Update
@@ -14,10 +31,13 @@ TODO: Update

## Install
+
```bash
pip install git+https://github.com/odell0111/turnstile_solver@main
```
+
### Install Patchright patched chromium browser
+
```bash
patchright install chromium
```
@@ -25,6 +45,7 @@ patchright install chromium
## How to use
### Run server
+
```bash
solver
```
@@ -32,23 +53,29 @@ solver
```bash
solver --port 8088 --secret jWRN7DH6 --browser-position --max-attempts 3 --captcha-timeout 30 --page-load-timeout 30 --reload-on-overrun
```
+
#### Use global browser proxy
+
```bash
solver --proxy-server http://myproxy.com:3128 --proxy-username user --proxy-password pass
```
+
##### Load proxy parameters from environment variables (all caps)
+
```bash
solver --proxy-server MY_PROXY_SERVER --proxy-username MY_PROXY_USERNAME --proxy-password MY_PROXY_PASSWORD
```
+
##### Use a proxy from file per browser context
+
```bash
solver --proxies myproxies.txt
```
-
### Get token
#### cURL
+
```bash
curl --location --request GET 'http://127.0.0.1:8088/solve' \
--header 'ngrok-skip-browser-warning: _' \
@@ -61,6 +88,7 @@ curl --location --request GET 'http://127.0.0.1:8088/solve' \
```
#### Python
+
```python
import requests
@@ -80,8 +108,8 @@ json_data = {
}
response = requests.get(
- url=url,
- headers=headers,
+ url=url,
+ headers=headers,
json=json_data,
)
@@ -100,31 +128,39 @@ print("Token:", token)
```
-## Disclaimer ‼️
+## Disclaimer ‼️
+
Use this project entirely at your own risk. I hold no responsibility for any negative outcomes, including but not limited to API blocking and IP bans
## Donate
-If you find my work useful and want to encourage further development, you can do so by donating
-[//]: # ([](https://oxapay.com/donate/42319117))
+If you find my work useful and want to encourage further development, you can do so by donating
-[//]: # (
)
+[//]: # '[](https://oxapay.com/donate/42319117)'
+[//]: # '
'
### [OxaPay](https://oxapay.com/donate/42319117)
### TON
+
```
UQCyCnWVYOmv97idVFZ4tIewToZacRhYVwfGNU658fN5w3Kl
```
+
### Bitcoin
+
```
1E9kw3FuaahfeproboNL7uvyBdjP9wY6CR
```
+
### Bitcoin (BEP20)
+
```
0x88046e6d0f2bf8629cd7fbd754e4e275083fc993
```
+
#### Speed Lightning Address username
+
```
bytechanger@speed.app
-```
\ No newline at end of file
+```
diff --git a/docker-compose.yaml b/docker-compose.yaml
new file mode 100644
index 0000000..e97d928
--- /dev/null
+++ b/docker-compose.yaml
@@ -0,0 +1,20 @@
+services:
+ solver:
+ container_name: solver
+ image: turnstile_solver
+ hostname: solver
+ build:
+ context: .
+ dockerfile: ./.docker/services/turnstile/solver.Dockerfile
+ restart: always
+ ports:
+ # Recommendation: Disable both ports in production
+ - 8088:8088
+ - 5901:5901
+ networks:
+ - solver
+
+networks:
+ solver:
+ name: solver
+ driver: bridge
diff --git a/src/turnstile_solver/__init__.py b/src/turnstile_solver/__init__.py
index 23bb1e5..555ffb2 100644
--- a/src/turnstile_solver/__init__.py
+++ b/src/turnstile_solver/__init__.py
@@ -1,4 +1,5 @@
-from .solver import TurnstileSolver
-from .turnstile_solver_server import TurnstileSolverServer
-from .constants import HOST, PORT
-from .main import main, run_server, main_cli
+# -*- mode: python ; coding: utf-8 -*-
+from turnstile_solver.solver import TurnstileSolver
+from turnstile_solver.turnstile_solver_server import TurnstileSolverServer
+from turnstile_solver.constants import HOST, PORT
+from turnstile_solver.main import main, run_server, main_cli
diff --git a/src/turnstile_solver/__main__.py b/src/turnstile_solver/__main__.py
index 79ae050..296e4c2 100644
--- a/src/turnstile_solver/__main__.py
+++ b/src/turnstile_solver/__main__.py
@@ -1,6 +1,7 @@
+# -*- mode: python ; coding: utf-8 -*-
import asyncio
-from .main import main
+from turnstile_solver.main import main
if __name__ == '__main__':
asyncio.run(main())
diff --git a/src/turnstile_solver/browser_context_pool.py b/src/turnstile_solver/browser_context_pool.py
index abeb649..6a52637 100644
--- a/src/turnstile_solver/browser_context_pool.py
+++ b/src/turnstile_solver/browser_context_pool.py
@@ -1,16 +1,17 @@
+# -*- mode: python ; coding: utf-8 -*-
import asyncio
import logging
from typing import TYPE_CHECKING
from patchright.async_api import Browser
-from .constants import MAX_PAGES_PER_CONTEXT, MAX_CONTEXTS
-from .page_pool import PagePool
-from .pool import Pool
-from .proxy_provider import ProxyProvider
+from turnstile_solver.constants import MAX_PAGES_PER_CONTEXT, MAX_CONTEXTS
+from turnstile_solver.page_pool import PagePool
+from turnstile_solver.pool import Pool
+from turnstile_solver.proxy_provider import ProxyProvider
if TYPE_CHECKING:
- from .solver import TurnstileSolver
+ from turnstile_solver.solver import TurnstileSolver
logger = logging.getLogger(__name__)
diff --git a/src/turnstile_solver/constants.py b/src/turnstile_solver/constants.py
index 617a941..ceadbb9 100644
--- a/src/turnstile_solver/constants.py
+++ b/src/turnstile_solver/constants.py
@@ -1,3 +1,4 @@
+# -*- mode: python ; coding: utf-8 -*-
import os
from pathlib import Path
diff --git a/src/turnstile_solver/custom_rich_help_formatter.py b/src/turnstile_solver/custom_rich_help_formatter.py
index 9f9527b..601682d 100644
--- a/src/turnstile_solver/custom_rich_help_formatter.py
+++ b/src/turnstile_solver/custom_rich_help_formatter.py
@@ -1,3 +1,4 @@
+# -*- mode: python ; coding: utf-8 -*-
from rich.console import RenderableType
from rich_argparse import RichHelpFormatter
diff --git a/src/turnstile_solver/enums.py b/src/turnstile_solver/enums.py
index 1cf1186..6799104 100644
--- a/src/turnstile_solver/enums.py
+++ b/src/turnstile_solver/enums.py
@@ -1,3 +1,4 @@
+# -*- mode: python ; coding: utf-8 -*-
from enum import Enum
diff --git a/src/turnstile_solver/main.py b/src/turnstile_solver/main.py
index 9f4cd82..cb4accd 100644
--- a/src/turnstile_solver/main.py
+++ b/src/turnstile_solver/main.py
@@ -1,3 +1,4 @@
+# -*- mode: python ; coding: utf-8 -*-
import asyncio
import logging
@@ -18,15 +19,15 @@
from rich.text import Text
from multiprocessing import Process
-from . import constants as c
-from .proxy import Proxy
-from .proxy_provider import ProxyProvider
-from .custom_rich_help_formatter import CustomRichHelpFormatter
-from .solver_console import SolverConsole
-from .solver_console_highlighter import SolverConsoleHighlighter
-from .solver import TurnstileSolver
-from .utils import init_logger, simulate_intensive_task, get_file_handler, load_proxy_param
-from .turnstile_solver_server import TurnstileSolverServer
+from turnstile_solver import constants as c
+from turnstile_solver.proxy import Proxy
+from turnstile_solver.proxy_provider import ProxyProvider
+from turnstile_solver.custom_rich_help_formatter import CustomRichHelpFormatter
+from turnstile_solver.solver_console import SolverConsole
+from turnstile_solver.solver_console_highlighter import SolverConsoleHighlighter
+from turnstile_solver.solver import TurnstileSolver
+from turnstile_solver.utils import init_logger, simulate_intensive_task, get_file_handler, load_proxy_param
+from turnstile_solver.turnstile_solver_server import TurnstileSolverServer
_console = SolverConsole()
diff --git a/src/turnstile_solver/page_pool.py b/src/turnstile_solver/page_pool.py
index 9b53ca1..51cfd52 100644
--- a/src/turnstile_solver/page_pool.py
+++ b/src/turnstile_solver/page_pool.py
@@ -1,9 +1,10 @@
+# -*- mode: python ; coding: utf-8 -*-
import logging
from patchright.async_api import BrowserContext, Page, Route
from turnstile_solver.pool import Pool
-from .constants import MAX_PAGES_PER_CONTEXT
+from turnstile_solver.constants import MAX_PAGES_PER_CONTEXT
logger = logging.getLogger(__name__)
diff --git a/src/turnstile_solver/pool.py b/src/turnstile_solver/pool.py
index d14ec64..8b997c4 100644
--- a/src/turnstile_solver/pool.py
+++ b/src/turnstile_solver/pool.py
@@ -1,3 +1,4 @@
+# -*- mode: python ; coding: utf-8 -*-
import asyncio
import logging
from collections import deque
diff --git a/src/turnstile_solver/proxy.py b/src/turnstile_solver/proxy.py
index 9bf9f22..9e56217 100644
--- a/src/turnstile_solver/proxy.py
+++ b/src/turnstile_solver/proxy.py
@@ -1,3 +1,4 @@
+# -*- mode: python ; coding: utf-8 -*-
import json
import logging
import re
diff --git a/src/turnstile_solver/proxy_provider.py b/src/turnstile_solver/proxy_provider.py
index 69f9c9e..10f398e 100644
--- a/src/turnstile_solver/proxy_provider.py
+++ b/src/turnstile_solver/proxy_provider.py
@@ -1,7 +1,8 @@
+# -*- mode: python ; coding: utf-8 -*-
import logging
from pathlib import Path
-from .proxy import Proxy
+from turnstile_solver.proxy import Proxy
logger = logging.getLogger(__name__)
diff --git a/src/turnstile_solver/solver.py b/src/turnstile_solver/solver.py
index f72d6b8..bc7d7cb 100644
--- a/src/turnstile_solver/solver.py
+++ b/src/turnstile_solver/solver.py
@@ -1,3 +1,4 @@
+# -*- mode: python ; coding: utf-8 -*-
import datetime
import logging
import time
@@ -5,19 +6,16 @@
from typing import Callable, Awaitable
from patchright.async_api import async_playwright, Page, BrowserContext, Browser, Playwright
-from . import constants as c
-from .enums import CaptchaApiMessageEvent
-from .proxy import Proxy
-from .solver_console import SolverConsole
-from .turnstile_result import TurnstileResult
-from .turnstile_solver_server import TurnstileSolverServer, CAPTCHA_EVENT_CALLBACK_ENDPOINT
+from turnstile_solver import constants as c
+from turnstile_solver.enums import CaptchaApiMessageEvent
+from turnstile_solver.proxy import Proxy
+from turnstile_solver.solver_console import SolverConsole
+from turnstile_solver.turnstile_result import TurnstileResult
+from turnstile_solver.turnstile_solver_server import TurnstileSolverServer, CAPTCHA_EVENT_CALLBACK_ENDPOINT
logger = logging.getLogger(__name__)
BROWSER_ARGS = {
- "--disable-blink-features=AutomationControlled", # avoid navigator.webdriver detection
- "--no-sandbox",
- "--disable-dev-shm-usage",
"--disable-background-networking",
"--disable-background-timer-throttling",
"--disable-backgrounding-occluded-windows",
@@ -63,6 +61,21 @@
'--log-level=3',
'--proxy-bypass-list=<-loopback>;localhost;127.0.0.1;*.local',
+ # Run the browser with SwiftShader and force it to accept software rendering
+ '--no-sandbox',
+ '--disable-gpu',
+ '--disable-software-rasterizer',
+ '--disable-dev-shm-usage',
+ '--disable-blink-features=AutomationControlled', # avoid navigator.webdriver detection
+ '--disable-setuid-sandbox',
+ '--lang=en-US,en',
+ '--start-maximized',
+ '--window-size=1024,720',
+ '--enable-unsafe-webgpu',
+ '--enable-webgl',
+ '--use-gl=swiftshader',
+ '--enable-features=SharedArrayBuffer,TrustTokens,PrivateNetworkAccessChecksBypassingPermissionPolicy',
+
# Not needed, here just for reference
# Network/Connection Tuning
# '--enable-features=NetworkService,ParallelDownloading',
diff --git a/src/turnstile_solver/solver_console.py b/src/turnstile_solver/solver_console.py
index a1766c4..d6c6b6a 100644
--- a/src/turnstile_solver/solver_console.py
+++ b/src/turnstile_solver/solver_console.py
@@ -1,8 +1,9 @@
+# -*- mode: python ; coding: utf-8 -*-
from rich.console import Console
from rich.theme import Theme
-from .constants import CONSOLE_THEME_STYLES
-from .solver_console_highlighter import SolverConsoleHighlighter
+from turnstile_solver.constants import CONSOLE_THEME_STYLES
+from turnstile_solver.solver_console_highlighter import SolverConsoleHighlighter
MAX_CONSOLE_WIDTH = 200
diff --git a/src/turnstile_solver/solver_console_highlighter.py b/src/turnstile_solver/solver_console_highlighter.py
index 75bd591..3cc2e25 100644
--- a/src/turnstile_solver/solver_console_highlighter.py
+++ b/src/turnstile_solver/solver_console_highlighter.py
@@ -1,3 +1,4 @@
+# -*- mode: python ; coding: utf-8 -*-
from rich.highlighter import ReprHighlighter
_ORIGINAL_NUMBER_HIGHLIGHTER = r"(?P(?