108 lines
3.6 KiB
TypeScript
108 lines
3.6 KiB
TypeScript
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<Course | null> {
|
|
// 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;
|
|
}
|