Greasy Fork

Greasy Fork is available in English.

课表助手 - xtu

一键导出课表/考试安排为ics文件

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         课表助手 - xtu
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  一键导出课表/考试安排为ics文件
// @author       DeltaX
// @match        http://jwxt.xtu.edu.cn/jsxsd/*
// @require      https://cdn.bootcdn.net/ajax/libs/FileSaver.js/1.3.8/FileSaver.min.js
// @grant        none
// @License      MIT
// ==/UserScript==

(function () {
  'use strict';

  let SEPARATOR = navigator.appVersion.indexOf('Win') !== -1 ? '\r\n' : '\n';

  // 设置上课时间及下课时间
  let class_start_summer = [
    [],
    [8, 0],
    [9, 40],
    [10, 10],
    [11, 50],
    [14, 30],
    [16, 10],
    [16, 40],
    [18, 10],
    [19, 30],
    [22, 5]
  ];
  let class_start_winter = [
    [],
    [8, 0],
    [9, 40],
    [10, 10],
    [11, 50],
    [14, 0],
    [15, 40],
    [16, 10],
    [17, 40],
    [19, 0],
    [21, 35]
  ];
  let class_time = 100;
  // let weekToNum = { "日": 0, "一": 1, "二": 2, "三": 3, "四": 4, "五": 5, "六": 6 }
  let weekRegexp = /(?:(\d{1,2}-\d{1,2})|(\d{1,2})\(单周\))/g;
  let nowDate = new Date();
  let weekStartQuery = term =>
    new Promise(function (resolve, reject) {
      let xhr = new XMLHttpRequest();
      xhr.open('POST', 'http://jwxt.xtu.edu.cn/jsxsd/jxzl/jxzl_query');
      xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
      xhr.withCredentials = true;
      xhr.onload = function () {
        if (xhr.status === 200) {
          resolve(
            /<tr height='28'><td>1<\/td><td title='(.*?)'>/.exec(
              this.response
            )[1]
          );
        } else {
          reject(this);
        }
      };
      xhr.send('xnxq01id=' + term);
    });
  let toString = function (date) {
    return (
      date
        .toISOString()
        .split(/-|:|[.]/)
        .slice(0, 4)
        .join('') + '00Z'
    );
  };

  // 生成课表日历文件
  function GenerateCourseICS() {
    // 获取main-->page_iframe-->iframe0
    let iframe0 = document.getElementById('Form1');
    // 添加按钮"保存ics"
    let buttonPre = iframe0.querySelector('select#xnxq01id');
    buttonPre.outerHTML +=
      ' <input type="button" onclick="saveics()" class="button" name="submit" value="保存ics">';
    if (typeof iframe0.saveics === 'function') return;
    // 添加按钮操作,核心部分
    iframe0.saveics = function () {
      window.console.log('开始生成...');
      if (
        navigator.userAgent.indexOf('MISE') > -1 &&
        navigator.userAgent.indexOf('MSIE 10') == -1
      ) {
        alert('浏览器不支持哦~😆');
        return;
      }
      let calendar_start = [
        'BEGIN:VCALENDAR',
        'VERSION:2.0',
        'PRODID:Curriculum-to-iCalendar'
      ].join(SEPARATOR);
      let calendar_end = SEPARATOR + 'END:VCALENDAR' + SEPARATOR;
      let calendarEvents = [];
      // 获取到课程表
      try {
        let table = document.querySelector('#kbtable').tBodies[0];
        let yearTerm = document
          .querySelector('#xnxq01id')
        [document.querySelector('#xnxq01id').selectedIndex].innerText.split(
          '-'
        );
        let year = yearTerm[0] + '-' + yearTerm[1];
        let term = yearTerm[2];
        // 请求起始周
        console.log('查询学期起始日期中,学期: ' + yearTerm.join('-'));
        weekStartQuery(yearTerm.join('-')).then(weekStart => {
          let termStartDate = new Date(weekStart.split(/年|月|日/));
          // let currentWeek = parseInt((nowDate - startDate) / 1000 * 60 * 60 * 24 * 7);
          // 逐行添加event至cal
          console.log('添加课程事件中...');
          for (let i = 1; i < table.rows.length - 1; i++) {
            // 逐课程添加
            // let timeAddress = table.rows[i].cells[0].innerText;
            // let events = timeAddress.split(' ').filter(n => n != " ");
            for (let j = 1; j < table.rows[i].cells.length; j++) {
              // 同一时间不同课程
              let lessonInfos = table.rows[i].cells[j].innerText.split(
                '---------------------\n'
              );
              for (let lesInfStr of lessonInfos) {
                let lessonInfo = lesInfStr.split('\n').filter(n => n !== '');
                // console.log(lessonInfo);
                // 空课程跳过
                if (lessonInfo.length < 4) {
                  continue;
                }
                let courseName = lessonInfo[0];
                let teacherName = lessonInfo[1];
                // TODO 多周问题
                let weeksStr = lessonInfo[2];
                let location = lessonInfo[3];
                let description = courseName + ' ' + teacherName + ' ';
                let weekDay = j;
                // let interval = parseInt(informations[2]);
                // TODO 判断夏令时冬令时
                let isSummerTime = false;
                let startTime = [];
                let endTime = [];
                if (isSummerTime) {
                  startTime = class_start_summer[2 * i - 1];
                  endTime = class_start_summer[2 * i];
                } else {
                  startTime = class_start_winter[2 * i - 1];
                  endTime = class_start_winter[2 * i];
                }
                // 按照多周划分后迭代
                for (
                  let weekStrs = weekRegexp.exec(weeksStr);
                  weekStrs !== null;
                  weekStrs = weekRegexp.exec(weeksStr)
                ) {
                  // 分多周 "2-5,7-9,11-13(周)" 和单周 "13(单周)"
                  let week = [];
                  if (weekStrs[1] !== undefined) {
                    week = weekStrs[1].split('-'); // 多周
                  } else {
                    week = [weekStrs[2], weekStrs[2]]; // 单周
                  }
                  let startWeek = parseInt(week[0]);
                  let endWeek = parseInt(week[1]);
                  let startDate = new Date(termStartDate),
                    endDate = new Date(termStartDate),
                    untilDate = new Date(termStartDate);
                  // 课程时间
                  startDate.setDate(
                    startDate.getDate() + (startWeek - 1) * 7 + weekDay - 1
                  );
                  startDate.setHours(startTime[0], startTime[1], 0, 0);
                  endDate.setDate(
                    endDate.getDate() + (startWeek - 1) * 7 + weekDay - 1
                  );
                  endDate.setHours(endTime[0], endTime[1]);
                  // 课程截止
                  untilDate.setDate(
                    untilDate.getDate() + (endWeek - 1) * 7 + weekDay
                  );
                  untilDate.setHours(endTime[0], endTime[1]);
                  calendarEvents.push(
                    [
                      'BEGIN:VEVENT',
                      'DTSTAMP:' + toString(nowDate),
                      'UID:' +
                      'COURSE' +
                      calendarEvents.length +
                      '@' +
                      'https://1000delta.top',
                      'SUMMARY:' + courseName,
                      'DTSTART:' + toString(startDate),
                      'DTEND:' + toString(endDate),
                      'RRULE:FREQ=WEEKLY;UNTIL=' + toString(untilDate),
                      'LOCATION:' + location,
                      'DESCRIPTION:' + description,
                      'END:VEVENT'
                    ].join(SEPARATOR)
                  );
                }
              }
            }
          }
          // console.log(calendarEvents);
          if (calendarEvents.length < 1) {
            alert('课表里没课哦~😆');
            return;
          }
          let filename = year + '学年第' + term + '学期课表.ics';
          let calendar =
            calendar_start +
            SEPARATOR +
            calendarEvents.join(SEPARATOR) +
            calendar_end;
          //保存到文件
          let blob;
          if (navigator.userAgent.indexOf('MSIE 10') === -1) {
            // chrome or firefox
            blob = new Blob([calendar]);
          } else {
            // ie
            let bb = new BlobBuilder();
            bb.append(calendar);
            blob = bb.getBlob(
              'text/x-vCalendar;charset=' + document.characterSet
            );
          }
          saveAs(blob, filename);
        });
      } catch (error) {
        console.log(error);
        alert('出错啦!/(ㄒoㄒ)/~~');
      }
    };
  }

  const EXAM_ORDER = 0,
    EXAM_SESSION = 1,
    EXAM_NAME = 2,
    EXAM_TYPE = 3,
    EXAM_MODE = 4,
    EXAM_TIME = 5,
    EXAM_LOCATION = 6,
    EXAM_SEAT = 7,
    EXAM_ID = 8,
    EXAM_NOTE = 9;

  function GenerateExamICS() {
    window.console.log('考试安排生成准备');
    let buttonPre = document.querySelector('input#btn_back');
    buttonPre.outerHTML +=
      ' <input type="button" onclick="saveics()" class="button" name="saveics" value="保存ics">';
    let term = /学年学期【(.*)】/.exec(buttonPre.parentElement ? buttonPre.parentElement.innerText : "");
    window.saveics = function () {
      // 格式编辑
      let calendar_start = [
        'BEGIN:VCALENDAR',
        'VERSION:2.0',
        'PRODID:Exam-to-iCalendar'
      ].join(SEPARATOR);
      let calendar_end = SEPARATOR + 'END:VCALENDAR' + SEPARATOR;
      let calendarEvents = [];
      // 数据获取
      let examTitle;
      let rows = document.querySelector('table#dataList').rows;
      for (let exam of rows) {
        if (exam.cells[EXAM_ORDER].innerText === '序号') {
          examTitle = exam;
          continue;
        }
        let timeTexts = exam.cells[EXAM_TIME].innerText.split(/ |~/);
        let startDate = new Date(timeTexts[0] + ' ' + timeTexts[1]);
        let endDate = new Date(timeTexts[0] + ' ' + timeTexts[2]);
        let description = `
${examTitle.cells[EXAM_SESSION].innerText}: ${exam.cells[EXAM_SESSION].innerText}
${examTitle.cells[EXAM_NAME].innerText}: ${exam.cells[EXAM_NAME].innerText}
${examTitle.cells[EXAM_TYPE].innerText}: ${exam.cells[EXAM_TYPE].innerText}
${examTitle.cells[EXAM_MODE].innerText}: ${exam.cells[EXAM_MODE].innerText}
${examTitle.cells[EXAM_SEAT].innerText}: ${exam.cells[EXAM_SEAT].innerText}
${examTitle.cells[EXAM_ID].innerText}: ${exam.cells[EXAM_ID].innerText}
${examTitle.cells[EXAM_NOTE].innerText}: ${exam.cells[EXAM_NOTE].innerText}
`;
        calendarEvents.push(
          [
            'BEGIN:VEVENT',
            'DTSTAMP:' + toString(nowDate),
            'UID:' +
            'EXAM-' +
            term +
            calendarEvents.length +
            '@' +
            'https://1000delta.top',
            'SUMMARY:' + exam.cells[EXAM_NAME].innerText,
            'DTSTART:' + toString(startDate),
            'DTEND:' + toString(endDate),
            'LOCATION:' + exam.cells[EXAM_LOCATION].innerText,
            'DESCRIPTION:' + description,
            'END:VEVENT'
          ].join(SEPARATOR)
        );
      }
      if (calendarEvents.length < 1) {
        window.alert('当前没有考试安排哦~');
        return;
      }
      let filename = term ? term[1] : "" + '考试安排.ics';
      let calendar =
        calendar_start +
        SEPARATOR +
        calendarEvents.join(SEPARATOR) +
        calendar_end;
      //保存到文件
      let blob;
      if (navigator.userAgent.indexOf('MSIE 10') === -1) {
        // chrome or firefox
        blob = new Blob([calendar]);
      } else {
        // ie
        let bb = new BlobBuilder();
        bb.append(calendar);
        blob = bb.getBlob('text/x-vCalendar;charset=' + document.characterSet);
      }
      saveAs(blob, filename);
    }
  }

  let queryTable = {
    'http://jwxt.xtu.edu.cn/jsxsd/xskb/xskb_list.do': GenerateCourseICS,
    'http://jwxt.xtu.edu.cn/jsxsd/xsks/xsksap_list': GenerateExamICS
  };

  // 窗口加载完成调用函数
  window.onload = function () {
    try {
      queryTable[document.URL]();
    } catch (error) {
      if (error.message !== window.error_message) {
        window.error_message = error.message;
        console.log(error);
      }
      return;
    }
  };
  // 保证页面刷新后重新执行
  // setInterval(onload, 50);
})();