USTC 自动评价 tqm.ustc.edu.cn
目前為
// ==UserScript==
// @name Auto grading
// @namespace http://tampermonkey.net/
// @version 0.6.4
// @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 clean(str) {
// Remove spaces
str = str.replace(/\s+/g, "");
// Remove leading asterisk
if (str[0] == '*') str = str.slice(1);
// Remove leading serial number
str = str.replace(/^\d*\./, "");
// Remove "(单选题)"/"(多选题)"
str = str.replace("(单选题)", "");
str = str.replace("(多选题)", "");
return str;
}
function add_item(display_name, hint, callback) {
const new_item = menu_root.appendChild(document.createElement("li"));
new_item.innerText = display_name;
new_item.onclick = callback;
new_item.className = "ant-menu-item";
new_item.title = hint;
}
function help() {
alert("食用方法:\n1. 进入未完成的评价问卷\n2. 侧栏选择你想要的操作或激活快捷键\n3. 等待脚本执行\n\n快捷键说明:\n- Enter: 智能执行以下中的一项: 下一位教师/选择标准答案/提交回答\n- Shift+Enter: 全自动评教\n- Backspace: 忽略并转到下一个");
}
function grade() {
const questions = document.querySelectorAll("[class^='index_subject-']");
const disabled = questions[0].querySelector(".ant-radio-wrapper-disabled");
if (disabled) return false;
let first_unchosen = null;
questions.forEach((question) => {
const required = Boolean(question.querySelector('[class^="index_necessary"]'));
if (!required) return;
const tmp = question.querySelector("[class^='index_title']");
const remark = tmp.querySelector("[class^='index_remarks-']");
const title = remark?.textContent || clean(tmp.querySelector("[class^='index_richTextContent']").textContent);
const standard_answer = standard_answers[title];
console.log(`[Auto grading] ${title}: ${standard_answer}`);
let chosen = false;
if (standard_answer) {
const options = question.querySelectorAll('[style="width: 100%;"]');
for (const option of options) {
const 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() {
const 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] 未找到忽略按钮!");
}
const 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()) { }
const timer = window.setInterval(() => {
if (!auto()) {
window.clearInterval(timer);
alert("执行结束!");
}
}, 500);
}
function dump() {
const questions = document.querySelectorAll("[class^='index_subject-']");
const disabled = questions[0].querySelector(".ant-radio-wrapper-disabled");
if (disabled) return false;
let data = {};
questions.forEach((question) => {
const required = Boolean(question.querySelector('[class^="index_necessary"]'));
if (!required) return;
const tmp = question.querySelector("[class^='index_title']");
const remark = tmp.querySelector("[class^='index_remarks-']");
const title = remark?.textContent || clean(tmp.querySelector("[class^='index_richTextContent']").textContent);
const options = question.querySelectorAll('[style="width: 100%;"]');
data[title] = [];
for (const option of options) {
data[title].push(option.innerText);
}
});
console.log(JSON.stringify(data));
}
function try_click(selector) {
const ele = document.querySelector(selector);
if (ele && ele.checkVisibility()) {
ele.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" || document.activeElement.nodeName != "TEXTAREA") { // Don't trigger when typing
switch (e.key) {
case "Enter":
if (!e.shiftKey) {
auto();
} else {
full_auto();
}
break;
case "Backspace":
ignore();
break;
default:
break;
}
}
});
})();