北交大iCalender课表生成

📁📄导出ics/csv格式的日程文件! 💻📱支持多端同步! 📝支持Excel编辑! 📆📅支持导入各系统原生日历!

目前为 2022-03-03 提交的版本。查看 最新版本

// ==UserScript==
// @name         北交大iCalender课表生成
// @namespace    https://github.com/ZiuChen/BJTU-Schedule-ics-csvGenerator
// @version      1.3
// @description  📁📄导出ics/csv格式的日程文件! 💻📱支持多端同步! 📝支持Excel编辑! 📆📅支持导入各系统原生日历!
// @author       Ziu
// @icon         https://gitee.com/ziuc/utool-filebed/raw/master/20210514-231824-0795.png
// @match        https://aa.bjtu.edu.cn/course_selection/courseselect/stuschedule/*
// @match        https://aa.bjtu.edu.cn/course_selection/courseselecttask/schedule/
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js
// @require      https://cdn.jsdelivr.net/gh/nwcell/ics.js@dfec67f37a3c267b3f97dd229c9b6a3521222794/demo/ics.deps.min.js
// @grant        none
// @license      MIT
// ==/UserScript==

"use strict";

const defaultStartMonday = "2022-02-28"; // 第一个教学周的第一个周一

if (localStorage.getItem("defaultStartMonday") === null) {
  localStorage.setItem("defaultStartMonday", defaultStartMonday);
}

function buttonGenerate() {
  $(".widget-title").append(/* html */ `
        <button id="scheduleIcsGenerate" class="btn btn-white btn-xs">导出为ics文件</button>
        <button id="csvGenerate" class="btn btn-white btn-xs">导出为csv文件</button>
        <button id="scheduleRedirect" title="点击跳转校历" class="btn btn-white btn-xs">校历</button>
        <input id="startMonday" class="form-control" type="text" placeholder="第一个教学周的第一个周一" title="第一个教学周的第一个周一" style="height: 26px;width: 200px;display: inline;vertical-align: middle;"></input>
        <button id="restoreSetting" title="点击重置设置" class="btn btn-white btn-xs">重置</button>
        `);
  bindEvents();
}

function bindEvents() {
  $("#scheduleRedirect").click(() => {
    window.open("https://bksy.bjtu.edu.cn/Semester.html");
  });
  let pageFlag = 0;
  if (window.location.href.search("/courseselect/stuschedule/") != -1) {
    // 本学期课表
  } else if (window.location.href.search("/courseselecttask/schedule/") != -1) {
    // 选课课表
    pageFlag = 1;
  }
  $("#scheduleIcsGenerate").click(() => {
    icsmain(pageFlag);
  });
  $("#csvGenerate").click(() => {
    csvmain(pageFlag);
  });
  $("#restoreSetting").click(() => {
    localStorage.clear();
    location.reload();
  });
  $("#startMonday")
    .change((e) => {
      localStorage.setItem("defaultStartMonday", e.target.value);
    })
    .val(localStorage.getItem("defaultStartMonday"));
}

// generateWeekTable() @github ygowill
function generateWeekTable() {
  let startMondayString = $("#startMonday").val();
  if (validCheck(startMondayString) === false) return;
  const startMonday = new Date(startMondayString);
  let weekDateTable = [];
  for (let i = 0; i < 30; i++) {
    // 生成到30周
    let weekArr = [];
    for (let j = 0; j < 7; j++) {
      let tmpDate = new Date(startMonday);
      tmpDate.setDate(tmpDate.getDate() + 7 * i + j);
      weekArr.push(tmpDate);
    }
    weekDateTable.push(weekArr);
  }
  return weekDateTable;
}

function validCheck(startMondayString) {
  const re = new RegExp(/^\d{4}-\d{1,2}-\d{1,2}/);
  if (re.test(startMondayString) === false) {
    alert("输入日期值非法,请重新输入。");
    throw Error("输入日期值非法");
  } else {
    return true;
  }
}

