Skip to content

Commit

Permalink
cleaned up content script and separated out scraping into separate fi…
Browse files Browse the repository at this point in the history
…le for maintainability
  • Loading branch information
Tacklebox committed Oct 26, 2018
1 parent ed4661f commit 1f74888
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 59 deletions.
30 changes: 30 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module.exports = {
"env": {
"browser": true,
"commonjs": true,
"es6": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"rules": {
"indent": [
"error",
2
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"never"
]
}
};
105 changes: 46 additions & 59 deletions src/content/main.js
Original file line number Diff line number Diff line change
@@ -1,92 +1,79 @@
const page_title = document.querySelector('div.pagetitlediv h2')
import * as scrape from './scraping-methods.js'
import iCal from 'ical-generator'

const MS_PER_DAY = 86400000

if (page_title.textContent == 'View detailed timetable') {
const calendar_title = 'UVic Schedule: ' + document.querySelector('div.staticheaders').childNodes[2].textContent.split(': ')[1]
const class_section_list = document.querySelectorAll('div#P_CrseSchdDetl > table.datadisplaytable')
let class_objects = []
class_section_list.forEach(class_selection => {
let info_table = class_selection.querySelector('table.datadisplaytable.tablesorter > tbody > tbody')
class_objects = class_objects.concat([...info_table.rows].map(row => { //row = [Type, Time, Days, Where, Date Range, Schedule Type, Instructors]
if (scrape.onDetailedSchedulePage()) {
const calendarTitle = 'UVic ' + scrape.getSemester()
const classSections = scrape.getClassSections()

let classes = []
classSections.forEach(classSection => {
const scheduledMeetingTimes = scrape.getScheduledMeetingTimes(classSection)
classes = classes.concat(scheduledMeetingTimes.map(meetingTime => { //meetingTime = [Type, Time, Days, Where, Date Range, Schedule Type, Instructors]

const floating = true
const summary = scrape.getClassSectionTitle(classSection)
const location = meetingTime.cells[3].textContent

let class_object = {floating: true}
class_object.summary = class_selection.querySelector('caption').textContent
const [start_date, end_date] = row.cells[4].textContent.split('-').map(el => el.trim())
const first_day_in_range = new Date(start_date).getDay()
let first_occurrance_order = []
const [startDate, endDate] = meetingTime.cells[4].textContent.split('-').map(el => el.trim())
const firstDayInRange = new Date(startDate).getDay()
let firstOccurranceOrder = []

for (let i = 0; i < 7; i++) {
first_occurrance_order.push((first_day_in_range + i) % 7)
firstOccurranceOrder.push((firstDayInRange + i) % 7)
}
// If the first day of the date range is a wednesday => [3,4,5,6,0,1,2]

const days_raw = row.cells[2].textContent // some combination of: m t w r f
let days = []

for (let i = 0; i < days_raw.length; i++) {
days.push(dayCharToInt(days_raw[i]))
}
const days = meetingTime.cells[2].textContent // some combination of: m t w r f
.split('')
.map(dayCharToInt)
// mwr => [1,3,4]



const first_occurrance_offset = Math.min(...days.map(day=>first_occurrance_order.indexOf(day)))
const firstOccurranceOffset = Math.min(...days.map(day=>firstOccurranceOrder.indexOf(day))) * MS_PER_DAY
// Find the first day of a class to create the inital calendar event. If semester starts on a tues and class is every monday wednesday and thurs, find that first wednesday.

let [start_time, end_time] = row.cells[1].textContent.split('-').map(el => to24(el.trim())) // Time of day class occurs
start_time = new Date(start_date + ' ' + start_time) //DateTime of First day in range at time of class start
end_time = new Date(start_date + ' ' + end_time)
let [startTime, endTime] = meetingTime.cells[1].textContent.split('-').map(el => to24(el.trim())) // Time of day class occurs
startTime = new Date(startDate + ' ' + startTime) //DateTime of First day in range at time of class start
endTime = new Date(startDate + ' ' + endTime)

class_object.location = row.cells[3].textContent
// Start event offset from the beginning of the range to the actual first occurrance of the class.
class_object.start = new Date(start_time.setTime(start_time.getTime() + (first_occurrance_offset * MS_PER_DAY)))
class_object.end = new Date(end_time.setTime(end_time.getTime() + (first_occurrance_offset * MS_PER_DAY)))

if (start_date != end_date) { //Some classes have many rows of ranges of a single day.
class_object.repeating = {
freq: 'WEEKLY',
until: new Date(end_date),
}
if (days.length > 1) { //To prevent timezone weirdness for late classes avoid setting byday if not needed.
class_object.repeating.byDay = []
days.forEach((day) => {
class_object.repeating.byDay.push(dayIntToRRULE(day))
})
}
}
return class_object
const start = new Date(startTime.setTime(startTime.getTime() + firstOccurranceOffset))
const end = new Date(endTime.setTime(endTime.getTime() + firstOccurranceOffset))

//Some classes have many meetingTimes of ranges of a single day.
const repeating = (startDate !== endDate) ? {
freq: 'WEEKLY',
until: new Date(endDate),
byDay: days.map(dayIntToRRULE)
} : null
return {start, end, summary, location, floating, repeating}
}))
})
console.log(class_objects)


let cal = require('ical-generator')({
name: calendar_title,
let calendar = iCal({
name: calendarTitle,
prodId: '//m20n.com//UVICalendar//EN',
timezone: 'America/Vancouver',
events: class_objects
events: classes
})

let export_button = document.createElement('a')
export_button.textContent = 'export'
export_button.href = 'data:text/calendar;charset=utf-8,' + encodeURIComponent(cal.toString())
export_button.download = calendar_title + '.calendar'
export_button.id = 'uvical_export'
let header_links = document.querySelector('span.pageheaderlinks')

// During dev reloading extension caused multiple buttons to be added
let old_button = header_links.querySelector('#uvical_export')
if (old_button) { header_links.removeChild(old_button) }
let exportLink = document.createElement('a')
exportLink.textContent = 'export'
exportLink.href = `data:text/calendar;charset=utf-8,${encodeURIComponent(calendar.toString())}`
exportLink.download = `${calendarTitle}.ics`
exportLink.id = 'uvical_export'

header_links.insertBefore(export_button, header_links.firstChild)
scrape.insertExportLink(exportLink)
}

function to24(time) {
let [hours, minutes] = time.split(':').map(el=>parseInt(el,10))
if (time.endsWith('pm') && hours !== 12) {
hours += 12
}
return hours.toString()+':'+ (minutes == 0?'00':minutes.toString())
return hours.toString()+':'+ (minutes === 0 ? '00' : minutes.toString())
}

function dayCharToInt(dayChar) {
Expand Down
34 changes: 34 additions & 0 deletions src/content/scraping-methods.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// DOM searching functions go here.

export function onDetailedSchedulePage() {
return (document.querySelector('div.pagetitlediv h2').textContent === 'View detailed timetable')
}

export function getSemester() {
return document.querySelector('div.staticheaders').childNodes[2].textContent.split(': ')[1]
}

export function getClassSections() {
return document.querySelectorAll('div#P_CrseSchdDetl > table.datadisplaytable')
}

export function getScheduledMeetingTimes(classSection) {
let scheduledMeetingTimesTable = classSection.querySelector('table.datadisplaytable.tablesorter > tbody > tbody')
return [...scheduledMeetingTimesTable.rows]
}

export function getClassSectionTitle(classSection) {
return classSection.querySelector('caption').textContent
}

const headerLinks = document.querySelector('span.pageheaderlinks')
export function insertExportLink(exportLink) {

// During dev reloading extension caused multiple buttons to be added
let oldLink = headerLinks.querySelector('#uvical_export')
if (oldLink) {
headerLinks.removeChild(oldLink)
}

headerLinks.insertBefore(exportLink, headerLinks.firstChild)
}

0 comments on commit 1f74888

Please sign in to comment.