ICVE 自动显示答案(单选/多选/判断题)

Vue SPA 页面,每题下方显示答案,支持单选、多选、判断题,特殊字符匹配

// ==UserScript==
// @name         ICVE 自动显示答案(单选/多选/判断题)
// @namespace    http://tampermonkey.net/
// @version      1.6
// @description  Vue SPA 页面,每题下方显示答案,支持单选、多选、判断题,特殊字符匹配
// @match        https://ai.icve.com.cn/preview-exam/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // ------------------- 工具函数 -------------------
    function getAdminToken() {
        const match = document.cookie.match(/(?:^|;\s*)admin-token=([^;]+)/);
        return match ? decodeURIComponent(match[1]) : null;
    }

    function getExamIdFromUrl() {
        const parts = window.location.pathname.split("/");
        return parts[2];
    }

    function numberToLetter(num) {
        const letters = "ABCDEFG";
        return letters[num] || "";
    }

    function normalizeText(str) {
        if (!str) return "";
        return str
            .replace(/\s+/g, "")                  // 去掉空格和换行
            .replace(/ /g, "")               // 去掉 HTML 空格
            .replace(/[^\w\u4e00-\u9fa5]/g, ""); // 只保留中文、字母、数字
    }

    async function fetchExamPaper(examId, token) {
        const url = `https://ai.icve.com.cn/prod-api/course/exam/paper?id=${examId}&groupId=0`;
        const res = await fetch(url, { headers: { "Authorization": "Bearer " + token } });
        return res.json();
    }

    function extractTaskExamRecord(paperData) {
        return (paperData && paperData.taskExamRecord) ? paperData.taskExamRecord : null;
    }

    function buildGetInfoUrl(taskExamRecord) {
        const courseInfoId = taskExamRecord.courseInfoId;
        const taskId = taskExamRecord.id;
        const examId = taskExamRecord.examId;
        const studentId = taskExamRecord.userId;
        return `https://ai.icve.com.cn/prod-api/course/exam/record/getInfo?courseInfoId=${courseInfoId}&taskId=${taskId}&examId=${examId}&studentId=${studentId}&type=2`;
    }

    async function fetchExamInfo(getInfoUrl, token) {
        const res = await fetch(getInfoUrl, { headers: { "Authorization": "Bearer " + token } });
        return res.json();
    }

    // ------------------- 插入答案函数 -------------------
    function insertAnswerSingle(single, q) {
        const titleEl = single.querySelector(".single-title-content");
        if (!titleEl) return;

        const singleText = normalizeText(titleEl.textContent);
        const qText = normalizeText(q.title);
        if (!singleText.includes(qText)) return;
        if (single.querySelector(".icve-answer-inserted")) return;

        const answerLetters = (q.answer || "").split("").map(n => numberToLetter(parseInt(n))).join("");

        const ansDiv = document.createElement("div");
        ansDiv.className = "icve-answer-inserted";
        ansDiv.innerText = "答案:" + answerLetters;
        ansDiv.style.color = "blue";
        ansDiv.style.fontWeight = "bold";
        ansDiv.style.marginTop = "4px";

        single.appendChild(ansDiv);
    }

    function insertAnswerMultiple(single, q) {
        const titleEl = single.querySelector(".multiple-title-content");
        if (!titleEl) return;

        const singleText = normalizeText(titleEl.textContent);
        const qText = normalizeText(q.title);
        if (!singleText.includes(qText)) return;
        if (single.querySelector(".icve-answer-inserted")) return;

        const answerLetters = (q.answer || "")
            .split("")
            .map(n => numberToLetter(parseInt(n)))
            .join(",");

        const ansDiv = document.createElement("div");
        ansDiv.className = "icve-answer-inserted";
        ansDiv.innerText = "答案:" + answerLetters;
        ansDiv.style.color = "blue";
        ansDiv.style.fontWeight = "bold";
        ansDiv.style.marginTop = "4px";

        single.appendChild(ansDiv);
    }

    function insertAnswerJudge(single, q) {
        const titleEl = single.querySelector(".judge-title-content");
        if (!titleEl) return;

        const singleText = normalizeText(titleEl.textContent);
        const qText = normalizeText(q.title);
        if (!singleText.includes(qText)) return;
        if (single.querySelector(".icve-answer-inserted")) return;

        // 判断题答案映射:0->A(正确), 1->B(错误)
        const answerMap = ["A", "B"];
        const answerLetters = answerMap[q.answer] || "";

        const ansDiv = document.createElement("div");
        ansDiv.className = "icve-answer-inserted";
        ansDiv.innerText = "答案:" + answerLetters;
        ansDiv.style.color = "blue";
        ansDiv.style.fontWeight = "bold";
        ansDiv.style.marginTop = "4px";

        single.appendChild(ansDiv);
    }

    // ------------------- 主函数 -------------------
    async function main() {
        const token = getAdminToken();
        if (!token) { alert("未找到 admin-token"); return; }
        const examId = getExamIdFromUrl();
        if (!examId) { alert("未找到 examId"); return; }

        try {
            const paperData = await fetchExamPaper(examId, token);
            const taskExamRecord = extractTaskExamRecord(paperData);
            if (!taskExamRecord) { alert("未找到 taskExamRecord"); return; }

            const infoUrl = buildGetInfoUrl(taskExamRecord);
            const infoData = await fetchExamInfo(infoUrl, token);
            const questions = infoData?.data?.questions || [];

            const containerParent = document.querySelector("body");
            if (!containerParent) return;

            const observer = new MutationObserver(() => {
                const singles = containerParent.querySelectorAll("div.single");
                singles.forEach(single => questions.forEach(q => insertAnswerSingle(single, q)));

                const multiples = containerParent.querySelectorAll("div.multiple");
                multiples.forEach(single => questions.forEach(q => insertAnswerMultiple(single, q)));

                const judges = containerParent.querySelectorAll("div.judge");
                judges.forEach(single => questions.forEach(q => insertAnswerJudge(single, q)));
            });

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

            // 处理已渲染的题目
            const singles = containerParent.querySelectorAll("div.single");
            singles.forEach(single => questions.forEach(q => insertAnswerSingle(single, q)));

            const multiples = containerParent.querySelectorAll("div.multiple");
            multiples.forEach(single => questions.forEach(q => insertAnswerMultiple(single, q)));

            const judges = containerParent.querySelectorAll("div.judge");
            judges.forEach(single => questions.forEach(q => insertAnswerJudge(single, q)));

        } catch (e) {
            console.error(e);
            alert("获取答案失败,请查看控制台");
        }
    }

    setTimeout(main, 500);
})();