function tableTransfer(rowTable, isOrigin) {
  // 7*7行转列
  let tmpTable = [];
  let columnTable = [];
  for (let i = 0; i < 7; i++) {
    if (isOrigin) {
      for (let j = 0; j < 7; j++) {
        tmpTable.push(rowTable[j]);
      }
    } else {
      for (let j = i; j < 49; j += 7) {
        tmpTable.push(rowTable[j]);
      }
    }
    columnTable[i] = tmpTable;
    tmpTable = [];
  }
  return columnTable;
}

function removeZero(iArr) {
  for (let i = 0; i < iArr.length; i++) {
    iArr[i] = parseInt(iArr[i], 10);
  }
  return iArr;
}

function dateStr2Arr(dateStr) {
  let dateArr = [];
  if (dateStr) {
    if (dateStr.indexOf("-") != -1) {
      // 第X-Y周
      let indexArr = dateStr.split("-");
      removeZero(indexArr);
      for (let i = indexArr[0]; i < indexArr[1] + 1; i++) {
        dateArr.push(i);
      }
    } else if (dateStr.indexOf(",") != -1) {
      // 单双周
      dateArr = dateStr.split(", ");
      removeZero(dateArr);
    } else dateArr.push(parseInt(dateStr, 10)); // 第X周
  }
  return dateArr;
}

// courseList[x]示例:
// allInfo: "国际贸易实务模拟 第03-06周思源东楼 SD401卜伟"
// courseNum: 1
// date: (4) [3, 4, 5, 6]
// initInfo: "第03-06周"
// location: "思源东楼 SD401"
// name: "国际贸易实务模拟 "
// teacher: "卜伟"
// weekNum: 6

function stuScheduleGetTable(isOrigin) {
  let courseListTmp = tableTransfer(
    $("tr>td[style!='height:80px;']"),
    isOrigin
  );
  let courseList = [];
  let courseTmp = {};
  for (let i = 0; i < 7; i++) {
    for (let j = 0; j < 7; j++) {
      for (
        let k = 0;
        k <
        courseListTmp[i][j].querySelectorAll('span[style="color:#000"]').length;
        k++
      ) {
        courseTmp.weekNum = i + 1;
        courseTmp.courseNum = j + 1;
        if (
          courseListTmp[i][j].querySelectorAll('span[style="color:#000"]')[k]
        ) {
          courseTmp.name = courseListTmp[i][j]
            .querySelectorAll('span[style="color:#000"]')
            [k].innerText.split("[本")[0];
          courseTmp.location = courseListTmp[i][j].querySelectorAll(
            'span[class="text-muted"]'
          )[k].innerText;
          let dateStr = courseListTmp[i][j].querySelectorAll(
            'div[style="max-width:120px;"]'
          )[k].innerText;
          dateStr = dateStr.substring(
            dateStr.indexOf("第") + 1,
            dateStr.indexOf("周")
          ); // 预处理
          courseTmp.initInfo = "第" + dateStr + "周";
          courseTmp.date = dateStr2Arr(dateStr);
          courseTmp.teacher =
            courseListTmp[i][j].querySelectorAll("i")[k].innerText;
          courseTmp.allInfo =
            courseTmp.name +
            " " +
            courseTmp.initInfo +
            " " +
            courseTmp.location +
            " " +
            courseTmp.teacher;
          courseList.push(courseTmp);
          courseTmp = {};
        }
      }
    }
  }
  return courseList;
}

function scheduleGetTable(isOrigin) {
  let courseListTmp = tableTransfer(
    $("tr>td[style!='height:80px;']"),
    isOrigin
  );
  let courseList = [];
  let courseTmp = {};
  for (let i = 0; i < 7; i++) {
    for (let j = 0; j < 7; j++) {
      for (
        let k = 0;
        k <
        courseListTmp[i][j].querySelectorAll('div[style="max-width:120px;"]')
          .length;
        k++
      ) {
        courseTmp.weekNum = i + 1;
        courseTmp.courseNum = j + 1;
        if (courseListTmp[i][j].querySelectorAll("span")[k]) {
          courseTmp.name = courseListTmp[i][j]
            .getElementsByTagName("span")
            [k * 3].innerText.split("\n")[1];
          courseTmp.location = courseListTmp[i][j].querySelectorAll(
            'span[class="text-muted"]'
          )[k].innerText;
          let dateStr = courseListTmp[i][j].querySelectorAll(
            'div[style="max-width:120px;"]'
          )[k].innerText;
          dateStr = dateStr.substring(
            dateStr.indexOf("第") + 1,
            dateStr.indexOf("周")
          ); // 预处理
          courseTmp.initInfo = "第" + dateStr + "周";
          courseTmp.date = dateStr2Arr(dateStr);
          courseTmp.teacher =
            courseListTmp[i][j].querySelectorAll("i")[k].innerText;
          courseTmp.allInfo =
            courseTmp.name +
            " " +
            courseTmp.initInfo +
            " " +
            courseTmp.location +
            " " +
            courseTmp.teacher;
          courseList.push(courseTmp);
          courseTmp = {};
        }
      }
    }
  }
  return courseList;
}

function timeConstructor(weekTh, weekNum, courseNum, isStamp, isDelay) {
  let standardTimeTable = [
    ["08:00", "09:50"],
    ["10:10", "12:00"],
    ["12:10", "14:00"],
    ["14:10", "16:00"],
    ["16:20", "18:10"],
    ["19:00", "20:50"],
    ["21:00", "21:50"],
  ];
  let delayTimeTable = [
    ["08:00", "09:50"],
    ["10:30", "12:20"],
    ["12:10", "14:00"],
    ["14:10", "16:00"],
    ["16:20", "18:10"],
    ["19:00", "20:50"],
    ["21:00", "21:50"],
  ];

  let WeekTable = generateWeekTable();
  let DayTime = new Date(WeekTable[weekTh - 1][weekNum - 1]);
  let rtnTime = [];
  let startTimeStamp, endTimeStamp;
  let delayClassroom = ["思源西楼", "逸夫"];

  for (let item of delayClassroom) {
    if (isDelay.search(item) != -1) {
      startTimeStamp = DayTime.setHours(
        delayTimeTable[courseNum - 1][0].split(":")[0],
        delayTimeTable[courseNum - 1][0].split(":")[1]
      );
      endTimeStamp = DayTime.setHours(
        delayTimeTable[courseNum - 1][1].split(":")[0],
        delayTimeTable[courseNum - 1][1].split(":")[1]
      );
    } else {
      startTimeStamp = DayTime.setHours(
        standardTimeTable[courseNum - 1][0].split(":")[0],
        standardTimeTable[courseNum - 1][0].split(":")[1]
      );
      endTimeStamp = DayTime.setHours(
        standardTimeTable[courseNum - 1][1].split(":")[0],
        standardTimeTable[courseNum - 1][1].split(":")[1]
      );
    }
  }

  if (isStamp === 1) {
    rtnTime.push(startTimeStamp);
    rtnTime.push(endTimeStamp);
    return rtnTime;
  }

  let startTime = new Date(startTimeStamp);
  let endTime = new Date(endTimeStamp);
  startTime = startTime.toString();
  endTime = endTime.toString();
  rtnTime.push(startTime);
  rtnTime.push(endTime);
  return rtnTime;
}

function icsConstructor(icsEventList) {
  let cal = ics();
  let today = new Date();
  today = today.toLocaleDateString();
  for (let i = 0; i < icsEventList.length; i++) {
    cal.addEvent(
      icsEventList[i].name,
      icsEventList[i].description,
      icsEventList[i].location,
      icsEventList[i].startTime,
      icsEventList[i].endTime
    );
  }
  cal.download("iCalender - 课表 - " + today);
}

