Spaces:
Runtime error
Runtime error
cloudwaddie commited on
Commit Β·
e5bdf17
1
Parent(s): 776f46d
fix
Browse files- INFO.md +62 -0
- LICENSE +0 -21
- README.md +1 -1
- chat_interactive.py +1 -1
- models.json +0 -0
- src/__pycache__/main.cpython-313.pyc +0 -0
- 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
|
| 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-
|
| 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 |
-
|
| 206 |
-
|
|
|
|
| 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 |
-
#
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 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 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
except Exception as e:
|
| 253 |
-
debug_print(f"
|
| 254 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
|
| 256 |
-
# 3.
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
}});
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
}}
|
| 275 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
|
| 277 |
if token:
|
| 278 |
-
|
|
|
|
|
|
|
| 279 |
return token
|
| 280 |
else:
|
| 281 |
-
debug_print("β
|
| 282 |
return None
|
| 283 |
|
| 284 |
except Exception as e:
|
| 285 |
-
debug_print(f"β Unexpected error
|
| 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 |
-
|
|
|
|
| 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())
|