Spaces:
Runtime error
Runtime error
cloudwaddie commited on
Commit Β·
776f46d
1
Parent(s): f9d8bdf
Add reCAPTCHA v3 integration for enhanced security in API requests
Browse files- 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 |
-
|
| 846 |
-
|
| 847 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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}")
|