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

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

目前為 2024-11-25 提交的版本,檢視 最新版本

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

You will need to install an extension such as Tampermonkey to install this script.

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         超星学习通+AI自动答题脚本(支持考试,作业,章节测试,支持新版学习通,一键最小化,多种AI)
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  超星学习通+ChatGPT自动答题脚本(支持考试和作业和章节测试,支持新版学习通)。直接获取到答案,可以复制答案到剪切板。
// @author       Cwyu
// @license      MIT
// @supportURL  [email protected]
// @contributionURL https://afdian.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">GPT-4(推荐)</option>
                            <option value="openai/GPT-4o-mini">GPT-4 Mini(最便宜,推荐)</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);
})();