-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathICSGenerator.js
797 lines (745 loc) · 25.1 KB
/
ICSGenerator.js
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
// ==UserScript==
// @name 课表ICS生成(EasyLZU)
// @namespace https://easylzu.york.moe/
// @version 2.4
// @description 兰州大学教务系统课表ICS日历文件生成
// @author MaPl
// @match http://jwk.lzu.edu.cn/academic/student/currcourse/currcourse.jsdo*
// @grant none
// @homepageURL https://github.com/moeyork/LZU-ICSGenerator
// @supportURL https://github.com/moeyork/LZU-ICSGenerator/issues
// @updateURL https://raw.githubusercontent.com/moeyork/LZU-ICSGenerator/main/ICSGenerator.js
// ==/UserScript==
/**
* @author Mapl
* @file CurrcourseParser.js
* @abstract 解析教务系统课表页面
* @exports parseDOMOfICS
* @license GPLv3
* @version 2.0
* @date 2021-08-04
*/
const TermInfo = { // 学期信息表
"1" : "春季",
"2" : "秋季",
"3" : "寒假",
"4" : "暑假"
}
const YearStart = 1980 // YearID与真实年份的偏移量
const WeekdayList = { // 星期几对照表
"星期一" : 1,
"星期二" : 2,
"星期三" : 3,
"星期四" : 4,
"星期五" : 5,
"星期六" : 6,
"星期日" : 7,
}
/**
* 计算对应YearID对应的真实年份
* @param { String | Number } yearID 年份ID
* @returns { Number } 对应的真实年份
*/
function getRealYear (yearID) {
return (yearID | 0) + YearStart
}
/**
* 计算TermID对应的学期名称
* @param { String } termID termID
* @returns { String } 对应的学期名称
*/
function getTermName (termID) {
return TermInfo[termID]
}
/**
* 计算教师字符串对应的教师列表
* @param { String } teacherText 教师字符串
* @returns { Array<String> } 对应的教师列表
*/
function getTeachersList (teacherText) {
return teacherText.split("\n").slice(0, -1)
}
/**
* 计算是否缓考
* @param { String } isDelayText 是否缓考字符串
* @returns { Boolean } 是否缓考
*/
function getIsDelayTest (isDelayText) {
return isDelayText != "非缓考"
}
/**
* 计算实际上课的周号
* @param { String } text 上课周次文本
* @returns { Array<Number> } 实际上课周次
*/
function getWeekList (text) {
let matchResult
if (matchResult = text.match(/^第(\d+)\-(\d+)周$/)) {
const [fullmatch, start, end] = matchResult
return Array.from({ // range(start, end + 1, 1)
"length" : end - start + 1
}, (v, k) => (k | 0) + (start | 0))
}
if (matchResult = text.match(/^第([0-9,]+)周$/)) {
return matchResult[1].split(",").map(e => e | 0)
}
if (matchResult = text.match(/^(\d+)\-(\d+)周全周$/)) {
const [fullmatch, start, end] = matchResult
return Array.from({ // range(start, end + 1, 1)
"length" : end - start + 1
}, (v, k) => (k | 0) + (start | 0))
}
if (matchResult = text.match(/^(\d+)\-(\d+)周单周$/)) {
const [fullmatch, start, end] = matchResult
return Array.from({ // range(start, end + 1, 2)
"length" : end - start + 1
}, (v, k) => (k | 0) + (start | 0)).filter(e => e%2 == 1)
}
if (matchResult = text.match(/^(\d+)\-(\d+)周双周$/)) {
const [fullmatch, start, end] = matchResult
return Array.from({ // range(start, end + 1, 2)
"length" : end - start + 1
}, (v, k) => (k | 0) + (start | 0)).filter(e => e%2 == 0)
}
}
/**
* 计算星期几对应的数字
* @param { String } text 星期几
* @returns { String } 星期几对应的数字
*/
function getWeekdayNumber (text) {
return WeekdayList[text]
}
/**
* 解析课程时间地点信息
* @param { String } text 信息文本
* @returns { Array } 结构化课程时间地点信息
*/
function getTimeInfoOfCourse (text) {
const timeTextList = text.split("\n")
const timeInfoList = []
for (const timeText of timeTextList) {
const timeInfoCells = timeText.split("\t")
const timeInfo = { // 提取时间地点信息
"weeklist" : getWeekList(timeInfoCells[0]),
"weekday" : getWeekdayNumber(timeInfoCells[1]),
"lesson" : timeInfoCells[2],
"address" : timeInfoCells[3],
}
timeInfoList.push(timeInfo)
}
return timeInfoList
}
/**
* 解析课表DOM
* @param { HTMLTableElement } domTable 课表对应的DOM元素
* @returns { Array } 结构化的课表信息
*/
function parseCourseTable (domTable) {
const courseInfoList = []
for (const row of domTable.rows) {
if (row.cells[0].innerText == "课程号") {
continue // 跳过标题行
}
const courseInfo = { // 按列提取信息
"课程号" : row.cells[0].innerText,
"课程序号" : row.cells[1].innerText | 0,
"课程名称" : row.cells[2].innerText.trim(),
"任课教师" : getTeachersList(row.cells[3].innerText),
"学分" : row.cells[4].innerText | 0,
"选课属性" : row.cells[5].innerText,
"考核方式" : row.cells[6].innerText,
"考试性质" : row.cells[7].innerText,
"是否缓考" : getIsDelayTest(row.cells[8].innerText),
"上课信息" : getTimeInfoOfCourse(row.cells[9].innerText),
}
courseInfoList.push(courseInfo)
}
return courseInfoList
}
/**
* 解析时间表DOM
* @param { HTMLTableElement } domTable 时间表对应的DOM元素
* @returns { Map } 结构化的课次对应的起止时间
*/
function parseTimeTable (domTable) {
const timeList = new Map()
for (const row of domTable.rows) {
if (row.cells[0].innerText == "序号") {
continue // 跳过标题行
}
const name = row.cells[1].innerText
const [start, end] = row.cells[3].innerText.split(" -- ")
timeList.set(name, { start, end })
}
return timeList
}
/**
* 解析课表页面DOM
* @param { Document } dom 课表页面DOM
* @returns { * } 结构化课表页面信息
*/
function parseDOMOfICS (dom) {
const yearID = dom.querySelector("eduaffair\\:ctrt").getAttribute("year") | 0
const termID = dom.querySelector("eduaffair\\:ctrt").getAttribute("term")
const year = getRealYear(yearID) // 年份
const term = getTermName(termID) // 学期
const tables = dom.querySelectorAll("table.infolist_tab")
const tableCourse = tables[0] // 课 表DOM
const tableTime = tables[1] // 时刻表DOM
const courseInfoList = parseCourseTable(tableCourse)
const timeMap = parseTimeTable(tableTime)
return {
year, term,
course : courseInfoList,
time : timeMap
}
}
// 在首页上进行测试
// parseDOMOfICS(window.frames[0].document.querySelector("#mainFrame").contentDocument)
/**
* @author Mapl
* @file CalendarParser.js
* @abstract 解析教务系统校历页面
* @exports parseDOMOfCalendar
* @license GPLv3
* @version 1.0
* @date 2021-08-05
*/
const calendarTermName = { // 校历学期对照表
"秋" : "秋季",
"春" : "春季",
"夏季学期" : "暑假"
}
const CalendarWeekdayList = { // 校历星期几对照表
"星期一" : 1,
"星期二" : 2,
"星期三" : 3,
"星期四" : 4,
"星期五" : 5,
"星期六" : 6,
"星期日" : 7,
}
/**
* 将校历的学期名称转为实际名称
* @param { String } text 校历所写的学期名称
* @returns { String } 对应的学期名称
*/
function getCalendarTerm (text) {
return calendarTermName[text]
}
/**
* 计算校历星期几对应的数字
* @param { String } text 校历星期几
* @returns { String } 星期几对应的数字
*/
function getCalendarWeekdayNumber (text) {
return CalendarWeekdayList[text]
}
/**
* 将校历中的时间字符串转为时间对象
* @param {*} text 校历中的时间字符串
* @returns { Date } 生成的Date对象(本地时间)
*/
function getDateForCalendarDateString (text) {
const [year, month, day] = text
.split("-")
.map(e => Number.parseInt(e, 10))
return new Date(year, month - 1, day)
}
/**
* 解析校历DOM
* @param { HTMLTableElement } domTable 校历对应的DOM元素
* @returns { Map } 结构化的校历信息
*/
function parseCalendarTable (domTable) {
const calendarMap = new Map()
for (const row of domTable.rows) {
if (row.cells[0].innerText == "学年") {
continue // 跳过标题行
}
const infoKey = [
row.cells[0].innerText | 0,
getCalendarTerm(row.cells[1].innerText)
]
const infoValue = {
"startDate" : getDateForCalendarDateString(row.cells[3].innerText),
"totalWeek" : row.cells[4].innerText | 0,
"weekStart" : getCalendarWeekdayNumber(row.cells[5].innerText)
}
calendarMap.set(infoKey, infoValue)
}
return calendarMap
}
/**
* 解析校历页面DOM
* @param { Document } dom 校历页面DOM
* @returns { * } 结构化校历页面信息
*/
function parseDOMOfCalendar (dom) {
const calendarTable = dom.querySelector(".datalist")
return parseCalendarTable(calendarTable)
}
// 在首页上进行测试
// parseDOMOfCalendar(window.frames[0].document.querySelector("#mainFrame").contentDocument)
/**
* @author Mapl
* @file ICS.js
* @abstract ICS包装类
* @exports ICSBlock
* @license GPLv3
* @version 1.0
* @date 2021-08-05
*/
const ICS_MIME = "text/calendar" // ICS文件MIME类型
/**
* 将Date对象转为文本形式(ISO)
* @param { Date } date Date对象
* @returns { String } 对应的文本形式时间
*/
function dateToText (date) {
return date.toISOString().replace(/[\-:]|\.\d{3}/g, "")
}
/**
* 将键值对打包为一条记录
* @param { String } key 键
* @param { String } value 值(文本)
* @returns { String } 记录字符串
*/
function packetTextRecord (key, value) {
const content = String(value)
.replace(/\\/g, "\\\\")
.replace(/\n/g, "\\n")
const record = `${key}:${content}`
.match(/.{75}|.+/g)
.join("\r\n\t")
return record
}
/**
* @abstract ICS块对象
* @extends Map
*/
class ICSBlock extends Map {
/**
* 创建ICS块
* @param { String } name 块的名称
* @param { Map | Object } map 预定义的键值对
*/
constructor (name, map = new Map()) {
super(map instanceof Map ? map : Object.entries(map))
this.name = name
}
/**
* 重写set方法,接收一或两个参数
* @param { [String, String] | [ICSBlock] } args 待添加的键值对或块
*/
set (...args) {
if (args.length == 2) { // 在内部新增加一条记录
const [key, value] = args
super.set(key, value)
} else if (args.length == 1) { // 在内部新增加一个块
if (!(args[0] instanceof ICSBlock)) {
console.warn("Unexcepted Type of object", args[0])
}
super.set(args[0].name + Math.random(), args[0])
} else {
console.warn("Unexcepted length of args", args)
}
}
/**
* 转为ICS文本
* @returns { String } ICS文本
*/
toString () {
const tempList = [`BEGIN:${this.name}`]
for (const [key, value] of this){
if (value instanceof ICSBlock) { // 类型为块
if (!key.startsWith(value.name)) {
console.warn("Unexcepted key of ICSBlock", key, value)
}
tempList.push(value.toString())
} else { // 类型为Record
if (value instanceof Date) { // Date型Record
const text = dateToText(value)
tempList.push(packetTextRecord(key, text))
} else { // Text型Record
tempList.push(packetTextRecord(key, value))
}
}
}
tempList.push(`END:${this.name}`)
return tempList.join("\r\n")
}
}
/**
* @author Mapl
* @file GenICS.js
* @abstract ICS生成
* @exports genICS
* @license GPLv3
* @version 1.3
* @date 2021-08-05
*/
/**
* 计算在经过n天后为哪一天
* @param { Date } date Date对象
* @param { Number } slot 经过的天数
* @returns { Date } 经过n天的Date对象
*/
function getDateAfterDate (date, slot) {
return (new Date(date)).setDate(date.getDate() + slot)
}
/**
* 设置指定Date对象的时间
* @param { Date } date 待设置时间的Date对象
* @param { String } hmString 时间字符串
* @returns { Date } 修改后的Date对象
*/
function setDateWithHM (date, hmString) {
const [hours, min] = hmString.split(":")
const tempDate = new Date(date)
tempDate.setHours(hours)
tempDate.setMinutes(min)
return tempDate
}
/**
* 生成RRule
* @param { Array[Number] } weeklist 周数列表
* @param { Number } weekday 在周几
* @param { Date } lastDay 最后一天的Date对象
* @returns { None | String } RRule字符串
*/
function genRRule (weeklist, weekday, lastDay) {
const week = ["SU", "MO", "TU", "WE", "TH", "FR", "SA"]
if (weeklist.length > 1) {
const a1 = weeklist[0]
const a2 = weeklist[1]
return (
"FREQ=WEEKLY;INTERVAL=" + (a2 - a1) +
";BYDAY=" + week[weekday % 7] +
`;UNTIL=${dateToText(lastDay)}`)
}
}
/**
* 时间Hash
* @param { Array[Number] } weeklist 周数列表
* @param { Number } weekday 在周几
* @param { Date } startTime 起始日期
* @returns { None | String } 时间Hash字符串
*/
function genTimeHash (weeklist, weekday, startTime) {
const week = ["SU", "MO", "TU", "WE", "TH", "FR", "SA"]
if (weeklist.length > 1) {
const a1 = weeklist[0]
const a2 = weeklist[1]
return (
`${a1}-${a2}-${a2-a1}`+
`${week[weekday % 7]}-${dateToText(startTime)}`)
} else {
console.info("use random time hash for uni event")
return Math.random().toString(36).slice(-8)
}
}
/**
* 在在校历Map中查找
* @param { Map } map Map对象
* @param { [Number, String] } key 待查找的Key
* @returns { Any } 结构化校历数据
*/
function getMapKeyArrryLike (map, key) {
for (const [keyM, value] of map) {
if (keyM[0] === key[0] && keyM[1] === key[1]) {
return value
}
}
}
/**
* 由课表时间信息生成日历信息
* @param { Currcourse } Currcourse 课表页面信息
* @param { Calendar } Calendar 校历页面信息
* @param { TimeInfo } timeInfo 上课信息
* @returns { Any } 结构化日历信息
*/
function getCourseDesp (Currcourse, Calendar, timeInfo) {
// 提取所需信息
const { year, term, time } = Currcourse
const { startDate, weekStart } = getMapKeyArrryLike(Calendar, [year, term])
const { weeklist, weekday, lesson } = timeInfo
const { start:startTime, end:endTime } = time.get(lesson)
// 计算课程所在第一天和最后一天
const offset = (weekday + 7 - weekStart) % 7
let slot = (weeklist[0] - 1) * 7 + offset
const firstDay = getDateAfterDate(startDate, slot)
slot = slot + 7 * (weeklist.slice(-1)[0] - 1)
const lastDay = getDateAfterDate(startDate, slot)
// 返回计算所得信息
return {
"start" : setDateWithHM(firstDay, startTime),
"end" : setDateWithHM(firstDay, endTime),
"rrule" : genRRule(
weeklist,
weekday,
setDateWithHM(lastDay, "23:59")
),
"timeHash": genTimeHash(
weeklist,
weekday,
setDateWithHM(firstDay, startTime)
)
}
}
/**
* 生成ICS文件
* @param { Currcourse } Currcourse 课表页面信息
* @param { Calendar } Calendar 校历页面信息
* @returns { ICSBlock } 结构化ICS文件
*/
function genICS (Currcourse, Calendar) {
const ics = new ICSBlock("VCALENDAR", { // ICS描述信息
"VERSION" : "2.0",
"PRODID" : "-//MaPl//EasyLZU ICS v1.0//ZH",
"CALSCALE": "GREGORIAN",
"METHOD" : "PUBLISH",
"X-WR-CALNAME" : `${Currcourse.year}年${Currcourse.term}学期课表`,
"X-WR-TIMEZONE" : "Asia/Shanghai",
})
for (const course of Currcourse.course) { // 每门课程
for (const timeInfo of course["上课信息"]) { // 每个时间段
if (!timeInfo.weeklist || !timeInfo.weekday || !timeInfo.lesson) {
// 课程时间不完整
console.dir(course["上课信息"])
continue
}
const {
start : startTime,
end : endTime,
rrule,
timeHash
} = getCourseDesp(Currcourse, Calendar, timeInfo)
const event = new ICSBlock("VEVENT", {
"UID" : (`${course["课程号"]}@${course["课程序号"]}`+
"@[email protected]@" + timeHash),
"DTSTAMP" : new Date(),
"STATUS" : "CONFIRMED",
"CLASS" : "PRIVATE",
"SUMMARY" : course["课程名称"],
"DESCRIPTION" : `任课教师:${course["任课教师"].join("/")}`,
"LOCATION" : timeInfo.address,
"DTSTART" : startTime,
"DTEND" : endTime
})
if (rrule) {
event.set("RRULE", rrule)
}
ics.set(event)
}
}
return ics
}
/**
* @author Mapl
* @file InjectPage.js
* @abstract 注入页面脚本
* @exports modifyHTML, getDOMOfCal, downloadBlob
* @license GPLv3
* @version 2.2
* @date 2021-08-05
*/
/**
* 从首页或注入页获取首页FrameSet的DOM
* @param { Window } win 当前页面的Window对象
* @returns { Document } 首页FrameSet的DOM
*/
function getDOMOfFrameSet (win) {
if (window.parent == window) {
return win.frames[0].document
} else {
return win.parent.document
}
}
/**
* 从首页FrameSet的DOM获取校历页面Href
* @param { Document } dom 首页FrameSet的DOM
* @returns { String } 校历页面Href
*/
function getCalHrefFromFrameSet (dom) {
return dom
.querySelector("#menuFrame").contentDocument
.querySelector("#li9 > a").getAttribute("href")
}
/**
* 从注入页面得到校历页面DOM
* @returns { Document } 校历页面DOM
*/
function getDOMOfCal () {
const frameSetDOM = getDOMOfFrameSet(window)
console.log(frameSetDOM)
const calHref = getCalHrefFromFrameSet(frameSetDOM)
return new Promise((resolve, reject) => {
const calFrame = frameSetDOM.createElement("iframe")
calFrame.src = calHref
frameSetDOM.querySelector("html").appendChild(calFrame)
calFrame.addEventListener("load", (event) => {
resolve(calFrame.contentDocument)
})
calFrame.addEventListener("error", (event) => {
reject(event)
})
})
}
/**
* 让table某一列可修改,并转为纯文本
* @param { HTMLTableElement } table TabelDOM对象
* @param { Number } col 列索引
*/
function makeTableColEditable (table, col) {
const rowLen = table.rows.length
table.rows[0].cells[col].innerText += "(可编辑)"
for (let index = 1; index < rowLen; index++) {
table.rows[index].cells[col].innerText = table.rows[index].cells[col].innerText
table.rows[index].cells[col].contentEditable = true
}
}
/**
* 阻止事件冒泡
* @param { Event } event
*/
function stopEvent (event) {
event.preventDefault()
event.stopPropagation()
}
/**
* 处理注入元素的鼠标进入事件
* @param { Event } event
*/
function mouseenterEventHandler (event) {
event.currentTarget.style.border = "1px dashed"
event.currentTarget.style.backgroundColor = "#fff"
}
/**
* 处理注入元素的鼠标离开事件
* @param { Event } event
*/
function mouseleaveEventHandler (event) {
event.currentTarget.style.border = "1px none"
event.currentTarget.style.backgroundColor = ""
}
/**
* 处理注入元素的鼠标点击事件
* @param { Event } event
*/
function mouseupEventHandler (event) {
if (event.button == 2) { // 鼠标右键
mouseleaveEventHandler(event)
stopEvent(event)
event.currentTarget.remove()
}
}
/**
* 注入课表时间列表项鼠标效果
* @param { NodeList } domList 列表项DOM的NodeList
*/
function enableHoverStyle (domList) {
for (const tr of domList) {
tr.addEventListener("mouseenter", mouseenterEventHandler)
tr.addEventListener("mouseleave", mouseleaveEventHandler)
tr.addEventListener("mouseup", mouseupEventHandler)
tr.addEventListener("contextmenu", stopEvent)
tr.setAttribute("title", "右键单击可删除该时间段")
tr
.parentElement
.parentElement
.parentElement
.addEventListener("contextmenu", stopEvent)
}
}
/**
* 修改注入页面
* @param { String } provider 功能提供者,用于渲染页面
* @returns { HTMLInputElement } 新增的按钮
*/
function modifyHTML (provider) {
const buttonTD = document.querySelector(
".broken_tab > tbody:nth-child(2) > tr:nth-child(1) > td:nth-child(5)"
) // 导出功能入口按钮位置
const brToAppend = document.querySelector("br") // 配置面板位置
const tableCourse = document.querySelector("table.infolist_tab") // 课表DOM
/* 导出功能入口按钮 */
const startButton = document.createElement("input")
startButton.setAttribute("class", "button")
startButton.setAttribute("type", "submit")
startButton.setAttribute("value", `导出ICS日历文件(${provider})`)
startButton.setAttribute("style", "margin-left: 15px;width: 220px;")
/* 导出配置面板 */
brToAppend.insertAdjacentHTML("afterEnd", `\
<table id="mapl-conf-title" class="subtitle" cellspacing="0" cellpadding="0" style="display: none;">
<tbody>
<tr>
<td class="subtitle">课表导出选项(由${provider}提供)</td>
</tr>
</tbody>
</table>
<table id="mapl-conf-content" class="broken_tab" cellspacing="0" cellpadding="0" style="display: none;">
<tbody>
<tr>
<td style="width: 25%;">请直接编辑下面表格中相应的<b>课程名称</b>进行修改<br>不需要导出的上课时间地点可<b>右键单击</b>进行删除<br><i>(只针对课表导出,不会影响教务系统实际内容)</i></td>
<td>
<input id="mapl-reload" type="submit" class="button" value="重 置">
<input id="mapl-export" class="button" type="submit" style="margin-left: 15px;width: 180px;" value="确定导出">
</td>
</tr>
</tbody>
</table>`)
/* 展示配置面板 && 启动课程名称修改功能 */
const confTitle = document.getElementById("mapl-conf-title")
const confContent = document.getElementById("mapl-conf-content")
const reloadButton = document.getElementById("mapl-reload")
const exportButton = document.getElementById("mapl-export")
startButton.addEventListener("click", (event) => {
/* 禁用导出按钮 */
startButton.disabled = true
startButton.style.background = "none"
startButton.style.borderColor = "#888"
/* 展示配置面板 */
confTitle.style.display = ""
confContent.style.display = ""
/* 启动课程名称修改功能 */
makeTableColEditable(tableCourse, 2)
const tableContent = tableCourse.innerHTML
/* 注入鼠标事件 */
enableHoverStyle(document.querySelectorAll("table.infolist_tab table tr"))
reloadButton.addEventListener("click", (event) => {
tableCourse.innerHTML = tableContent
enableHoverStyle(document.querySelectorAll("table.infolist_tab table tr"))
})
})
/* 挂载导出按钮 */
buttonTD.appendChild(startButton)
return exportButton
}
/**
* 下载Blob对象
* @param { String } fileName 文件名
* @param { Blob } blob Blob对象
*/
function downloadBlob (fileName, blob) {
const downloadLiink = document.createElement('a')
downloadLiink.download = fileName
downloadLiink.href = URL.createObjectURL(blob)
document.body.appendChild(downloadLiink)
downloadLiink.click()
document.body.removeChild(downloadLiink)
}
// 注入页面测试
// modifyHTML()
// getDOMOfCal().then(console.log)
const button = modifyHTML('课表导出助手')
button.addEventListener("click", (event) => {
getDOMOfCal().then((calDOM) => {
const Currcourse = parseDOMOfICS(document)
const Calendar = parseDOMOfCalendar(calDOM)
const ics = genICS(Currcourse, Calendar)
const blob = new Blob([ics.toString()], { type : ICS_MIME})
downloadBlob(ics.get("X-WR-CALNAME") + ".ics", blob)
})
})