diff --git a/src/background.ts b/src/background.ts index be4c2f7..8dca9a2 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,11 +1,11 @@ // These scripts run in the background continuously import './assets/temp_icon.png'; - import './features/canvas/CanvasIntegration'; import './features/tempBookmarks/TemporaryBookmarks'; - import Settings from './shared/settings'; import './shared/hot_reload'; +import './popup.pug'; +import './features/FreeRooms/FreeRooms'; import {name, version} from '../package.json'; // To see the console output, click the "background page" link on `chrome://extensions` diff --git a/src/features/FreeRooms/FreeRooms.ts b/src/features/FreeRooms/FreeRooms.ts new file mode 100644 index 0000000..f2e9e62 --- /dev/null +++ b/src/features/FreeRooms/FreeRooms.ts @@ -0,0 +1,335 @@ +import ROOMS from './Rooms'; + +async function getRoomSchedHTML(year, semester) { + /* + Input is an integer representing the year that you'd like to query + as well as a string representing the semester for which you'd like + to query. + + Possible Semester Values: + F - Fall + S - Spring + A - Summer Session A + B - Summer Session B + W - Winter Session + + Output is a Promise that, if successful, returns a string of HTML + that is received when making a GET request to the + Stevens room schedule website. + */ + + try { + const response = await fetch( + `https://web.stevens.edu/roomsched?year=${year}&session=${semester}`, + { + method: 'GET', + headers: { + 'Content-Type': 'text/html', + }, + } + ); + return await response.text(); + } catch (e) { + console.error(e); + } +} + +async function getTablesPerRoom(year, semester) { + /* + Input is an integer representing the year that you'd like to query + as well as a string representing the semester for which you'd like + to query. + + Output is an object whose properties are the names of the rooms listed on + web.stevens.edu and whose values for those properties are the HTML table elements objects + corresponding to those rooms. + */ + let HTMLString; + let roomTableDict = {}; + HTMLString = await getRoomSchedHTML(year, semester); + let domparser = new DOMParser(); + let document = domparser.parseFromString(HTMLString, 'text/html'); + let roomTables = document.querySelectorAll('table'); + for (let i = 0; i < ROOMS.length; i++) { + //match all of the tables loaded in the HTML to a the name of a room + //checking by hand, it seems there are only as many tables as there are rooms + //no other tables for decoration or some other data, etc. + roomTableDict[ROOMS[i]] = roomTables[i]; + } + return roomTableDict; +} + +function getDaysRow(day, table) { + /* + Input is an integer representing the day of the week, and a + HTML table object that corresponds to a room. + + Output is a table row object that corresponds to the selected day. + */ + + if (day === 0 || day === 6) { + //It's the weekend, there are no classes scheduled + return null; + } + const dayOfWeek = { + 1: 'M', + 2: 'T', + 3: 'W', + 4: 'R', + 5: 'F', + }; + + let tableRow; + //filter through all rows of the table, finding the one that + //has a td tag with today's day and returning that one + table.querySelectorAll('tr').forEach(row => { + row.querySelectorAll('td').forEach(td => { + if (td.innerHTML === dayOfWeek[day]) { + tableRow = row; + } + }); + }); + return tableRow; +} + +function hasNoScheduledEvent(row, time) { + /* + Input is an HTML row representing a room's availability + for a given day, and a time given by a Date(). + + Output is a boolean indicating whether the room has an event + during the time passed in. + */ + + if (!row) { + //It's the weekend + return true; + } + + //Each row is subdivided into 56 different segments + //with the first segment representing the time 8:00 AM + //and the last segment representing 9:45 PM. + + //There are a additional 3 segments that are on the table but unaccounted for. + + if (time.getHours() < 8 || time.getHours() > 22) { + //These times are outside building operation hours + return false; + } + let colspanTime = convertTimetoColspan(time); + let totalColspan = 0; + let flag = false; + let dataElements = row.querySelectorAll('td'); + for (let td of dataElements) { + if (td.hasAttribute('colspan')) { + totalColspan += parseInt(td.getAttribute('colspan')); + if (totalColspan >= colspanTime) { + if (td.innerHTML === '') { + flag = true; + } else { + break; + } + } + } + } + return flag; +} + +function convertTimetoColspan(time) { + /* + Input is a time given by a Date() object between + 8 AM and 9:45 PM. + Output is the column of the table this time occupies on the + room scheduling website. + */ + + //8 AM -> 0, 9:45 PM -> 55 + + let hours = time.getHours(); + let minutes = time.getMinutes(); + return (hours - 8) * 4 + Math.floor(minutes / 15); +} + +function getSemesterFromDate(date) { + /* + Input is a Date() object. + + Output is the current semester that corresponds to that date. + */ + + //TODO: more accurately determine the current semester + //without relying only on the month + + let month = date.getMonth(); + if (month === 0) { + return 'W'; + } else if (month >= 1 && month <= 4) { + return 'S'; + } else if (month === 5) { + return 'A'; + } else if (month >= 6 && month <= 7) { + return 'B'; + } + return 'F'; +} + +async function getAvailableRooms(date) { + /* + Input is a Date() object. + + Output is a Promise that on success + returns an array of rooms with no classes scheduled according + to the room scheduling website. + */ + + let availableRooms = []; + + let year = date.getFullYear(); + let semester = getSemesterFromDate(date); + let day = date.getDay(); + + let tables = await getTablesPerRoom(year, semester); + for (let room in tables) { + let row = getDaysRow(day, tables[room]); + if (hasNoScheduledEvent(row, date)) { + availableRooms.push(room); + } + } + return availableRooms; +} + +chrome.runtime.onInstalled.addListener(details => { + getAvailableRooms(new Date()).then(rooms => { + chrome.storage.local.set({availableRooms: rooms}); + chrome.alarms.create('rooms', { + periodInMinutes: 15, + }); + }); + + chrome.alarms.onAlarm.addListener(alarm => { + if (alarm.name === 'rooms') { + getAvailableRooms(new Date()).then(rooms => { + chrome.storage.local.set({availableRooms: rooms}); + }); + } + }); +}); + +// async function testGetAvailableRooms() { +// /* +// A test to ensure that getAvailableRooms is functional. Returns +// true if working as expected. +// */ + +// const availableRooms = await getAvailableRooms( +// new Date('February 24, 2020 08:00:00') +// ); +// console.log( +// 'getAvailableRooms() thinks that these are the available rooms on Monday at 8 AM:' +// ); +// console.log(availableRooms); +// return ( +// JSON.stringify(availableRooms) === +// JSON.stringify([ +// 'A115', +// 'A501', +// 'ABS301', +// 'B123', +// 'B126', +// 'B312', +// 'B313', +// 'B314', +// 'B430', +// 'B514', +// 'B517', +// 'B518', +// 'B519', +// 'B620', +// 'B714', +// 'B715', +// 'BC104', +// 'BC110', +// 'BC122', +// 'BC202', +// 'BC203', +// 'BC204', +// 'BC210', +// 'BC212', +// 'BC219', +// 'BC220', +// 'BC221', +// 'BC304', +// 'BC310', +// 'BC312', +// 'BC319', +// 'BC320', +// 'BC321', +// 'BC532B', +// 'BC541', +// 'BCHFSL', +// 'C315', +// 'D231', +// 'D242', +// 'E011', +// 'E111', +// 'E130', +// 'E201', +// 'E229', +// 'E229A', +// 'E231', +// 'E308', +// 'E329', +// 'E330', +// 'GN204', +// 'GN213', +// 'GN303', +// 'GS021', +// 'GS024', +// 'GS025', +// 'GS121', +// 'GS122', +// 'GS123', +// 'GS216', +// 'K228', +// 'K350', +// 'K360', +// 'K380', +// 'K390', +// 'M101', +// 'M105', +// 'M201', +// 'M203', +// 'M205', +// 'M324', +// 'NB101', +// 'NB102', +// 'NB105', +// 'P116', +// 'P120', +// 'P216', +// 'P218', +// 'P220', +// 'R201', +// 'S4TH', +// 'X003', +// 'X004', +// 'X011', +// 'X104', +// 'X105', +// 'X106', +// 'X118', +// 'X119', +// 'X120', +// 'X203', +// 'X218A', +// 'X218B', +// 'X219', +// 'X323', +// 'X414', +// 'X429', +// 'X504', +// 'X510', +// 'X522', +// ]) +// ); +// } diff --git a/src/features/FreeRooms/FreeRoomsDisplay.pug b/src/features/FreeRooms/FreeRoomsDisplay.pug new file mode 100644 index 0000000..72ffc9b --- /dev/null +++ b/src/features/FreeRooms/FreeRoomsDisplay.pug @@ -0,0 +1,9 @@ +h3 Available Rooms +p +| These are the available rooms as reflected by the data available on the +a(href='https://web.stevens.edu/roomsched/') web.stevens.edu/roomsched/ +| website. +p +ul + each room in availableRooms ? availableRooms : [] + li= room diff --git a/src/features/FreeRooms/Rooms.ts b/src/features/FreeRooms/Rooms.ts new file mode 100644 index 0000000..176d3fe --- /dev/null +++ b/src/features/FreeRooms/Rooms.ts @@ -0,0 +1,108 @@ +const ROOMS = [ + 'A115', + 'A501', + 'ABS301', + 'B118', + 'B123', + 'B126', + 'B312', + 'B313', + 'B314', + 'B430', + 'B514', + 'B517', + 'B518', + 'B519', + 'B620', + 'B714', + 'B715', + 'BC104', + 'BC110', + 'BC122', + 'BC202', + 'BC203', + 'BC204', + 'BC210', + 'BC212', + 'BC219', + 'BC220', + 'BC221', + 'BC304', + 'BC310', + 'BC312', + 'BC319', + 'BC320', + 'BC321', + 'BC532B', + 'BC541', + 'BCHFSL', + 'C315', + 'C316', + 'D231', + 'D242', + 'E011', + 'E111', + 'E130', + 'E201', + 'E222', + 'E229', + 'E229A', + 'E230', + 'E231', + 'E308', + 'E329', + 'E330', + 'GN204', + 'GN213', + 'GN303', + 'GS021', + 'GS024', + 'GS025', + 'GS121', + 'GS122', + 'GS123', + 'GS216', + 'K228', + 'K350', + 'K360', + 'K380', + 'K390', + 'M101', + 'M103', + 'M105', + 'M201', + 'M203', + 'M205', + 'M324', + 'NB101', + 'NB102', + 'NB105', + 'P116', + 'P120', + 'P216', + 'P218', + 'P220', + 'R201', + 'S4TH', + 'X003', + 'X004', + 'X011', + 'X104', + 'X105', + 'X106', + 'X118', + 'X119', + 'X120', + 'X203', + 'X218A', + 'X218B', + 'X219', + 'X323', + 'X414', + 'X429', + 'X504', + 'X510', + 'X522', +]; + +export default ROOMS; diff --git a/src/popup.pug b/src/popup.pug index f924a96..ad7a8e9 100644 --- a/src/popup.pug +++ b/src/popup.pug @@ -10,3 +10,5 @@ block append content h1 Stevens Chrome Extension #canvas-integration include features/canvas/CanvasDisplay.pug + #free-rooms + include features/freeRooms/FreeRoomsDisplay.pug diff --git a/src/popup.ts b/src/popup.ts index 8132b5c..b6a5c28 100644 --- a/src/popup.ts +++ b/src/popup.ts @@ -1,4 +1,5 @@ const UpdateCanvas = require('./features/canvas/CanvasDisplay.pug'); +const UpdateRooms = require('./features/freeRooms/FreeRoomsDisplay.pug'); import log from './shared/utility'; document.addEventListener('DOMContentLoaded', () => { @@ -15,4 +16,12 @@ document.addEventListener('DOMContentLoaded', () => { }); } }); + //Room Availability + chrome.storage.local.get('availableRooms', res => { + if (res.availableRooms) { + document.querySelector('#free-rooms').innerHTML = UpdateRooms({ + availableRooms: res.availableRooms, + }); + } + }); });