From 9e8c6315979c399d39db24f4351078e479242e73 Mon Sep 17 00:00:00 2001
From: Waver Velvet
Date: Tue, 4 Jun 2024 23:52:26 +0800
Subject: [PATCH] refactor: remove calendar stuff temporarily due to
compatibility
---
app/api/calendar/route.tsx | 47 ----
app/api/cron/deploy/route.tsx | 29 ---
app/faq/page.tsx | 17 --
app/layout.tsx | 1 -
app/schedule/page.tsx | 169 --------------
app/schedule/sis-parser-dialog.tsx | 81 -------
components/component/calendar/course-card.tsx | 221 ------------------
data/schedule/calendar-event.ts | 56 -----
data/schedule/index.ts | 59 -----
data/schedule/path-advisor.ts | 64 -----
data/schedule/sis-parser.ts | 11 -
scripts/update-data.mjs | 15 --
12 files changed, 770 deletions(-)
delete mode 100644 app/api/calendar/route.tsx
delete mode 100644 app/api/cron/deploy/route.tsx
delete mode 100644 app/schedule/page.tsx
delete mode 100644 app/schedule/sis-parser-dialog.tsx
delete mode 100644 components/component/calendar/course-card.tsx
delete mode 100644 data/schedule/calendar-event.ts
delete mode 100644 data/schedule/index.ts
delete mode 100644 data/schedule/path-advisor.ts
delete mode 100644 data/schedule/sis-parser.ts
diff --git a/app/api/calendar/route.tsx b/app/api/calendar/route.tsx
deleted file mode 100644
index 40932f5..0000000
--- a/app/api/calendar/route.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import {type NextRequest} from 'next/server';
-import {sectionMap} from '@/data/schedule';
-import * as ics from 'ics';
-import {PathAdvisor} from '@/data/schedule/path-advisor';
-import {generateEventAttributes} from '@/data/schedule/calendar-event';
-
-export const dynamic = 'force-dynamic';
-
-/**
- * GET: /api/calendar
- * Gets the iCalendar file for the given class (section) numbers.
- * Example: webcal://ust-rankings.vercel.app/api/calendar?number=1023&number=1024
- */
-export async function GET(request: NextRequest) {
- const numbers = request.nextUrl.searchParams
- .getAll('number')
- .map(it => Number.parseInt(it, 10))
- .filter(it => !Number.isNaN(it));
-
- const webcal = Boolean(request.nextUrl.searchParams.get('webcal'));
-
- const sections = numbers.flatMap(it => sectionMap[it]);
- const event = ics.createEvents(sections.map(it => ({
- ...generateEventAttributes(it),
- description: `Path Advisor: ${PathAdvisor.findPathTo(it.room)!}`,
- })));
-
- if (event.error) {
- console.error(event.error);
- return new Response(event.error.message, {status: 500});
- }
-
- if (webcal) {
- return new Response(event.value, {
- headers: {
- 'Content-Type': 'text/calendar; charset=utf-8',
- },
- });
- }
-
- return new Response(event.value, {
- headers: {
- 'Content-Type': 'text/calendar; charset=utf-8',
- 'Content-Disposition': 'attachment; filename="calendar.ics"',
- },
- });
-}
diff --git a/app/api/cron/deploy/route.tsx b/app/api/cron/deploy/route.tsx
deleted file mode 100644
index 9d80f01..0000000
--- a/app/api/cron/deploy/route.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import {type NextRequest, NextResponse} from 'next/server';
-
-export async function GET(request: NextRequest) {
- if (request.headers.get('Authorization') !== `Bearer ${process.env.CRON_SECRET}`) {
- return NextResponse.json({error: 'Unauthorized'}, {status: 401});
- }
-
- await redeploy();
- return NextResponse.json({});
-}
-
-async function redeploy() {
- const API = `https://api.vercel.com/v13/deployments?${new URLSearchParams({forceNew: '1'}).toString()}`;
- const resp = await fetch(API, {
- method: 'POST',
- headers: {
- Authorization: `Bearer ${process.env.VERCEL_TOKEN}`,
- },
- body: JSON.stringify({
- deploymentId: process.env.VERCEL_DEPLOYMENT_ID,
- name: `Cron Deployment ${new Date().toISOString()}`,
- target: 'production',
- }),
- });
-
- if (!resp.ok) {
- throw new Error(`Failed to redeploy: ${resp.status} ${resp.statusText} ${await resp.text()}`);
- }
-}
diff --git a/app/faq/page.tsx b/app/faq/page.tsx
index 90a2974..b83cdfa 100644
--- a/app/faq/page.tsx
+++ b/app/faq/page.tsx
@@ -98,23 +98,6 @@ export default function Faq() {
-
- FAQ - Schedule
-
-
-
- Why not using the Timetable Planner?
-
- While it is true that Timetable Planner is able to export the schedule to the calendar, it is not flexible as
- UST Schedule. Users are not allowed to add duplicate courses (except for using the "import to current
- timetable" feature, but still, it is not flexible). Users are not allowed to add individual lectures,
- tutorials or labs without adding the full course.
-
-
- Additionally, UST Schedule is able to show the location of the venue via Path Advisor.
-
-
-
Others
diff --git a/app/layout.tsx b/app/layout.tsx
index 6a52d40..177ce94 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -33,7 +33,6 @@ export default function RootLayout({children}: Readonly<{children: React.ReactNo
className='flex h-16 items-center px-8 gap-8 text-white bg-gradient-to-r from-[#003366] via-[#2b6297] to-[#003366] dark:from-[#003366] dark:via-[#224e77] dark:to-[#003366] text-lg font-medium'>
- Schedule
FAQ
diff --git a/app/schedule/page.tsx b/app/schedule/page.tsx
deleted file mode 100644
index bdcf9c2..0000000
--- a/app/schedule/page.tsx
+++ /dev/null
@@ -1,169 +0,0 @@
-'use client';
-
-import {Input} from '@/components/ui/input';
-import React, {type ChangeEvent} from 'react';
-import {WindowVirtualizer} from 'virtua';
-import {CourseCard} from '@/components/component/calendar/course-card';
-import {courses, searchCourses} from '@/data/schedule';
-import {Button} from '@/components/ui/button';
-import {CalendarPlus, Download, HelpCircle} from 'lucide-react';
-import {Collapsible, CollapsibleContent} from '@/components/ui/collapsible';
-import {Tabs, TabsContent, TabsList, TabsTrigger} from '@/components/ui/tabs';
-import {NewDomainBanner} from '@/components/component/new-domain-banner';
-import {SisParserDialog} from '@/app/schedule/sis-parser-dialog';
-import {toast} from 'sonner';
-
-export default function Home() {
- const [shoppingCart, setShoppingCart] = React.useState>(new Set());
-
- const isSectionInShoppingCart = (section: number) => shoppingCart.has(section);
-
- const addSectionToShoppingCart = (section: number) => {
- shoppingCart.add(section);
- setShoppingCart(new Set(shoppingCart));
- };
-
- const removeSectionFromShoppingCart = (section: number) => {
- shoppingCart.delete(section);
- setShoppingCart(new Set(shoppingCart));
- };
-
- const submitSisParserDialog = (classNumbers: number[]) => {
- classNumbers.forEach(addSectionToShoppingCart);
- };
-
- const [showHelp, setShowHelp] = React.useState(false);
-
- const [query, setQuery] = React.useState('');
- const queryResult = query === '' ? courses : searchCourses(query);
-
- return (
- <>
-
-
-
- UST Schedule
-
-
-
-
- ) => {
- setQuery(e.target.value);
- }}
-
- className='rounded-full focus-visible:ring-gray-700 text-md h-12'
- placeholder='Search by...' type='search'
- />
- {
- setShowHelp(!showHelp);
- }}>
-
-
- {
- const params = new URLSearchParams([...shoppingCart].map(it => ['number', it.toString()]));
- window.open('/api/calendar?' + params.toString());
- }}>
-
-
- {
- const params = new URLSearchParams([...shoppingCart].map(it => ['number', it.toString()]));
- const url = `webcal://${window.location.host}/api/calendar?` + params.toString();
- window.open(url);
- await navigator.clipboard.writeText(url);
- toast('The link is also copied to clipboard');
- }}>
-
-
-
-
-
-
-
-
- Search for courses by their name, code, instructors, room or section number.
-
-
- Features:
-
-
-
- Click (or tap) on sections to add them to (or remove them from) the shopping cart.
-
-
- Click (or tap) on the calendar icon to import the schedules in the shopping cart into the
- calendar.
-
-
- Click (or tap) on the download icon to download the schedules in the shopping cart to the device. You
- can import the downloaded file into the calendar app manually.
-
-
- Click (or tap) on rooms to find their location by Path Advisor.
-
-
-
- More features are coming soon...!
-
-
-
-
-
-
-
-
-
- All
- Shopping Cart
-
-
-
- {queryResult
- .filter(it => it.sections.length)
- .map(course => (
-
-
-
- ))}
-
-
-
-
-
-
- {queryResult
- .filter(it => it.sections.length)
- .filter(it => it.sections.some(section => shoppingCart.has(section.number)))
- .map(it => ({...it, sections: it.sections.filter(section => shoppingCart.has(section.number))}))
- .map(course => (
-
-
-
- ))}
-
-
-
-
- >
- );
-}
diff --git a/app/schedule/sis-parser-dialog.tsx b/app/schedule/sis-parser-dialog.tsx
deleted file mode 100644
index 0aa4615..0000000
--- a/app/schedule/sis-parser-dialog.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import React, {type HTMLAttributes} from 'react';
-import {SisParser, SisUrl} from '@/data/schedule/sis-parser';
-import {findClass} from '@/data/schedule';
-import {
- Dialog, DialogClose,
- DialogContent,
- DialogDescription, DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from '@/components/ui/dialog';
-import {Button} from '@/components/ui/button';
-import {Import} from 'lucide-react';
-import {Textarea} from '@/components/ui/textarea';
-import {Label} from '@/components/ui/label';
-
-type SisParserDialogProps = {
- callback: (classNumbers: number[]) => void;
-} & HTMLAttributes;
-
-export function SisParserDialog({callback, ...props}: SisParserDialogProps) {
- const [text, setText] = React.useState('');
- const classNumbers = SisParser.parse(text);
- const classes = classNumbers.map(it => findClass(it));
-
- function openSis() {
- window.open(SisUrl, 'sis', 'popup=true');
- }
-
- function handleSubmit() {
- callback(classNumbers);
- }
-
- return
-
-
- Import from SIS
-
-
-
-
- Import from SIS
-
-
- Go to SIS .
- Press Ctrl/⌘ + A to select all text on the page.
- Press Ctrl/⌘ + C to copy the text.
- Press Ctrl/⌘ + V to paste the text here.
-
-
-
-
- ;
-}
diff --git a/components/component/calendar/course-card.tsx b/components/component/calendar/course-card.tsx
deleted file mode 100644
index 1de214a..0000000
--- a/components/component/calendar/course-card.tsx
+++ /dev/null
@@ -1,221 +0,0 @@
-import {Card, CardContent, CardDescription, CardHeader, CardTitle} from '@/components/ui/card';
-import React, {type ReactNode} from 'react';
-import {cn, groupBy} from '@/lib/utils';
-import {type Course, format, type Section, type SectionSchedule} from '@/data/schedule';
-import {DateTimeFormatter, LocalTime} from '@js-joda/core';
-import {PathAdvisor} from '@/data/schedule/path-advisor';
-import {Button} from '@/components/ui/button';
-import {toast} from 'sonner';
-
-type Cell = {
- key: string | number;
- data: ReactNode;
- rowSpan: number;
- colSpan: number;
- remove: boolean;
- className: string;
-};
-
-function newCell(data: ReactNode, key: string | any, className?: string): Cell {
- return {
- key: typeof key === 'string' ? key : JSON.stringify(key),
- data,
- rowSpan: 1,
- colSpan: 1,
- remove: false,
- className: className ?? '',
- };
-}
-
-function SectionCell(props: {
- section: Section;
- selected: boolean;
- select: (section: number) => void;
- unselect: (section: number) => void;
-}) {
- const [selected, setSelected] = React.useState(props.selected);
- const variant = selected ? 'default' : 'secondary';
- return {
- setSelected(!selected);
- if (selected) {
- props.unselect(props.section.number);
- toast(`${format(props.section)} removed from shopping cart.`);
- } else {
- props.select(props.section.number);
- toast(`${format(props.section)} added to shopping cart.`);
- }
- }}>
- {props.section.section} ({props.section.number})
- ;
-}
-
-function InstructorsCell({instructors}: {instructors: string[]}) {
- return <>
- {instructors.map(i => {i} )}
- >;
-}
-
-function ScheduleCell({schedule}: {schedule: SectionSchedule}) {
- const m = {
- MONDAY: 'Mon',
- TUESDAY: 'Tue',
- WEDNESDAY: 'Wed',
- THURSDAY: 'Thu',
- FRIDAY: 'Fri',
- SATURDAY: 'Sat',
- SUNDAY: 'Sun',
- };
- const f = DateTimeFormatter.ofPattern('HH:mm');
- return <>
- {m[schedule.day]} {LocalTime.parse(schedule.start).format(f)}-{LocalTime.parse(schedule.end).format(f)}
- >;
-}
-
-function RoomCell({room}: {room: string}) {
- function parseRoomInfo(input: string): {
- segments: string[];
- number?: number;
- } {
- const match = /(.*) \((\d+)\)/.exec(input);
- if (match) {
- let segments = match[1].split(', ');
- segments = segments.map((segment, index) =>
- index === segments.length - 1 ? segment : segment + ', ',
- );
- return {
- segments,
- number: parseInt(match[2], 10),
- };
- }
-
- let segments = input.split(', ');
- segments = segments.map((segment, index) =>
- index === segments.length - 1 ? segment : segment + ', ',
- );
- return {
- segments,
- };
- }
-
- const roomInfoEl = parseRoomInfo(room)
- .segments
- .map(segment => {segment} );
-
- const hrefPathAdvisor = PathAdvisor.findPathTo(room);
- return ;
-}
-
-type SectionTableProps = {
- sections: Section[];
-
- isSectionSelected: (section: number) => boolean;
- selectSection: (section: number) => void;
- unselectSection: (section: number) => void;
-};
-
-function SectionTable(props: SectionTableProps) {
- const {sections} = props;
-
- const sectionGroups = groupBy(sections, section => section.section);
- const tableObj = Object.entries(sectionGroups)
- .flatMap(([, sections]) => sections.flatMap(section => {
- const selected = props.isSectionSelected(section.number);
- return ({
- key: JSON.stringify(section),
- cells: [
- newCell( , section.number, 'p-1'),
- newCell(, section.schedule),
- newCell(, section.instructors),
- newCell(, section.room),
- ],
- });
- }));
-
- const headerCell: Array = tableObj[0]?.cells.map(() => undefined);
- for (const row of tableObj) {
- const {cells} = row;
-
- for (let i = 0; i < cells.length; i++) {
- if (!headerCell[i] || cells[i].key !== headerCell[i]!.key) {
- headerCell[i] = cells[i];
- } else {
- headerCell[i]!.rowSpan++;
- cells[i].remove = true;
- }
- }
- }
-
- return
-
-
- Section
- Schedule
- Instructors
- Room
-
-
-
- {/*
- h-0: fake height for the inner button to stretch to the height of the cell.
- See: https://stackoverflow.com/questions/3215553/make-a-div-fill-an-entire-table-cell
- */}
- {tableObj.map(row =>
- {row.cells.map((cell, i) => {
- if (cell.remove) {
- return null;
- }
-
- return
- {cell.data}
- ;
- })}
- )}
-
- ;
-}
-
-type CourseCardProps = {
- course: Course;
-
- isSectionSelected: (section: number) => boolean;
- selectSection: (section: number) => void;
- unselectSection: (section: number) => void;
-};
-
-export function CourseCard(props: CourseCardProps) {
- const {course} = props;
- return (
-
-
-
-
- {course.name}
-
- {course.program} {course.code}
-
-
-
-
-
-
-
-
- );
-}
diff --git a/data/schedule/calendar-event.ts b/data/schedule/calendar-event.ts
deleted file mode 100644
index 4d454eb..0000000
--- a/data/schedule/calendar-event.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import {convert, DayOfWeek, type LocalDate, LocalTime, nativeJs} from '@js-joda/core';
-import type * as ics from 'ics';
-import {RRule} from 'rrule';
-import {type Section, sectionToCourse} from '@/data/schedule/index';
-import currentTerm from '@/data/schedule/term.json';
-import calendarObj from '@/data/schedule/calendar.json';
-
-const [ACADEMIC_YEAR, TERM] = currentTerm.split(' ');
-const SEMESTER_START = nativeJs(
- new Date(
- Object.values(calendarObj)
- .filter(event => event.type === 'VEVENT')
- .map(event => event as {summary: string; start: string})
- .find(event => `${TERM} Term commences` === event.summary)!
- .start,
- ),
-).toLocalDate();
-const SEMESTER_END = nativeJs(
- new Date(
- Object.values(calendarObj)
- .filter(event => event.type === 'VEVENT')
- .map(event => event as {summary: string; start: string})
- .find(event => `Last day of ${TERM} Term classes` === event.summary)!
- .start,
- ),
-).toLocalDate();
-
-function findDayOfWeekAfter(after: LocalDate, dayOfWeek: DayOfWeek): LocalDate {
- while (after.dayOfWeek() !== dayOfWeek) {
- after = after.plusDays(1);
- }
-
- return after;
-}
-
-export function generateEventAttributes(section: Section): ics.EventAttributes {
- const course = sectionToCourse.get(section.number)!;
-
- const start = findDayOfWeekAfter(SEMESTER_START, DayOfWeek[section.schedule.day])
- .atTime(LocalTime.parse(section.schedule.start));
- const end = findDayOfWeekAfter(SEMESTER_START, DayOfWeek[section.schedule.day])
- .atTime(LocalTime.parse(section.schedule.end));
-
- const rrule = new RRule({
- freq: RRule.WEEKLY,
- until: convert(SEMESTER_END).toDate(),
- });
-
- return {
- start: [start.year(), start.monthValue(), start.dayOfMonth(), start.hour(), start.minute()],
- end: [end.year(), end.monthValue(), end.dayOfMonth(), end.hour(), end.minute()],
- title: `${course.program} ${course.code} ${section.section} (${section.number}) - ${course.name}`,
- location: section.room,
- recurrenceRule: rrule.toString().replace(/^RRULE:/, ''),
- };
-}
diff --git a/data/schedule/index.ts b/data/schedule/index.ts
deleted file mode 100644
index f41ace2..0000000
--- a/data/schedule/index.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import scheduleObj from './schedule.json';
-import Fuse from 'fuse.js';
-import {groupBy} from '@/lib/utils';
-
-export type Schedule = Record;
-export type Course = {
- program: string;
- code: string;
- name: string;
- units: number;
- description: string;
- sections: Section[];
-};
-export type Section = {
- section: string;
- number: number;
- schedule: SectionSchedule;
- instructors: string[];
- room: string;
- quota: number[];
-};
-export type SectionSchedule = {
- day: 'MONDAY' | 'TUESDAY' | 'WEDNESDAY' | 'THURSDAY' | 'FRIDAY' | 'SATURDAY' | 'SUNDAY';
- start: string;
- end: string;
-};
-
-export const schedule = scheduleObj as Schedule;
-export const courses = Object.values(schedule).flat();
-export const sections = courses.flatMap(course => course.sections);
-export const sectionMap = groupBy(sections, section => section.number);
-
-export const sectionToCourse = new Map(courses.flatMap(course => course.sections.map(section => [section.number, course])));
-
-export function findClass(number: number): [Course, Section[]] {
- const sections = sectionMap[number];
- const course = sectionToCourse.get(number)!;
- return [course, sections];
-}
-
-const courseForSearch = courses.map(course => ({
- ...course,
- key: `${course.program} ${course.code}`,
-}));
-const fuse = new Fuse(courseForSearch, {
- keys: ['name', 'key', 'sections.number', 'sections.room', 'sections.instructors'],
- shouldSort: false,
- useExtendedSearch: true,
- threshold: 0.2,
-});
-
-export function searchCourses(query: string): Course[] {
- return fuse.search(query).map(it => it.item);
-}
-
-export function format(section: Section) {
- const course = sectionToCourse.get(section.number)!;
- return `${course.program} ${course.code} ${section.section} (${section.number})`;
-}
diff --git a/data/schedule/path-advisor.ts b/data/schedule/path-advisor.ts
deleted file mode 100644
index 3e96fcb..0000000
--- a/data/schedule/path-advisor.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-export const PathAdvisor = {
- fromScheduleQuotaLocation(location: string): string | undefined {
- // The rules of Path Advisor are as follows:
- // if the name is like "Rm 1409, Lift 25-26 (60)", then it should be "ROOM 1409";
- // if the name is like "Lecture Theater A", then it should be "LTA";
- // if the name is like "G001, CYT Bldg", then it should be "CYTG001";
- // if the name is like "G001, LSK Bldg (70)", then it should be "LSKG001";
- // if the name is like "Rm 103, Shaw Auditorium", then it should be "SA103";
- // if the name is still like "Rm 1104, xxx", then it should be "ROOM 1104";
- // otherwise, return null.
-
- const reMainBuilding1 = /Rm (\w+), Lift (.*)/g;
- const reLT = /Lecture Theater (\w+)/g;
- const reCYT = /(\w+), CYT Bldg/g;
- const reLSK = /(\w+), LSK Bldg/g;
- const reSA = /Rm (\w+), Shaw Auditorium/g;
- const reMainBuilding2 = /Rm (\w+)/g;
-
- const reMainBuildingResult1 = reMainBuilding1.exec(location);
- if (reMainBuildingResult1) {
- return `${reMainBuildingResult1[1]}`;
- }
-
- const reLTResult = reLT.exec(location);
- if (reLTResult) {
- return `LT${reLTResult[1]}`;
- }
-
- const reCYTResult = reCYT.exec(location);
- if (reCYTResult) {
- return `CYT${reCYTResult[1]}`;
- }
-
- const reLSKResult = reLSK.exec(location);
- if (reLSKResult) {
- return `LSK${reLSKResult[1]}`;
- }
-
- const reSAResult = reSA.exec(location);
- if (reSAResult) {
- return `SA${reSAResult[1]}`;
- }
-
- const reMainBuilding2Result = reMainBuilding2.exec(location);
- if (reMainBuilding2Result) {
- return `${reMainBuilding2Result[1]}`;
- }
-
- return undefined;
- },
-
- /**
- * Find the path to a location (room of Schedule & Quota representation).
- * @param location
- */
- findPathTo(location: string): string | undefined {
- const path = PathAdvisor.fromScheduleQuotaLocation(location);
- if (path) {
- return `https://pathadvisor.ust.hk/interface.php?roomno=${path}`;
- }
-
- return undefined;
- },
-};
diff --git a/data/schedule/sis-parser.ts b/data/schedule/sis-parser.ts
deleted file mode 100644
index d8830b4..0000000
--- a/data/schedule/sis-parser.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-export const SisUrl = String.raw`https://sisprod.psft.ust.hk/psp/SISPROD/EMPLOYEE/HRMS/c/SA_LEARNER_SERVICES.SSS_STUDENT_CENTER.GBL?pslnkid=Z_HC_SSS_STUDENT_CENTER_LNK&FolderPath=PORTAL_ROOT_OBJECT.Z_HC_SSS_STUDENT_CENTER_LNK&IsFolder=false&IgnoreParamTempl=FolderPath%2cIsFolder`;
-
-const matcher = /^\w{3} \((\d{4})\)$/gm;
-export const SisParser = {
- parse(sisString: string): number[] {
- const matches = [...sisString.matchAll(matcher)];
- return matches
- .map(it => it[1])
- .map(it => Number(it));
- },
-};
diff --git a/scripts/update-data.mjs b/scripts/update-data.mjs
index 0ac9261..8a1954a 100644
--- a/scripts/update-data.mjs
+++ b/scripts/update-data.mjs
@@ -1,7 +1,5 @@
import fetch from 'node-fetch'
import fs from 'fs/promises'
-import he from 'he'
-import ical from 'node-ical';
async function fetchText(url) {
const resp = await fetch(url)
@@ -13,19 +11,6 @@ async function updateData() {
await fs.writeFile('data/data.json', await fetchText(DATA_URL))
}
-async function updateCalendar() {
- const CURRENT_TERM_URL = "https://github.com/FlandiaYingman/quota-data-at-ust/raw/main/data/current-term.txt"
- const currentTerm = await fetchText(CURRENT_TERM_URL)
-
- const SCHEDULE_URL = `https://github.com/FlandiaYingman/quota-data-at-ust/raw/main/data/${currentTerm} Slim.json`
- const CALENDAR_URL = "https://caldates.ust.hk/cgi-bin/eng/ical.php"
-
- await fs.writeFile('data/schedule/term.json', JSON.stringify(currentTerm))
- await fs.writeFile('data/schedule/schedule.json', await fetchText(SCHEDULE_URL))
- await fs.writeFile('data/schedule/calendar.json', JSON.stringify(ical.sync.parseICS(he.decode(await fetchText(CALENDAR_URL))), null, 2))
-}
-
await Promise.all([
updateData(),
- updateCalendar(),
])
|