Auto grading

USTC 自动评价 tqm.ustc.edu.cn

当前为 2023-07-26 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Auto grading
// @namespace    http://tampermonkey.net/
// @version      0.6.2
// @description  USTC 自动评价 tqm.ustc.edu.cn
// @author       PRO_2684
// @match        https://tqm.ustc.edu.cn/index.html*
// @icon         https://tqm.ustc.edu.cn/favicon.ico
// @grant        none
// @license      gpl-3.0
// @require      https://greasyfork.org/scripts/468177-%E6%95%99%E5%AD%A6%E8%B4%A8%E9%87%8F%E7%AE%A1%E7%90%86%E5%B9%B3%E5%8F%B0%E6%A0%87%E5%87%86%E7%AD%94%E6%A1%88/code/%E6%95%99%E5%AD%A6%E8%B4%A8%E9%87%8F%E7%AE%A1%E7%90%86%E5%B9%B3%E5%8F%B0%E6%A0%87%E5%87%86%E7%AD%94%E6%A1%88.js
// ==/UserScript==

(function() {
    'use strict';
    let menu_root;
    function add_item(display_name, hint, callback) {
        let new_item = document.createElement("li");
        new_item.innerText = display_name;
        new_item.onclick = callback;
        new_item.className = "ant-menu-item";
        new_item.title = hint;
        menu_root.appendChild(new_item);
    }
    function help() {
        alert("食用方法:\n1. 进入未完成的评价问卷\n2. 侧栏选择你想要的操作或激活快捷键\n3. 等待脚本执行\n\n快捷键说明:\n- Enter: 智能执行以下中的一项: 下一位教师/选择标准答案/提交回答\n- Shift+Enter: 全自动评教\n- Backspace: 忽略并转到下一个");
    }
    function grade() {
        let questions = document.querySelectorAll("[class|='index_subject']");
        let disabled = questions[0].querySelector(".ant-radio-wrapper-disabled");
        if (disabled) return false;
        let first_unchosen = null;
        questions.forEach((question) => {
            let required = Boolean(question.querySelector('[class|="index_necessary"]'));
            if (!required) return;
            let tmp = question.querySelector("[class|='index_title']");
            let title = tmp.textContent.replace(/\s+/g, ""); // Remove spaces
            let standard_answer = standard_answers[title];
            console.log(`[Auto grading] ${title}: ${standard_answer}`);
            let chosen = false;
            if (standard_answer) {
                let options = question.querySelectorAll('[style="width: 100%;"]');
                for (let option of options) {
                    let is_standard_answer = (standard_answer.indexOf(option.innerText) >= 0);
                    if (is_standard_answer) {
                        option.firstChild.click();
                        chosen = true;
                        // break; // Compatible for multiple answers
                    }
                }
            }
            if (!chosen && first_unchosen == null) first_unchosen = question;
        });
        if (first_unchosen != null) {
            first_unchosen.scrollIntoView({ behavior: "smooth" });
            return false;
        }
        return true;
    }
    function ignore() {
        let ignore_btn = root_node.querySelector("[class|='TaskDetailsMainContent_normalButton']");
        if (ignore_btn && ignore_btn.parentElement.parentElement.parentElement.getAttribute('aria-hidden') == 'false') {
            ignore_btn.click();
        } else {
            console.log("[Auto grading] 未找到忽略按钮!");
        }
        let tabs = root_node.querySelector("[class='ant-tabs-nav-scroll']");
        if (tabs) {
            tabs = tabs.children[0].children[0];
        } else {
            console.log("[Auto grading] 未找到教师/助教列表!");
            return;
        }
        let flag = false;
        let tab;
        for (tab of tabs.children) {
            if (flag) {
                tab.click();
                break;
            } else if (tab.getAttribute('aria-selected') == 'true') {
                flag = true;
            }
        }
    }
    function auto() {
        if (try_click("button[class^='ant-btn ant-btn-primary']")) // Next teacher/course
            return true;
        if (grade()) { // Select standard answer
            try_click("button[class^='ant-btn index_submit']"); // Submit
            return true;
        }
        return false;
    }
    function full_auto() {
        while (auto()) { }
        alert("执行结束!");
    }
    function dump() {
        let questions = document.querySelectorAll("[class|='index_subject']");
        let disabled = questions[0].querySelector(".ant-radio-wrapper-disabled");
        if (disabled) return false;
        let data = {};
        questions.forEach((question) => {
            let required = Boolean(question.querySelector('[class|="index_necessary"]'));
            if (!required) return;
            let tmp = question.querySelector("[class|='index_title']");
            let title = tmp.textContent.replace(/\s+/g, ""); // Remove spaces
            let options = question.querySelectorAll('[style="width: 100%;"]');
            data[title] = [];
            for (let option of options) {
                data[title].push(option.innerText);
            }
        });
        console.log(JSON.stringify(data));
    }
    function try_click(selector) {
        let btn = document.querySelector(selector);
        if (btn && btn.checkVisibility()) {
            btn.click();
            return true;
        } else {
            return false;
        }
    }
    // Side bar
    const root_node = document.getElementById('root');
    const config = { attributes: false, childList: true, subtree: true };
    const callback = function(mutations, observer) {
        menu_root = root_node.querySelector('.ant-menu-root');
        if (menu_root) {
            observer.disconnect();
            add_item("使用说明", "自动评教脚本使用说明", help);
            add_item("自动评价", "自动选择标准答案", grade);
            add_item("忽略并转到下一个", "(若可能)忽略当前助教并转到下一个助教", ignore);
            add_item("全自动评教", "(实验性功能)彻底解放双手", full_auto);
            add_item("输出答案", "(调试用)输出当前问卷的所有答案", dump);
        }
    }
    const observer = new MutationObserver(callback);
    observer.observe(root_node, config);
    // Shortcut
    document.addEventListener("keyup", (e) => {
        if (document.activeElement.nodeName != "INPUT") { // No invoke if user is typing
            switch (e.key) {
                case "Enter":
                    if (!e.shiftKey) {
                        auto();
                    } else {
                        full_auto();
                    }
                    break;
                case "Backspace":
                    ignore();
                    break;
                default:
                    break;
            }
        }
    });
})();