LMArena Code Downloader (Universal Fix)

Adds a download button to code blocks on lmarena.ai. Universal fix for all script managers.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         LMArena Code Downloader (Universal Fix)
// @name:zh-CN   大模型竞技场代码下载器 (通用修复版)
// @namespace    http://tampermonkey.net/
// @version      2.1
// @description  Adds a download button to code blocks on lmarena.ai. Universal fix for all script managers.
// @description:zh-CN 在 LMArena.ai 页面为代码块添加下载按钮。通用修复版,兼容所有脚本管理器。
// @author       AI & Human
// @match        https://lmarena.ai/*
// @icon         data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLWRvd25sb2FkIj48cGF0aCBkPSJNMjEgMTV2NGEyIDIgMCAwIDEtMiAySDVhMiAyIDAgMCAxLTItMlYxNSIvPjxwYXRoIGQ9Im03IDEwIDEgNSA1LTUtNSIvPjxwYXRoIGQ9Ik0xMiAxNVY0Ii8+PC9zdmc+
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // --- 辅助函数 ---

    /**
     * 向页面添加全局CSS样式,替代GM_addStyle,兼容所有环境。
     * @param {string} css - 要添加的CSS规则字符串。
     */
    function addGlobalStyle(css) {
        const head = document.head || document.getElementsByTagName('head')[0];
        if (!head) { return; }
        const style = document.createElement('style');
        style.type = 'text/css';
        style.innerHTML = css;
        head.appendChild(style);
    }

    function getFileExtension(preElement) {
        const codeElement = preElement.querySelector('code');
        if (!codeElement) return 'txt';

        const className = codeElement.className || '';
        const match = className.match(/language-(\w+)/);
        if (match && match[1]) {
            const langMap = {
                python: 'py', javascript: 'js', typescript: 'ts', html: 'html',
                css: 'css', java: 'java', csharp: 'cs', cpp: 'cpp', c: 'c',
                bash: 'sh', shell: 'sh', json: 'json', markdown: 'md',
                go: 'go', rust: 'rs', sql: 'sql', yaml: 'yml', xml: 'xml',
            };
            return langMap[match[1].toLowerCase()] || match[1];
        }
        return 'txt';
    }

    function getModelName(preElement) {
        const messageContainer = preElement.closest('div.flex.gap-6');
        if (messageContainer) {
            const modelNameElement = messageContainer.querySelector('p.font-mono');
            if (modelNameElement) {
                return modelNameElement.textContent.trim().replace(/[^a-zA-Z0-9-.]/g, '_');
            }
        }
        return 'unknown-model';
    }

    function downloadCode(codeContent, filename) {
        const blob = new Blob([codeContent], { type: 'text/plain;charset=utf-8' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = filename;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);
    }

    // --- 核心逻辑 ---

    function addDownloadButtons() {
        // 使用更精确的选择器,专门针对聊天内容区的代码块
        const codeBlocks = document.querySelectorAll('main ol div.prose pre:not([data-download-button-added])');

        if (codeBlocks.length > 0) {
            console.log(`[LMArena Downloader] Found ${codeBlocks.length} new code blocks to process.`);
        }

        codeBlocks.forEach(pre => {
            pre.setAttribute('data-download-button-added', 'true');

            // 按钮将被添加到<pre>标签内的相对定位的div中
            const relativeContainer = pre.querySelector('div.relative');
            if (!relativeContainer) {
                console.error('[LMArena Downloader] Could not find relative container for a code block.');
                return;
            }

            // 找到原生的复制按钮,我们的按钮会放在它旁边
            const nativeCopyButton = relativeContainer.querySelector('button[data-sentry-component="CopyButton"]');
            if (!nativeCopyButton) {
                console.warn('[LMArena Downloader] Could not find the native copy button. The download button will be appended at the end.');
            }

            const downloadButton = document.createElement('button');
            downloadButton.className = 'code-downloader-button';
            downloadButton.title = 'Download code';
            downloadButton.innerHTML = `
                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                    <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V15"/>
                    <polyline points="7 10 12 15 17 10"/>
                    <line x1="12" y1="15" x2="12" y2="3"/>
                </svg>
            `;

            downloadButton.addEventListener('click', (e) => {
                e.stopPropagation();
                const codeElement = pre.querySelector('code');
                const codeContent = codeElement ? (codeElement.textContent || '') : '';
                const extension = getFileExtension(pre);
                const modelName = getModelName(pre);
                const filename = `${modelName}-${Date.now()}.${extension}`;
                downloadCode(codeContent, filename);
            });

            // 如果找到了复制按钮,就把下载按钮插在它前面,否则就添加到容器末尾
            if (nativeCopyButton) {
                nativeCopyButton.parentElement.insertBefore(downloadButton, nativeCopyButton);
            } else {
                relativeContainer.appendChild(downloadButton);
            }
        });
    }

    // --- 启动与监听 ---

    // 1. 先注入样式
    addGlobalStyle(`
        .code-downloader-button {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            padding: 6px;
            border-radius: 6px;
            cursor: pointer;
            color: var(--interactive-active);
            transition: background-color 0.2s, color 0.2s;
            border: none;
            background-color: transparent;
        }
        .code-downloader-button:hover {
            background-color: var(--surface-hover, #f0f0f0);
            color: var(--text-primary, #000);
        }
        .code-downloader-button svg {
            width: 18px;
            height: 18px;
        }
    `);


    // 2. 使用 MutationObserver 监听 DOM 变化
    const observer = new MutationObserver((mutations) => {
        if (mutations.some(m => m.addedNodes.length > 0)) {
            setTimeout(addDownloadButtons, 300);
        }
    });

    // 3. 等待核心聊天区域出现后,再开始执行和监听
    const startupInterval = setInterval(() => {
        const chatArea = document.querySelector('main ol');
        if (chatArea) {
            console.log('[LMArena Downloader] Chat area found. Initializing script.');
            clearInterval(startupInterval);

            addDownloadButtons(); // 立即执行一次

            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
        }
    }, 500);

})();