自动评教:适用于 MyCOS / 麦可思 的自动评教 MyCOS Auto Review
// ==UserScript==
// @name 自动评教:适用于 MyCOS / 麦可思 的自动评教 MyCOS Auto Review
// @namespace https://github.com/lcandy2/MyCOS-Auto-Review
// @version 2.0.1
// @author lcandy2
// @description 一键评教,自动完成课程评价,支持单选、多选、文本评价。支持仅填充评价和填充并提交评价两种模式。适用于所有采用 MyCOS / 麦可思 (评教系统左上角有MyCOS或M标识)系统的高校或其他单位。
// @license MIT
// @icon http://www.mycos.com.cn/Uploads/icopic/54a0fcc38f623.ico
// @match *://*.edu.cn/*
// @match *://*.mycospxk.com/*
// @require https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js
// @run-at document-start
// ==/UserScript==
(function ($) {
'use strict';
const config = {
// 是否自动提交 true: 自动提交, false: 不自动提交
autoSubmit: false,
// 评价内容
// 单选范围 0: 非常同意,1: 同意,2: 一般,3: 不同意,4: 非常不同意
radio: [0, 1],
// 多选题评价 true: 全部选中, false: 全部不选中
checkbox: true,
// 文本评价 任意文本: 自动填充文本, 留空: 不填充
comment: "我对本课程非常满意。",
// Dev Only
// 评价页面的 href,无特殊情况不需要修改
reviewHref: "answer",
// 评价页面的父元素,无特殊情况不需要修改
reviewParentElement: "div.ant-tabs div.ant-tabs-bar div.ant-tabs-nav-container div.ant-tabs-nav-wrap div.ant-tabs-nav-scroll",
// 评价页面的单选框选项,无特殊情况不需要修改(由好到不好)
reviewRadioField: ["非常", "", "一般", "不", "非常不"],
// 提交评价按钮,无特殊情况不需要修改
reviewSubmitElement: ".ant-btn.ant-btn-primary:not(.--lcandy2-mycos-auto-review)",
// 评价弹窗,无特殊情况不需要修改
reviewModalElement: "div.ant-modal-body"
};
const fillInput = (element, value) => {
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
const inputEvent = new Event("input", { bubbles: true });
nativeInputValueSetter.call(element, value);
element.dispatchEvent(inputEvent);
};
const getRnd = (min, max) => {
return Math.floor(Math.random() * (max + 1 - min)) + min;
};
const seleRadio = (selection, fixedTexts = ["非常", "", "一般", "不", "非常不"]) => {
let positions = new Array(5).fill(-1);
let result = false;
let radioSelection = selection;
$(".ant-radio-group").each((index, element) => {
let options = $(element).find(".ant-radio-wrapper");
options.each((index2, element2) => {
let text = $(element2).text().trim();
if (text.includes(fixedTexts[0]) && !text.includes(fixedTexts[4])) {
positions[0] = index2;
}
if (text.includes(fixedTexts[4])) {
positions[4] = index2;
}
if (text.includes(fixedTexts[3]) && !text.includes(fixedTexts[4])) {
positions[3] = index2;
}
if (text.includes(fixedTexts[2])) {
positions[2] = index2;
} else if (index2 > 0 && index2 < 4) {
let prevText = options.eq(index2 - 1).text().trim();
let nextText = options.eq(index2 + 1).text().trim();
let character1Prev = prevText.replace(fixedTexts[0], "").replace(fixedTexts[3], "");
let character1Next = nextText.replace(fixedTexts[0], "").replace(fixedTexts[3], "");
let character1Current = text.replace(fixedTexts[0], "").replace(fixedTexts[3], "");
if (character1Current !== character1Prev && character1Current !== character1Next) {
positions[2] = index2;
}
}
if (!fixedTexts[1] == "") {
if (text.startWith(fixedTexts[1])) {
positions[1] = index2;
}
}
if (!text.includes(fixedTexts[0]) && !text.includes(fixedTexts[3]) && positions[2] !== index2 && positions[1] == -1) {
positions[1] = index2;
}
});
radioSelection.sort();
let randomIndex = getRnd(radioSelection[0], radioSelection[radioSelection.length - 1]);
console.log("[单选题] 第 " + (index + 1) + " 题,随机选择第 " + (randomIndex + 1) + " 个选项:" + fixedTexts[randomIndex] + "同意。");
if (positions[randomIndex] !== -1) {
options.eq(positions[randomIndex]).trigger("click");
result = true;
positions = Array(5).fill(-1);
}
});
return result;
};
const seleCheckbox = () => {
let checkbox_list = $(".ant-checkbox-group");
for (let i = 0; i < checkbox_list.length; i++) {
let lists = checkbox_list[i].children;
for (let j = 0; j < lists.length; j++) {
let btn = $(checkbox_list[i]).find(".ant-checkbox-input")[j];
$(btn).trigger("click");
}
}
};
const fillComments = (comment) => {
const textbox_list = $(".ant-input");
for (let i = 0; i < textbox_list.length; i++) {
const textArea = textbox_list[i];
fillInput(textArea, comment);
}
};
const Review = () => {
const seleRadioResult = seleRadio(config.radio);
console.log(seleRadioResult ? "[单选题] 评价完成" : "[单选题] 未找到单选题");
{
const seleCheckboxResult = seleCheckbox();
console.log(seleCheckboxResult ? "[多选题] 评价完成" : "[多选题] 未找到多选题");
}
{
const fillCommentsResult = fillComments(config.comment);
console.log(fillCommentsResult ? "[文本评价] 评价完成" : "[文本评价] 未找到文本评价");
}
console.log("[自动评教] 全部评教完成");
};
const addReviewButton = (listener) => {
if ($("button.--lcandy2-mycos-auto-review").length)
return;
const $parentElement = $(config.reviewParentElement);
const $reviewButton = $(`<button type="button" class="ant-btn ant-btn-default --lcandy2-mycos-auto-review">一 键 评 教</button>`);
$reviewButton.on("click", () => {
config.autoSubmit = false;
listener();
});
const $reviewAndSubmitButton = $(`<button type="button" class="ant-btn ant-btn-primary --lcandy2-mycos-auto-review">评 教 并 <b>提 交</b></button>`);
$reviewAndSubmitButton.on("click", () => {
config.autoSubmit = true;
listener();
});
$parentElement.append($reviewButton);
$parentElement.append($reviewAndSubmitButton);
console.log("Add Review Button", $reviewButton, $reviewAndSubmitButton);
};
const mycosTest = async () => {
const configJs = $("script").filter((index, element) => {
const src = $(element).attr("src");
return src && src.includes("config.js");
});
if (!configJs.length) {
return false;
}
const response = await fetch(configJs.attr("src"));
const responseText = await response.text();
const test = responseText.includes("mycos");
return test;
};
const watchUrlChange = (onChange) => {
const originalPushState = history.pushState;
history.pushState = function(state, title, url) {
originalPushState.apply(this, arguments);
onChange(url);
};
const originalReplaceState = history.replaceState;
history.replaceState = function(state, title, url) {
originalReplaceState.apply(this, arguments);
onChange(url);
};
window.addEventListener("popstate", () => {
onChange(document.location.href);
});
};
const submitReview = () => {
setTimeout(() => {
$(config.reviewSubmitElement).trigger("click");
}, 500);
};
const removeModal = ($button) => {
$button.prop("disabled", false);
$button.trigger("click");
};
const executeReview = async () => {
Review();
const $submitButton = $(config.reviewSubmitElement);
$submitButton.children().text("评价完成,点击提交");
if (config.autoSubmit) {
submitReview();
alert("评价完成,已自动提交。");
}
};
const main = () => {
addReviewButton(executeReview);
};
const observer = (func, func2) => {
const observer2 = new MutationObserver((mutations) => {
for (let mutation of mutations) {
if (mutation.addedNodes.length) {
const $topContent = $(config.reviewParentElement);
const href = window.location.href;
const hrefTest = href.includes(config.reviewHref);
if ($topContent.length && hrefTest) {
observer2.disconnect();
func();
break;
}
const $modalBody = $(config.reviewModalElement);
const $button = $modalBody.find("button.ant-btn-primary");
if ($modalBody.length && $button.length) {
observer2.disconnect();
func2($button);
console.log("已移除评价弹窗");
break;
}
}
}
});
observer2.observe(document.body, {
childList: true,
subtree: true
});
};
$(async () => {
if (!mycosTest())
return;
observer(main, removeModal);
watchUrlChange((newUrl) => {
observer(main, removeModal);
});
});
})(jQuery);