type Course = { id: number; start: string; // e.g. "2025-09-24T10:45" (local wall time) end: string; // e.g. "2025-09-24T12:45" [k: string]: any; }; import { token } from "./getToken"; const TZ = "Europe/Paris"; /** Format a Date (interpreted in the given time zone) to YYYYMMDD */ function formatYMD(date: Date, timeZone = TZ): string { const fmt = new Intl.DateTimeFormat("en-CA", { timeZone, year: "numeric", month: "2-digit", day: "2-digit", }); // en-CA gives "YYYY-MM-DD" -> strip dashes return fmt.format(date).replaceAll("-", ""); } /** Return Monday 00:00 and Sunday 23:59:59.999 of the week that contains `now` in TZ */ function getWeekBounds(now: Date, timeZone = TZ): { monday: Date; sunday: Date } { // Get the "wall-clock" Y/M/D in TZ const parts = new Intl.DateTimeFormat("en-GB", { timeZone, year: "numeric", month: "2-digit", day: "2-digit", weekday: "short", }).formatToParts(now); const y = Number(parts.find((p) => p.type === "year")!.value); const m = Number(parts.find((p) => p.type === "month")!.value); const d = Number(parts.find((p) => p.type === "day")!.value); const wk = parts.find((p) => p.type === "weekday")!.value; // "Mon".."Sun" // Build a Date that represents midnight in TZ by using UTC as a container // (Date.UTC so it's not shifted by the host machine's local tz) const midnightInTZ = new Date(Date.UTC(y, m - 1, d, 0, 0, 0, 0)); // Map Mon..Sun -> 0..6 (offset from Monday) const weekdayIdx = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"].indexOf(wk); const monday = new Date(midnightInTZ); monday.setUTCDate(monday.getUTCDate() - weekdayIdx); const sunday = new Date(monday); sunday.setUTCDate(sunday.getUTCDate() + 6); sunday.setUTCHours(23, 59, 59, 999); return { monday, sunday }; } /** * Returns the course occurring at the moment this function runs. * If none is in progress, returns null. */ export async function getCurrentCourse(now: Date = new Date()): Promise { // Compute Mon..Sun bounds in Europe/Paris, then format as YYYYMMDD for the query. const { monday, sunday } = getWeekBounds(now, TZ); const startYMD = formatYMD(monday, TZ); // e.g. "20250922" const endYMD = formatYMD(sunday, TZ); // e.g. "20250928" const url = `https://learning.estia.fr/pegasus/index.php?com=emergement&job=get-cours.json&start=${startYMD}&end=${endYMD}`; const res = await fetch(url, { credentials: "include", headers: { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0", Accept: "application/json, text/javascript, */*; q=0.01", "Accept-Language": "en-US,en;q=0.5", "X-Requested-With": "XMLHttpRequest", "Sec-GPC": "1", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", Pragma: "no-cache", "Cache-Control": "no-cache", cookie: `PHPSESSID=${token}`, Referer: "https://learning.estia.fr/pegasus/index.php?com=emergement&job=load-cours-programmes-apprenant", }, method: "GET", mode: "cors", }); if (!res.ok) { throw new Error(`Failed to fetch courses: ${res.status} ${res.statusText}`); } const courses: Course[] = (await res.json()) as Course[]; // Sort by start time just in case (ascending) courses.sort((a, b) => new Date(a.start).getTime() - new Date(b.start).getTime()); // Find the one currently in progress: start <= now < end const current = courses.find((c) => { const start = new Date(c.start).getTime(); const end = new Date(c.end).getTime(); const t = now.getTime(); return t >= start && t < end; }); console.log("Current course:", current); return current ?? null; }