[归档] 上海市大学生安全教育在线 - 自动答题(在线题库版)

可处理正式考试,顺便去除各种限制。

// ==UserScript==
// @name        [归档] 上海市大学生安全教育在线 - 自动答题(在线题库版)
// @description 可处理正式考试,顺便去除各种限制。
// @version     1
// @namespace   UnKnown
// @author      UnKnown
// @match       http://www.halnedu.com/pcexam/test/start*
// @require     https://greasyfork.org/scripts/383541.js
// @grant       none
// @run-at      document-end
// ==/UserScript==

/* TODO:
   模糊匹配
   匹配时去除题目名称或答案内容中的中英文括号等符号
   选择题按答案内容而非 ABCD 匹配

   对于自动答题失败的题目:
      输出可供直接搜索的、去除中英文括号与空格的文本
      搞个搜索答案按钮?点击后跳转到搜索引擎

   分类统计自动答题失败的题目数量
   无失败题目时,输出表示成功的日志
*/

// 解除各种限制
(function () {
  document.oncontextmenu = null;
  document.onselectstart = null;
  document.onkeydown     = null;
  document.oncopy        = null;
})();
// 准备待答题目

// 序号: ( 一 OR 二位十进制数字 ) + ( 中文逗号 OR ( 英文句点 + 空格 ) )
const getQuestionName =
aQuestionNodeList => Array.from(aQuestionNodeList).map(
  aQuestionDiv => aQuestionDiv.querySelector('h1').textContent
  .replace(/^\d{1,2}(,|\. |\.)/, "") // 去除题目序号
  .replace(/\s/g, "")                 // 去除空白字符
);

var questionAll = document.querySelectorAll('.question');
var questionNameAll = getQuestionName(questionAll);
/*
1. True or False
2. Single Choice Question
3. Multiple Choice Question
*/
// Questions' Elements
const [ToF, SCQ, MCQ] = ["q1", "q2", "q3"].map(
  qn => document.getElementById(qn)
);

// Questions' NodeLists
var [ToFQuestionAll, SCQQuestionAll, MCQQuestionAll]
  = [ToF, SCQ, MCQ].map(
    questionDiv => questionDiv.querySelectorAll('.question')
  );

// Question names' Arrays
var ToFQuestionNameAll = getQuestionName(ToFQuestionAll);
var SCQQuestionNameAll = getQuestionName(SCQQuestionAll);
var MCQQuestionNameAll = getQuestionName(MCQQuestionAll);

// Uncheck all checked checkbox under a div
function unselect(aDiv) {
  aDiv.querySelectorAll('.active').forEach(aSelected => {
    aSelected.classList.remove("active");
    aSelected.querySelector('input').checked = false;
  });
}
// Uncheck all checked checkbox in Multiple Choice Question first
// in case of duplicated click
if (MCQ.querySelector('.active') !== null) unselect(MCQ);

// ! function below will only run after the file loaded

// AL === AnswerLib

function ToFQuestionFiller(theALMap) {

  ToFQuestionNameAll.forEach((ToFQuestionName, index) => {

    var answer = theALMap.get(ToFQuestionName);
    var questionDiv = ToFQuestionAll[index];

    try {

      if (answer === undefined) {
        throw "未找到第" + (index + 1) + "道判断题的答案,题目开头为:" + ToFQuestionName.slice(0, 50);
      } else if ((answer === "正确")) {
        questionDiv.querySelector('input[value="1"]').click();
      } else if ((answer === "错误")) {
        questionDiv.querySelector('input[value="0"]').click();
      } else {
        throw "第" + (index + 1) + "道判断题的答案数据有误,题目开头为:" + ToFQuestionName.slice(0, 50);
      }

    } catch (e) { console.log(e); }
  });

}

// Answer String > Verify > Answer Element > click
function SCQQuestionFiller(theALMap) {

  SCQQuestionNameAll.forEach((SCQQuestionName, index) => {

    var answer = theALMap.get(SCQQuestionName);
    var questionDiv = SCQQuestionAll[index];

    try {

      if (answer === undefined) {
        throw "未找到第" + (index + 1) + "道单选题的答案,题目开头为:" + SCQQuestionName.slice(0, 50);
      } else {

        var answerKey = answer.charAt(0); // 目前仅取答案首个字符(ABCD),不判断答案内容

        if (["A", "B", "C", "D"].includes(answerKey)) {
          questionDiv.querySelector('input[value="' + answerKey + '"]').click();
        } else {
          throw "第" + (index + 1) + "道单选题的答案数据有误,题目开头为:" + SCQQuestionName.slice(0, 50);
        }

      }

    } catch (e) { console.log(e); }

  });

}

// Answer String > Answer Array > Verify > Answer Element Array > click
function MCQQuestionFiller(theALMap) {

  MCQQuestionNameAll.forEach((MCQQuestionName, index) => {

    var answer = theALMap.get(MCQQuestionName);
    var questionDiv = MCQQuestionAll[index];

    // Unselect when selected by previous answebLib
    if ( questionDiv.querySelector('.active') !== null ) {
      unselect(questionDiv);
    }

    try {

      if ( answer === undefined ) {
        throw "未找到第" + (index + 1) + "道多选题的答案,题目开头为:" + MCQQuestionName.slice(0, 50);
      } else if ( !Array.isArray(answer) ) {
        throw "第" + (index + 1) + "道多选题的答案数据有误(应为数组),题目开头为:" + MCQQuestionName.slice(0, 50);
      } else {
          // 目前仅取答案首个字符(ABCD),不判断答案内容
        var answerKeyAll = answer.map( answer => answer.charAt(0) );

        if ( answerKeyAll.every(
          answerKey => ["A", "B", "C", "D"].includes(answerKey)
        ) ) {
          answerKeyAll.forEach(
            answerKey => questionDiv.querySelector('input[value="' + answerKey + '"]').click()
          );
        } else {
          throw "第" + (index + 1) + "道多选题的答案数据有误,题目开头为:" + MCQQuestionName.slice(0, 50);
        }

      }

    } catch (e) { console.log(e); }

  });

}

function questionFiller(theALMap) {
  var aLine = "-".repeat(22);

  console.log(aLine + "判断题" + aLine);
  ToFQuestionFiller(theALMap);
  console.log(aLine + "单选题" + aLine);
  SCQQuestionFiller(theALMap);
  console.log(aLine + "多选题" + aLine);
  MCQQuestionFiller(theALMap);
}

function Array2Filter2Map(theArray, theSeparator) {
  let theMap = new Map();

  theArray.forEach(item => {
    var splited  = item.split(theSeparator);
    splited.shift(); // Remove ID
    var type     = splited.shift();
    var question = splited.shift();
    var answer   = splited.filter( x => (x !== "") );
    
    if (answer.length === 1) {
      answer = answer[0];
    }
    // 仅提取页面上的题目
    if (questionNameAll.includes(question)) {
      theMap.set(question, answer);
    }
  });

  return theMap;
}

function init() {
  var AnswerLibText = AnswerLib;
  var AnswerLibArray = AnswerLibText.trim().split("\n");
  var AnswerLibSeparator = AnswerLibArray.shift().charAt(0);

  var AnswerLibMap = Array2Filter2Map(AnswerLibArray, AnswerLibSeparator);

  console.log(AnswerLibMap);
  questionFiller(AnswerLibMap);
}

init();