科举小抄

科举小抄 - 阿里云大学“科考”辅助工具

当前为 2020-09-22 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         科举小抄
// @namespace    https://github.com/fuckKeju/fuckKeju
// @version      0.0.4
// @description  科举小抄 - 阿里云大学“科考”辅助工具
// @author       fuckKeju
// @match        *.developer.aliyun.com/*
// @run-at       document-start
// @grant        unsafeWindow
// ==/UserScript==

/* 题库数据 */
var customQuestionsDatabase = []
var useCustomQuestionsDatabase = false

var hasInit = false

function fuckKeju () {
  if (hasInit) { return false }
  console.log('科举小抄 init suc')
  hasInit = true

  const subjectTitle = document.querySelector('.yq-main-examination .top-info h2.title-content').innerText
  const isExamMode = document.querySelector('.yq-main-examination .time-info')

  let questionsDatabase = []
  try {
    questionsDatabase = JSON.parse(localStorage.getItem(subjectTitle) || '[]')
  } catch (e) {
    questionsDatabase = []
  }

  /* 使用预置数据,而非定义的数据 */
  if (useCustomQuestionsDatabase) {
    questionsDatabase = customQuestionsDatabase
  }

  var result = []
  var items = document.querySelectorAll('.question-panel .question-item')
  if (items) {
    items.forEach(item => {
      const type = item.querySelector('.q-title .q-tag').innerText.trim()
      const title = item.querySelector('.q-title .q-t-text').innerText.trim().replace(/\s+/gi, ' ').replace(/\?{2,}/gi, ' ')
      const answerList = []
      const answerListEl = item.querySelectorAll('.q-option .answer-text')
      console.log(answerListEl)
      answerListEl.forEach(answerEl => {
        let answer = answerEl.innerText.trim()
        const checkedEl = answerEl.parentNode.querySelector('input')
        if (checkedEl && checkedEl.checked) {
          answer += '  --checked'
        }
        answerList.push(answer)
      })

      const data = {
        title,
        type,
        answerList
      }

      const pointEl = item.querySelector('.e-point .p-detail')
      if (pointEl) {
        data.point = '相关知识点:' + pointEl.innerText.trim()
      } else {
        data.point = '未匹配到任何相关知识点'
      }

      const rightAnswerEl = item.querySelector('.right-answer')
      if (rightAnswerEl) {
        data.rightAnswer = rightAnswerEl.innerText.trim() || '答案正确'
      } else {
        if (isExamMode) {
          data.rightAnswer = '答案未知'
        } else {
          data.rightAnswer = '答案正确'
        }
      }

      result.push(data)

      /* 判断当前题目是否已经存在 */
      let hasInDatabase = false

      /* 提取相关问题 */
      const titleTxt = title.replace(/^\d+\./, '')
      let relatedQuestions = []

      function relatedQuestionsHandler (item) {
        const itemTitle = item.title.replace(/\s+/gi, ' ').replace(/\?{2,}/gi, ' ')
        if (itemTitle.includes(titleTxt)) {
          const answerA = answerList[0].replace(/^[ABCDEFG]\.\s/, '').replace(/\s+--checked/, '')
          for (let i = 0; i < item.answerList.length; i++) {
            const answerTxt = item.answerList[i]
            if (answerTxt.includes(answerA)) {
              relatedQuestions.push(item)
              break
            }
          }
        }

        if (itemTitle === title) {
          hasInDatabase = true
        }
      }

      questionsDatabase.forEach(list => {
        if (Array.isArray(list)) {
          list.forEach(item => {
            relatedQuestionsHandler(item)
          })
        } else {
          relatedQuestionsHandler(list)
        }
      })

      /* 查找是否存在答对的历史记录,优先显示答对的数据 */
      if (relatedQuestions.length > 1) {
        const rightAnswerArr = []
        const wrongAnswerArr = []
        relatedQuestions.forEach(data => {
          if (data.rightAnswer === '答案正确') {
            rightAnswerArr.push(data)
          } else {
            wrongAnswerArr.push(data)
          }
        })
        relatedQuestions = rightAnswerArr.concat(wrongAnswerArr)
      }

      /* 收集题目数据 */
      if (!isExamMode && !hasInDatabase) {
        questionsDatabase.push(data)
      }

      const dd = document.createElement('dd')
      dd.setAttribute('class', 'relatedQuestions')
      dd.style.marginTop = '30px'
      dd.style.display = 'none'
      dd.style.border = '1px solid #ccc'
      dd.style.borderRadius = '5px'
      // dd.style.padding = '10px'
      // dd.style.backgroundColor = '#f9f9f9'

      if (item.querySelector('.relatedQuestions')) {
        item.removeChild(item.querySelector('.relatedQuestions'))
      }

      if (relatedQuestions.length) {
        const codeEl = document.createElement('pre')
        codeEl.style.border = 'none'
        codeEl.innerHTML = JSON.stringify(relatedQuestions, null, 2)
        dd.appendChild(codeEl)
        item.appendChild(dd)
      } else {
        dd.innerText = '暂无相关题目信息,先考几遍,然后查看考试结果再试试吧'
        item.appendChild(dd)
      }

      item.ondblclick = function (event) {
        const relatedQuestions = item.querySelector('.relatedQuestions')
        if (relatedQuestions) {
          if (relatedQuestions.style.display === 'none') {
            relatedQuestions.style.display = 'block'
            relatedQuestions.style.opacity = 0.4
            relatedQuestions.style.overflow = 'auto'
            relatedQuestions.style.maxHeight = '200px'
          } else {
            relatedQuestions.style.display = 'none'
          }
        }
      }
    })
  }

  localStorage.setItem(subjectTitle, JSON.stringify(questionsDatabase))

  /* 切换题目时候,隐藏小抄 */
  const switchDoms = document.querySelectorAll('.question-num span.item')
  const switchDoms02 = document.querySelectorAll('.e-opt-panel a')

  function hideRelatedQuestions () {
    const relatedQuestionsEls = document.querySelectorAll('.relatedQuestions')
    relatedQuestionsEls.forEach(item => {
      item.style.display = 'none'
    })
  }

  switchDoms.forEach(el => {
    el.onmouseenter = hideRelatedQuestions
  })
  switchDoms02.forEach(el => {
    el.onclick = hideRelatedQuestions
  })

  console.log(JSON.stringify(result, null, 2))
}

