// ==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 = $(``); $reviewButton.on("click", () => { config.autoSubmit = false; listener(); }); const $reviewAndSubmitButton = $(``); $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);