165 lines
5.4 KiB
JavaScript
165 lines
5.4 KiB
JavaScript
/* Functions */
|
|
function openNewTabWithHTML(htmlContent) {
|
|
const newTab = window.open("", "_blank");
|
|
newTab.document.write(htmlContent);
|
|
newTab.document.close();
|
|
};
|
|
|
|
function fetchUrlContent(url, callback) {
|
|
fetch(url)
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
}
|
|
return response.text();
|
|
})
|
|
.then(data => callback(null, data))
|
|
.catch(error => {
|
|
console.error('Fetch error: ', error);
|
|
callback(error, "");
|
|
});
|
|
};
|
|
|
|
function formatDate(dateStr) {
|
|
return new Date(dateStr).toISOString().replace(/[-:]/g, "").split(".")[0] + "Z";
|
|
}
|
|
|
|
function formatDateReadable(dateStr) {
|
|
return new Date(dateStr).toDateString();
|
|
}
|
|
|
|
|
|
function jsonToIcal(data) {
|
|
if (!data.start || !data.end || !data.title || !data.id) {
|
|
alert("Required fields missing from input data.");
|
|
throw new Error("Required fields missing from input data.");
|
|
}
|
|
|
|
let icalStr = 'BEGIN:VCALENDAR\nVERSION:1.0\nPRODID:-//42Cal//\n';
|
|
icalStr += 'BEGIN:VEVENT\n';
|
|
|
|
icalStr += `UID:${data.id}\n`;
|
|
icalStr += `SUMMARY:${data.title}\n`;
|
|
icalStr += `DESCRIPTION:${`DURATION: ${data.duration}\\n\\n` + data.description.trim()}\n`;
|
|
icalStr += `LOCATION:${data.location}\n`;
|
|
icalStr += `DTSTART:${formatDate(data.start)}\n`;
|
|
icalStr += `DTEND:${formatDate(data.end)}\n`;
|
|
|
|
icalStr += 'END:VEVENT\n';
|
|
icalStr += 'END:VCALENDAR';
|
|
|
|
return icalStr;
|
|
};
|
|
|
|
function addExtraData(event, callback) {
|
|
let kind = 'events';
|
|
if (event.kind === 'exam')
|
|
{
|
|
kind = 'exams';
|
|
}
|
|
fetchUrlContent(`https://profile.intra.42.fr/${kind}/${event.id}`, (error, data) => {
|
|
if (error) {
|
|
alert('Error2: ' + error);
|
|
console.log('Error2: ' + error);
|
|
callback();
|
|
} else {
|
|
parser = new DOMParser();
|
|
doc = parser.parseFromString(data, "text/html");
|
|
const description = doc.querySelector('.notification-text').textContent;
|
|
const duration = doc.querySelector('.icon-clock')?.parentElement.children[2].textContent;
|
|
const _location = doc.querySelector('.icon-location')?.parentElement.children[1].textContent;
|
|
if (description)
|
|
event.description = description;
|
|
else
|
|
event.description = 'Could not fetch description';
|
|
if (duration)
|
|
event.duration = duration;
|
|
else
|
|
event.duration = 'Could not fetch duration';
|
|
if (_location)
|
|
event.location = _location;
|
|
else
|
|
event.location = 'Could not fetch location';
|
|
callback(event)
|
|
}
|
|
});
|
|
}
|
|
|
|
function eventsToHtml(events) {
|
|
let htmlString = '<h1>Download iCals</h1><ol>';
|
|
events.sort((a,b) => new Date(a.start) - new Date(b.start)).forEach(event => {
|
|
const icalString = jsonToIcal(event);
|
|
const base64Data = btoa(unescape(encodeURIComponent(icalString)));
|
|
const fileName = event.title ? event.title.replace(/\s+/g, '_') + '.ics' : `event_${index + 1}.ics`;
|
|
const date_formatted = formatDateReadable(event.start);
|
|
|
|
htmlString += `<li>
|
|
<a href="data:text/calendar;base64,${base64Data}" download="${fileName}">
|
|
<button style="background-color: ${event.is_subscribed ? 'lightgreen' : 'rgb(239, 239, 239)'}; ">
|
|
${date_formatted}: Download '<span style="color: #1443d3; font-weight: bold; ">${event.title || 'Event'}.ical</span>'
|
|
</button>
|
|
</a>
|
|
</li>`;
|
|
});
|
|
htmlString += '</ol>';
|
|
return htmlString;
|
|
}
|
|
|
|
function generateDownloadPage(dataList, only_subscribed_events) {
|
|
|
|
const promises = dataList.map((event, index) => {
|
|
return new Promise((resolve, reject) => {
|
|
if (!event.id || !event.title || !event.end || !event.start) {
|
|
resolve();
|
|
return;
|
|
}
|
|
|
|
if (only_subscribed_events && !event.is_subscribed) {
|
|
resolve();
|
|
return;
|
|
}
|
|
|
|
addExtraData(event, event => {
|
|
resolve(event);
|
|
});
|
|
});
|
|
});
|
|
|
|
return Promise.all(promises)
|
|
.then(events => {
|
|
return eventsToHtml(events.filter(Boolean));
|
|
});
|
|
};
|
|
|
|
function generateApiDateParams() {
|
|
const currentDate = new Date();
|
|
const year = currentDate.getFullYear();
|
|
const nextYear = year + 1;
|
|
const month = String(currentDate.getMonth() + 1).padStart(2, '0');
|
|
const day = String(currentDate.getDate()).padStart(2, '0');
|
|
const formattedDateStart = `${year}-${month}-${day}`;
|
|
const formattedDateEnd = `${nextYear}-${month}-${day}`;
|
|
|
|
return `start=${formattedDateStart}&end=${formattedDateEnd}`
|
|
};
|
|
|
|
function open_upcoming_events_new_tab(only_subscribed_events) {
|
|
fetchUrlContent(`https://profile.intra.42.fr/events.json?${generateApiDateParams()}`, (error, data) => {
|
|
if (error) {
|
|
alert('Error: ' + error);
|
|
} else {
|
|
generateDownloadPage(JSON.parse(data), only_subscribed_events)
|
|
.then(html => {
|
|
openNewTabWithHTML(html)
|
|
})
|
|
.catch(error => {
|
|
alert("Error1: " + error);
|
|
}
|
|
);
|
|
}
|
|
});
|
|
}
|
|
/* End Functions */
|
|
|
|
open_upcoming_events_new_tab(false);
|