cloudwaddie commited on
Commit
e5bdf17
Β·
1 Parent(s): 776f46d
Files changed (7) hide show
  1. INFO.md +62 -0
  2. LICENSE +0 -21
  3. README.md +1 -1
  4. chat_interactive.py +1 -1
  5. models.json +0 -0
  6. src/__pycache__/main.cpython-313.pyc +0 -0
  7. src/main.py +130 -52
INFO.md ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Here is the updated plan for `main.py`.
2
+
3
+ ###The Plan1. **Modify `get_recaptcha_v3_token**`:
4
+ * **Headless Mode:** Ensure `AsyncCamoufox(headless=False, ...)` is set.
5
+ * **Navigation:** Go to `https://lmarena.ai/`.
6
+ * **Cloudflare Challenge:** Immediately after loading, inject a check for the Turnstile widget.
7
+ * Use a loop to check if the page title is "Just a moment..." or if the Turnstile selector exists.
8
+ * Call your existing `click_turnstile(page)` function if detected.
9
+ * Wait for the challenge to clear (title change or selector disappearance).
10
+
11
+
12
+ * **Remove Auth Scraper:** Delete the loop that waits specifically for the `arena-auth-prod-v1` cookie. We only care about passing the challenge so the `grecaptcha` library loads.
13
+ * **Proceed:** Continue to the existing "Side-Channel" logic (human pause -> wait for library -> trigger execution).
14
+
15
+
16
+
17
+ ###Edit Instructions for `main.py`**Step 1:** In `get_recaptcha_v3_token`, confirm the browser init line is:
18
+
19
+ ```python
20
+ async with AsyncCamoufox(headless=False, main_world_eval=True) as browser:
21
+
22
+ ```
23
+
24
+ **Step 2:** Replace the "Auth Loop" section (approx lines 180-205 in your provided code) with this Challenge Logic:
25
+
26
+ ```python
27
+ # ... inside get_recaptcha_v3_token ...
28
+ debug_print(" 🌐 Navigating to lmarena.ai...")
29
+ await page.goto("https://lmarena.ai/", wait_until="domcontentloaded")
30
+
31
+ # --- NEW: Cloudflare/Turnstile Pass-Through ---
32
+ debug_print(" πŸ›‘οΈ Checking for Cloudflare Turnstile...")
33
+
34
+ # Allow time for the widget to render if it's going to
35
+ try:
36
+ # Check for challenge title or widget presence
37
+ for _ in range(5):
38
+ title = await page.title()
39
+ if "Just a moment" in title:
40
+ debug_print(" πŸ”’ Cloudflare challenge active. Attempting to click...")
41
+ clicked = await click_turnstile(page)
42
+ if clicked:
43
+ debug_print(" βœ… Clicked Turnstile.")
44
+ # Give it time to verify
45
+ await asyncio.sleep(3)
46
+ else:
47
+ # If title is normal, we might still have a widget on the page
48
+ await click_turnstile(page)
49
+ break
50
+ await asyncio.sleep(1)
51
+
52
+ # Wait for the page to actually settle into the main app
53
+ await page.wait_for_load_state("domcontentloaded")
54
+ except Exception as e:
55
+ debug_print(f" ⚠️ Error handling Turnstile: {e}")
56
+ # ----------------------------------------------
57
+
58
+ # 1. Wake up the page (Humanize) - Keep this as is
59
+ debug_print(" πŸ–±οΈ Waking up page...")
60
+ # ... rest of function ...
61
+
62
+ ```
LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 Edward Fazackerley
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md CHANGED
@@ -1,4 +1,4 @@
1
- # LM Arena Bridge - CURRENTLY BROKEN DUE TO ANTI-BOT MEASURES BY LMARENA (https://github.com/CloudWaddie/LMArenaBridge/issues/27)
2
 
3
  ## Description
4
 
 
1
+ # LM Arena Bridge - CURRENTLY EXPERIMENTALLY FIXED DUE TO ANTI-BOT MEASURES BY LMARENA (https://github.com/CloudWaddie/LMArenaBridge/issues/27)
2
 
3
  ## Description
4
 
chat_interactive.py CHANGED
@@ -8,7 +8,7 @@ import sys
8
 
9
  # Configuration
10
  BASE_URL = "http://localhost:8000/api/v1"
11
- API_KEY = "sk-lmab-4d4c13f6-7846-4f94-a261-f59911838196" # Replace with your API key
12
 
13
  def list_available_models(client):
14
  """List all available models"""
 
8
 
9
  # Configuration
