Skip to content

Instantly share code, notes, and snippets.

@hexclover
Last active December 1, 2023 06:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hexclover/4ad6ad90e2ef229ce84ce18116268b53 to your computer and use it in GitHub Desktop.
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).
#!/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