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); +});