【油猴脚本】课程表生成ics文件

一、原理

通过截取课表xhr的json文件,提取内容生成ics

二、用法

登录进西大的远程一站式网上办事大厅

然后点进办事大厅

接着就是正常的查询课表流程,进入教务系统,查询学生课表

三、设计思路

1.拦截xhr

使用了油猴脚本开发指南 XHR劫持代码原理解释-油猴中文网的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function addXMLRequestCallback(callback){
//是一个劫持的函数
var oldSend, i;
if( XMLHttpRequest.callbacks ) {
//判断XMLHttpRequest对象下是否存在回调列表,存在就push一个回调的函数
XMLHttpRequest.callbacks.push( callback );
} else {
XMLHttpRequest.callbacks = [callback];
//如果不存在则在xmlhttprequest函数下创建一个回调列表
oldSend = XMLHttpRequest.prototype.send;
//获取旧xml的send函数,并对其进行劫持
XMLHttpRequest.prototype.send = function(){
for( i = 0; i < XMLHttpRequest.callbacks.length; i++ ) {
XMLHttpRequest.callbacks[i]( this );
}
//循环回调xml内的回调函数
// call the native send()
oldSend.apply(this, arguments);
//由于我们获取了send函数的引用,并且复写了send函数,这样我们在调用原send的函数的时候,需要对其传入引用,而arguments是传入的参数
}
}
}

// e.g.
addXMLRequestCallback( function( xhr ) {
//调用劫持函数,填入一个function的回调函数
//回调函数监听了对xhr调用了监听load状态,并且在触发的时候再次调用一个function,进行一些数据的劫持以及修改
xhr.addEventListener("load", function(){
if ( xhr.readyState == 4 && xhr.status == 200 ) {
console.log( xhr.responseURL );
}
});

});

2.分析课表内容

由于课表是一个json文件,通过拦截读取时不是json,而是字符串,需要使用**JSON.parse()**函数转化为json对象

得到对象之后,分析结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"qsxqj":"", // 不清楚,大概是学期数
"xsxx":{ }, // 学生信息,不管
"sjkList": [], // 不知道是啥
"xqjmcMap":{}, // 星期对象,方便显示星期,压缩代码中的字符串
"xskbsfxstkzt":"", // 不清楚
"rqazcList": [], // 不清楚
"kbList":[ // 课表本体(分析对象)

],
"xsbjList":[], // 里面放的是课程类型
"zckbsfxssj": "", // 不清楚
"djdzList": [], // 不清楚
"kblx": , // 不清楚,课表类型?
"sfxsd": "", // 不清楚
"xqbzxxszList": [], // 不清楚
"xkkg": , // 不清楚
"jxhjkcList": [], // 不清楚
"xnxqsfkz": "" // 不清楚
}

单独分析”kbList”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// 每个{}代表一个课程
[
{
"cd_id": "", // 什么id,值是上课地点
"cdmc": "", // 上课地点
"cxbj": "", // 不清楚
"cxbjmc": "", // 不清楚
"date": "", // 查询当天的日期(不是很理解)
"dateDigit": "", // 查询当天的日期(不是很理解)
"dateDigitSeparator": "", // 查询当天的日期(不是很理解)
"day": "", // 查询当天的日期
"jc": "", // 课程节数
"jcor": "", // 课程节数
"jcs": "", // 课程节数
"jgh_id": "", // 课程开课时间??不清楚
"jgpxzd": "", // 不清楚
"jxb_id": "", // 课程id吧,应该
"jxbmc": "", // 课程名称
"jxbsftkbj": "", // 不清楚
"jxbzc": "", // 年份?
"kcbj": "", // 课程类型(主修)
"kch": "", // 课程号吧
"kch_id": "", // 课程号id?
"kclb": "", // 课程类型(通识教育课程)
"kcmc": "", // 课程名称
"kcxszc": "", // 课程学习分布(讲课:xx,实验:xx)
"kcxz": "", // 课程类型简写(通必)
"khfsmc": "", // 不清楚
"kkzt": "", // 不清楚
"listnav": "", // 不清楚
"localeKey": "", // 本地语言
"month": "", // 查询当天的月份
"oldjc": "", // 不清楚
"oldzc": "", // 不清楚
"pageTotal": , // 翻译是页面总数,不清楚
"pageable": , // 是否可翻页,不清楚
"pkbj": "", // 不清楚
"queryModel": { // 查询的参数
"currentPage": ,
"currentResult": ,
"entityOrField": ,
"limit": ,
"offset": ,
"pageNo": ,
"pageSize": ,
"showCount": ,
"sorts": [],
"totalCount": ,
"totalPage": ,
"totalResult":
},
"rangeable": , // 不清楚
"rk": "", // 不清楚
"rsdzjs": , // 不清楚
"skfsmc": "", // 授课方式
"sxbj": "", // 不清楚
"totalResult": "",
"userModel": { // 不清楚
"monitor": ,
"roleCount": ,
"roleKeys": "",
"roleValues": "",
"status": ,
"usable":
},
"xf": "", // 学分
"xkbz": "", // 不清楚
"xm": "", // 教师名字
"xnm": "", // 学年
"xqdm": "", // 不清楚
"xqh1": ",", // 不清楚
"xqh_id": "", // 不清楚
"xqj": "", // 上课星期
"xqjmc": "", // 上课星期
"xqm": "", // 不清楚
"xqmc": "", // 上课地点区域
"xsdm": "", // 不清楚
"xslxbj": "", // 课程类型
"year": "", // 学年吧
"zcd": "", // 上课周期
"zcmc": "", // 教师类型
"zfjmc": "", // 不清楚
"zhxs": "", // 不清楚
"zxs": "", // 不清楚
"zxxx": "", // 不清楚
"zyfxmc": "", // 不清楚
"zzrl": "" // 不清楚
}
]

3.提取重要信息

在我的脚本里面,我用了一个全局变量来装提取之后的信息

结构是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"termStart":"", // 学期第一天星期一
"termEnd":"", // 学期最后一天
"kbList":[
{
"SUMMARY":"", // 课程名
"FREQ":"", // 若为x,则为不循环课程
"UNTIL":"",
"INTERVAL":"",
"DTSTART":"Asia/Shanghai:",
"DTEND":"Asia/Shanghai:",
"UID":"" // 随机生成,
"DESCRIPTION1":"", // 第x-x节\nx区 教室\n教师
"DESCRIPTION2":"", // 课程名@x区 教室\n
"LOCATION":"" // x区 教室 教师
}
]
}

中间那些关键词是ICS格式的标准参数

具体看iCalendar Specification Excerpts

4.合成并下载ICS文件

ics文件的格式可以下载一个课表文件作为参考,就用字符串串起来就好

别忘了换行(\n),如果想要在日历内换行,别忘了转义符

下载的原理就是a标签,函数为

1
2
3
4
5
6
7
8
9
// 下载函数
download_file (fileName, content) {
var aTag = document.createElement('a')
var blob = new Blob([content])
aTag.download = fileName
aTag.href = URL.createObjectURL(blob)
aTag.click()
URL.revokeObjectURL(blob)
}

四、使用地址

greasyfork

欢迎在反馈中提bug,看到了我会修的