function eventConstructor(courseList) {
  let icsEvent = {};
  let icsEventList = [];
  for (let i = 0; i < courseList.length; i++) {
    for (let j = 0; j < courseList[i].date.length; j++) {
      let timeRst = timeConstructor(
        courseList[i].date[j],
        courseList[i].weekNum,
        courseList[i].courseNum,
        0,
        courseList[i].location
      );
      let timeRstStamp = timeConstructor(
        courseList[i].date[j],
        courseList[i].weekNum,
        courseList[i].courseNum,
        1,
        courseList[i].location
      );
      icsEvent.name = courseList[i].name;
      icsEvent.description =
        courseList[i].location +
        " " +
        courseList[i].initInfo +
        " 任课教师:" +
        courseList[i].teacher;
      icsEvent.location = courseList[i].location;
      icsEvent.startTime = timeRst[0];
      icsEvent.endTime = timeRst[1];
      icsEvent.startTimeStamp = timeRstStamp[0];
      icsEvent.endTimeStamp = timeRstStamp[1];
      icsEventList.push(icsEvent);
      icsEvent = {};
    }
  }
  return icsEventList;
}

function toExcelFormatter(courseList) {
  let standardTimeTable = [
    ["08:00", "09:50"],
    ["10:10", "12:00"],
    ["12:10", "14:00"],
    ["14:10", "16:00"],
    ["16:20", "18:10"],
    ["19:00", "20:50"],
    ["21:00", "21:50"],
  ];
  let jsonData = [
    {
      column1: "",
      column2: "",
      column3: "",
      column4: "",
      column5: "",
      column6: "",
      column7: "",
    },
    {
      column1: "",
      column2: "",
      column3: "",
      column4: "",
      column5: "",
      column6: "",
      column7: "",
    },
    {
      column1: "",
      column2: "",
      column3: "",
      column4: "",
      column5: "",
      column6: "",
      column7: "",
    },
    {
      column1: "",
      column2: "",
      column3: "",
      column4: "",
      column5: "",
      column6: "",
      column7: "",
    },
    {
      column1: "",
      column2: "",
      column3: "",
      column4: "",
      column5: "",
      column6: "",
      column7: "",
    },
    {
      column1: "",
      column2: "",
      column3: "",
      column4: "",
      column5: "",
      column6: "",
      column7: "",
    },
    {
      column1: "",
      column2: "",
      column3: "",
      column4: "",
      column5: "",
      column6: "",
      column7: "",
    },
  ];
  let charArr = [
    "第一节",
    "第二节",
    "第三节",
    "第四节",
    "第五节",
    "第六节",
    "第七节",
  ];
  let objKeys = Object.keys(jsonData[0]);

  for (let i = 0; i < 7; i++) {
    for (let j = 0; j < 7; j++) {
      let tmpKey = objKeys[j + 1];
      jsonData[i].column1 =
        charArr[i] +
        " [" +
        standardTimeTable[i][0] +
        " - " +
        standardTimeTable[i][0] +
        "]";
      for (let k = 0; k < courseList.length; k++) {
        if (
          courseList[k].courseNum == i + 1 &&
          courseList[k].weekNum == j + 1
        ) {
          jsonData[i][tmpKey] =
            jsonData[i][tmpKey] + "  " + courseList[k].allInfo;
        }
      }
    }
  }
  return jsonData;
}

// tableToExcel() @csdn hhzzcc_
function tableToExcel(jsonData) {
  let str = `课程|星期,星期一,星期二,星期三,星期四,星期五,星期六,星期日\n`;
  for (let i = 0; i < jsonData.length; i++) {
    for (let key in jsonData[i]) {
      str += `${jsonData[i][key] + "\t"},`;
    }
    str += "\n";
  }
  const uri = "data:text/csv;charset=utf-8,\ufeff" + encodeURIComponent(str);
  const link = document.createElement("a");
  link.href = uri;
  link.download = "课程表.csv";
  link.click();
}

function icsmain(icase) {
  let icsEventList;
  if (icase === 0) {
    icsEventList = eventConstructor(stuScheduleGetTable());
  } else if (icase === 1) {
    icsEventList = eventConstructor(scheduleGetTable());
  }
  icsConstructor(icsEventList);
}

function csvmain(icase) {
  let jsonData;
  if (icase === 0) {
    jsonData = toExcelFormatter(stuScheduleGetTable());
  } else if (icase === 1) {
    jsonData = toExcelFormatter(scheduleGetTable());
  }
  tableToExcel(jsonData);
}

buttonGenerate();