Fffffd commited on
Commit
689ea4f
·
verified ·
1 Parent(s): e70dd2a

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +165 -0
app.py ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ import uuid
4
+ import asyncio
5
+ import logging
6
+ from quart import Quart, request, jsonify
7
+ from camoufox.async_api import AsyncCamoufox
8
+
9
+
10
+ logging.basicConfig(
11
+ level=logging.INFO,
12
+ format='[%(asctime)s] [%(levelname)s] %(message)s',
13
+ datefmt='%H:%M:%S',
14
+ )
15
+ logger = logging.getLogger("TurnstileSolver")
16
+
17
+
18
+ SECRET_TOKEN = os.environ.get("SOLVER_SECRET", "changeme_please_123")
19
+
20
+
21
+ HTML_TEMPLATE = """
22
+ <!DOCTYPE html>
23
+ <html lang="en">
24
+ <head>
25
+ <meta charset="UTF-8">
26
+ <title>Turnstile Solver</title>
27
+ <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async></script>
28
+ </head>
29
+ <body>
30
+ <!-- cf turnstile -->
31
+ </body>
32
+ </html>
33
+ """
34
+
35
+
36
+ class TurnstileSolver:
37
+ def __init__(self, headless=True):
38
+ self.headless = headless
39
+
40
+ async def solve(self, url, sitekey, action=None, cdata=None, timeout=60):
41
+ start = time.time()
42
+ browser = None
43
+ try:
44
+ browser = await AsyncCamoufox(headless=self.headless).start()
45
+ page = await browser.new_page()
46
+
47
+ url_with_slash = url + "/" if not url.endswith("/") else url
48
+
49
+ turnstile_div = f'<div class="cf-turnstile" data-sitekey="{sitekey}"'
50
+ if action:
51
+ turnstile_div += f' data-action="{action}"'
52
+ if cdata:
53
+ turnstile_div += f' data-cdata="{cdata}"'
54
+ turnstile_div += '></div>'
55
+
56
+ page_data = HTML_TEMPLATE.replace("<!-- cf turnstile -->", turnstile_div)
57
+
58
+ await page.route(url_with_slash, lambda route: route.fulfill(body=page_data, status=200))
59
+ await page.goto(url_with_slash)
60
+
61
+ token = None
62
+ for i in range(int(timeout / 2)):
63
+ try:
64
+ val = await page.input_value("[name=cf-turnstile-response]", timeout=2000)
65
+ if val and len(val) > 20:
66
+ token = val
67
+ break
68
+ else:
69
+ try:
70
+ await page.click("//div[@class='cf-turnstile']", timeout=1000)
71
+ except Exception:
72
+ pass
73
+ except Exception:
74
+ pass
75
+ await asyncio.sleep(2)
76
+
77
+ elapsed = round(time.time() - start, 3)
78
+
79
+ if token:
80
+ return {"status": "success", "value": token, "elapsed_time": elapsed}
81
+ else:
82
+ return {"status": "failure", "value": None, "elapsed_time": elapsed, "reason": "Timeout"}
83
+
84
+ except Exception as e:
85
+ return {"status": "error", "value": None, "elapsed_time": round(time.time() - start, 3), "reason": str(e)}
86
+ finally:
87
+ if browser:
88
+ try:
89
+ await browser.close()
90
+ except Exception:
91
+ pass
92
+
93
+
94
+ app = Quart(__name__)
95
+ results = {}
96
+ solver = TurnstileSolver(headless=True)
97
+
98
+
99
+ def check_auth():
100
+ token = request.args.get("token")
101
+ return token == SECRET_TOKEN
102
+
103
+
104
+ @app.route("/")
105
+ async def index():
106
+ return jsonify({
107
+ "service": "Turnstile Solver API",
108
+ "status": "running",
109
+ "endpoints": {
110
+ "/turnstile": "GET ?url=...&sitekey=...&token=...",
111
+ "/result": "GET ?id=...&token=...",
112
+ },
113
+ })
114
+
115
+
116
+ @app.route("/turnstile")
117
+ async def create_task():
118
+ if not check_auth():
119
+ return jsonify({"error": "Unauthorized"}), 401
120
+
121
+ url = request.args.get("url")
122
+ sitekey = request.args.get("sitekey")
123
+ action = request.args.get("action")
124
+ cdata = request.args.get("cdata")
125
+
126
+ if not url or not sitekey:
127
+ return jsonify({"error": "url and sitekey required"}), 400
128
+
129
+ task_id = str(uuid.uuid4())
130
+ results[task_id] = {"status": "pending"}
131
+
132
+ async def run_solver():
133
+ try:
134
+ result = await solver.solve(url, sitekey, action, cdata)
135
+ results[task_id] = result
136
+ except Exception as e:
137
+ results[task_id] = {"status": "error", "reason": str(e)}
138
+
139
+ asyncio.create_task(run_solver())
140
+
141
+ return jsonify({"task_id": task_id}), 202
142
+
143
+
144
+ @app.route("/result")
145
+ async def get_result():
146
+ if not check_auth():
147
+ return jsonify({"error": "Unauthorized"}), 401
148
+
149
+ task_id = request.args.get("id")
150
+ if not task_id or task_id not in results:
151
+ return jsonify({"error": "invalid task_id"}), 400
152
+
153
+ result = results[task_id]
154
+
155
+ if result.get("status") == "pending":
156
+ return jsonify({"status": "pending"}), 202
157
+ elif result.get("status") == "success":
158
+ return jsonify(result), 200
159
+ else:
160
+ return jsonify(result), 422
161
+
162
+
163
+ if __name__ == "__main__":
164
+ port = int(os.environ.get("PORT", 7860))
165
+ app.run(host="0.0.0.0", port=port)