exam

exam faster

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name         exam
// @description  exam faster
// @namespace    http://tampermonkey.net/
// @version      0.5.0
// @author       You
// @match        https://l.jd.com/student/exam/analyze.du*
// @match        https://l.jd.com/student/exam/no-answer-analyze.du*
// @match        https://l.jd.com/student/exam/exam.du*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=jd.com
// @grant        none
// @license      MIT
// ==/UserScript==
;(function () {
  "use strict"

  const STORAGE_KEY = "exam_answers_map"

  function getStoredAnswers() {
    const stored = localStorage.getItem(STORAGE_KEY)
    return stored ? JSON.parse(stored) : {}
  }

  function saveAnswers(answers) {
    localStorage.setItem(STORAGE_KEY, JSON.stringify(answers))
  }

  function mergeAnswers(oldAnswers, newAnswers) {
    return { ...oldAnswers, ...newAnswers }
  }

  function createModal(existingAnswers, newAnswers) {
    const overlay = document.createElement("div")
    overlay.style.cssText = `
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: rgba(0,0,0,0.5);
      z-index: 99999;
      display: flex;
      align-items: center;
      justify-content: center;
    `

    const modal = document.createElement("div")
    modal.style.cssText = `
      background: white;
      border-radius: 8px;
      padding: 20px;
      max-width: 800px;
      width: 90%;
      max-height: 80vh;
      overflow: auto;
    `

    const title = document.createElement("h3")
    title.innerText = "答案管理"
    title.style.cssText = "margin: 0 0 15px 0; text-align: center;"

    const content = document.createElement("div")
    content.style.cssText = "display: flex; gap: 20px; margin-bottom: 20px;"

    const leftPanel = document.createElement("div")
    leftPanel.style.cssText = "flex: 1; border: 1px solid #ddd; padding: 10px; border-radius: 4px; max-height: 400px; overflow: auto;"
    const leftTitle = document.createElement("h4")
    leftTitle.innerText = "已有答案"
    leftTitle.style.cssText = "margin: 0 0 10px 0; color: #333;"
    const leftContent = document.createElement("pre")
    leftContent.style.cssText = "margin: 0; font-size: 12px; white-space: pre-wrap; word-break: break-all;"
    leftContent.innerText = JSON.stringify(existingAnswers, null, 2)
    leftPanel.appendChild(leftTitle)
    leftPanel.appendChild(leftContent)

    const rightPanel = document.createElement("div")
    rightPanel.style.cssText = "flex: 1; border: 1px solid #ddd; padding: 10px; border-radius: 4px; max-height: 400px; overflow: auto;"
    const rightTitle = document.createElement("h4")
    rightTitle.innerText = "新获取答案"
    rightTitle.style.cssText = "margin: 0 0 10px 0; color: #333;"
    const rightContent = document.createElement("pre")
    rightContent.style.cssText = "margin: 0; font-size: 12px; white-space: pre-wrap; word-break: break-all;"
    rightContent.innerText = JSON.stringify(newAnswers, null, 2)
    rightPanel.appendChild(rightTitle)
    rightPanel.appendChild(rightContent)

    content.appendChild(leftPanel)
    content.appendChild(rightPanel)

    const btnGroup = document.createElement("div")
    btnGroup.style.cssText = "display: flex; gap: 10px; justify-content: center;"

    const overwriteBtn = document.createElement("button")
    overwriteBtn.innerText = "覆盖更新"
    overwriteBtn.style.cssText = "padding: 8px 20px; cursor: pointer; background: #ff6b6b; color: white; border: none; border-radius: 4px;"
    overwriteBtn.onclick = () => {
      saveAnswers(newAnswers)
      document.body.removeChild(overlay)
      alert("已覆盖更新到 localStorage")
    }

    const mergeBtn = document.createElement("button")
    mergeBtn.innerText = "合并更新"
    mergeBtn.style.cssText = "padding: 8px 20px; cursor: pointer; background: #4ecdc4; color: white; border: none; border-radius: 4px;"
    mergeBtn.onclick = () => {
      const merged = mergeAnswers(existingAnswers, newAnswers)
      saveAnswers(merged)
      document.body.removeChild(overlay)
      alert("已合并更新到 localStorage")
    }

    const cancelBtn = document.createElement("button")
    cancelBtn.innerText = "取消"
    cancelBtn.style.cssText = "padding: 8px 20px; cursor: pointer; background: #ddd; color: #333; border: none; border-radius: 4px;"
    cancelBtn.onclick = () => {
      document.body.removeChild(overlay)
    }

    btnGroup.appendChild(overwriteBtn)
    btnGroup.appendChild(mergeBtn)
    btnGroup.appendChild(cancelBtn)

    modal.appendChild(title)
    modal.appendChild(content)
    modal.appendChild(btnGroup)
    overlay.appendChild(modal)
    document.body.appendChild(overlay)
  }

  function exportToFile() {
    const answers = getStoredAnswers()
    const dataStr = JSON.stringify(answers, null, 2)
    const blob = new Blob([dataStr], { type: "application/json" })
    const url = URL.createObjectURL(blob)
    const a = document.createElement("a")
    a.href = url
    a.download = `exam_answers_${new Date().toISOString().slice(0, 10)}.json`
    a.click()
    URL.revokeObjectURL(url)
  }

  function importFromFile() {
    const input = document.createElement("input")
    input.type = "file"
    input.accept = ".json"
    input.onchange = (e) => {
      const file = e.target.files[0]
      if (!file) return
      const reader = new FileReader()
      reader.onload = (event) => {
        try {
          const importedAnswers = JSON.parse(event.target.result)
          const currentAnswers = getStoredAnswers()
          const merged = mergeAnswers(currentAnswers, importedAnswers)
          saveAnswers(merged)
          alert("导入成功")
        } catch (err) {
          alert("文件格式错误,请确保是有效的 JSON")
        }
      }
      reader.readAsText(file)
    }
    input.click()
  }

  function createImportExportModal() {
    const overlay = document.createElement("div")
    overlay.style.cssText = `
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: rgba(0,0,0,0.5);
      z-index: 99999;
      display: flex;
      align-items: center;
      justify-content: center;
    `

    const modal = document.createElement("div")
    modal.style.cssText = `
      background: white;
      border-radius: 8px;
      padding: 20px;
      max-width: 800px;
      width: 90%;
      max-height: 80vh;
      overflow: auto;
    `

    const title = document.createElement("h3")
    title.innerText = "答案管理"
    title.style.cssText = "margin: 0 0 15px 0; text-align: center;"

    const textarea = document.createElement("textarea")
    textarea.style.cssText = `
      width: 100%;
      height: 400px;
      font-family: monospace;
      font-size: 12px;
      padding: 10px;
      border: 1px solid #ddd;
      border-radius: 4px;
      resize: vertical;
      box-sizing: border-box;
    `
    const currentAnswers = getStoredAnswers()
    textarea.value = JSON.stringify(currentAnswers, null, 2)

    const btnGroup = document.createElement("div")
    btnGroup.style.cssText = "display: flex; gap: 10px; justify-content: center; margin-top: 15px;"

    const exportBtn = document.createElement("button")
    exportBtn.innerText = "导出到文件"
    exportBtn.style.cssText = "padding: 8px 20px; cursor: pointer; background: #4ecdc4; color: white; border: none; border-radius: 4px;"
    exportBtn.onclick = () => {
      exportToFile()
    }

    const importBtn = document.createElement("button")
    importBtn.innerText = "从文件导入"
    importBtn.style.cssText = "padding: 8px 20px; cursor: pointer; background: #ff6b6b; color: white; border: none; border-radius: 4px;"
    importBtn.onclick = () => {
      importFromFile()
      textarea.value = JSON.stringify(getStoredAnswers(), null, 2)
    }

    const saveBtn = document.createElement("button")
    saveBtn.innerText = "保存修改"
    saveBtn.style.cssText = "padding: 8px 20px; cursor: pointer; background: #51cf66; color: white; border: none; border-radius: 4px;"
    saveBtn.onclick = () => {
      try {
        const answers = JSON.parse(textarea.value)
        saveAnswers(answers)
        alert("保存成功")
      } catch (e) {
        alert("JSON 格式错误")
      }
    }

    const closeBtn = document.createElement("button")
    closeBtn.innerText = "关闭"
    closeBtn.style.cssText = "padding: 8px 20px; cursor: pointer; background: #ddd; color: #333; border: none; border-radius: 4px;"
    closeBtn.onclick = () => {
      document.body.removeChild(overlay)
    }

    btnGroup.appendChild(exportBtn)
    btnGroup.appendChild(importBtn)
    btnGroup.appendChild(saveBtn)
    btnGroup.appendChild(closeBtn)

    modal.appendChild(title)
    modal.appendChild(textarea)
    modal.appendChild(btnGroup)
    overlay.appendChild(modal)
    document.body.appendChild(overlay)
  }

  const paper = document.querySelector('.jdu-exam')
  const btnCtn = document.createElement("div")
  btnCtn.className = "jdu-btn-con";
  btnCtn.style.position = "fixed";
  paper.appendChild(btnCtn)

  const btn = document.createElement("a")
  btn.href = "#"
  btn.innerText = "Go!"

  const importExportBtn = document.createElement("a")
  importExportBtn.href = "#"
  importExportBtn.innerText = "答案管理"
  importExportBtn.style.marginLeft = "10px"
  importExportBtn.addEventListener("click", (e) => {
    e.preventDefault()
    createImportExportModal()
  })

  btnCtn.appendChild(btn)
  btnCtn.appendChild(importExportBtn)
  setTimeout(() => {
    if (location.pathname.endsWith("analyze.du")) {
      btn.addEventListener("click", () => {
        const questions = document.querySelectorAll(".question")
        const newAnswers = {}

        Array.from(questions).map((question) => {
          const questionId = question.id
          const options = Array.from(
            question.querySelectorAll(".answer-options li")
          )
          const answersStr = question
            .querySelector(".jdu-answer-analyze > div:first-child")
            .innerText.substr(5)
          newAnswers[questionId] = answersStr.split("").reduce((prev, answerKey) => {
            const answerStr = options.find(
              (option) => option.innerText[1] == answerKey
            )
            const answer = answerStr.innerText.substr(1)
            prev.push(answer.trim().replace(/^ ?[ABCDEFGH]\. ?/, ""))
            return prev
          }, [])
        })

        const existingAnswers = getStoredAnswers()
        createModal(existingAnswers, newAnswers)
      })
    }

    if (location.pathname.endsWith("exam.du")) {
      btn.addEventListener("click", () => {
        const questions = document.querySelectorAll(".question")
        const answersMap = getStoredAnswers()

        if (Object.keys(answersMap).length === 0) {
          alert("localStorage 中没有答案数据")
          return
        }

        questions.forEach((question) => {
          const answers = answersMap[question.id] || []
          const options = question.querySelectorAll(".answer-options li")
          options.forEach((option) => {
            const purifiedOption = option.innerText
              .trim()
              .replace(/^ ?[ABCDEFGH]\. ?/, "")
            if (
              answers.some((answer) => {
                return answer === purifiedOption
              })
            ) {
              option.click()
            }
          })
        })
      })
    }
  }, 1000)
})()