Greasy Fork

Greasy Fork is available in English.

自动评教:适用于 MyCOS / 麦可思 的自动评教 MyCOS Auto Review

一键评教,自动完成课程评价,支持单选、多选、文本评价。支持仅填充评价和填充并提交评价两种模式。适用于所有采用 MyCOS / 麦可思 (评教系统左上角有MyCOS或M标识)系统的高校或其他单位。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==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/[email protected]/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);