cloudwaddie commited on
Commit
776f46d
Β·
1 Parent(s): f9d8bdf

Add reCAPTCHA v3 integration for enhanced security in API requests

Browse files
Files changed (1) hide show
  1. src/main.py +147 -5
src/main.py CHANGED
@@ -194,6 +194,122 @@ def debug_print(*args, **kwargs):
194
  if DEBUG:
195
  print(*args, **kwargs)
196
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  # Custom UUIDv7 implementation (using correct Unix epoch)
198
  def uuid7():
199
  """
@@ -485,6 +601,12 @@ conversation_tokens: Dict[str, str] = {}
485
  # Track failed tokens per request to avoid retrying with same token
486
  request_failed_tokens: Dict[str, set] = {}
487
 
 
 
 
 
 
 
488
  # --- Helper Functions ---
489
 
490
  def get_config():
@@ -842,10 +964,17 @@ async def startup_event():
842
  save_models(get_models())
843
  # Load usage stats from config
844
  load_usage_stats()
845
- # Start initial data fetch
846
- asyncio.create_task(get_initial_data())
847
- # Start periodic refresh task (every 30 minutes)
 
 
 
 
 
 
848
  asyncio.create_task(periodic_refresh_task())
 
849
  except Exception as e:
850
  debug_print(f"❌ Error during startup: {e}")
851
  # Continue anyway - server should still start
@@ -1896,6 +2025,17 @@ async def api_chat_completions(request: Request, api_key: dict = Depends(rate_li
1896
 
1897
  # Use API key + conversation tracking
1898
  api_key_str = api_key["key"]
 
 
 
 
 
 
 
 
 
 
 
1899
 
1900
  # Generate conversation ID from context (API key + model + first user message)
1901
  import hashlib
@@ -1964,7 +2104,8 @@ async def api_chat_completions(request: Request, api_key: dict = Depends(rate_li
1964
  "experimental_attachments": experimental_attachments,
1965
  "metadata": {}
1966
  },
1967
- "modality": modality
 
1968
  }
1969
  url = "https://lmarena.ai/nextjs-api/stream/create-evaluation"
1970
  debug_print(f"πŸ“€ Target URL: {url}")
@@ -1989,7 +2130,8 @@ async def api_chat_completions(request: Request, api_key: dict = Depends(rate_li
1989
  "experimental_attachments": experimental_attachments,
1990
  "metadata": {}
1991
  },
1992
- "modality": modality
 
1993
  }
1994
  url = f"https://lmarena.ai/nextjs-api/stream/post-to-evaluation/{session['conversation_id']}"
1995
  debug_print(f"πŸ“€ Target URL: {url}")
 
194
  if DEBUG:
195
  print(*args, **kwargs)
196
 
197
+ # --- New reCAPTCHA Functions ---
198
+
199
+ # Updated constants from gpt4free/g4f/Provider/needs_auth/LMArena.py
200
+ RECAPTCHA_SITEKEY = "6Led_uYrAAAAAKjxDIF58fgFtX3t8loNAK85bW9I"
201
+ RECAPTCHA_ACTION = "chat_submit"
202
+
203
+ async def get_recaptcha_v3_token() -> Optional[str]:
204
+ """
205
+ Mirrors get_grecaptcha from gpt4free/LMArena.py.
206
+ Waits for the site to load the reCAPTCHA library naturally using saved cookies.
207
+ """
208
+ debug_print("πŸ” Starting reCAPTCHA v3 token retrieval...")
209
+
210
+ # Load saved cookies (specifically cf_clearance) to look trusted
211
+ config = get_config()
212
+ cf_clearance = config.get("cf_clearance", "")
213
+
214
+ try:
215
+ async with AsyncCamoufox(headless=True) as browser:
216
+ # Set context with cookies if available
217
+ context = await browser.new_context()
218
+ if cf_clearance:
219
+ await context.add_cookies([{
220
+ "name": "cf_clearance",
221
+ "value": cf_clearance,
222
+ "domain": ".lmarena.ai",
223
+ "path": "/"
224
+ }])
225
+ debug_print(" πŸͺ Loaded cf_clearance cookie")
226
+
227
+ page = await context.new_page()
228
+
229
+ # Go to main page
230
+ debug_print(" 🌐 Navigating to lmarena.ai...")
231
+ await page.goto("https://lmarena.ai/", wait_until="domcontentloaded")
232
+
233
+ # 1. Wait for Cloudflare
234
+ try:
235
+ await page.wait_for_function(
236
+ "() => document.title.indexOf('Just a moment...') === -1",
237
+ timeout=15000
238
+ )
239
+ except Exception:
240
+ debug_print("❌ Cloudflare challenge failed.")
241
+ return None
242
+
243
+ # 2. Wait for reCAPTCHA Enterprise (Mirroring gpt4free logic)
244
+ # gpt4free waits for: window.grecaptcha && window.grecaptcha.enterprise
245
+ debug_print(" ⏳ Waiting for window.grecaptcha.enterprise...")
246
+ try:
247
+ await page.wait_for_function(
248
+ "() => window.grecaptcha && window.grecaptcha.enterprise",
249
+ timeout=30000
250
+ )
251
+ debug_print(" βœ… reCAPTCHA script detected.")
252
+ except Exception as e:
253
+ debug_print(f"❌ Failed to detect reCAPTCHA script (timeout): {e}")
254
+ return None
255
+
256
+ # 3. Execute reCAPTCHA (Mirroring gpt4free execution logic)
257
+ debug_print(" πŸ‘€ Executing reCAPTCHA...")
258
+ token = await page.evaluate(f"""
259
+ new Promise((resolve) => {{
260
+ window.grecaptcha.enterprise.ready(async () => {{
261
+ try {{
262
+ const token = await window.grecaptcha.enterprise.execute(
263
+ '{RECAPTCHA_SITEKEY}',
264
+ {{ action: '{RECAPTCHA_ACTION}' }}
265
+ );
266
+ resolve(token);
267
+ }} catch (e) {{
268
+ console.error("[LMArena Bridge] reCAPTCHA execute failed:", e);
269
+ resolve(null);
270
+ }}
271
+ }});
272
+ // Safety timeout
273
+ setTimeout(() => resolve(null), 10000);
274
+ }});
275
+ """)
276
+
277
+ if token:
278
+ debug_print(f"βœ… reCAPTCHA v3 token retrieved: {token[:20]}...")
279
+ return token
280
+ else:
281
+ debug_print("❌ Failed to retrieve reCAPTCHA token (returned null).")
282
+ return None
283
+
284
+ except Exception as e:
285
+ debug_print(f"❌ Unexpected error during reCAPTCHA retrieval: {type(e).__name__}: {e}")
286
+ return None
287
+
288
+ async def refresh_recaptcha_token():
289
+ """Checks if the global reCAPTCHA token is expired and refreshes it if necessary."""
290
+ global RECAPTCHA_TOKEN, RECAPTCHA_EXPIRY
291
+
292
+ current_time = datetime.now(timezone.utc)
293
+ # Check if token is expired (set a refresh margin of 10 seconds)
294
+ if RECAPTCHA_TOKEN is None or current_time > RECAPTCHA_EXPIRY - timedelta(seconds=10):
295
+ debug_print("πŸ”„ Recaptcha token expired or missing. Refreshing...")
296
+ new_token = await get_recaptcha_v3_token()
297
+ if new_token:
298
+ RECAPTCHA_TOKEN = new_token
299
+ # reCAPTCHA v3 tokens typically last 120 seconds (2 minutes)
300
+ RECAPTCHA_EXPIRY = current_time + timedelta(seconds=120)
301
+ debug_print(f"βœ… Recaptcha token refreshed, expires at {RECAPTCHA_EXPIRY.isoformat()}")
302
+ return new_token
303
+ else:
304
+ debug_print("❌ Failed to refresh recaptcha token.")
305
+ # Set a short retry delay if refresh fails
306
+ RECAPTCHA_EXPIRY = current_time + timedelta(seconds=10)
307
+ return None
308
+
309
+ return RECAPTCHA_TOKEN
310
+
311
+ # --- End New reCAPTCHA Functions ---
312
+
313
  # Custom UUIDv7 implementation (using correct Unix epoch)
314
  def uuid7():
315
  """
 
