42cal_bookmarklet/42cal_bookmarklet.js

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