FIX: HDU教务系统培养计划

在HDU教务系统培养计划中对“通过情况”进行补全,方便查看

当前为 2021-03-29 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name        FIX: HDU教务系统培养计划
// @namespace   Violentmonkey Scripts
// @match       http://jxgl.hdu.edu.cn/*
// @grant       none
// @version     1.2
// @author      Rainbow Yang
// @description 在HDU教务系统培养计划中对“通过情况”进行补全,方便查看
// ==/UserScript==

const createStorage = (symbol) => ({
  set: (code, value = 1) => sessionStorage.setItem(symbol + '-' + code, value),
  get: (code) => sessionStorage.getItem(symbol + '-' + code),
})

const scores = createStorage('scores')
const replacements = createStorage('replace')
const readingSources = createStorage('reading')

const getURL = (page, gnmkdm) => () => window.location.href
  .replace('pyjh.aspx', page)
  .replace('N121607', gnmkdm)

const getScoreURL = getURL('xscjcx_dq.aspx', 'N121605')
const getReplacementURL = getURL('xs_kctdcx.aspx', 'N121622')
const getReadingURL = getURL('xsxkqk.aspx', 'N121621')

const openAndUntilWindowLoad = (url) =>
  new Promise(resolve => {
    let theWindow = window.open(url)
    theWindow.onload = () => resolve(theWindow)
  })

const readTable = (table, ...cells) => [...table.rows].slice(1)
  .map(row => cells.map(cell => row.cells[cell].innerHTML))

const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms))

const readScore = async () => {
  const scoreWindow = await openAndUntilWindowLoad(getScoreURL())

  scoreWindow.document.querySelector('#ddlxq').selectedIndex = 0
  scoreWindow.document.querySelector('#ddlxn').selectedIndex = 0
  scoreWindow.document.querySelector('#btnCx').click()

  let scoreTable = scoreWindow.document.querySelector('#DataGrid1 > tbody')
  while (!scoreTable) {
    await delay(200)
    scoreTable = scoreWindow.document.querySelector('#DataGrid1 > tbody')
  }

  readTable(scoreTable, 2, 11).forEach(([code, score]) =>
    scores.set(code, score))

  scoreWindow.close()
}

const readReplacement = async () => {
  const replacementWindow = await openAndUntilWindowLoad(getReplacementURL())

  const singleTable = replacementWindow.document.querySelector('#dbgrid')
  const composeTable = replacementWindow.document.querySelector('#Datagrid1')

  readTable(singleTable, 1, 3).forEach(([code, replacementCode]) =>
    replacements.set(code, replacementCode))
  readTable(composeTable, 1, 3).forEach(([code, replacementCode]) =>
    replacements.set(code, replacementCode))

  replacementWindow.close()
}

const readReading = async () => {
  const readingWindow = await openAndUntilWindowLoad(getReadingURL())

  const readingTable = readingWindow.document.querySelector('#DBGrid')
  readTable(readingTable, 0).forEach(([code]) =>
    readingSources.set(code.split('-')[3]))

  readingWindow.close()
}

const addReadButton = () => {
  const readScoreButton = document.createElement('input')
  Object.assign(readScoreButton, {
    id: 'ReadButton',
    type: 'button',
    className: 'button',
    value: '读取成绩',
  })
  readScoreButton.onclick = () => {
    if (confirm('点击按钮之后\n将会自动弹出三个页面\n' +
      '分别用于读取课程的『成绩』『替代情况』和『修读情况』\n')) {
      Promise.all([readScore(), readReplacement(), readReading()]).then(() =>
        sessionStorage.setItem('hasRead', 'true'),
      )
    }
  }

  document.querySelector('#Button1').parentNode.appendChild(readScoreButton)
}

// 只需选择修一门的课程(目前仅限计科)
const optionsArray = [
  ['A1101121', 'A1101122', 'A1101123'], // 大学英语精读1A/B/C
  ['A1101181', 'A1101182', 'A1101183'], // 大学英语听说1A/B/C
  ['A1101141', 'A1101142', 'A1101143'], // 大学英语精读2A/B/C
  ['A1101191', 'A1101192', 'A1101193'], // 大学英语听说2A/B/C
  ['A0714202', 'A0714222'], // 高等数学A2/C2
  ['A0715011', 'A0715051'], // 大学物理1/物理学原理及工程应用1
  ['A0715012', 'A0715052'], // 大学物理2/物理学原理及工程应用2
  ['A0500820', 'A0502380'], // 面向对象程序设计(Java/C++)
  ['A0303090', 'A0507970'], // 项目管理/项目管理与案例分析
  ['B0505120', 'B0500660'], // Android/IOS移动开发
]

const getResult = (code) => {
  let score = scores.get(code)
  if (score) {
    return score
  }

  let isReading = readingSources.get(code)
  if (isReading) {
    return '修读中'
  }

  let hasReplacement = replacements.get(code)
  if (hasReplacement) {
    return hasReplacement.split(',').map(code => scores.get(code) + `(${code})`)
  }

  let options = optionsArray.filter(options => options.includes(code))[0]
  if (options) {
    for (const option of options) {
      if (
        scores.get(option) ||
        replacements.get(option) ||
        readingSources.get(option)
      ) {
        return `已选(${option})`
      }
    }
  }
}

const writeScores = () => {
  let hasRead = sessionStorage.getItem('hasRead')
  const planTable = document.querySelector('#DBGrid');
  [...planTable.rows].slice(1, -1).forEach(row => {
    let code = row.cells[0].innerHTML
    row.cells[16].innerHTML = getResult(code) || (hasRead ? '未知' : '请点击 读取成绩')
  })
}

function main () {
  if (document.getElementById('HyperLink1')?.innerText === '查看培养计划说明') {
    if (!document.getElementById('ReadButton')) {
      addReadButton()
    } else {
      writeScores()
    }
  }
  setTimeout(main, 1000)
}

main()