// ==UserScript==
// @name HEU课表生成器
// @namespace greasyfork.org/zh-CN/users/816568-chichow5
// @version 0.3
// @home-url https://greasyfork.org/zh-CN/users/816568-chichow5
// @description 哈尔滨工程大学 / HEU course iCalendar generator/ HEU ics格式课表生成器
// @author Chi Chow 201906
// @match *://*.hrbeu.edu.cn/jsxsd/xskb/*
// @grant none
// @downloadURL none
// ==/UserScript==
'use strict';
var mp = [];
var courses = [];
var type;
var alarm = -1;
var reminder = -1;
var box;
var year, month, day;
class Course{
constructor(raw, w, tweek){
this.weekday = w
this.name = raw[0]
this.teacher = raw[1]
let t = tweek.indexOf('-')
if (t === -1){
this.start_week = this.end_week = parseInt(tweek)
}else{
this.start_week = parseInt(tweek.substring(0, t))
this.end_week = parseInt(tweek.substring(t+1))
}
t = raw[3].substring(0, raw[3].length-2)
this.start_time = parseInt(t.substring(1, 3))
this.end_time = parseInt(t.substring(t.length-2))
this.location = raw[4]
this.flag = (this.start_time === this.end_time && this.start_time === 1);
this.wasted = false;
}
show(){
return 'Week:' + this.weekday + '\n'
+ 'Name:' + this.name + '\n'
+ 'Teac:' + this.teacher + '\n'
+ 'Dura:' + this.start_week + '-' + this.end_week + '\n'
+ 'Time:' + this.start_time + '-' + this.end_time + '\n'
+ 'Loca:' + this.location + '\n';
}
generateEvent(){
let result = ""
+ "BEGIN:VEVENT\r\n"
+ "CATEGORIES:课程\r\n";
let det = 7 * this.start_week + this.weekday - 8;
result += "DTSTART:" + mp[det]
+ getStartTime(type, this.start_time, this.end_time) + '\r\n';
result += "DTEND:" + mp[det]
+ getEndTime(type, this.start_time, this.end_time) + '\r\n';
//result += "UID:" + uuidv4()+'\r\n';
//result += "DTSTAMP:20210919T111500Z\r\n"
if (this.start_week != this.end_week){
result += ""
+ "RRULE:FREQ=WEEKLY;WKST=MO;COUNT="
+ (this.end_week-this.start_week+1)
+ ";BYDAY=" + getWeekEn(this.weekday)+'\r\n';
}
result += ""
+ "DESCRIPTION:"+this.teacher+'\r\n'
+ "LOCATION:"+this.location+'\r\n'
+ "SUMMARY:"+this.name+'\r\n';
result += ""
+ "SEQUENCE:0\r\n"
+ "TRANSP:OPAQUE\r\n";
if (reminder != -1){
result += ""
+ "BEGIN:VALARM\r\n"
//+ "X-WR-ALARMUID:" + uuidv4()+'\r\n'
+ "TRIGGER:-PT" + reminder + "M\r\n"
+ "ACTION:DISPLAY\r\n"
+ "DESCRIPTION:" + this.location + " "+ this.name + "\r\n"
+ "END:VALARM\r\n";
}
if (alarm != -1){
result += ""
+ "BEGIN:VALARM\r\n"
+ "TRIGGER:-PT" + alarm + "M\r\n"
+ "ACTION:AUDIO\r\n"
+ "END:VALARM\r\n"
}
result += "END:VEVENT\r\n";
return result;
}
}
UI();
function doit(){
/*初始化重新读入*/
alarm = -1;
reminder = -1;
let re = getParam();
//console.log(alarm);
if (re === false){
console.log('param get error');
return;
}
let ics = generateCalendar();
let ele = document.createElement('a');
ele.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(ics));
ele.setAttribute('download', 'output.ics');
ele.style.display = 'none';
box.appendChild(ele);
alert("课表不保证完全正确,请导入到新建的日历表中,便于管理");
ele.click();
box.removeChild(ele);
}
function UI(){
if (document.getElementById('kbtable') == null) return;
box = document.createElement('div');
box.id = 'myAlertBox';
box.innerHTML += '请输入当前学期第一周周一的年月日
'
+ ' '
+ ' '
+ ' '
+ '
第3-4节课是否延后'
+ ''
+ '
添加闹钟 '
+ ' '
+ ' 分钟'
+ '
添加提醒 '
+ ' '
+ ' 分钟'
+ '
'
+ '
* 本脚本参考RFC关于iCalendar部分'
+ '以及其他互联网文档,由于软件实现或者作者个人水平原因,提醒和闹钟不一定起作用';
box.style.cssText =
' background: white; ' +
' border: 2px solid red; ' +
' padding: 4px; ' +
' position: absolute; ' +
' top: 90px; right: 8px;' +
' max-width: 300px; ';
//' font-size :16px ';
document.body.appendChild( box );
document.getElementById("ig_do").addEventListener("click", doit, false);
}
function getParam(){
year = document.getElementById("ig_year").value;
month = document.getElementById("ig_month").value;
day = document.getElementById("ig_day").value;
let se = document.getElementById("ig_type").value;
let ck_alarm = document.getElementById('ig_alarm');
let ck_reminder = document.getElementById('ig_reminder');
let t_alarm = document.getElementById('ig_alarm_time').value;
let t_reminder = document.getElementById('ig_reminder_time').value;
if (year === "" || month === "" || day === ""){
alert('时间未设置!');
return false;
}
year = year = parseInt(year);
if (year <= 0){
alert('年份超出范围!');
return false;
}
month = month = parseInt(month);
if (month <= 0 || month >= 13){
alert('月份超出范围');
return false;
}
day = day = parseInt(day);
if (day <= 0 || day > daysOfMonth(month, year)){
alert('日超出范围');
return false;
}
if (se === ""){
alert('未选择3-4节课是否延后!');
return false;
}
type = parseInt(se);
if (ck_alarm.checked){
if (t_alarm === ""){
alert("闹钟时间未设置!");
return false;
}
alarm = parseInt(t_alarm);
if (alarm < 0){
alert("闹钟时间是负数!");
return false;
}
}
if (ck_reminder.checked){
if (t_reminder === ""){
alert("提醒时间未设置!");
return false;
}
reminder = parseInt(t_reminder);
if (reminder < 0){
alert("提醒时间是负数!");
return false;
}
}
return true;
}
function generateCalendar(){
for (let i = 0; i < 365; i ++){
let t = year+"";
t += (month<=9)?("0"):"";
t += ""+month;
t += (day<=9)?"0":"";
t += ""+day;
mp.push(t);
day += 1;
if (day-1 === daysOfMonth(year, month)){
day = 1;
month ++;
if (month == 13){
month = 1;
year++;
}
}
}
var content = ""
content += "BEGIN:VCALENDAR\r\n"
content += "PRODID:-//Google Inc//Google Calendar 70.9054//EN\r\n";
content += "VERSION:2.0\r\n";
content += "CALSCALE:GREGORIAN\r\n";
content += "METHOD:PUBLISH\r\n";
content += "X-WR-CALNAME:导出的课表\r\n";
content += "X-WR-TIMEZONE:Asia/Shanghai\r\n";
CourseFactory();
let events = ""
for (let i = 0; i < courses.length; i ++){
if (courses[i].wasted) continue;
events += courses[i].generateEvent();
}
content += events;
content += "END:VCALENDAR\r\n";
return content;
}
/**
* 通过年月获取天数
*/
function daysOfMonth(year, month){
switch(month){
case 2:{
let f = (year % 4 === 0);
if (year % 100 === 0) f = false;
if (year % 400 === 0) f = true;
return f?29:28;
}
case 4:
case 6:
case 9:
case 11: return 30;
default: return 31;
}
}
function CourseFactory(){
let table = document.getElementById('kbtable');
if (table == null) return;
let items = table.getElementsByClassName('kbcontent');
for (let i = 0; i < items.length; i ++){
/*删除'<>'以及包含的htnl元素,和' '*/
let c = items.item(i).innerHTML.replace(/(<\/?[^>]+(>|$)| )/g, "\n").split("\n");
let res = []
c.forEach(function(item, index, array){
let d = item.trim()
/*删除空行以及'------'分割行*/
if (d.length > 0 && d.charAt(0) != '-') res.push(d)
})
for (let j = 0; j < res.length; j += 5){
/*时间不连续,按','分割,生成多个Course对象*/
let e = res[j+2].substring(0,res[j+2].length-3).split(',');
for (let k = 0; k < e.length; k ++){
let cours = new Course(res.slice(j, j+5), (i%7)+1, e[k]);
if (cours.flag){
/*时间显示为[01节]*/
switch(Math.floor(i/7)+1){
case 1:cours.start_time=1;cours.end_time=2;break;
case 2:cours.start_time=3;cours.end_time=5;break;
case 3:cours.start_time=6;cours.end_time=7;break;
case 4:cours.start_time=8;cours.end_time=10;break;
case 5:cours.start_time=11;cours.end_time=13;break;
}
/*与已添加的项合并*/
for (let l = 0; l < courses.length; l ++){
if (cc(courses[l], cours) && courses[l].end_time+1 === cours.start_time){
cours.wasted = true;
courses[l].end_time = cours.end_time;
break;
}
}
}
if (!cours.wasted) courses.push(cours);
}
}
}
/*查重*/
for (let i = 0; i < courses.length; i ++){
if (courses[i].wasted){continue;}
for (let j = i + 1; j < courses.length; j ++){
if (courses[j].wasted){continue;}
if (cc(courses[i],courses[j])
&& courses[i].start_time===courses[j].start_time
&& courses[i].end_time === courses[j].end_time){
//console.log(courses[i].show());
//console.log(courses[j].show());
courses[j].wasted=true;
}
}
}
}
function cc(c1, c2){
return c1.weekday === c2.weekday
&& c1.name === c2.name
&& c1.teacher === c2.teacher
&& c1.start_week === c2.start_week
&& c1.end_week === c2.end_week
&& c1.location == c2.location;
}
function getStartTime(type, start, end){
switch(start){
case 1:return 'T080000';
case 2:return 'T085000';
case 3:return (type==2&&end==4)?'T102000':'T095500';
case 4:return (type==2&&end==4)?'T111000':'T104500';
case 5:return 'T113500';
case 6:return 'T133000';
case 7:return 'T142000';
case 8:return 'T152500';
case 9:return 'T161500';
case 10:return 'T170500';
case 11:return 'T183000';
case 12:return 'T192000';
case 13:return 'T201000';
}
}
function getEndTime(type, start, end){
switch(end){
case 1:return 'T084500';
case 2:return 'T093500';
case 3:return (type==2)?'T110500':'T104000';
case 4:return (type==2)?'T115500':'T113000';
case 5:return 'T122000';
case 6:return 'T141500';
case 7:return 'T150500';
case 8:return 'T161000';
case 9:return 'T170000';
case 10:return 'T175000';
case 11:return 'T191500';
case 12:return 'T200500';
case 13:return 'T205500';
}
}
function getWeekEn(w){
switch(w){
case 1: return 'MO'
case 2: return 'TU'
case 3: return 'WE'
case 4: return 'TH'
case 5: return 'FR'
case 6: return 'SA'
case 7: return 'SU'
}
}