feat: add manual semester data update script

pull/23/head
Francesco Minnocci 9 months ago
parent b21cc30c8a
commit 48445020c8

@ -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
}
}

@ -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 = ({}) => {
</p>
<button
onClick={() => {
setDate('2025-09-29T00:00:00.000Z')
const dateToSet =
academicData?.firstMondayDate ||
'2025-09-29T00:00:00.000Z'
setDate(dateToSet)
}}
>
Vai al 29 settembre! 🚀
{academicData?.buttonText || 'Vai al 29 settembre! 🚀'}
</button>
{/* <p>

@ -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 = /<a[^>]*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);
});
Loading…
Cancel
Save