From 48445020c896723eced61a948bb7a0abf834698f Mon Sep 17 00:00:00 2001 From: Francesco Minnocci Date: Thu, 28 Aug 2025 14:55:11 +0200 Subject: [PATCH 1/2] feat: add manual semester data update script --- public/timetable-ids-dates.json | 16 +++ src/main.jsx | 40 +++++- src/scripts/update-semester-data.js | 186 ++++++++++++++++++++++++++++ 3 files changed, 239 insertions(+), 3 deletions(-) create mode 100644 public/timetable-ids-dates.json create mode 100644 src/scripts/update-semester-data.js diff --git a/public/timetable-ids-dates.json b/public/timetable-ids-dates.json new file mode 100644 index 0000000..6c115f6 --- /dev/null +++ b/public/timetable-ids-dates.json @@ -0,0 +1,16 @@ +{ + "timetableIds": { + "anno-1": "687e66d62d13e6001edd366d", + "anno-2": "687e681301ee9300198320e6", + "anno-3": "687e68bb01ee9300198320eb", + "magistrale": "687e6a9f96ea080019d439d5" + }, + "semesterStartDateStr": "24 settembre 2025", + "firstMondayDate": "2025-09-29T00:00:00.000Z", + "buttonText": "Vai al 29 settembre! ๐Ÿš€", + "lastUpdated": "2025-08-28T11:47:04.874Z", + "academicYear": { + "startYear": 2025, + "endYear": 2026 + } +} \ No newline at end of file diff --git a/src/main.jsx b/src/main.jsx index ae547fa..b85a901 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -32,13 +32,30 @@ window._ = _ window.dataBuffer = {} // NOTA: magistrale *non* รจ quello con i corsi a cavallo -const TIMETABLE_IDS = { +// These IDs will be dynamically updated from the academic data +let TIMETABLE_IDS = { 'anno-1': '687e66d62d13e6001edd366d', 'anno-2': '687e681301ee9300198320e6', 'anno-3': '687e68bb01ee9300198320eb', 'magistrale': '687e6a9f96ea080019d439d5', } +// Load academic data directly +async function loadAcademicData() { + try { + // Use relative path to work with Vite's base URL configuration + const response = await fetch('./timetable-ids-dates.json') + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`) + } + const data = await response.json() + return data + } catch (error) { + console.error('Failed to load academic data:', error) + return null + } +} + // const DEFAULT_DATE_RANGE = { // from: '2023-10-09T00:00:00.000Z', // to: '2023-10-14T00:00:00.000Z', @@ -177,6 +194,20 @@ const App = ({}) => { // Use any random string of your choice // clearOldPersistentStates('e73cba02') + // Academic data loading + const [academicData, setAcademicData] = useState(null) + useEffect(() => { + loadAcademicData().then(data => { + if (data) { + setAcademicData(data) + if (data.timetableIds) { + // Update global TIMETABLE_IDS for backward compatibility + Object.assign(TIMETABLE_IDS, data.timetableIds) + } + } + }) + }, []) + const [date, setDate] = useState(new Date().toISOString()) // Data Sources @@ -267,10 +298,13 @@ const App = ({}) => {

