Greasy Fork

Greasy Fork is available in English.

MyCOS 自动评教

自动完成评教功能,适用于所有采用MyCOS(评教系统左上角有MyCOS或M标识)系统的高校或其他单位。

目前为 2023-05-29 提交的版本,查看 最新版本

// ==UserScript==
// @name         MyCOS 自动评教
// @namespace    https://github.com/lcandy2
// @version      1.1
// @description  自动完成评教功能,适用于所有采用MyCOS(评教系统左上角有MyCOS或M标识)系统的高校或其他单位。
// @match        *://*/*
// @homepage     https://github.com/lcandy2/MyCOS
// @run-at       document-end
// @grant        none
// ==/UserScript==

(function () {
  'use strict';

  let comment = "我对本课程非常满意。"; // 评价内容
  let radioSelection = [0, 1, 2]; // 0: 非常同意,1: 同意,2: 一般,3: 不同意,4: 非常不同意
  let checkboxSelection = 1; // 要全选所有多选题,请将此值改为1,否则请将此值改为0
  let autoSubmission = 0; // 要自动提交,请将此值改为1,否则请将此值改为0
  let fillComment = 1; // 要填写评价,请将此值改为1,否则请将此值改为0

  // 以下代码请勿修改
  fetch('/config.js')
    .then(response => response.text())
    .then(data => {
      if (data.toLowerCase().includes('mycos')) {
        // 如果config.js中包含'mycos'(忽略大小写),则执行脚本
        appendLogWindow();
        appendLog('准备开始评教,等待2秒...', 'salmon');
        setTimeout(() => {
          executeMyCOS();
        }, 1200);
        console.log('Found mycos in /config.js. Running script...');
      } else {
        console.log('No mycos found in /config.js.');
      }
    })
    .catch((error) => {
      console.error('Error:', error);
    });

  //函数getRnd,用于生成给定范围内的随机数
  const getRnd = (min, max) => {
    return Math.floor(Math.random() * (max + 1 - min)) + min;
  }

  //函数seleRadio,用于点击指定的单选按钮
  const seleRadio = (fixedTexts = ["非常", "", "一般", "不", "非常不"]) => {
    // fixedTexts = ["非常不", "不", "一般", "", "非常"]
    let positions = new Array(5).fill(-1);// 初始化五个选项的位置数组

    $('.ant-radio-group').each(function () {
      let options = $(this).find('.ant-radio-wrapper');
      options.each(function (index) {
        let text = $(this).text().trim();

        // 非常同意
        // * 如果文本以"非常"开头,但不以"非常不"开头,则记录最好评价选项的位置
        if (text.includes(fixedTexts[0]) && !text.includes(fixedTexts[4])) {
          positions[0] = index;
        }

        // 非常不同意
        // * 如果文本以"非常不"开头,则记录最差评价选项的位置
        if (text.includes(fixedTexts[4])) {
          positions[4] = index;
        }

        // 不同意
        // * 如果文本以"不"开头,则记录较差评价选项的位置
        if (text.includes(fixedTexts[3]) && !text.includes(fixedTexts[4])) {
          positions[3] = index;
        }

        // 一般
        // * 检测中立评价选项,如果文本包含"一般",则记录中立评价选项的位置
        if (text.includes(fixedTexts[2])) {
          positions[2] = index;
        } else if (index > 0 && index < 4) { // 如果文本不包含"中立",通过前后选项确定中立选项的位置
          let prevText = options.eq(index - 1).text().trim();
          let nextText = options.eq(index + 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] = index;
          }
        }

        // 同意
        // * 如果文本不以"非常"和"不"开头,并且未被标记为中立评价选项,则记录较好评价选项的位置
        if (!fixedTexts[1] == "") {
          if (text.startWith(fixedTexts[1])) {
            positions[1] = index;
          }
        }
        if (!text.includes(fixedTexts[0]) && !text.includes(fixedTexts[3]) && positions[2] !== index && positions[1] == -1) {
          positions[1] = index;
        }
      });

      radioSelection.sort();
      let randomIndex = getRnd(radioSelection[0], radioSelection[radioSelection.length - 1]);
      // console.log(randomIndex);
      // console.log(positions);
      if (positions[randomIndex] !== -1) {
        options.eq(positions[randomIndex]).click();
        // 初始化 positions
        positions = Array(5).fill(-1);
      }
      // console.log(positions);
    });
    // return positions
  }

  //获取所有多选按钮组,并遍历每个多选按钮组
  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 fillInput = () => {
    let textbox_list = $(".ant-input");
    for (let i = 0; i < textbox_list.length; i++) {
      let textArea = textbox_list[i];
      // 获取到原生的输入值设置器
      let nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
      // 创建一个新的事件
      let inputEvent = new Event('input', { bubbles: true });
      // 设置textarea的值
      nativeInputValueSetter.call(textArea, comment);
      // 触发`input`事件
      textArea.dispatchEvent(inputEvent);
    }
  };

  const submitTask = () => {
    setTimeout(() => {
      $('.ant-btn-primary').click();
    }, 1000);
  }

  const nextTeacher = () => {
    let nextTeacherButton = Array.from(document.querySelectorAll('button')).find(el => el.textContent === '下一位教师' || el.textContent === '下一门课程');
    if (!nextTeacherButton) {
      let modalElement = document.querySelector('div.ant-modal');
      if (!modalElement) {
        console.warn('警告:未找到 ant-modal,将使用按钮 ant-btn-primary 寻找。');
      } else {
        nextTeacherButton = modalElement.querySelector('ant-btn-primary');
      }
    }
    if (nextTeacherButton) {
      nextTeacherButton.click();
      appendLog('已自动切换下一课程/教师');
    } else {
      appendLog('未找到切换按钮,请手动点击切换', 'firebrick');
    }
  }

  const executeMyCOS = () => {
    if (radioSelection.length > 0) {
      seleRadio();
      appendLog('已完成单选题!');
    } else { appendLog('单选题选择功能已由用户关闭。', 'grey'); }
    if (checkboxSelection == 1) {
      seleCheckbox();
      appendLog('已完成多选题!');
    } else { appendLog('多选题全选功能已由用户关闭。', 'grey'); }
    if (fillComment == 1) {
      fillInput();
      appendLog('已填写文本题目:' + comment);
    } else { appendLog('文本题目填写功能已由用户关闭。', 'grey'); }
    if (autoSubmission == 1) {
      appendLog('自动提交已开启,等待1秒准备提交...', 'salmon');
      submitTask();
      appendLog('准备切换下一位教师,等待2秒...', 'salmon');
      setTimeout(() => {
        nextTeacher();
      }, 2000);
    } else { appendLog('自动提交功能已由用户关闭。', 'grey'); }
    appendLog('已完成《' + $("h1").text() + '》!\n', 'green');
  }

  let logWindow = document.createElement('div');
  const logContent = document.createElement('div');
  const appendLogWindow = () => {
    // Create log window title
    const logWindowTitle = document.createElement('div');
    logWindowTitle.innerHTML = 'MyCOS自动评教<br> - <a href="https://support.microsoft.com/zh-cn/microsoft-edge/%E5%9C%A8-microsoft-edge-%E4%B8%AD%E6%B7%BB%E5%8A%A0-%E5%85%B3%E9%97%AD%E6%88%96%E5%88%A0%E9%99%A4%E6%89%A9%E5%B1%95-9c0ec68c-2fbc-2f2c-9ff0-bdc76f46b026" target="_blank">了解如何禁用扩展程序?</a>';
    logWindowTitle.style.backgroundColor = '#d3d3d3';
    logWindowTitle.style.color = '#000000';
    logWindowTitle.style.padding = '5px';
    logWindowTitle.style.fontWeight = 'bold';
    logWindowTitle.style.borderRadius = '10px 10px 0 0';

    // Log window styles
    logWindow.id = 'log-window';
    logWindow.style.position = 'fixed';
    logWindow.style.zIndex = '10000';
    logWindow.style.backgroundColor = '#f3f3f3';
    logWindow.style.borderRadius = '10px';
    logWindow.style.boxShadow = '0 2px 10px rgba(0,0,0,0.5)';
    logWindow.style.width = '300px';
    logWindow.style.height = '400px';
    logWindow.style.overflowY = 'scroll';
    logWindow.style.bottom = '10px';
    logWindow.style.left = '10px';
    logWindow.style.cursor = 'move';

    // Append title to log window
    logWindow.appendChild(logWindowTitle);

    // Create log content div and style it
    logContent.style.padding = '10px';
    logWindow.appendChild(logContent);

    // 使其可拖动
    logWindow.onmousedown = function (event) {
      logWindow.style.position = 'absolute';
      logWindow.style.zIndex = 1000;

      function moveAt(e) {
        logWindow.style.left = e.pageX - logWindow.offsetWidth / 2 + 'px';
        logWindow.style.top = e.pageY - logWindow.offsetHeight / 2 + 'px';
      }

      // 在鼠标移动时移动窗口
      document.addEventListener('mousemove', moveAt);

      // 在鼠标松开时停止移动窗口
      logWindow.onmouseup = function () {
        document.removeEventListener('mousemove', moveAt);
        logWindow.onmouseup = null;
      };
    };

    // 禁止浏览器选择
    logWindow.ondragstart = function () {
      return false;
    };

    // 添加到页面
    document.body.appendChild(logWindow);
  }

  const appendLog = (message, color = 'black') => {
    console.log(message);
    let timestamp = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
    logContent.innerHTML += `<div style="color:${color};">${timestamp} - ${message}</div>`;
    logWindow.scrollTop = logWindow.scrollHeight;
  };

})();