OwenPowell commited on
Commit
af4e312
·
verified ·
1 Parent(s): 2e5b966

Delete scripts

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