function ready (selector, fn, shadowRoot) {
  const listeners = []
  const win = window
  const doc = shadowRoot || win.document
  const MutationObserver = win.MutationObserver || win.WebKitMutationObserver
  let observer

  function $ready (selector, fn) {
    // 储存选择器和回调函数
    listeners.push({
      selector: selector,
      fn: fn
    })
    if (!observer) {
      // 监听document变化
      observer = new MutationObserver(check)
      observer.observe(shadowRoot || doc.documentElement, {
        childList: true,
        subtree: true
      })
    }
    // 检查该节点是否已经在DOM中
    check()
  }

  function check () {
    for (let i = 0; i < listeners.length; i++) {
      var listener = listeners[i]
      var elements = doc.querySelectorAll(listener.selector)
      for (let j = 0; j < elements.length; j++) {
        var element = elements[j]
        if (!element._isMutationReady_) {
          element._isMutationReady_ = true
          listener.fn.call(element, element)
        }
      }
    }
  }

  $ready(selector, fn)
}

ready('.question-panel .question-item', () => {
  /**
   * 此处必须延迟执行,题目渲染和选中渲染是异步操作
   * 需要延时等待选中的渲染成功才执行初始化逻辑
   */
  console.log('检查到进入了试题页面,即将为你初始化小炒逻辑')
  setTimeout(function () {
    fuckKeju()
  }, 1000 * 3)
})