10
  BASE_URL = "http://localhost:8000/api/v1"
11
+ API_KEY = "sk-lmab-50664b6f-87c7-4115-b630-eb38a9b55021" # Replace with your API key
12
 
13
  def list_available_models(client):
14
  """List all available models"""
models.json CHANGED
The diff for this file is too large to render. See raw diff
 
src/__pycache__/main.cpython-313.pyc DELETED
Binary file (79 kB)
 
src/main.py CHANGED
@@ -200,20 +200,50 @@ def debug_print(*args, **kwargs):
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([{
@@ -222,67 +252,114 @@ async def get_recaptcha_v3_token() -> Optional[str]:
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():
@@ -784,7 +861,7 @@ async def rate_limit_api_key(key: str = Depends(API_KEY_HEADER)):
784
  async def get_initial_data():
785
  debug_print("Starting initial data retrieval...")
786
  try:
787
- async with AsyncCamoufox(headless=True) as browser:
788
  page = await browser.new_page()
789
 
790
  # Set up route interceptor BEFORE navigating
@@ -970,7 +1047,8 @@ async def startup_event():
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())
 
200
  RECAPTCHA_SITEKEY = "6Led_uYrAAAAAKjxDIF58fgFtX3t8loNAK85bW9I"
201
  RECAPTCHA_ACTION = "chat_submit"
202
 
203
+ async def click_turnstile(page):
204
+ """
205
+ Attempts to locate and click the Cloudflare Turnstile widget.
206
+ Based on gpt4free logic.
207
+ """
208
+ debug_print(" πŸ–±οΈ Attempting to click Cloudflare Turnstile...")
209
+ try:
210
+ # Common selectors used by LMArena's Turnstile implementation
211
+ selectors = [
212
+ '#cf-turnstile',
213
+ 'iframe[src*="challenges.cloudflare.com"]',
214
+ '[style*="display: grid"] iframe' # The grid style often wraps the checkbox
215
+ ]
216
+
217
+ for selector in selectors:
218
+ element = await page.query_selector(selector)
219
+ if element:
220
+ # Get bounding box to click specific coordinates if needed
221
+ box = await element.bounding_box()
222
+ if box:
223
+ x = box['x'] + (box['width'] / 2)
224
+ y = box['y'] + (box['height'] / 2)
225
+ debug_print(f" 🎯 Found widget at {x},{y}. Clicking...")
226
+ await page.mouse.click(x, y)
227
+ await asyncio.sleep(2)
228
+ return True
229
+ return False
230
+ except Exception as e:
231
+ debug_print(f" ⚠️ Error clicking turnstile: {e}")
232
+ return False
233
+
234
  async def get_recaptcha_v3_token() -> Optional[str]:
235
  """
236
+ Retrieves reCAPTCHA v3 token using a 'Side-Channel' approach.
237
+ We write the token to a global window variable and poll for it,
238
+ bypassing Promise serialization issues in the Main World bridge.
239
  """
240
+ debug_print("πŸ” Starting reCAPTCHA v3 token retrieval (Side-Channel Mode)...")
241
 
 
242
  config = get_config()
243
  cf_clearance = config.get("cf_clearance", "")
244
 
245
  try:
246
+ async with AsyncCamoufox(headless=True, main_world_eval=True) as browser:
 
247
  context = await browser.new_context()
248
  if cf_clearance:
249
  await context.add_cookies([{
 
252
  "domain": ".lmarena.ai",
253
  "path": "/"
254
  }])
 
255
 
256
  page = await context.new_page()
257
 
 
258
  debug_print(" 🌐 Navigating to lmarena.ai...")
259
  await page.goto("https://lmarena.ai/", wait_until="domcontentloaded")
260
 
261
+ # --- NEW: Cloudflare/Turnstile Pass-Through ---
262
+ debug_print(" πŸ›‘οΈ Checking for Cloudflare Turnstile...")
263
+
264
+ # Allow time for the widget to render if it's going to
 
 
 
 
 
 
 
 
 
265
  try:
266
+ # Check for challenge title or widget presence
267
+ for _ in range(5):
268
+ title = await page.title()
269
+ if "Just a moment" in title:
270
+ debug_print(" πŸ”’ Cloudflare challenge active. Attempting to click...")
271
+ clicked = await click_turnstile(page)
272
+ if clicked:
273
+ debug_print(" βœ… Clicked Turnstile.")
274
+ # Give it time to verify
275
+ await asyncio.sleep(3)
276
+ else:
277
+ # If title is normal, we might still have a widget on the page
278
+ await click_turnstile(page)
279
+ break
280
+ await asyncio.sleep(1)
281
+
282
+ # Wait for the page to actually settle into the main app
283
+ await page.wait_for_load_state("domcontentloaded")
284
  except Exception as e:
285
+ debug_print(f" ⚠️ Error handling Turnstile: {e}")
286
+ # ----------------------------------------------
287
+
288
+ # 1. Wake up the page (Humanize)
289
+ debug_print(" πŸ–±οΈ Waking up page...")
290
+ await page.mouse.move(100, 100)
291
+ await page.mouse.wheel(0, 200)
292
+ await asyncio.sleep(2) # Vital "Human" pause
293
+
294
+ # 2. Check for Library
295
+ debug_print(" ⏳ Checking for library...")
296
+ lib_ready = await page.evaluate("mw:() => !!(window.grecaptcha && window.grecaptcha.enterprise)")
297
+ if not lib_ready:
298
+ debug_print(" ⚠️ Library not found immediately. Waiting...")
299
+ await asyncio.sleep(3)
300
+ lib_ready = await page.evaluate("mw:() => !!(window.grecaptcha && window.grecaptcha.enterprise)")
301
+ if not lib_ready:
302
+ debug_print("❌ reCAPTCHA library never loaded.")
303
+ return None
304
 
305
+ # 3. SETUP: Initialize our global result variable
306
+ # We use a unique name to avoid conflicts
307
+ await page.evaluate("mw:window.__token_result = 'PENDING'")
308
+
309
+ # 4. TRIGGER: Execute reCAPTCHA and write to the variable
310
+ # We do NOT await the result here. We just fire the process.
311
+ debug_print(" πŸš€ Triggering reCAPTCHA execution...")
312
+ trigger_script = f"""mw:() => {{
313
+ try {{
314
+ window.grecaptcha.enterprise.execute('{RECAPTCHA_SITEKEY}', {{ action: '{RECAPTCHA_ACTION}' }})
315
+ .then(token => {{
316
+ window.__token_result = token;
317
+ }})
318
+ .catch(err => {{
319
+ window.__token_result = 'ERROR: ' + err.toString();
320
  }});
321
+ }} catch (e) {{
322
+ window.__token_result = 'SYNC_ERROR: ' + e.toString();
323
+ }}
324
+ }}"""
325
+
326
+ await page.evaluate(trigger_script)
327
+
328
+ # 5. POLL: Watch the variable for changes
329
+ debug_print(" πŸ‘€ Polling for result...")
330
+ token = None
331
+
332
+ for i in range(20): # Wait up to 20 seconds
333
+ # Read the global variable
334
+ result = await page.evaluate("mw:window.__token_result")
335
+
336
+ if result != 'PENDING':
337
+ if result and result.startswith('ERROR'):
338
+ debug_print(f"❌ JS Execution Error: {result}")
339
+ return None
340
+ elif result and result.startswith('SYNC_ERROR'):
341
+ debug_print(f"❌ JS Sync Error: {result}")
342
+ return None
343
+ else:
344
+ token = result
345
+ debug_print(f"βœ… Token captured! ({len(token)} chars)")
346
+ break
347
+
348
+ if i % 2 == 0:
349
+ debug_print(f" ... waiting ({i}s)")
350
+ await asyncio.sleep(1)
351
 
352
  if token:
353
+ global RECAPTCHA_TOKEN, RECAPTCHA_EXPIRY
354
+ RECAPTCHA_TOKEN = token
355
+ RECAPTCHA_EXPIRY = datetime.now(timezone.utc) + timedelta(seconds=110)
356
  return token
357
  else:
358
+ debug_print("❌ Timed out waiting for token variable to update.")
359
  return None
360
 
361
  except Exception as e:
362
+ debug_print(f"❌ Unexpected error: {e}")
363
  return None
364
 
365
  async def refresh_recaptcha_token():
 
861
  async def get_initial_data():
862
  debug_print("Starting initial data retrieval...")
863
  try:
864
+ async with AsyncCamoufox(headless=True, main_world_eval=True) as browser:
865
  page = await browser.new_page()
866
 
867
  # Set up route interceptor BEFORE navigating
 
1047
  await get_initial_data()
1048
 
1049
  # 2. Now start the initial reCAPTCHA fetch (using the cookie we just got)
1050
+ # Block startup until we have a token or fail, so we don't serve 403s
1051
+ await refresh_recaptcha_token()
1052
 
1053
  # 3. Start background tasks
1054
  asyncio.create_task(periodic_refresh_task())