plaguis / LMArena Auto Refresh

// ==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 ====================
    // ================================================================= //

})();