Last active
December 1, 2023 06:24
-
-
Save hexclover/4ad6ad90e2ef229ce84ce18116268b53 to your computer and use it in GitHub Desktop.
NJU course schedule => iCalendar: A quick and dirty script to convert JSON data downloaded from NJU ehall containing course schedule to ICalendar file (.ics).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import json | |
import uuid | |
import datetime as dt | |
from dataclasses import dataclass | |
firstDayOfTerm = dt.date(year=2023, month=9, day=4) | |
lessonStartTime = list(map(dt.time.fromisoformat, ['08:00','09:00','10:10','11:10','14:00','15:00','16:10','17:10','18:30', '19:30','20:40','21:30'])) | |
lessonDuration = dt.timedelta(minutes=50) | |
def fmtDateTime(datetime): | |
return f'TZID=/Asia/Shanghai:{datetime.strftime("%Y%m%dT%H%M%S")}' | |
def fmtUtcDateTime(datetime): | |
return f'{datetime.strftime("%Y%m%dT%H%M%SZ")}' | |
@dataclass(frozen=True) | |
class Course: | |
name: str | |
weekDay: int | |
firstWeek: int | |
perWeeks: int | |
times: int | |
location: str | |
description: str | |
print(""" | |
Usage: | |
1. Login to https://ehall.nju.edu.cn/ | |
2. Find the 课表 app | |
3. Open the developer tools/inspect element etc., and select the Network tab | |
4. Refresh the page and locate the request to xspkjgcx.do | |
5. In the response tab, locate datas.xspkjgcx.rows | |
6. Right click on it and choose Copy Value | |
7. Save clipboard content as courses.json. Its content should look like: | |
{ | |
"rows": [ | |
{ | |
// ... | |
""") | |
decoder = json.decoder.JSONDecoder() | |
with open('courses.json', 'r') as f: | |
resp = f.read() | |
dic = decoder.decode(resp) | |
outfn = f'course-schedule-{dt.datetime.today().isoformat()}.ics' | |
print(f'Trying to saving as {outfn}...\n') | |
with open(outfn, 'wb') as f: | |
emit = lambda s: f.write((s + '\r\n').encode()) | |
emit('BEGIN:VCALENDAR') | |
emit('VERSION:2.0') | |
emit('PRODID:gen') | |
emit('SUMMARY:课表') | |
courses = {} | |
for course in dic['rows']: | |
weekDay = int(course["XQ"]) | |
assert 1 <= weekDay <= 7 | |
name = course["KCMC"] | |
location = course["JASMC"] | |
description = f"教师:{course['JSXM']}" | |
key = (name, weekDay) | |
firstWeek, lastWeek = course['ZCMC'][:-1].strip().split('-') | |
firstWeek = int(firstWeek) | |
lastWeek = int(lastWeek) | |
perWeeks = 1 # TODO: ... | |
times = (lastWeek - firstWeek) // perWeeks + 1 | |
obj = Course( | |
name=name, | |
weekDay=weekDay, | |
firstWeek=firstWeek, | |
perWeeks=perWeeks, | |
times=times, | |
location=location, | |
description=description | |
) | |
if (lessons := courses.get(obj)) is None: | |
lessons = courses[obj] = [] | |
lessons.append(int(course["KSJCDM"])) | |
for lessons in courses.values(): | |
lessons.sort() | |
for it in courses.items(): | |
print(it) | |
course, lessons = it | |
assert lessons == list(range(lessons[0], lessons[-1]+1)), lessons | |
firstDay = firstDayOfTerm + dt.timedelta(days=7*(firstWeek - 1) + course.weekDay - 1) | |
firstLessonStartDt = dt.datetime.combine(firstDay, lessonStartTime[lessons[0]-1]) | |
firstLessonEndDt = dt.datetime.combine(firstDay, lessonStartTime[lessons[-1]-1]) + lessonDuration | |
emit(f'BEGIN:VEVENT') | |
emit(f'SUMMARY:{course.name}') | |
emit(f'UID:{uuid.uuid1()}') | |
emit(f'DTSTAMP:{fmtUtcDateTime(dt.datetime.utcnow())}') | |
emit(f'DTSTART;{fmtDateTime(firstLessonStartDt)}') | |
emit(f'DTEND;{fmtDateTime(firstLessonEndDt)}') | |
emit(f'DESCRIPTION:{course.description}') | |
emit(f'LOCATION:{course.location}') | |
emit(f'RRULE:FREQ=WEEKLY;INTERVAL={course.perWeeks};COUNT={course.times}') | |
emit(f'END:VEVENT') | |
emit('END:VCALENDAR') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment