超星学习通+AI自动答题脚本(支持考试,作业,章节测试,支持新版学习通,一键最小化,多种AI)

超星学习通+ChatGPT自动答题脚本(支持考试和作业和章节测试,支持新版学习通)。直接获取到答案,可以复制答案到剪切板。

目前為 2024-12-01 提交的版本,檢視 最新版本

// ==UserScript==
// @name         超星学习通+AI自动答题脚本(支持考试,作业,章节测试,支持新版学习通,一键最小化,多种AI)
// @namespace    http://tampermonkey.net/
// @version      1.11
// @description  超星学习通+ChatGPT自动答题脚本(支持考试和作业和章节测试,支持新版学习通)。直接获取到答案,可以复制答案到剪切板。
// @author       Cwyu
// @license      MIT
// @supportURL  [email protected]
// @contributionURL https://ifdian.net/a/cwyuu
// @match        *://mooc1.chaoxing.com/exam-ans/mooc2/exam/preview?*
// @match        *://mooc1.chaoxing.com/mooc2/work/dowork?*
// @match        *://mooc1.chaoxing.com/mycourse/studentstudy?*
// @match        *://mooc1.chaoxing.com/mycourse/studentstudy*
// @match        *://mooc1.chaoxing.com/mooc-ans/mooc2/work/dowork*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=chaoxing.com
// @grant        unsafeWindow
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';
    
    GM_addStyle(`
        #my-window {
            position: fixed;
            top: 10px;
            left: 10px;
            width: 500px;
            height: 400px;
            background-color: rgba(255, 255, 255, 0.95);
            border: 1px solid #e2e8f0;
            border-radius: 8px;
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
            z-index: 9999;
            overflow: hidden;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            resize: both;
            min-width: 300px;
            min-height: 200px;
        }
        
        #my-window .header {
            background-color: #1a202c;
            color: white;
            padding: 8px 12px;
            font-size: 14px;
            font-weight: 600;
            border-radius: 8px 8px 0 0;
            cursor: move;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        
        #my-window .xxt-content {
            padding: 12px;
            display: flex;
            flex-direction: column;
            overflow: hidden;
            width: auto;
            min-height: 0;
		}
        
        #my-window .question-section,
        #my-window .answer-section {
                background-color: #fff;
				border: 1px solid #e2e8f0;
				padding: 12px;
				border-radius: 4px;
				margin-bottom: 8px;
				word-break: break-word;
				overflow-wrap: break-word;
        }
        
        #my-window .answer-section {
            flex: 1;
            overflow-y: auto;
        }

        #my-window .controls {
            display: flex;
            flex-direction: column;
            gap: 8px;
            margin-bottom: 10px;
        }
        
        #my-window .settings-row {
            display: flex;
            align-items: center;
            gap: 12px;
        }
        
        #my-window .button-row {
            display: flex;
            gap: 8px;
        }
        
        #my-window button {
            padding: 4px 8px;
            border-radius: 4px;
            font-size: 12px;
            cursor: pointer;
            border: none;
            background: #e2e8f0;
            transition: background-color 0.2s;
        }
        
        #my-window button:hover {
            background: #cbd5e1;
        }
        
        #my-window button#refresh-btn {
            background: #3b82f6;
            color: white;
        }
        
        #my-window button#refresh-btn:hover {
            background: #2563eb;
        }
        
        #my-window button#copy-btn {
            background: #10b981;
            color: white;
        }
        
        #my-window button#copy-btn:hover {
            background: #059669;
        }
        
        #my-window select {
            padding: 4px;
            border-radius: 4px;
            font-size: 12px;
            border: 1px solid #e2e8f0;
            flex: 1;
        }
        
        #my-window input[type="text"] {
        padding: 4px 8px;
        border-radius: 4px;
        font-size: 12px;
        border: 1px solid #e2e8f0;
        flex: 1;
        }
        
        #floating-button {
        position: fixed;
        top: 20px;
        left: -45px;
        width: 50px;
        height: 50px;
        background-color: #1a202c;
        color: white;
        border-radius: 25px;
        text-align: center;
        line-height: 50px;
        cursor: pointer;
        transition: all 0.3s ease;
        z-index: 9999;
        font-size: 12px;
        }
        #floating-button:hover {
            left: 0;
        }
		
		#my-window .label {
			color: #64748b;
			font-size: 12px;
			margin-bottom: 4px;
		}
		
		#my-window .subscription-row {
			display: flex;
			gap: 8px;
			width: 100%;
		}

        #my-window .subscription-row input {
        flex: 1;
        }
        
        #my-window .subscription-row button {
        white-space: nowrap;
        }
        
        #my-window button svg {
        display: inline-block;
        vertical-align: middle;
        margin-right: 4px;
        }
        
        #my-window #prev-btn svg,
        #my-window #next-btn svg {
        margin-right: 0;
        }
        
        #my-window .status-info-row {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 4px 8px;
            border-top: 1px solid #e2e8f0;
            font-size: 12px;
            color: #64748b;
        }
        
        `);

    const windowHTML = `
        <div id="my-window">
            <div class="header">
                <span>AI答题(Alt+Z 快速隐藏)</span>
                <button id="hide-btn">×</button>
            </div>
            <div class="xxt-content">
                <div class="question-section">
                    <div class="label">当前题目:</div>
                    <div id="question"></div>
                </div>
                <div class="answer-section">
                    <div class="label">答案:</div>
                    <div id="answer"></div>
                </div>
                <div class="controls">
                    <div class="settings-row">
                        <label><input type="checkbox" id="use-tiku" checked> 使用题库</label>
                        <label><input type="checkbox" id="need-analysis"> 需要解析</label>
                        <select id="ai-model">
                            <option value="openai/GPT-4o-mini">GPT-4 Mini(最便宜,推荐)</option>
                            <option value="openai/GPT-4o">GPT-4(推荐)</option>
                            <option value="openai/o1-preview">GPT-o1-preview(非常贵,不推荐,但是最强)</option>
                            <option value="openai/o1-mini">GPT-o1-mini(贵,不推荐)</option>
                            <option value="claude/Claude-3-Opus">Claude 3 Opus(非常贵)</option>
                            <option value="claude/Claude-3-Sonnet">Claude 3 Sonnet</option>
                            <option value="claude/Claude-3-Haiku">Claude 3 Haiku(claude里最便宜的)</option>
                            <option value="claude/Claude-3.5-Sonnet">Claude 3.5 Sonnet(非常贵,第二强)</option>
                            <option value="claude/Claude-3.5-Haiku">Claude 3.5 Haiku(较贵)</option>
                        </select>
                    </div>
                    <div class="button-row">
                        <button id="prev-btn">
                            <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                <path d="M15 18l-6-6 6-6" />
                            </svg>
                        </button>
                        <button id="next-btn">
                            <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                <path d="M9 18l6-6-6-6" />
                            </svg>
                        </button>
                        <button id="refresh-btn">
                            <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
                                class="mr-1">
                                <path d="M21 12a9 9 0 11-9-9 9 9 0 019 9z" />
                                <path d="M9 12l2 2 4-4" />
                            </svg>
                            获取答案
                        </button>
                        <button id="copy-btn">
                            <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
                                class="mr-1">
                                <rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
                                <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" />
                            </svg>
                            复制答案
                        </button>
                    </div>
                    <div class="subscription-row">
                        <input type="text" id="subscription-key" placeholder="请输入订阅链接">
                        <button id="save-btn">保存订阅</button>
                    </div>
                    <div class="status-info-row">
                        <div class="status" id="card_status">状态内容</div>
                        <div class="info">by: cwyu</div>
                    </div>
                </div>
            </div>
        </div>
        <div id="floating-button">显示</div>
    `;

    document.body.insertAdjacentHTML('beforeend', windowHTML);

    const myWindow = document.getElementById('my-window');
    const floatingButton = document.getElementById('floating-button');
    const hideBtn = document.getElementById('hide-btn');
    const header = document.querySelector('.header');
    const answerEl = document.getElementById('answer');
    const questionEl = document.getElementById('question');
    const subscriptionKey = document.getElementById('subscription-key');

    let answerCache = {};
    let currentIndex = 0;
    let timu = ["正在获取题目"];

    let isDragging = false;
    let currentX;
    let currentY;
    let initialX;
    let initialY;
    let xOffset = 0;
    let yOffset = 0;

    header.addEventListener('mousedown', dragStart);
    document.addEventListener('mousemove', drag);
    document.addEventListener('mouseup', dragEnd);

    function dragStart(e) {
        initialX = e.clientX - xOffset;
        initialY = e.clientY - yOffset;

        if (e.target === header) {
            isDragging = true;
        }
    }

    function drag(e) {
        if (isDragging) {
            e.preventDefault();
            currentX = e.clientX - initialX;
            currentY = e.clientY - initialY;
            xOffset = currentX;
            yOffset = currentY;
            myWindow.style.transform = `translate(${currentX}px, ${currentY}px)`;
        }
    }

    function dragEnd() {
        isDragging = false;
    }

    function toggleWindow() {
        if (myWindow.style.display === 'none') {
            myWindow.style.display = 'block';
            floatingButton.style.display = 'none';
        } else {
            myWindow.style.display = 'none';
            floatingButton.style.display = 'block';
        }
    }

    document.addEventListener('keydown', function(e) {
    if (e.altKey && e.key.toLowerCase() === 'z') {
    toggleWindow();
    }
    });
    
    hideBtn.addEventListener('click', toggleWindow);
    floatingButton.addEventListener('click', toggleWindow);

    floatingButton.style.display = 'none';

    const savedKey = GM_getValue('subscription_key');
    if (savedKey) {
    subscriptionKey.value = savedKey;
    (async () => {
    const data = { serial: savedKey };
    try {
    const response = await fetch('https://xxt.uycc.xyz/api/v1/serial/check', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data)
    });
    
    if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
    
    const result = await response.json();
    document.getElementById('card_status').textContent = "可用的token余额:" + result.balances;
    } catch (error) {
    console.error('Serial check API error:', error);
    document.getElementById('card_status').textContent = "卡密验证失败,请检查卡密是否正确";
    }
    })();
    }
    
    async function decrypt(str) {
    const data = { text: timu };
    try {
    const response = await fetch("https://xxt.uycc.xyz/api/v1/decrypt", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(data)
    });
    
    if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
    const result = await response.json();
    timu = result.text;
    updateDisplay(0);
    } catch (error) {
    console.error('Decrypt API error:', error);
    answerEl.textContent = "解密失败,请稍后重试";
    }
    }

    async function fetchAnswer() {
        const currentQuestion = timu[currentIndex];
        answerEl.textContent = "获取答案中...";
        
        const data = {
            serial: subscriptionKey.value,
            question: currentQuestion,
            model: document.getElementById('ai-model').value,
            use_tiku: document.getElementById('use-tiku').checked,
            have_analyze: document.getElementById('need-analysis').checked
        };

        try {
            const response = await fetch('https://xxt.uycc.xyz/api/v1/search', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(data)
            });

            if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);

            const result = await response.json();
            if (result.answer) {
                answerCache[currentIndex] = result.answer;
                answerEl.textContent = result.answer;
                if (result.cost===0) {
                    document.getElementById('card_status').textContent = 
                        `题库中包含此题,不花费token`;
                }
                
                if (result.cost && result.balances) {
                    document.getElementById('card_status').textContent = 
                        `本次花费: ${result.cost} | 剩余余额: ${result.balances}`;
                }
            }
        } catch (error) {
            console.error('Search API error:', error);
            answerEl.textContent = "请求失败,请检查卡密或网络连接";
        }
    }

    function updateDisplay(index) {
        questionEl.textContent = timu[index];
        answerEl.textContent = answerCache[index] || '';
    }

    document.getElementById('prev-btn').addEventListener('click', () => {
        if (currentIndex > 0) {
            currentIndex--;
            updateDisplay(currentIndex);
        }
    });

    document.getElementById('next-btn').addEventListener('click', () => {
        if (currentIndex < timu.length - 1) {
            currentIndex++;
            updateDisplay(currentIndex);
        }
    });

    document.getElementById('refresh-btn').addEventListener('click', fetchAnswer);

    document.getElementById('copy-btn').addEventListener('click', () => {
        const answerText = answerEl.textContent;
        navigator.clipboard.writeText(answerText)
            .catch(err => console.error('复制失败:', err));
    });

    document.getElementById('save-btn').addEventListener('click', async () => {
        const key = subscriptionKey.value;
        GM_setValue('subscription_key', key);

        const data = { serial: key };
        try {
            const response = await fetch('https://xxt.uycc.xyz/api/v1/serial/check', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(data)
            });

            if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);

            const result = await response.json();
            document.getElementById('card_status').textContent = "可用的token余额:" + result.balances;
        } catch (error) {
            console.error('Serial check API error:', error);
            document.getElementById('card_status').textContent = "卡密验证失败,请检查卡密是否正确";
        }
    });

    if (document.title === "学生学习页面") {
        queryElements();
    }

    if (document.title == "作业作答") {
        const divs = document.querySelectorAll('.padBom50.questionLi:not(script)');
        const texts = [];
        divs.forEach(div => {
            const text = div.textContent.trim().replace(/\s+/g, ' ');
            texts.push(text);
        });
        timu = texts.map((text) =>
            text.replace(/var\s+wordNum\s*=.*$/gm, "").trim()
        );
        updateDisplay(0);
    }

    if (document.title == "整卷预览") {
        const elements = document.querySelectorAll('div.questionLi:not(script)');
        const texts = [];
        for (let i = 0; i < elements.length; i++) {
            const element = elements[i];
            const text = element.textContent.trim().replace(/\s+/g, ' ');
            texts.push(text);
        }
        timu = texts.map((text) =>
            text.replace(/window\.UEDITOR_CONFIG\.initialFrameWidth.*保存/g, "").trim()
        );
        updateDisplay(0);
    }

    async function queryElements() {
        const iframe = document.getElementById('iframe');
        if (!iframe) {
            setTimeout(queryElements, 3000);
            return;
        }

        const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
        const second = iframeDoc.querySelector('iframe');
        if (!second) {
            setTimeout(queryElements, 3000);
            return;
        }

        const secondDoc = second.contentDocument || second.contentWindow.document;
        const third = secondDoc.getElementById('frame_content');
        if (!third) {
            setTimeout(queryElements, 3000);
            return;
        }

        const thirdDoc = third.contentDocument || third.contentWindow.document;
        const elements = thirdDoc.querySelectorAll('div.TiMu:not(script)');
        if (elements.length > 0) {
            const texts = [];
            elements.forEach(element => {
                const text = element.textContent.trim().replace(/\s+/g, ' ');
                texts.push(text);
            });
		const style = $("style[type='text/css']", thirdDoc);
            const fontData = style.text().match(/'(data:application\/font-ttf;.*?)'/)[1];
            timu = texts.map((text) =>
                text
                    .replace(/var\s+wordNum\s*=.*$/gm, "")
                    .replace(/点击上传x[^}]*}/gm, "")
                    .replace(/填写答案[^x]*x/gm, "")
                    .trim()
            );
            decrypt(fontData.split(",")[1]);
        } else {
            setTimeout(queryElements, 3000);
        }
    }

    questionEl.addEventListener('dblclick', function(e) {
        e.preventDefault();
        
        const editableDiv = document.createElement('div');
        editableDiv.setAttribute('contenteditable', true);
        editableDiv.textContent = questionEl.textContent;
        editableDiv.style.background = '#fff';
        editableDiv.style.padding = '4px';
        editableDiv.style.border = '1px solid #3b82f6';
        editableDiv.style.borderRadius = '4px';
        editableDiv.style.minHeight = '50px';

        questionEl.replaceWith(editableDiv);
        editableDiv.focus();

        function saveEdit() {
            timu[currentIndex] = editableDiv.textContent;
            questionEl.textContent = editableDiv.textContent;
            editableDiv.replaceWith(questionEl);
        }

        editableDiv.addEventListener('blur', saveEdit);
        editableDiv.addEventListener('keydown', function(e) {
            if (e.key === 'Enter' && !e.shiftKey) {
                e.preventDefault();
                saveEdit();
            }
        });
    });

    const resizeObserver = new ResizeObserver(entries => {
        for (const entry of entries) {
            const { width, height } = entry.contentRect;
            const content = myWindow.querySelector('.xxt-content');
            if (content) {
                content.style.height = `${height - 40}px`;
            }
        }
    });

    resizeObserver.observe(myWindow);
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址