本文首发于我的个人博客小小柴io
简要介绍
作者是一名学生,需要在手机上记录课表,虽然现在已经有很多的课程表App了,但是有些呢不支持从教务系统导入,有些呢会有很多广告,再有些就是界面很丑啦~
因此,为了能够便捷的查看课表,免去现有的课程表软件带来的烦恼,我决定!开发一款无害的课表软件。于是,这款“课表助手”就应运而生(虽然不是一个App,后面再详细解释),它的作用不是显示课表,而是将教务系统中的课表转为iCalendar文件。iCalendar是一种日历数据交换的标准,既然是标准了,那大家肯定都支持啦,目前已经测试了Win10/Android9/iPadOS13,都可以导入至日历。导入后就可以愉快的使用系统日历查看课表了,不得不说,系统自带的日历界面清新、使用流畅、没有广告(用过的都说好🙄)。
- 脚本安装请查看Greasyfork
- 完整源码请查看Github
开发过程
准备工作
为了明确我要做什么,首先打开了教务系统课程表页面,他是这个样子的:
看了一下,嗯!挺好的,适合序列化。然后再看一下iCalendar文件的样子:
BEGIN:VCALENDAR
VERSION:2.0
PRODID:Curriculum-to-iCalendar
BEGIN:VEVENT
DTSTAMP:20200313T032000Z
UID:0@https://chaiqingao.github.io/
SUMMARY:summary
DTSTART:20200217T000000Z
DTEND:20200217T013500Z
RRULE:FREQ=WEEKLY;UNTIL=20200428T013500Z;INTERVAL=1
LOCATION:location
DESCRIPTION:description
END:VEVENT
BEGIN:VEVENT
......
END:VEVENT
END:VCALENDAR
不难看出,一个iCalendar文件由三部分组成:头 + [VENVENT数组] + 尾。每一个VENVENT
有DTSTAMP
创建的时间、UID
标识符、SUMMARY
事件名、DTSTART
事件开始时间、DTEND
事件结束时间、RRULE
重复方式(包括FREQ
频率、UNTIL
重复截至时间、INTERVAL
间隔)、LOCATION
地点、DESCRIPTION
详细信息。掌握了这些东西,下面就开始吧!
定位至教务系统中的课程表
查看网页源码,可以发现,教务系统的课程表嵌套在两层iframe(top->page_iframe->iframe0
)中,经过一番努力,定位到了!
var page_iframe = document.getElementById("page_iframe");
var iframe0 = page_iframe.contentDocument.getElementById("iframe0");
var table = iframe0.contentDocument.getElementsByTagName("table")[0];
下面就可以对表格里的内容为所欲为进行操作了。
获取表格中课程名和上课时间非空的行
第一要事!确保这个课是存在的,这个简单一点:
for (let i = 1; i < table.rows.length; i++) {
var courseName = table.rows[i].cells[1].innerText;
var teacherName = table.rows[i].cells[5].innerText;
var timeAddress = table.rows[i].cells[9].innerText;
if (courseName !== "" && timeAddress !== "") {
...
}
}
构建VEVENT
courseName
就作为SUMMARY
吧,下面是对timeAddress
的解析,从中提取出日期时间地址
var toString = function (date) {
return date.toISOString().split(/-|:|[.]/).slice(0, 4).join("") + "00Z";
}
var events = timeAddress.split(' ').filter(n => n != "");
for (let j = 0; j < events.length; j++) {
//周一:1-11周,每1周;1-2节,3区,附3-401 ==> [一,1-11,1,1-2,3区,附3-401]
var informations = events[j].split(/周,每|周|节,|:|,|;/).filter(n => n != "");
var description = "第" + events[j].split(";")[1] + " " + teacherName;
var weekDay = weekToNum[informations[0]];
var startWeek = parseInt(informations[1].split("-")[0]);
var endWeek = parseInt(informations[1].split("-")[1]);
var interval = parseInt(informations[2]);
var startTime = class_start[parseInt(informations[3].split('-')[0])];
var endTime = class_start[parseInt(informations[3].split('-')[1])];
var address = [informations[4], informations[5], teacherName].join(" ");
var startDate = new Date(),endDate=new Date(),untilDate=new Date();
startDate.setDate(startDate.getDate() - startDate.getDay() - (currentWeek - startWeek) * 7 + weekDay);
startDate.setHours(startTime[0], startTime[1], 0, 0);
endDate.setDate(endDate.getDate() - endDate.getDay() - (currentWeek - startWeek) * 7 + weekDay);
endDate.setHours(endTime[0],endTime[1]+class_time);
untilDate.setDate(untilDate.getDate() - untilDate.getDay() + (endWeek - currentWeek) * 7 + weekDay + 1);
untilDate.setHours(endTime[0],endTime[1]+class_time);
calendarEvents.push([
'BEGIN:VEVENT',
'DTSTAMP:' + toString(new Date()),
'UID:' + calendarEvents.length + '@' + 'https://chaiqingao.github.io/',
'SUMMARY:' + courseName,
'DTSTART:' + toString(startDate),
'DTEND:' + toString(endDate),
'RRULE:FREQ=WEEKLY;UNTIL=' + toString(untilDate) + ';INTERVAL=' + interval,
'LOCATION:' + address,
'DESCRIPTION:' + description,
'END:VEVENT'
].join(SEPARATOR));
}
这里的calendarEvents
是一个事件数组,里面存储了所有的课程,加上头尾就可以保存了😁
保存文件
这里使用了FileSaver.js
用于保存文件
var fileName = year + "学年第" + term + "学期.ics";
var calendar = calendar_start + SEPARATOR + calendarEvents.join(SEPARATOR) + calendar_end;
var blob;
if (navigator.userAgent.indexOf('MSIE 10') === -1) { // chrome or firefox
blob = new Blob([calendar]);
} else { // ie
var bb = new BlobBuilder();
bb.append(calendar);
blob = bb.getBlob('text/x-vCalendar;charset=' + document.characterSet);
}
结果展示
这里将ics文件导入至登录了Outlook邮箱的Win10日历,还是很不错的嘛😜~
再打开iPadOS日历看看,已经同步了👍~(前提是登录相同的邮箱)
就剩Android咯,打开一看发现并没有同步😥,又去搜了一些教程,并没有什么很好的解决方法,只好把ics文件发送至手机然后手动打开了。
脚本下载地址
将以上内容做成了油猴助手脚本并上传至了greasyfork,关于油猴助手请点这里,下载脚本请点这里
💫脚本特点:一键保存ics文件,方便快捷
Github项目地址
千山万水总是情,给个Star行不行 Curriculum-to-iCalendar
目前支持的教务系统
- WHU
总结
第一次写这种油猴助手的脚本,也算是练习了Javascript的应用吧,过程磕磕绊绊的,从有这个想法到实现经历了两天时间,找朋友测试了一下,发现了很多🐛bug…修改再测试,测试再修改,到现在还算是挺完善的,也欢迎大家在Github提Issue。