Greasy Fork

Greasy Fork is available in English.

UOOC 优课联盟助手 - 全能版 (自动刷课/测验AI/讨论区)

【作者:afdsafg】优课联盟(UOOC)全能辅助工具:1. 视频自动刷课(倍速/静音/连播/防暂停);2. 视频中途弹题秒杀;3. 章节测验AI助手(支持DeepSeek/ChatGPT等多模型并发会诊/一键复制题目);4. 讨论区自动复读刷屏。一站式解决刷课烦恼。

当前为 2025-12-13 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         UOOC 优课联盟助手 - 全能版 (自动刷课/测验AI/讨论区)
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  【作者:afdsafg】优课联盟(UOOC)全能辅助工具:1. 视频自动刷课(倍速/静音/连播/防暂停);2. 视频中途弹题秒杀;3. 章节测验AI助手(支持DeepSeek/ChatGPT等多模型并发会诊/一键复制题目);4. 讨论区自动复读刷屏。一站式解决刷课烦恼。
// @author       afdsafg
// @match        *://*.uooc.net.cn/*
// @match        *://*.uooconline.com/*
// @connect      *
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_setClipboard
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // ==========================================
    // 核心调度中心
    // ==========================================
    if (window.self === window.top) {
        // --- 主窗口环境 ---
        initVideoHelper();      // 模块A: 视频刷课 & 弹题
        initDiscussionHelper(); // 模块B: 讨论区自动回复
    } else {
        // --- Iframe 环境 ---
        initQuizHelper();       // 模块C: 章节测验 AI 助手
    }


    // ==========================================
    // 模块A:视频刷课 + 悬浮球 + 弹题秒杀
    // ==========================================
    function initVideoHelper() {
        console.log("[UOOC全能助手] 视频模块加载...");

        // 1. 注入视频面板样式
        const css = `
            #uooc-video-panel { position:fixed; top:100px; right:50px; width:220px; background:#2c3e50; color:#fff; z-index:99999; border-radius:6px; font-size:12px; box-shadow:0 4px 15px rgba(0,0,0,0.5); display:flex; flex-direction:column; }
            #uooc-drag-bar { padding:8px; background:#34495e; cursor:move; border-radius:6px 6px 0 0; display:flex; justify-content:space-between; align-items:center; user-select:none; }
            #uooc-min-ball { position:fixed; top:100px; right:50px; width:45px; height:45px; background:#34495e; border-radius:50%; z-index:99999; display:none; align-items:center; justify-content:center; cursor:move; box-shadow:0 4px 15px rgba(0,0,0,0.5); font-size:20px; user-select:none; border:2px solid #ecf0f1; }
            .uooc-row { display:block; margin-bottom:5px; cursor:pointer; }
            .uooc-row input { vertical-align:middle; margin-right:5px; }
        `;
        GM_addStyle(css);

        // 2. 创建 UI
        const div = document.createElement('div');
        div.innerHTML = `
            <div id="uooc-video-panel" style="display:none;"> <div id="uooc-drag-bar">
                    <span>🤖 全能控制台</span>
                    <span id="uooc-min-btn" style="cursor:pointer; padding:0 5px;">➖</span>
                </div>
                <div id="uooc-content" style="padding:10px;">
                    <label class="uooc-row"><input type="checkbox" id="cb-rate" checked> 2倍速 + 静音</label>
                    <label class="uooc-row"><input type="checkbox" id="cb-auto" checked> 自动连播</label>
                    <label class="uooc-row" style="color:#f1c40f;"><input type="checkbox" id="cb-pop-quiz" checked> 自动答视频弹题</label>
                    <label class="uooc-row" style="color:#e74c3c;"><input type="checkbox" id="cb-quiz-alert" checked> 章节测验提醒</label>
                    <div id="uooc-log" style="margin-top:8px; height:80px; background:#222; color:#0f0; overflow-y:auto; padding:5px; border-radius:4px; font-family:monospace;">系统就绪...</div>
                </div>
            </div>
            <div id="uooc-min-ball" title="点击展开">🤖</div>
        `;
        document.body.appendChild(div);

        const panel = document.getElementById('uooc-video-panel');
        const ball = document.getElementById('uooc-min-ball');
        const minBtn = document.getElementById('uooc-min-btn');
        const dragBar = document.getElementById('uooc-drag-bar');

        // 默认显示大面板
        panel.style.display = 'flex';

        // 拖拽逻辑
        function makeDraggable(trigger, target, partner) {
            let isDragging = false, startX, startY, initLeft, initTop, hasMoved = false;
            trigger.addEventListener('mousedown', e => { isDragging=true; hasMoved=false; startX=e.clientX; startY=e.clientY; initLeft=target.offsetLeft; initTop=target.offsetTop; });
            document.addEventListener('mousemove', e => {
                if (isDragging) {
                    if (Math.abs(e.clientX - startX) > 3) hasMoved = true;
                    target.style.left = initLeft + (e.clientX - startX) + 'px'; target.style.top = initTop + (e.clientY - startY) + 'px';
                    if(partner) { partner.style.left = target.style.left; partner.style.top = target.style.top; }
                }
            });
            document.addEventListener('mouseup', () => { isDragging = false; });
            return () => hasMoved;
        }
        makeDraggable(dragBar, panel, ball);
        const checkBallMoved = makeDraggable(ball, ball, panel);

        minBtn.onclick = () => { panel.style.display = 'none'; ball.style.display = 'flex'; };
        ball.onclick = () => { if (!checkBallMoved()) { ball.style.display = 'none'; panel.style.display = 'flex'; } };

        function log(msg) {
            const el=document.getElementById('uooc-log');
            if(el){el.innerHTML+=`<div>>${msg}</div>`;el.scrollTop=el.scrollHeight;}
        }

        // --- 核心循环 (只在有视频的页面生效) ---
        setInterval(() => {
            let video = document.querySelector('video');

            // 只有当前页面有视频标签时,才执行视频逻辑
            if(!video) return;

            // 1. 弹题处理
            if(document.getElementById('cb-pop-quiz').checked) autoAnswerPopQuiz();

            // 2. 视频控制
            if(document.getElementById('cb-rate').checked) {
                if(video.playbackRate !== 2.0) video.playbackRate = 2.0;
                if(!video.muted) video.muted = true;
            }
            if(video.paused && !video.ended) video.play().catch(()=>{});

            // 3. 监听结束
            if(!video.getAttribute('hooked')) {
                video.addEventListener('ended', () => {
                    if(document.getElementById('cb-auto').checked) { log("播放结束, 找下一集"); findNext(); }
                });
                video.setAttribute('hooked', 'true');
                log("捕获到视频对象");
            }
        }, 2000);

        // 自动答视频内弹题
        function autoAnswerPopQuiz() {
            let quizLayer = document.querySelector('#quizLayer');
            if (!quizLayer || quizLayer.style.display === 'none') return;
            log("⚡ 检测到弹题,正在秒杀...");
            try {
                let videoDiv = document.querySelector('div[uooc-video]');
                if (!videoDiv) return;
                let sourceData = JSON.parse(videoDiv.getAttribute('source'));
                let quizList = sourceData.quiz;
                let questionEl = document.querySelector('.smallTest-view .ti-q-c');
                if (!questionEl) return;
                let currentQuestionHTML = questionEl.innerHTML;
                let targetQuiz = quizList.find(q => q.question === currentQuestionHTML);
                if (targetQuiz) {
                    let correctAnswers = [];
                    try { correctAnswers = eval(targetQuiz.answer); } catch(e) { correctAnswers = targetQuiz.answer; }
                    if (!Array.isArray(correctAnswers)) correctAnswers = [correctAnswers];
                    let optionsContainer = quizLayer.querySelector('div.ti-alist');
                    if (optionsContainer) {
                        correctAnswers.forEach(ans => {
                            let index = ans.charCodeAt(0) - 65;
                            if(optionsContainer.children[index]) optionsContainer.children[index].click();
                        });
                        setTimeout(() => {
                            let submitBtn = quizLayer.querySelector('button');
                            if (submitBtn) { submitBtn.click(); log("✅ 弹题已自动提交"); }
                        }, 500);
                    }
                }
            } catch (err) { console.error(err); }
        }

        // 自动下一集
        function findNext() {
            let all = document.querySelectorAll('.basic');
            let activeIdx = -1;
            for(let i=0; i<all.length; i++) { if(all[i].classList.contains('active') && all[i].querySelector('.taskpoint')) { activeIdx=i; break; } }
            if(activeIdx===-1) for(let i=0; i<all.length; i++) if(all[i].classList.contains('active')) activeIdx=i;

            if(activeIdx!==-1 && activeIdx+1<all.length) {
                let next = all[activeIdx+1];
                next.scrollIntoView({block:"center"});

                let isTask = next.querySelector('.taskpoint');
                let isVideo = next.querySelector('.icon-video');

                if(isVideo) {
                    log("跳转下一集..."); setTimeout(()=>next.click(), 1500);
                } else if(!isTask) {
                    log("展开目录..."); next.click(); setTimeout(findNext, 2000);
                } else {
                    log("⚠️ 遇到测验/作业!");
                    setTimeout(() => {
                        next.click();
                        if(document.getElementById('cb-quiz-alert').checked) alertUser("遇到测验,请使用AI助手!");
                    }, 1500);
                }
            }
        }

        function alertUser(msg) {
            try { let u = new SpeechSynthesisUtterance(msg); window.speechSynthesis.speak(u); } catch(e){}
            log(msg);
        }
    }


    // ==========================================
    // 模块B:讨论区自动回复助手 (从独立脚本合并)
    // ==========================================
    function initDiscussionHelper() {
        console.log("[UOOC全能助手] 讨论区模块监听中...");

        let autoTimer = null;
        let isRunning = false;

        // 监听 DOM 变化,等待讨论框出现
        const observer = new MutationObserver((mutations) => {
            const editorArea = document.querySelector('.replay-editor');
            if (editorArea && !document.getElementById('uooc-auto-reply-panel')) {
                console.log("[UOOC全能助手] 检测到讨论区,注入控制面板");
                addControlPanel(editorArea);
            }
        });

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

        function addControlPanel(targetElement) {
            const panel = document.createElement('div');
            panel.id = 'uooc-auto-reply-panel';
            panel.style.cssText = `margin-top: 15px; padding: 15px; background: #f9f9f9; border: 1px dashed #0B99FF; border-radius: 5px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap;`;

            panel.innerHTML = `
                <div style="flex: 1; min-width: 200px;">
                    <label style="font-size: 12px; color: #666; display: block; margin-bottom: 5px;">自动回复内容:</label>
                    <input type="text" id="auto-reply-text" placeholder="输入要重复发送的内容..." style="width: 100%; padding: 5px; border: 1px solid #ddd; border-radius: 3px;">
                </div>
                <div style="width: 100px;">
                    <label style="font-size: 12px; color: #666; display: block; margin-bottom: 5px;">频率 (秒):</label>
                    <input type="number" id="auto-reply-interval" value="10" min="3" style="width: 100%; padding: 5px; border: 1px solid #ddd; border-radius: 3px;">
                </div>
                <div style="margin-top: 18px;">
                    <button id="auto-reply-toggle" style="padding: 6px 15px; background: #ccc; color: white; border: none; border-radius: 3px; cursor: pointer;">开启刷屏</button>
                </div>
                <div id="auto-reply-status" style="width: 100%; font-size: 12px; color: #ff0000;"></div>
            `;

            targetElement.appendChild(panel);
            document.getElementById('auto-reply-toggle').addEventListener('click', toggleAutoReply);
        }

        function toggleAutoReply() {
            const btn = document.getElementById('auto-reply-toggle');
            const textInput = document.getElementById('auto-reply-text');
            const intervalInput = document.getElementById('auto-reply-interval');
            const statusDiv = document.getElementById('auto-reply-status');

            if (isRunning) {
                clearInterval(autoTimer);
                isRunning = false;
                btn.innerText = "开启刷屏";
                btn.style.background = "#ccc";
                textInput.disabled = false;
                intervalInput.disabled = false;
                statusDiv.innerText = "已停止。";
            } else {
                const content = textInput.value;
                const interval = parseInt(intervalInput.value);
                if (!content) { alert("请输入内容!"); return; }
                if (interval < 3) { alert("间隔不能太短!"); return; }

                isRunning = true;
                btn.innerText = "停止刷屏";
                btn.style.background = "#ff4d4f";
                textInput.disabled = true;
                intervalInput.disabled = true;
                statusDiv.innerText = `运行中... 每 ${interval} 秒发送一次`;

                doReplyAction(content);
                autoTimer = setInterval(() => { doReplyAction(content); }, interval * 1000);
            }
        }

        function doReplyAction(content) {
            const textarea = document.querySelector('.replay-editor-area textarea');
            const submitBtn = document.querySelector('.replay-editor-btn');
            if (textarea && submitBtn) {
                textarea.value = content;
                textarea.dispatchEvent(new Event('input', { bubbles: true }));
                textarea.dispatchEvent(new Event('change', { bubbles: true }));
                setTimeout(() => {
                    submitBtn.click();
                    const statusDiv = document.getElementById('auto-reply-status');
                    if(statusDiv) statusDiv.innerText = `已发送: ${new Date().toLocaleTimeString()}`;
                }, 200);
            }
        }
    }


    // ==========================================
    // 模块C:章节测验 AI 助手 (Iframe)
    // ==========================================
    function initQuizHelper() {
        console.log("[UOOC全能助手] AI测验模块启动");
        let apiConfigs = GM_getValue('ai_configs_v2', [{ name: "DeepSeek", url: "https://api.deepseek.com/chat/completions", key: "", model: "deepseek-chat" }]);

        const css = `
            #uooc-ai-panel { position:fixed; top:20px; left:20px; width:360px; background:#fff; border:1px solid #ddd; z-index:999999; box-shadow:0 0 20px rgba(0,0,0,0.2); font-family:'Segoe UI', sans-serif; border-radius:8px; display:flex; flex-direction:column; max-height:85vh; }
            #uooc-ai-header { padding:12px; background:#2980b9; color:white; font-weight:bold; cursor:move; border-radius:8px 8px 0 0; display:flex; justify-content:space-between; align-items:center; user-select:none; }
            #uooc-ai-body { padding:10px; overflow-y:auto; flex:1; background:#f5f6fa; }
            #uooc-ai-ball { position:fixed; top:20px; left:20px; width:45px; height:45px; background:#2980b9; border-radius:50%; z-index:99999; display:none; align-items:center; justify-content:center; cursor:move; box-shadow:0 4px 15px rgba(0,0,0,0.3); color:white; font-weight:bold; user-select:none; border:2px solid white; }
            .ai-btn { width:100%; padding:10px; background:#2980b9; color:white; border:none; border-radius:4px; cursor:pointer; font-weight:bold; transition:0.2s; margin-bottom:10px; }
            .btn-group { display:flex; gap:10px; margin-bottom:10px; }
            .btn-group .ai-btn { margin-bottom:0; flex:1; }
            #ai-results-grid { display:grid; grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); gap:8px; margin-top:10px; }
            .ai-result-card { background:#fff; border-radius:6px; padding:8px; text-align:center; border:1px solid #dcdde1; box-shadow:0 2px 5px rgba(0,0,0,0.05); }
            .ai-name { font-size:11px; color:#7f8c8d; margin-bottom:5px; font-weight:bold; border-bottom:1px solid #eee; padding-bottom:3px;}
            .ai-ans { font-size:16px; font-weight:bold; color:#2c3e50; line-height:1.4; word-break: break-all; }
            #ai-config-area { display:none; background:#fff; padding:10px; border-radius:4px; border:1px solid #ddd; margin-bottom:10px; }
            .cfg-item { border-bottom:1px dashed #eee; padding-bottom:8px; margin-bottom:8px; }
            .cfg-input { width:100%; padding:4px; margin:2px 0; border:1px solid #ddd; border-radius:3px; font-size:11px; box-sizing:border-box; }
            .cfg-actions { display:flex; justify-content:space-between; margin-top:5px; align-items:center; }
            .btn-xs { padding:2px 8px; font-size:10px; cursor:pointer; border:1px solid #ddd; background:#fff; border-radius:3px; }
            .btn-del { color: red; border-color: #ffcccc; }
        `;
        GM_addStyle(css);

        const div = document.createElement('div');
        div.innerHTML = `
            <div id="uooc-ai-panel">
                <div id="uooc-ai-header">
                    <span>🏥 AI 答题助手</span>
                    <div><span id="ai-toggle-config" style="font-size:18px; cursor:pointer; margin-right:10px;">⚙️</span><span id="ai-min-btn" style="cursor:pointer;">➖</span></div>
                </div>
                <div id="uooc-ai-body">
                    <div id="ai-config-area"><div id="cfg-list"></div><button id="btn-add-cfg" class="btn-xs" style="width:100%;background:#2ecc71;color:white;padding:6px;margin-top:5px;">+ API</button><button id="btn-save-cfg" class="ai-btn" style="margin-top:10px; background:#27ae60;">💾 保存</button></div>
                    <div id="ai-status" style="font-size:12px; color:#666; margin-bottom:5px; text-align:center;">准备就绪</div>
                    <div class="btn-group"><button id="btn-copy" class="ai-btn" style="background:#27ae60;">📋 复制题目</button><button id="btn-run" class="ai-btn">⚡ AI 会诊</button></div>
                    <div id="ai-results-grid"></div>
                </div>
            </div>
            <div id="uooc-ai-ball" title="点击展开">AI</div>
        `;
        document.body.appendChild(div);

        const panel = document.getElementById('uooc-ai-panel');
        const ball = document.getElementById('uooc-ai-ball');
        const header = document.getElementById('uooc-ai-header');

        function makeDraggable(trigger, target, partner) {
            let isDragging = false, startX, startY, initLeft, initTop, hasMoved = false;
            trigger.addEventListener('mousedown', e => { isDragging=true; hasMoved=false; startX=e.clientX; startY=e.clientY; initLeft=target.offsetLeft; initTop=target.offsetTop; });
            document.addEventListener('mousemove', e => {
                if (isDragging) {
                    if (Math.abs(e.clientX - startX) > 3) hasMoved = true;
                    const l = initLeft + (e.clientX - startX) + 'px';
                    const t = initTop + (e.clientY - startY) + 'px';
                    target.style.left = l; target.style.top = t;
                    if(partner) { partner.style.left = l; partner.style.top = t; }
                }
            });
            document.addEventListener('mouseup', () => isDragging=false);
            return () => hasMoved;
        }
        makeDraggable(header, panel, ball);
        const checkBallMoved = makeDraggable(ball, ball, panel);

        document.getElementById('ai-min-btn').onclick = () => { panel.style.display='none'; ball.style.display='flex'; };
        ball.onclick = () => { if(!checkBallMoved()) { ball.style.display='none'; panel.style.display='flex'; } };

        // 配置与答题逻辑
        const configArea = document.getElementById('ai-config-area');
        const cfgList = document.getElementById('cfg-list');
        function renderConfigs() {
            cfgList.innerHTML = '';
            apiConfigs.forEach((cfg, index) => {
                const item = document.createElement('div');
                item.className = 'cfg-item';
                const headerDiv = document.createElement('div');
                headerDiv.className = 'cfg-actions';
                headerDiv.innerHTML = `<span style="font-size:11px;font-weight:bold;">模型 #${index + 1}</span>`;
                if (apiConfigs.length > 0) {
                    const delBtn = document.createElement('button');
                    delBtn.className = 'btn-xs btn-del'; delBtn.innerText = '删除';
                    delBtn.onclick = function() { if(confirm('确定删除?')) { apiConfigs.splice(index, 1); renderConfigs(); } };
                    headerDiv.appendChild(delBtn);
                }
                item.appendChild(headerDiv);
                const inputsDiv = document.createElement('div');
                inputsDiv.innerHTML = `<input type="text" class="cfg-input name" value="${cfg.name}" placeholder="名称"><input type="text" class="cfg-input url" value="${cfg.url}" placeholder="URL"><input type="password" class="cfg-input key" value="${cfg.key}" placeholder="Key"><input type="text" class="cfg-input model" value="${cfg.model}" placeholder="Model">`;
                item.appendChild(inputsDiv);
                cfgList.appendChild(item);
            });
        }
        document.getElementById('ai-toggle-config').onclick = () => { if(configArea.style.display==='none'||!configArea.style.display){ renderConfigs(); configArea.style.display='block'; } else { configArea.style.display='none'; } };
        document.getElementById('btn-add-cfg').onclick = () => { apiConfigs.push({name:"New AI",url:"",key:"",model:""}); renderConfigs(); };
        document.getElementById('btn-save-cfg').onclick = () => {
            apiConfigs = [];
            cfgList.querySelectorAll('.cfg-item').forEach(item => { apiConfigs.push({ name: item.querySelector('.name').value, url: item.querySelector('.url').value, key: item.querySelector('.key').value, model: item.querySelector('.model').value }); });
            GM_setValue('ai_configs_v2', apiConfigs); alert("保存成功"); configArea.style.display='none';
        };
        function extractQuestionsText() {
            let textBuffer = [];
            let questions = document.querySelectorAll('.queContainer');
            if(questions.length === 0) return document.body.innerText.replace(/\s+/g, ' ').substring(0, 5000);
            questions.forEach((q, idx) => {
                let indexText = q.querySelector('.index') ? q.querySelector('.index').innerText.trim() : "";
                let bodyText = q.querySelector('.ti-q-c') ? q.querySelector('.ti-q-c').innerText.trim() : "";
                let optionsText = "";
                q.querySelectorAll('.ti-a').forEach(opt => {
                    let label = opt.querySelector('.ti-a-i') ? opt.querySelector('.ti-a-i').innerText.trim() : "";
                    let val = opt.querySelector('.ti-a-c') ? opt.querySelector('.ti-a-c').innerText.trim() : "";
                    optionsText += `\n${label} ${val}`;
                });
                textBuffer.push(`${indexText} ${bodyText}${optionsText}`);
            });
            return textBuffer.join('\n\n----------------\n\n');
        }
        const statusDiv = document.getElementById('ai-status');
        document.getElementById('btn-copy').onclick = () => {
            const fullText = extractQuestionsText();
            if(fullText.length < 10) { statusDiv.innerText = "提取失败(太短)"; return; }
            GM_setClipboard(fullText); statusDiv.innerText = "✅ 已复制到剪贴板";
        };
        const resultGrid = document.getElementById('ai-results-grid');
        document.getElementById('btn-run').onclick = async () => {
            const validConfigs = apiConfigs.filter(c => c.key && c.url);
            if (validConfigs.length === 0) { alert("请配置API"); return; }
            let contentText = extractQuestionsText();
            if(contentText.length < 10) { statusDiv.innerText = "❌ 没提取到题目"; return; }
            statusDiv.innerText = `请求 ${validConfigs.length} 个模型中...`; resultGrid.innerHTML = '';
            validConfigs.forEach((cfg, idx) => {
                const card = document.createElement('div'); card.className = 'ai-result-card'; card.id = `card-${idx}`;
                card.innerHTML = `<div class="ai-name">${cfg.name}</div><div class="ai-ans" style="color:#999;">...</div>`; resultGrid.appendChild(card);
            });
            const promises = validConfigs.map((cfg, idx) => callSingleAI(cfg, contentText, idx));
            await Promise.allSettled(promises); statusDiv.innerText = "✅ 完成";
        };
        function callSingleAI(cfg, question, idx) {
            return new Promise((resolve, reject) => {
                const systemPrompt = `你是一个做题助手。用户发给你试题。请识别【每一道题】,并按顺序输出答案。格式要求:1. 请用逗号分隔每个答案(例如:A, B, √, C)。2. 不要输出解释。3. 只要答案。`;
                GM_xmlhttpRequest({
                    method: "POST", url: cfg.url, headers: { "Content-Type": "application/json", "Authorization": "Bearer " + cfg.key },
                    data: JSON.stringify({ model: cfg.model, messages: [ { role: "system", content: systemPrompt }, { role: "user", content: "试题:\n" + question } ], temperature: 0.1 }),
                    onload: function(response) {
                        const cardAns = document.querySelector(`#card-${idx} .ai-ans`);
                        if (response.status === 200) { try { const resJson = JSON.parse(response.responseText); let rawAns = resJson.choices[0].message.content.trim(); rawAns = rawAns.replace(/[。.]/g, ''); cardAns.innerText = rawAns; cardAns.style.color = "#e74c3c"; resolve(); } catch (e) { cardAns.innerText = "解析错"; reject(e); } } else { cardAns.innerText = "Err " + response.status; reject("Status " + response.status); }
                    }, onerror: function(err) { document.querySelector(`#card-${idx} .ai-ans`).innerText = "网路错"; reject(err); }
                });
            });
        }
    }

})();