{/*

diff --git a/src/scripts/update-semester-data.js b/src/scripts/update-semester-data.js new file mode 100644 index 0000000..e745b87 --- /dev/null +++ b/src/scripts/update-semester-data.js @@ -0,0 +1,186 @@ +#!/usr/bin/env node + +// Manual update script for semester timetable data +// Run this script manually each semester to update timetable IDs and dates +// Usage: node src/scripts/update-semester-data.js + +import https from 'https'; +import fs from 'fs'; + +// Get current academic year +function getCurrentAcademicYear() { + const now = new Date(); + const currentYear = now.getFullYear(); + const currentMonth = now.getMonth(); // 0-based + + // Academic year starts in September, but August belongs to the new year + const startYear = currentMonth >= 7 ? currentYear : currentYear - 1; + const endYear = startYear + 1; + + return { startYear, endYear }; +} + +// Fetch webpage content +function fetchPage(url) { + return new Promise((resolve, reject) => { + https.get(url, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('end', () => { + resolve(data); + }); + }).on('error', (err) => { + reject(err); + }); + }); +} + +// Parse timetable IDs from the schedule page +async function parseTimetableIds() { + try { + const html = await fetchPage('https://www.dm.unipi.it/didattica/lezioni-esami/orario-delle-lezioni/'); + const ids = {}; + + // Extract links with their text to match correctly + const linkPattern = /]*href="[^"]*linkCalendarioId=([a-f0-9]{24})"[^>]*>([^<]+)<\/a>/g; + const links = [...html.matchAll(linkPattern)]; + + for (const [fullMatch, id, text] of links) { + const cleanText = text.trim(); + + if (cleanText === 'Triennale I anno') { + ids['anno-1'] = id; + } else if (cleanText === 'Triennale II anno') { + ids['anno-2'] = id; + } else if (cleanText === 'Triennale III anno') { + ids['anno-3'] = id; + } else if (cleanText === 'Magistrale') { + ids['magistrale'] = id; + } + } + + return Object.keys(ids).length > 0 ? ids : null; + } catch (error) { + console.error('Error parsing timetable IDs:', error.message); + return null; + } +} + +// Parse semester start date from calendar page +async function parseSemesterStartDate() { + try { + const { startYear, endYear } = getCurrentAcademicYear(); + const currentMonth = new Date().getMonth(); + const currentSemester = currentMonth >= 1 && currentMonth <= 6 ? 2 : 1; + + const url = `https://www.dm.unipi.it/didattica/lezioni-esami/calendario-delle-attivita-didattiche/calendario-delle-attivita-didattiche-a-a-${startYear}-${endYear.toString().slice(-2)}/`; + + const html = await fetchPage(url); + + // Look for the appropriate semester start date + const semesterText = currentSemester === 1 ? 'I semestre' : 'II semestre'; + const semesterRegex = new RegExp(`Lezioni\\s+${semesterText}.*?(\\d{1,2}\\s+\\w+\\s+\\d{4})`, 'si'); + const match = html.match(semesterRegex); + + if (match) { + return match[1]; + } else { + console.error(`Could not find ${semesterText} start date in calendar`); + return null; + } + } catch (error) { + console.error('Error parsing semester start date:', error.message); + return null; + } +} + +// Find the first Monday after a given date string +function getFirstMondayAfter(dateStr) { + // Parse Italian date format (e.g., "24 settembre 2025") + const [day, monthName, year] = dateStr.split(' '); + + const monthMap = { + 'gennaio': 0, 'febbraio': 1, 'marzo': 2, 'aprile': 3, + 'maggio': 4, 'giugno': 5, 'luglio': 6, 'agosto': 7, + 'settembre': 8, 'ottobre': 9, 'novembre': 10, 'dicembre': 11 + }; + + const month = monthMap[monthName.toLowerCase()]; + if (month === undefined) { + throw new Error(`Unknown month: ${monthName}`); + } + + const startDate = new Date(parseInt(year), month, parseInt(day)); + const dayOfWeek = startDate.getDay(); // 0=Sunday, 1=Monday, etc. + + // Calculate days to add to get to the next Monday + let daysToAdd; + if (dayOfWeek === 0) { // Sunday + daysToAdd = 1; // Next day is Monday + } else if (dayOfWeek === 1) { // Monday + daysToAdd = 7; // Next Monday + } else { // Tuesday-Saturday (2-6) + daysToAdd = 8 - dayOfWeek; // Days until next Monday + } + + const result = new Date(startDate); + result.setDate(startDate.getDate() + daysToAdd); + + // Create a new date in UTC to avoid timezone issues with the calendar + return new Date(Date.UTC(result.getFullYear(), result.getMonth(), result.getDate())); +} + +// Format date for button text (Italian format) +function formatDateForButton(date) { + const day = date.getUTCDate(); + const monthNames = [ + 'gennaio', 'febbraio', 'marzo', 'aprile', 'maggio', 'giugno', + 'luglio', 'agosto', 'settembre', 'ottobre', 'novembre', 'dicembre' + ]; + const month = monthNames[date.getUTCMonth()]; + + return `${day} ${month}`; +} + +// Main function +async function updateAcademicData() { + const [timetableIds, semesterStartDateStr] = await Promise.all([ + parseTimetableIds(), + parseSemesterStartDate() + ]); + + // Fail fast if parsing failed + if (!timetableIds) { + throw new Error('Failed to parse timetable IDs from the website'); + } + + if (!semesterStartDateStr) { + throw new Error('Failed to parse semester start date from the calendar'); + } + + const firstMonday = getFirstMondayAfter(semesterStartDateStr); + + const academicData = { + timetableIds, + semesterStartDateStr, + firstMondayDate: firstMonday.toISOString(), + buttonText: `Vai al ${formatDateForButton(firstMonday)}! ๐Ÿš€`, + lastUpdated: new Date().toISOString(), + academicYear: getCurrentAcademicYear() + }; + + // Save to public/timetable-ids-dates.json + fs.writeFileSync('public/timetable-ids-dates.json', JSON.stringify(academicData, null, 2)); + + console.log('โœ… Academic data updated successfully'); +} + +// Run the script +updateAcademicData().catch(error => { + console.error('๐Ÿ’ฅ Script failed:', error); + process.exit(1); +}); From 97e3758bc14dac52b32fe1c9372cb67a0bf2f752 Mon Sep 17 00:00:00 2001 From: Francesco Minnocci Date: Thu, 28 Aug 2025 15:19:18 +0200 Subject: [PATCH 2/2] chore: refactor JSON timetable import --- src/main.jsx | 46 ++----------------- src/scripts/update-semester-data.js | 12 ++--- .../semester-data.json | 2 +- 3 files changed, 11 insertions(+), 49 deletions(-) rename public/timetable-ids-dates.json => src/semester-data.json (89%) diff --git a/src/main.jsx b/src/main.jsx index b85a901..cf5dd37 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,6 +1,7 @@ import _ from 'lodash' import { render } from 'preact' import { useEffect, useState } from 'preact/hooks' +import semesterData from './semester-data.json' // import { ToolOverlay } from './components/ToolOverlay.jsx' // @@ -32,29 +33,7 @@ window._ = _ window.dataBuffer = {} // NOTA: magistrale *non* รจ quello con i corsi a cavallo -// These IDs will be dynamically updated from the academic data -let TIMETABLE_IDS = { - 'anno-1': '687e66d62d13e6001edd366d', - 'anno-2': '687e681301ee9300198320e6', - 'anno-3': '687e68bb01ee9300198320eb', - 'magistrale': '687e6a9f96ea080019d439d5', -} - -// Load academic data directly -async function loadAcademicData() { - try { - // Use relative path to work with Vite's base URL configuration - const response = await fetch('./timetable-ids-dates.json') - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`) - } - const data = await response.json() - return data - } catch (error) { - console.error('Failed to load academic data:', error) - return null - } -} +const TIMETABLE_IDS = semesterData.timetableIds // const DEFAULT_DATE_RANGE = { // from: '2023-10-09T00:00:00.000Z', @@ -194,20 +173,6 @@ const App = ({}) => { // Use any random string of your choice // clearOldPersistentStates('e73cba02') - // Academic data loading - const [academicData, setAcademicData] = useState(null) - useEffect(() => { - loadAcademicData().then(data => { - if (data) { - setAcademicData(data) - if (data.timetableIds) { - // Update global TIMETABLE_IDS for backward compatibility - Object.assign(TIMETABLE_IDS, data.timetableIds) - } - } - }) - }, []) - const [date, setDate] = useState(new Date().toISOString()) // Data Sources @@ -298,13 +263,10 @@ const App = ({}) => {

{/*

diff --git a/src/scripts/update-semester-data.js b/src/scripts/update-semester-data.js index e745b87..70d2a44 100644 --- a/src/scripts/update-semester-data.js +++ b/src/scripts/update-semester-data.js @@ -164,7 +164,7 @@ async function updateAcademicData() { const firstMonday = getFirstMondayAfter(semesterStartDateStr); - const academicData = { + const semesterData = { timetableIds, semesterStartDateStr, firstMondayDate: firstMonday.toISOString(), @@ -172,11 +172,11 @@ async function updateAcademicData() { lastUpdated: new Date().toISOString(), academicYear: getCurrentAcademicYear() }; - - // Save to public/timetable-ids-dates.json - fs.writeFileSync('public/timetable-ids-dates.json', JSON.stringify(academicData, null, 2)); - - console.log('โœ… Academic data updated successfully'); + + // Save to src/semester-data.json + fs.writeFileSync('src/semester-data.json', JSON.stringify(semesterData, null, 2)); + + console.log('โœ… Semester data updated successfully'); } // Run the script diff --git a/public/timetable-ids-dates.json b/src/semester-data.json similarity index 89% rename from public/timetable-ids-dates.json rename to src/semester-data.json index 6c115f6..4e1b93e 100644 --- a/public/timetable-ids-dates.json +++ b/src/semester-data.json @@ -8,7 +8,7 @@ "semesterStartDateStr": "24 settembre 2025", "firstMondayDate": "2025-09-29T00:00:00.000Z", "buttonText": "Vai al 29 settembre! ๐Ÿš€", - "lastUpdated": "2025-08-28T11:47:04.874Z", + "lastUpdated": "2025-08-28T13:15:33.748Z", "academicYear": { "startYear": 2025, "endYear": 2026