From 4acd83630f04c0982d9b455206b67b4ebeaac2da Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Sun, 15 Apr 2018 16:49:30 +0200 Subject: Add .json endpoint for parsed schedule --- src/server/lib/schools/hetmml/parseSchedule.js | 100 +++++++++++++++++++++++++ src/server/routes/getSchedule.js | 88 ++++++++++++++-------- 2 files changed, 155 insertions(+), 33 deletions(-) create mode 100644 src/server/lib/schools/hetmml/parseSchedule.js (limited to 'src/server') diff --git a/src/server/lib/schools/hetmml/parseSchedule.js b/src/server/lib/schools/hetmml/parseSchedule.js new file mode 100644 index 0000000..14e861e --- /dev/null +++ b/src/server/lib/schools/hetmml/parseSchedule.js @@ -0,0 +1,100 @@ +const { JSDOM } = require('jsdom'); + +function fixFirstLargeScheduleItem(trNodeList) { + return Array.from(trNodeList).some((trNode, timeOfDay) => { + const tdNodeList = trNode.children; + + return Array.from(tdNodeList).some((tdNode, dayOfWeek) => { + const height = tdNode.rowSpan / 2; + if (height === 1) { + return false; + } + + tdNode.rowSpan = 2; // eslint-disable-line no-param-reassign + + for (let i = 1; i < height; i += 1) { + // Are we at the end of the table? + if (dayOfWeek === 4) { + // If so, we cannot use insertBefore, because the is no node to insert + // it before. Use appendChild instead. + trNodeList[timeOfDay + i].appendChild(tdNode.cloneNode(true)); + } else { + trNodeList[timeOfDay + i] + .insertBefore( + tdNode.cloneNode(true), + trNodeList[timeOfDay + i].children[dayOfWeek], + ); + } + } + + return true; + }); + }); +} + +function parseSchedule(axiosResponse) { + const dom = new JSDOM(axiosResponse.data); + const { window } = dom; + const { document } = window; + + const tableNode = document.querySelector('center > table'); + const tbodyNode = tableNode.querySelector('tbody'); + const trNodeList = tbodyNode.children; + + Array.from(trNodeList).forEach((trNode, timeOfDay) => { + const tdNodeList = trNode.children; + + if (timeOfDay === 0 || trNode.children.length === 0) { + tbodyNode.removeChild(trNode); + return; + } + + Array.from(tdNodeList).forEach((tdNode, dayOfWeek) => { + if (dayOfWeek === 0) { + trNode.removeChild(tdNode); + } + }); + }); + + let shouldContinue = true; + while (shouldContinue) { + shouldContinue = fixFirstLargeScheduleItem(trNodeList); + } + + const scheduleItems = []; + + Array.from(trNodeList).forEach((trNode, timeOfDay) => { + const tdNodeList = trNode.children; + Array.from(tdNodeList).forEach((tdNode, dayOfWeek) => { + if (tdNode.textContent.trim() === '') { + return; + } + + const childTableNode = tdNode.querySelector('table'); + const childTrNodeList = childTableNode.querySelectorAll('tr'); + + Array.from(childTrNodeList).forEach((childTrNode) => { + const subject = childTrNode.children[0].textContent.trim(); + const attendees = childTrNode.children[1] + ? childTrNode.children[1].textContent.trim() + : undefined; + const location = childTrNode.children[2] + ? childTrNode.children[2].textContent.trim() + : undefined; + + scheduleItems.push({ + startTime: timeOfDay, + endTime: timeOfDay + 1, + dayOfWeek, + subject, + attendees, + location, + }); + }); + }); + }); + + return scheduleItems; +} + +module.exports = parseSchedule; diff --git a/src/server/routes/getSchedule.js b/src/server/routes/getSchedule.js index 04df28a..7a84dd2 100644 --- a/src/server/routes/getSchedule.js +++ b/src/server/routes/getSchedule.js @@ -25,11 +25,14 @@ const router = express.Router(); const getScheduleData = require('../lib/schools/hetmml/getScheduleData'); const getURLOfUser = require('../lib/schools/hetmml/getURLOfUser'); const axios = require('../lib/schools/hetmml/axios'); +const parseSchedule = require('../lib/schools/hetmml/parseSchedule'); // copied from http://www.meetingpointmco.nl/Roosters-AL/doc/dagroosters/untisscripts.js, // were using the same code as they do to be sure that we always get the same // week number. -function getWeekNumber(target) { +function currentWeekNumber() { + const target = new Date(); + const dayNr = (target.getDay() + 6) % 7; // eslint-disable-next-line target.setDate(target.getDate() - dayNr + 3); @@ -43,39 +46,58 @@ function getWeekNumber(target) { return 1 + Math.ceil((firstThursday - target) / 604800000); } +async function getSchedule(userType, userValue, week, scheduleType = 'dag') { + const { users } = await getScheduleData(); + const user = + users.filter(user_ => user_.type === userType && user_.value === userValue)[0]; + + if (!user) { + throw new Error(`${userType}/${userValue} is not in the user index.`); + } + + if (!week) { + week = currentWeekNumber(); // eslint-disable-line no-param-reassign + } + + const { index } = user; + const url = getURLOfUser(scheduleType, userType, index, week); + + return axios.get(url); +} + +router.get('/:type/:value.json', (req, res, next) => { + const { type, value } = req.params; + const { week, type: scheduleType } = req.query; + + getSchedule(type, value, week, scheduleType) + .then((response) => { + const schedule = parseSchedule(response); + res.json(schedule); + }) + .catch((err) => { + if (err.response) { + // eslint-disable-next-line no-param-reassign + err.status = err.response.status; + } + next(err); + }); +}); + router.get('/:type/:value', (req, res, next) => { - getScheduleData().then(({ users }) => { - const { type, value } = req.params; - let { week } = req.query; - const user = - users.filter(user_ => user_.type === type && user_.value === value)[0]; - - if (!user) { - next(new Error(`${type}/${value} is not in the user index.`)); - } - - if (!week) { - week = getWeekNumber(new Date()); - } - - const { index } = user; - - const scheduleType = req.query.type || 'dag'; - - const url = getURLOfUser(scheduleType, type, index, week); - - axios.get(url) - .then((response) => { - res.status(response.status).end(response.data); - }) - .catch((err) => { - if (err.response) { - // eslint-disable-next-line no-param-reassign - err.status = err.response.status; - } - next(err); - }); - }); + const { type, value } = req.params; + const { week, type: scheduleType } = req.query; + + getSchedule(type, value, week, scheduleType) + .then((response) => { + res.status(response.status).end(response.data); + }) + .catch((err) => { + if (err.response) { + // eslint-disable-next-line no-param-reassign + err.status = err.response.status; + } + next(err); + }); }); module.exports = router; -- cgit v1.1