601
  # Track failed tokens per request to avoid retrying with same token
602
  request_failed_tokens: Dict[str, set] = {}
603
 
604
+ # --- New Global State for reCAPTCHA ---
605
+ RECAPTCHA_TOKEN: Optional[str] = None
606
+ # Initialize expiry far in the past to force a refresh on startup
607
+ RECAPTCHA_EXPIRY: datetime = datetime.now(timezone.utc) - timedelta(days=365)
608
+ # --------------------------------------
609
+
610
  # --- Helper Functions ---
611
 
612
  def get_config():
 
964
  save_models(get_models())
965
  # Load usage stats from config
966
  load_usage_stats()
967
+
968
+ # 1. First, get initial data (cookies, models, etc.)
969
+ # We await this so we have the cookie BEFORE trying reCAPTCHA
970
+ await get_initial_data()
971
+
972
+ # 2. Now start the initial reCAPTCHA fetch (using the cookie we just got)
973
+ asyncio.create_task(refresh_recaptcha_token())
974
+
975
+ # 3. Start background tasks
976
  asyncio.create_task(periodic_refresh_task())
977
+
978
  except Exception as e:
979
  debug_print(f"❌ Error during startup: {e}")
980
  # Continue anyway - server should still start
 
2025
 
2026
  # Use API key + conversation tracking
2027
  api_key_str = api_key["key"]
2028
+
2029
+ # --- NEW: Get reCAPTCHA v3 Token for Payload ---
2030
+ recaptcha_token = await refresh_recaptcha_token()
2031
+ if not recaptcha_token:
2032
+ debug_print("❌ Cannot proceed, failed to get reCAPTCHA token.")
2033
+ raise HTTPException(
2034
+ status_code=503,
2035
+ detail="Service Unavailable: Failed to acquire reCAPTCHA token. The bridge server may be blocked."
2036
+ )
2037
+ debug_print(f"πŸ”‘ Using reCAPTCHA v3 token: {recaptcha_token[:20]}...")
2038
+ # -----------------------------------------------
2039
 
2040
  # Generate conversation ID from context (API key + model + first user message)
2041
  import hashlib
 
2104
  "experimental_attachments": experimental_attachments,
2105
  "metadata": {}
2106
  },
2107
+ "modality": modality,
2108
+ "recaptchaV3Token": recaptcha_token, # <--- ADD TOKEN HERE
2109
  }
2110
  url = "https://lmarena.ai/nextjs-api/stream/create-evaluation"
2111
  debug_print(f"πŸ“€ Target URL: {url}")
 
2130
  "experimental_attachments": experimental_attachments,
2131
  "metadata": {}
2132
  },
2133
+ "modality": modality,
2134
+ "recaptchaV3Token": recaptcha_token, # <--- ADD TOKEN HERE
2135
  }
2136
  url = f"https://lmarena.ai/nextjs-api/stream/post-to-evaluation/{session['conversation_id']}"
2137
  debug_print(f"πŸ“€ Target URL: {url}")