NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name LMArena Auto Refresh
// @version 69 mwehehhe
// @description Automates refreshing and other interactions on LMArena, with summarizer compatibility.
// @author Gemini best vibecode buddy
// @match https://*.lmarena.ai/*
// @run-at document-start
// @all-frames true
// @license MIT
// ==/UserScript==
(function () {
'use strict';
// ================================================================= //
// ==================== CONFIGURATION ====================
// ================================================================= //
const CONFIG = {
SERVER_URL: "ws://localhost:9080/ws",
// ================================================================= //
// API Version Configuration (can be dynamically switched)
API_VERSION: 'nextjs-api', // 'auto' | 'nextjs-api' | 'api'
// API Endpoint Configuration
API_ENDPOINTS: {
'nextjs-api': {
signup: '/nextjs-api/sign-up',
stream: '/nextjs-api/stream/create-evaluation',
base: 'https://lmarena.ai'
},
'api': {
signup: '/api/sign-up',
stream: '/api/stream/create-evaluation',
base: 'https://lmarena.ai'
}
},
// Next.js Action IDs (update from lmarena-fd)
NEXT_ACTIONS: {
signup: '40b0280bb60c443da043f8c177fb444d955700cdef',
fileSignUrl: '7024e01382a21d9d64f03b58d7adf4a71b9f971f58',
fileNotify: '6037d6e166db46b3939b254da061e5caf89587b1e2',
verify: '40fc0d1f8f1f9cade86c05d74881beb408c1fbf3c4'
},
// Other Configurations
REQUIRED_COOKIE: "arena-auth-prod-v1",
TURNSTILE_SITEKEY: '0x4AAAAAAA65vWDmG-O_lPtT',
MAX_MODEL_SEARCH_DEPTH: 15, // Borrowed from lmarena-fd
// Debug Mode
DEBUG: false
};
// ================================================================= //
// ==================== STATE MANAGEMENT ====================
// ================================================================= //
let socket;
let isRefreshing = false;
let pendingRequests = [];
let modelRegistrySent = false;
let currentAPIVersion = null; // Dynamically store the currently used API version
const activeFetchControllers = new Map();
// ================================================================= //
// ==================== LOGGING UTILITIES ====================
// ================================================================= //
const log = {
info: (...args) => console.log('[Injector]', ...args),
warn: (...args) => console.warn('[Injector]', ...args),
error: (...args) => console.error('[Injector]', ...args),
debug: (...args) => CONFIG.DEBUG && console.log('[DEBUG]', ...args),
success: (msg) => console.log('%c' + msg, 'color: #28a745; font-weight: bold;')
};
// ================================================================= //
// ==================== API HELPERS (RESTORED) ====================
// ================================================================= //
async function detectAPIVersion() {
if (CONFIG.API_VERSION !== 'auto') {
currentAPIVersion = CONFIG.API_VERSION;
log.info(`Using configured API version: ${currentAPIVersion}`);
return currentAPIVersion;
}
// Fallback logic, though we are forcing the version
currentAPIVersion = 'nextjs-api';
log.warn(`API detection skipped, defaulting to: ${currentAPIVersion}`);
return currentAPIVersion;
}
function getAPIEndpoint(type) {
if (!currentAPIVersion) {
currentAPIVersion = CONFIG.API_VERSION === 'auto' ? 'nextjs-api' : CONFIG.API_VERSION;
}
const endpoints = CONFIG.API_ENDPOINTS[currentAPIVersion];
if (!endpoints || !endpoints[type]) {
log.error(`API endpoint for type "${type}" not found in config for version "${currentAPIVersion}"`);
// Provide a safe fallback to prevent crashing
return `https://lmarena.ai/nextjs-api/stream/create-evaluation`;
}
return `${endpoints.base}${endpoints[type]}`;
}
// ================= COMPLETE RECAPTCHA HELPER (REPLACE YOUR EXISTING ONE) ================= //
async function getRecaptchaToken() {
return new Promise(async (resolve) => {
log.info("🕵️ Performing advanced humanity simulation for Recaptcha...");
// 1. Simulate MORE realistic mouse movement
try {
const movements = [
{ x: Math.random() * 50, y: Math.random() * 100 + 50 },
{ x: window.innerWidth / 4, y: window.innerHeight / 3 },
{ x: window.innerWidth / 2, y: window.innerHeight / 2 },
{ x: window.innerWidth - 100, y: window.innerHeight - 100 }
];
for (const pos of movements) {
document.body.dispatchEvent(new MouseEvent('mousemove', {
clientX: pos.x,
clientY: pos.y,
bubbles: true
}));
await new Promise(r => setTimeout(r, 300 + Math.random() * 400));
}
} catch(e) {
log.debug("Mouse simulation error (non-critical):", e);
}
// 2. Multiple click simulations (important for reCAPTCHA v3)
for (let i = 0; i < 3; i++) {
simulateHumanClick();
await new Promise(r => setTimeout(r, 400 + Math.random() * 300));
}
// 3. Wait longer before requesting token (critical!)
await new Promise(r => setTimeout(r, 5000 + Math.random() * 1000));
// 4. Auto-detect Site Key
let siteKey = null;
const scripts = document.querySelectorAll('script[src*="recaptcha"]');
for (const script of scripts) {
const src = script.getAttribute('src');
const match = src.match(/[?&](k|render)=([^&]+)/);
if (match && match[2]) {
siteKey = match[2];
log.info(`🔑 Detected reCAPTCHA site key: ${siteKey.substring(0, 20)}...`);
break;
}
}
// Fallback to known key
if (!siteKey) {
siteKey = "6Led_uYrAAAAAKjxDIF58fgFtX3t8loNAK85bW9I";
log.warn("⚠️ Using fallback site key");
}
// 5. Generate Token with CORRECT action
if (window.grecaptcha && window.grecaptcha.enterprise) {
try {
window.grecaptcha.enterprise.ready(async () => {
try {
// CRITICAL FIX: Use 'battle' action to match LMArena's backend expectation
const token = await window.grecaptcha.enterprise.execute(siteKey, {
action: 'battle' // ← Changed from 'submit'
});
log.success(`✅ Generated reCAPTCHA V3 Token (Action: battle)`);
log.debug(`Token preview: ${token.substring(0, 30)}...`);
resolve(token);
} catch (e) {
log.warn("⚠️ Recaptcha execute failed:", e);
// Try one more time with a longer delay
await new Promise(r => setTimeout(r, 2000));
try {
const retryToken = await window.grecaptcha.enterprise.execute(siteKey, {
action: 'battle'
});
log.success(`✅ Generated reCAPTCHA token on retry`);
resolve(retryToken);
} catch (retryError) {
log.error("❌ Recaptcha retry failed:", retryError);
resolve(null);
}
}
});
} catch (e) {
log.warn("⚠️ Recaptcha ready error:", e);
resolve(null);
}
} else {
log.warn("⚠️ window.grecaptcha not found - reCAPTCHA script may not be loaded");
resolve(null);
}
});
}
// ================================================================= //
// ==================== ENHANCED MODEL EXTRACTION (from lmarena-fd) ====================
// ================================================================= //
function extractModelRegistry() {
log.info('🔍 Extracting model registry using enhanced algorithm...');
try {
const scripts = document.querySelectorAll('script');
let modelData = null;
for (const script of scripts) {
const content = script.textContent || script.innerHTML;
// Method 1: Use self.__next_f.push pattern (from lmarena-fd)
const regex = /self\.__next_f\.push\(\[(\d+),"([^"]+?):(.*?)"\]\)/g;
let match;
while ((match = regex.exec(content)) !== null) {
const moduleNumber = match[1];
const moduleId = match[2];
const moduleDataStr = match[3];
if (moduleDataStr.includes('initialModels') || moduleDataStr.includes('initialState')) {
log.debug(`Found potential model data in module ${moduleNumber}`);
try {
// Unescape data
const unescapedData = moduleDataStr
.replace(/\\"/g, '"')
.replace(/\\\\/g, '\\')
.replace(/\\n/g, '\n')
.replace(/\\r/g, '\r')
.replace(/\\t/g, '\t');
const parsedData = JSON.parse(unescapedData);
// Deep recursive search (max depth 15)
modelData = findModelsRecursively(parsedData);
if (modelData && modelData.length > 0) {
log.success(`✅ Found ${modelData.length} models using __next_f method`);
break;
}
} catch (parseError) {
log.debug(`Parse error in module ${moduleNumber}: ${parseError.message}`);
// Fallback method: try to extract JSON array
try {
const bracketMatch = moduleDataStr.match(/(\[.*\])/);
if (bracketMatch) {
const bracketData = bracketMatch[1]
.replace(/\\"/g, '"')
.replace(/\\\\/g, '\\');
const parsedBracketData = JSON.parse(bracketData);
modelData = findModelsRecursively(parsedBracketData);
if (modelData && modelData.length > 0) {
log.success(`✅ Found ${modelData.length} models using bracket extraction`);
break;
}
}
} catch (altError) {
// Silently fail and continue trying other methods
}
}
}
}
if (modelData) break;
}
// Method 2: If the above method fails, try searching for initialState (fallback)
if (!modelData || modelData.length === 0) {
log.info('Trying fallback extraction method...');
for (const script of scripts) {
const content = script.textContent || script.innerHTML;
if (content.includes('initialState') || content.includes('initialModels')) {
try {
// Try to extract a large JSON block
const jsonMatch = content.match(/\{[\s\S]*"initialState"[\s\S]*\}/);
if (jsonMatch) {
const jsonStr = jsonMatch[0];
const parsed = JSON.parse(jsonStr);
modelData = findModelsRecursively(parsed);
if (modelData && modelData.length > 0) {
log.success(`✅ Found ${modelData.length} models using fallback method`);
break;
}
}
} catch (e) {
// Continue trying other scripts
}
}
}
}
if (!modelData || modelData.length === 0) {
log.warn('⚠️ Model extraction failed - no models found');
return null;
}
// Build model registry
const registry = {};
modelData.forEach(model => {
if (!model || typeof model !== 'object' || !model.publicName) return;
if (registry[model.publicName]) return; // Avoid duplicates
// Determine model type
let type = 'chat';
if (model.capabilities && model.capabilities.outputCapabilities) {
if (model.capabilities.outputCapabilities.image) type = 'image';
else if (model.capabilities.outputCapabilities.video) type = 'video';
}
registry[model.publicName] = {
type: type,
...model
};
});
log.success(`✅ Extracted ${Object.keys(registry).length} unique models`);
return registry;
} catch (error) {
log.error('❌ Error extracting model registry:', error);
return null;
}
}
// Recursive search for model data (supports depth up to 15)
function findModelsRecursively(obj, depth = 0) {
if (depth > CONFIG.MAX_MODEL_SEARCH_DEPTH) return null;
if (!obj || typeof obj !== 'object') return null;
// Check for various possible property names
const modelKeys = ['initialModels', 'initialState', 'models', 'modelList'];
for (const key of modelKeys) {
if (obj[key] && Array.isArray(obj[key])) {
const models = obj[key];
// Validate if it's a valid model array
if (models.length > 0 && models[0] &&
(models[0].publicName || models[0].name || models[0].id)) {
log.debug(`Found models at depth ${depth} with key "${key}"`);
return models;
}
}
}
// Recursively search in arrays
if (Array.isArray(obj)) {
for (const item of obj) {
const result = findModelsRecursively(item, depth + 1);
if (result) return result;
}
}
// Recursively search in object properties
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const result = findModelsRecursively(obj[key], depth + 1);
if (result) return result;
}
}
return null;
}
// ================================================================= //
// ==================== HUMAN-LIKE INTERACTION ====================
// ================================================================= //
function simulateHumanClick() {
try {
const el = document.body;
// 1. Mouse Events
const opts = {bubbles: true, cancelable: true, clientX: 150, clientY: 300};
el.dispatchEvent(new MouseEvent('mousedown', opts));
el.dispatchEvent(new MouseEvent('mouseup', opts));
el.dispatchEvent(new MouseEvent('click', opts));
// 2. Touch Events (CRITICAL FOR ANDROID)
if (typeof Touch !== 'undefined' && typeof TouchEvent !== 'undefined') {
const touch = new Touch({
identifier: Date.now(),
target: el,
clientX: 150,
clientY: 300,
radiusX: 2.5,
radiusY: 2.5,
rotationAngle: 10,
force: 0.5,
});
const touchStart = new TouchEvent('touchstart', {
cancelable: true,
bubbles: true,
touches: [touch],
targetTouches: [touch],
changedTouches: [touch],
});
const touchEnd = new TouchEvent('touchend', {
cancelable: true,
bubbles: true,
touches: [touch],
targetTouches: [touch],
changedTouches: [touch],
});
el.dispatchEvent(touchStart);
el.dispatchEvent(touchEnd);
}
} catch (e) {
// Ignore errors if Touch API is missing
}
}
// ================================================================= //
// ==================== AUTHENTICATION (REPAIRED) ==================
// ================================================================= //
async function initializeRecaptchaIfNeeded() {
if (window.grecaptcha?.enterprise) return; // Already loaded
log.info("🔧 Initializing Google reCAPTCHA script...");
const script = document.createElement('script');
script.src = 'https://www.google.com/recaptcha/enterprise.js?render=6Led_uYrAAAAAKjxDIF58fgFtX3t8loNAK85bW9I';
script.async = true;
script.defer = true;
document.head.appendChild(script);
// Wait for the script to load and be ready
await new Promise(resolve => setTimeout(resolve, 3000));
}
async function performAuthentication(recaptchaToken) {
log.info(`🔐 Starting authentication process to get session cookie...`);
try {
const signupUrl = getAPIEndpoint('signup');
const provisionalUserId = crypto.randomUUID();
const authResponse = await fetch(signupUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Accept': '*/*' },
body: JSON.stringify({
turnstileToken: recaptchaToken, // The API expects this key name
provisionalUserId: provisionalUserId
})
});
if (!authResponse.ok) {
const responseText = await authResponse.text();
if (authResponse.status === 400 && responseText.includes("User already exists")) {
log.warn(`⚠️ User already exists (400). This is normal and can be ignored.`);
return; // Don't throw an error, a cookie might be set silently.
}
throw new Error(`Auth request failed with status ${authResponse.status}: ${responseText}`);
}
const authData = await authResponse.json();
const cookieValue = `base64-${btoa(JSON.stringify(authData))}`;
document.cookie = `${CONFIG.REQUIRED_COOKIE}=${cookieValue}; path=/; domain=.lmarena.ai; secure; samesite=lax`;
log.success(`✅ Authentication successful. Auth cookie set.`);
} catch (error) {
log.error(`❌ Authentication failed:`, error);
throw error;
}
}
// This function is ONLY for getting the initial cookie.
async function ensureCookieExists() {
if (checkAuthCookie()) {
log.success(`✅ Auth cookie already present.`);
return;
}
log.warn(`⚠️ Missing auth cookie, running initial authentication...`);
await initializeRecaptchaIfNeeded();
const token = await getRecaptchaToken();
if (!token) throw new Error("Could not get reCAPTCHA token for initial auth.");
await performAuthentication(token);
}
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
return null;
}
function checkAuthCookie() {
const authCookie = getCookie(CONFIG.REQUIRED_COOKIE);
return !!authCookie;
}
// ================================================================= //
// ==================== CLOUDFLARE HANDLING ====================
// ================================================================= //
function isCurrentPageCloudflareChallenge() {
try {
if (document.title.includes('Just a moment') ||
document.title.includes('Checking your browser') ||
document.title.includes('Please wait')) {
log.debug("🛡️ CF challenge detected in page title");
return true;
}
const cfIndicators = [
'cf-browser-verification',
'cf-challenge-running',
'cf-wrapper',
'cf-error-details',
'cloudflare-static'
];
for (const indicator of cfIndicators) {
if (document.getElementById(indicator) ||
document.querySelector(`[class*="${indicator}"]`) ||
document.querySelector(`[id*="${indicator}"]`)) {
log.debug(`🛡️ CF challenge detected: found element with ${indicator}`);
return true;
}
}
const bodyText = document.body ? document.body.textContent || document.body.innerText : '';
if (bodyText.includes('Checking your browser before accessing') ||
bodyText.includes('DDoS protection by Cloudflare') ||
bodyText.includes('Enable JavaScript and cookies to continue') ||
bodyText.includes('Please complete the security check') ||
bodyText.includes('Verifying you are human')) {
log.debug("🛡️ CF challenge detected in page text content");
return true;
}
const scripts = document.querySelectorAll('script');
for (const script of scripts) {
const scriptContent = script.textContent || script.innerHTML;
if (scriptContent.includes('__cf_chl_jschl_tk__') ||
scriptContent.includes('window._cf_chl_opt') ||
scriptContent.includes('cf_challenge_response')) {
log.debug("🛡️ CF challenge detected in script content");
return true;
}
}
const normalPageIndicators = [
'nav', 'header', 'main',
'[data-testid]', '[class*="chat"]', '[class*="model"]',
'input[type="text"]', 'textarea'
];
let normalElementsFound = 0;
for (const selector of normalPageIndicators) {
if (document.querySelector(selector)) {
normalElementsFound++;
}
}
if (normalElementsFound >= 3) {
log.debug(`✅ Normal page detected: found ${normalElementsFound} normal elements`);
return false;
}
const totalElements = document.querySelectorAll('*').length;
if (totalElements < 50) {
log.debug(`🛡️ Possible CF challenge: page has only ${totalElements} elements`);
return true;
}
log.debug("✅ No CF challenge indicators found in current page");
return false;
} catch (error) {
log.warn(`⚠️ Error checking current page for CF challenge: ${error.message}`);
return false;
}
}
function isCloudflareChallenge(responseText) {
return responseText.includes('Checking your browser before accessing') ||
responseText.includes('DDoS protection by Cloudflare') ||
responseText.includes('cf-browser-verification') ||
responseText.includes('cf-challenge-running') ||
responseText.includes('__cf_chl_jschl_tk__') ||
responseText.includes('cloudflare-static') ||
responseText.includes('<title>Just a moment...</title>') ||
responseText.includes('Enable JavaScript and cookies to continue') ||
responseText.includes('window._cf_chl_opt') ||
(responseText.includes('cloudflare') && responseText.includes('challenge'));
}
async function waitForCloudflareAuth() {
log.info("⏳ Waiting for Cloudflare authentication to complete...");
const maxWaitTime = 45000;
const checkInterval = 500;
let waitTime = 0;
while (waitTime < maxWaitTime) {
try {
if (!isCurrentPageCloudflareChallenge()) {
log.success(`✅ Cloudflare authentication completed after ${waitTime}ms`);
return true;
}
log.debug(`⏳ Still waiting for CF auth... (${waitTime}ms elapsed)`);
} catch (error) {
log.debug(`⏳ CF auth check failed, continuing to wait... (${waitTime}ms elapsed) - ${error.message}`);
}
await new Promise(resolve => setTimeout(resolve, checkInterval));
waitTime += checkInterval;
}
log.warn(`⚠️ CF authentication wait timeout after ${maxWaitTime}ms`);
return false;
}
async function handleCloudflareRefresh() {
if (isRefreshing) {
log.info("🔄 Already refreshing, skipping duplicate refresh request");
return;
}
isRefreshing = true;
log.info("🔄 Cloudflare challenge detected! Refreshing page to get new token...");
try {
const storedRequests = localStorage.getItem('lmarena_pending_requests');
if (storedRequests) {
const requests = JSON.parse(storedRequests);
log.info(`💾 Found ${requests.length} pending requests, refreshing page...`);
}
window.location.reload();
} catch (error) {
log.error("❌ Error during CF refresh:", error);
isRefreshing = false;
}
}
async function handleRateLimitRefresh() {
if (isRefreshing) {
log.info("🔄 Already refreshing, skipping duplicate rate limit refresh request");
return;
}
isRefreshing = true;
log.info("🚫 Rate limit (429) detected! Deleting auth cookie and refreshing to create new identity...");
try {
log.info(`🗑️ Deleting ALL cookies to ensure fresh identity...`);
const cookies = document.cookie.split(";");
for (let cookie of cookies) {
const eqPos = cookie.indexOf("=");
const name = eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim();
if (name) {
document.cookie = `${name}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
document.cookie = `${name}=; path=/; domain=lmarena.ai; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
document.cookie = `${name}=; path=/; domain=.lmarena.ai; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
}
}
log.info(`🗑️ Deleted ${cookies.length} cookies`);
localStorage.removeItem('lmarena_auth_data');
localStorage.removeItem('lmarena_auth_timestamp');
localStorage.removeItem('lmarena_needs_fresh_identity'); // Clear any fresh identity flag
log.info(`🗑️ Cleared stored auth data`);
const storedRequests = localStorage.getItem('lmarena_pending_requests');
if (storedRequests) {
const requests = JSON.parse(storedRequests);
log.info(`💾 Found ${requests.length} pending requests, refreshing page...`);
}
window.location.reload();
} catch (error) {
log.error("❌ Error during rate limit refresh:", error);
isRefreshing = false;
}
}
// ================================================================= //
// ==================== FILE UPLOAD HANDLING ====================
// ================================================================= //
function base64ToBlob(base64, contentType) {
const byteCharacters = atob(base64);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
return new Blob([byteArray], { type: contentType });
}
async function handleUploadAndChat(requestId, payload, filesToUpload) {
const abortController = new AbortController();
activeFetchControllers.set(requestId, abortController);
try {
log.info(`🚀 Starting upload and chat for request ${requestId}`);
await ensureAuthenticationReady(requestId);
const attachments = [];
for (const file of filesToUpload) {
log.info(`Processing file: ${file.fileName}`);
// Step 1: Get Signed URL
log.info(`Step 1: Getting signed URL for ${file.fileName}`);
const signUrlResponse = await fetch('https://lmarena.ai/?mode=direct', {
method: 'POST',
headers: {
'Content-Type': 'text/plain;charset=UTF-8',
'Accept': 'text/x-component',
'next-action': CONFIG.NEXT_ACTIONS.fileSignUrl,
'next-router-state-tree': '%5B%22%22%2C%7B%22children%22%3A%5B%5B%22locale%22%2C%22en%22%2C%22d%22%5D%2C%7B%22children%22%3A%5B%22(app)%22%2C%7B%22children%22%3A%5B%22(with-sidebar)%22%2C%7B%22children%22%3A%5B%22__PAGE__%3F%7B%5C%22mode%5C%22%3A%5C%22direct%5C%22%7D%22%2C%7B%7D%2C%22%2F%3Fmode%3Ddirect%22%2C%22refresh%22%5D%7D%2Cnull%2Cnull%5D%7D%2Cnull%2Cnull%2Ctrue%5D%7D%2Cnull%2Cnull%5D%7D%2Cnull%2Cnull%5D',
'origin': 'https://lmarena.ai',
'referer': 'https://lmarena.ai/'
},
body: JSON.stringify([file.fileName, file.contentType]),
signal: abortController.signal
});
const signUrlText = await signUrlResponse.text();
log.debug("Received for signed URL:", signUrlText);
let signUrlData = null;
let match = signUrlText.match(/\d+:({.*})/);
if (match && match.length >= 2) {
log.debug(`Found data with '${match[0].split(':')[0]}:' prefix`);
signUrlData = JSON.parse(match[1]);
} else {
try {
signUrlData = JSON.parse(signUrlText);
log.debug("Parsed entire response as JSON");
} catch (e) {
const jsonMatches = signUrlText.match(/{[^}]*"uploadUrl"[^}]*}/g);
if (jsonMatches && jsonMatches.length > 0) {
signUrlData = JSON.parse(jsonMatches[0]);
log.debug("Found JSON object containing uploadUrl");
} else {
throw new Error(`Could not parse signed URL response. Response: ${signUrlText}`);
}
}
}
if (!signUrlData || !signUrlData.data || !signUrlData.data.uploadUrl) {
throw new Error('Signed URL data is incomplete or invalid after parsing.');
}
const { uploadUrl, key } = signUrlData.data;
log.info(`Got signed URL. Key: ${key}`);
// Step 2: Upload file to storage
log.info(`Step 2: Uploading file to cloud storage...`);
const blob = base64ToBlob(file.data, file.contentType);
const uploadResponse = await fetch(uploadUrl, {
method: 'PUT',
headers: { 'Content-Type': file.contentType },
body: blob,
signal: abortController.signal
});
if (!uploadResponse.ok) throw new Error(`File upload failed with status ${uploadResponse.status}`);
log.success(`File uploaded successfully.`);
// Step 3: Notify LMArena of upload
log.info(`Step 3: Notifying LMArena of upload completion...`);
const notifyResponse = await fetch('https://lmarena.ai/?mode=direct', {
method: 'POST',
headers: {
'Content-Type': 'text/plain;charset=UTF-8',
'Accept': 'text/x-component',
'next-action': CONFIG.NEXT_ACTIONS.fileNotify,
'next-router-state-tree': '%5B%22%22%2C%7B%22children%22%3A%5B%5B%22locale%22%2C%22en%22%2C%22d%22%5D%2C%7B%22children%22%3A%5B%22(app)%22%2C%7B%22children%22%3A%5B%22(with-sidebar)%22%2C%7B%22children%22%3A%5B%22__PAGE__%3F%7B%5C%22mode%5C%22%3A%5C%22direct%5C%22%7D%22%2C%7B%7D%2C%22%2F%3Fmode%3Ddirect%22%2C%22refresh%22%5D%7D%2Cnull%2Cnull%5D%7D%2Cnull%2Cnull%2Ctrue%5D%7D%2Cnull%2Cnull%5D%7D%2Cnull%2Cnull%5D',
'origin': 'https://lmarena.ai',
'referer': 'https://lmarena.ai/'
},
body: JSON.stringify([key]),
signal: abortController.signal
});
const notifyText = await notifyResponse.text();
log.debug(`Notification sent. Response:`, notifyText);
const finalUrlDataLine = notifyText.split('\n').find(line => line.startsWith('1:'));
if (!finalUrlDataLine) throw new Error('Could not find final URL data in notification response.');
const finalUrlData = JSON.parse(finalUrlDataLine.substring(2));
const finalUrl = finalUrlData.data.url;
if (!finalUrl) throw new Error('Final URL not found in notification response data.');
log.info(`Extracted final GetObject URL: ${finalUrl}`);
attachments.push({
name: key,
contentType: file.contentType,
url: finalUrl
});
}
// Step 4: Modify payload with attachments and send final request
log.info('All files uploaded. Modifying final payload...');
const userMessage = payload.messages.find(m => m.role === 'user');
if (userMessage) {
userMessage.experimental_attachments = attachments;
} else {
throw new Error("Could not find user message in payload to attach files to.");
}
log.info('Payload modified. Initiating final chat stream.');
await executeFetchAndStreamBack(requestId, payload);
} catch (error) {
if (error.name === 'AbortError') {
log.info(`Upload process aborted for request ${requestId}`);
} else {
log.error(`Error during file upload process for request ${requestId}:`, error);
if (error.message && error.message.includes('429')) {
log.info(`🚫 Rate limit detected during upload`);
const existingRequests = JSON.parse(localStorage.getItem('lmarena_pending_requests') || '[]');
const alreadyStored = existingRequests.some(req => req.requestId === requestId);
if (!alreadyStored) {
existingRequests.push({
requestId,
payload,
files_to_upload: filesToUpload
});
localStorage.setItem('lmarena_pending_requests', JSON.stringify(existingRequests));
log.info(`💾 Stored upload request ${requestId} with ${filesToUpload.length} files for retry`);
}
handleRateLimitRefresh();
return;
}
sendToServer(requestId, JSON.stringify({ error: `File upload failed: ${error.message}` }));
sendToServer(requestId, "[DONE]");
}
} finally {
activeFetchControllers.delete(requestId);
}
}
// ================================================================= //
// ==================== FETCH AND STREAM (CRITICAL FIX) ============
// ================================================================= //
async function executeFetchAndStreamBack(requestId, payload) {
const abortController = new AbortController();
activeFetchControllers.set(requestId, abortController);
try {
log.info(`🚀 Starting fetch for request ${requestId}`);
// Step 1: Make sure the base session cookie exists.
await ensureCookieExists();
// Step 2: Get a FRESH reCAPTCHA token for THIS SPECIFIC request.
log.info(" sourcing fresh reCAPTCHA token for chat request...");
const freshRecaptchaToken = await getRecaptchaToken();
if (!freshRecaptchaToken) {
throw new Error("Failed to get a fresh reCAPTCHA token for the chat request.");
}
// Step 3: Add the fresh token to the payload.
const payloadWithToken = {
...payload,
turnstileToken: freshRecaptchaToken // The API expects this key name
};
const streamUrl = getAPIEndpoint('stream');
log.info(`Using stream endpoint: ${streamUrl}`);
const response = await fetch(streamUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }, // Use application/json
body: JSON.stringify(payloadWithToken), // Send the payload with the token
signal: abortController.signal
});
if (!response.ok) {
const responseText = await response.text();
console.error("❌ Fetch failed!");
console.error("❌ Status:", response.status);
response.headers.forEach((value, key) => { console.error(` ${key}: ${value}`); });
console.error("❌ Full response body:", responseText);
if (response.status === 429) {
log.error("🚫 Rate limit (429) detected. Triggering full refresh.");
handleRateLimitRefresh();
return;
}
throw new Error(`Fetch failed with status ${response.status}`);
}
if (!response.body) throw new Error(`No response body received`);
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { value, done } = await reader.read();
if (done) {
sendToServer(requestId, "[DONE]");
break;
}
sendToServer(requestId, decoder.decode(value));
}
} catch (error) {
if (error.name === 'AbortError') {
log.info(`Fetch aborted for request ${requestId}`);
} else {
log.error(`❌ Error during fetch for request ${requestId}:`, error);
sendToServer(requestId, JSON.stringify({ error: error.message }));
sendToServer(requestId, "[DONE]");
}
} finally {
activeFetchControllers.delete(requestId);
}
}
// ================================================================= //
// ==================== PENDING REQUESTS PROCESSING (CLEANED) ========
// ================================================================= //
async function processPendingRequests() {
const storedRequests = localStorage.getItem('lmarena_pending_requests');
if (!storedRequests) return;
let requests;
try {
requests = JSON.parse(storedRequests);
} catch (e) {
localStorage.removeItem('lmarena_pending_requests');
return;
}
if (!requests || requests.length === 0) {
localStorage.removeItem('lmarena_pending_requests');
return;
}
log.info(`🔄 Found ${requests.length} pending requests. Waiting for page to be ready...`);
localStorage.removeItem('lmarena_pending_requests'); // Clear it immediately
// This is critical: wait for the main Cloudflare page to load before doing anything.
await waitForCloudflareAuth();
await new Promise(r => setTimeout(r, 2000)); // Extra wait for page to settle
for (const req of requests) {
log.info(`🔄 Retrying request ${req.requestId}`);
try {
if (req.files_to_upload && req.files_to_upload.length > 0) {
await handleUploadAndChat(req.requestId, req.payload, req.files_to_upload);
} else {
await executeFetchAndStreamBack(req.requestId, req.payload);
}
} catch (error) {
log.error(`❌ Failed to retry request ${req.requestId}:`, error);
}
await new Promise(r => setTimeout(r, 750)); // Stagger retries
}
}
// ================================================================= //
// ==================== WEBSOCKET CONNECTION (FINAL CLEANUP) =========
// ================================================================= //
function connect() {
log.info(`Connecting to server at ${CONFIG.SERVER_URL}...`);
socket = new WebSocket(CONFIG.SERVER_URL);
socket.onopen = async () => {
log.success("✅ Connection established with local server.");
await detectAPIVersion();
// This now handles all recovery.
processPendingRequests();
if (!modelRegistrySent) {
setTimeout(() => {
sendModelRegistry();
}, 2000);
}
};
socket.onmessage = async (event) => {
try {
const message = JSON.parse(event.data);
// Simplified listeners
if (message.type === 'ping') { return; }
if (message.type === 'refresh_models') { sendModelRegistry(); return; }
if (message.type === 'model_registry_ack') { modelRegistrySent = true; return; }
if (message.type === 'abort_request') {
const controller = activeFetchControllers.get(message.request_id);
if (controller) {
controller.abort();
activeFetchControllers.delete(message.request_id);
log.success(`✅ Aborted fetch request ${message.request_id}`);
}
return;
}
const { request_id, payload, files_to_upload } = message;
if (!request_id || !payload) {
log.error("Invalid message from server:", message);
return;
}
if (files_to_upload && files_to_upload.length > 0) {
log.info(`⬆️ Received request with ${files_to_upload.length} file(s).`);
await handleUploadAndChat(request_id, payload, files_to_upload);
} else {
log.info(`⬇️ Received standard text request ${request_id}.`);
await executeFetchAndStreamBack(request_id, payload);
}
} catch (error) {
log.error("Error processing message from server:", error);
}
};
socket.onclose = () => {
log.warn("🔌 Connection to local server closed. Retrying in 5 seconds...");
modelRegistrySent = false;
activeFetchControllers.forEach(controller => controller.abort());
activeFetchControllers.clear();
setTimeout(connect, 5000);
};
socket.onerror = (error) => {
log.error("❌ WebSocket error:", error);
socket.close();
};
}
function sendToServer(requestId, data) {
if (activeFetchControllers.get(requestId)?.signal.aborted) return;
if (socket && socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify({ request_id: requestId, data: data }));
}
}
function sendModelRegistry() {
if (!socket || socket.readyState !== WebSocket.OPEN) {
log.warn('⚠️ WebSocket not ready, cannot send model registry');
return;
}
const models = extractModelRegistry();
if (models && Object.keys(models).length > 0) {
const message = { type: 'model_registry', models: models };
socket.send(JSON.stringify(message));
log.success(`📤 Sent model registry with ${Object.keys(models).length} models`);
modelRegistrySent = true; // Mark as sent
} else {
log.warn('⚠️ No models extracted, not sending registry');
}
}
// ================================================================= //
// ==================== INITIALIZATION ====================
// ================================================================= //
log.success('🚀 LMArena Proxy Injector V2 Starting...');
// Check if we need a fresh identity
if (localStorage.getItem('lmarena_needs_fresh_identity') === 'true') {
log.info('🔄 Fresh identity requested, clearing all auth data...');
localStorage.removeItem('lmarena_needs_fresh_identity');
// Clear all auth-related data
localStorage.removeItem('lmarena_auth_data');
localStorage.removeItem('lmarena_auth_timestamp');
// Clear cookies
const cookies = document.cookie.split(";");
for (let cookie of cookies) {
const eqPos = cookie.indexOf("=");
const name = eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim();
if (name) {
document.cookie = `${name}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
document.cookie = `${name}=; path=/; domain=lmarena.ai; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
document.cookie = `${name}=; path=/; domain=.lmarena.ai; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
}
}
log.success('✅ Cleared all authentication data for fresh start');
}
connect();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
setTimeout(sendModelRegistry, 3000);
});
} else {
setTimeout(sendModelRegistry, 3000);
}
log.success('✅ LMArena Proxy Injector V2 initialized successfully!');
// ================================================================= //
// ==================== END OF SCRIPT ====================
// ================================================================= //
})();