Created
June 15, 2016 02:02
-
-
Save Synvox/7d9095ebc2eb5adb3cb96dc2477f0b9b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// legacy Il2 scraping | |
let il2 = (()=>{ | |
let host = 'https://byui.brainhoney.com', | |
url = '/Frame/Component/Home', | |
week = 604800000, | |
now = Date.now() | |
function timeStampToDate(dateString) { | |
var dt = new Date(); | |
var dtS = dateString.slice(0, dateString.indexOf('T')); | |
var TimeArray = dtS.split('-'); | |
dt.setUTCFullYear(TimeArray[0], TimeArray[1] - 1, TimeArray[2]); | |
dtS = dateString.slice(dateString.indexOf('T') + 1, dateString.indexOf('.')); | |
if ((dtS === '23:59:00') || (dtS === '23:59:59')) { | |
dt.setFullYear(TimeArray[0]); | |
//Set date first to avoid first of month problem | |
dt.setDate(TimeArray[2]); | |
dt.setMonth(TimeArray[1] - 1); | |
dt.setHours(23, 59); | |
} else { | |
TimeArray = dtS.split(':'); | |
dt.setUTCHours(TimeArray[0], TimeArray[1], TimeArray[2]); | |
} | |
return dt; | |
} | |
let il2 = { | |
events: [], | |
grades: [], | |
getUserId () { | |
return new Promise((Ok, fail)=>{ | |
$.get(host + url,(html,status,xhr)=>{ | |
html = html.split('\n') | |
// There's not a great way to get the user's id. | |
// Here we just scan the index for this trigger | |
// word and extract it right from there. | |
let triggerword = "userId: '" | |
html.forEach(function(line){ | |
line = line.trim() | |
if (line.startsWith(triggerword)) { | |
line = line.substring(triggerword.length) | |
line = line.substring(0, line.indexOf("'")) | |
Ok(line) | |
} | |
}) | |
}).fail(()=>{ | |
fail({error: 'Unable to find user\'s id.', text: 'Log In (IL2)', href: host + url }) | |
}) | |
}) | |
}, | |
scrape() { | |
return new Promise((Ok, fail)=>{ | |
il2.getUserId() | |
.catch((e)=>{fail(e)}) | |
.then((userid)=>{ | |
$.getJSON(`${host}/Admin/Admin.ashx?cmd=getusergradebook2&_format=json&userid=${userid}&itemid=**`).then((data)=>{ | |
let il2Courses = data.response.enrollments.enrollment | |
if (!il2Courses){ | |
Ok() | |
return | |
} | |
let courses = [], | |
grades = [] | |
il2Courses.forEach((il2Course)=>{ | |
// Guard against old courses | |
if (timeStampToDate(il2Course.enddate) < now) { | |
return | |
} | |
let courseTitle = il2Course.entity.title, | |
courseId = il2Course.id | |
let percent = (il2Course.grades.achieved / il2Course.grades.possible * 100).toFixed(0) | |
let course = { | |
id: courseId, | |
title: il2Course.entity.title, | |
grade: il2Course.grades.letter, | |
numerator: il2Course.grades.achieved, | |
denominator: il2Course.grades.possible, | |
percent: `${ percent }%` | |
} | |
courses.push(course) | |
il2Course.grades.items.item.forEach((grade)=>{ | |
if (grade.duedate) { | |
let date = timeStampToDate(grade.duedate) | |
il2.events.push({ | |
orgUnitId: courseId, | |
eventId: grade.itemid, | |
course: courseTitle, | |
title: grade.title, | |
endDate: date, | |
href: host + '/Frame/Component/CoursePlayer?enrollmentid=' + courseId + '&itemid=' + grade.itemid, | |
}) | |
} | |
if (grade.scoreddate) { | |
let scoredate = timeStampToDate(grade.scoreddate) | |
let percent = (grade.achieved / grade.possible * 100).toFixed(0) | |
grades.push({ | |
orgUnitId: courseId, | |
eventId: grade.itemid, | |
course: courseTitle, | |
title: grade.title, | |
numerator: grade.achieved, | |
denominator: grade.possible, | |
percent: percent + '%' | |
}) | |
} | |
}) | |
course.grades = grades | |
il2.grades.push(course) | |
}) | |
Ok() | |
}) | |
}) | |
}) | |
} | |
} | |
return il2 | |
})() | |
// Brightspace | |
// | |
// This is the new scraper. It's much more robust | |
const opts = { | |
storagePrefix: 'darkspace', | |
get host () { | |
const user = storage.get(`${opts.storagePrefix}-user`) || {} | |
return user.host || 'https://byui.brightspace.com' | |
} | |
} | |
const storage = { | |
get: (id)=>{ | |
return JSON.parse(localStorage.getItem(id)) | |
}, | |
set: (id,data)=>{ | |
localStorage.setItem(id,JSON.stringify(data)) | |
} | |
} | |
const getJSON = (url, cb)=>{ | |
return new Promise((Ok, fail)=>{ | |
fetch(`${ opts.host }${url}`, { credentials: 'include' }) | |
.then((res)=>{ | |
if (res.status >= 200 && res.status < 300) { | |
return res | |
} else { | |
var error = new Error(res.statusText) | |
error.res = res | |
fail(error) | |
} | |
}) | |
.then(res=>res.json()) | |
.then(res=>Ok(res)) | |
}) | |
} | |
const getUser = ()=>{ | |
return new Promise((Ok, fail)=>{ | |
getJSON(`/d2l/api/lp/1.5/users/whoami`) | |
.catch((e)=>{fail({error: 'Unable to find user\'s id.', text: 'Log In (D2L)', href: opts.host })}) | |
.then((userData)=>{ | |
Ok({ | |
id: userData.Identifier, | |
firstName: userData.FirstName, | |
lastName: userData.LastName, | |
unique: userData.UniqueName, | |
host: opts.host | |
}) | |
}) | |
}) | |
} | |
const getEnrollments = ()=>{ | |
return new Promise((Ok, fail)=>{ | |
getJSON(`/d2l/api/lp/1.5/enrollments/myenrollments/`).then((res)=>{ | |
let courses = res.Items.length ? res.Items : [] | |
Ok(courses.filter((courseDoc)=>{ | |
return courseDoc.OrgUnit.Type.Id === 3 | |
}).map((courseDoc)=>{ | |
return { | |
course: courseDoc.OrgUnit, | |
orgUnitId: courseDoc.OrgUnit.Id, | |
name: courseDoc.OrgUnit.Name | |
} | |
})) | |
}) | |
}) | |
} | |
const getEvents = (orgUnitId)=>{ | |
return new Promise((Ok, fail)=>{ | |
getJSON(`/d2l/api/le/1.5/${ orgUnitId }/calendar/events/`).then((newEvents)=>{ | |
let events = newEvents.map((e)=>{ | |
return { | |
orgUnitId, | |
eventId: e.CalendarEventId, | |
endDate: e.EndDateTime ? new Date(e.EndDateTime) : new Date(e.EndDate), | |
title: e.Title, | |
courseName: e.OrgUnitName, | |
href: `${opts.host}/d2l/le/calendar/${orgUnitId}/event/${e.CalendarEventId}/detailsview` | |
} | |
}) | |
Ok(events) | |
}) | |
}) | |
} | |
const getGrades = (orgUnitId)=>{ | |
return new Promise((Ok, fail)=>{ | |
getJSON(`/d2l/api/le/1.5/${ orgUnitId }/grades/values/myGradeValues/`).then((newGrades)=>{ | |
let categories = [], | |
items = [], | |
numerator = 0, | |
denominator = 0, | |
percent = '-- %' | |
const parse = (grade)=>{ | |
let percent = (grade.PointsNumerator / grade.PointsDenominator * 100).toFixed(0) | |
return { | |
title: grade.GradeObjectName, | |
numerator: grade.PointsNumerator, | |
denominator: grade.PointsDenominator, | |
weightedNumerator: grade.WeightedNumerator || grade.PointsNumerator, | |
weightedDenominator: grade.WeightedDenominator || grade.PointsDenominator, | |
percent: `${ percent }%` | |
} | |
} | |
newGrades.forEach((grade)=>{ | |
if (grade.GradeObjectType === 9 /*Category*/) | |
categories.push(parse(grade)) | |
else if (grade.GradeObjectType === 1 /*Numeric*/) | |
items.push(parse(grade)) | |
}) | |
categories.forEach((cat)=>{ | |
denominator += cat.weightedDenominator | |
numerator += cat.weightedNumerator | |
}) | |
if (denominator > 0) | |
percent = `${ (numerator / denominator * 100).toFixed(2) }%` | |
Ok({ categories, items, numerator, denominator, percent }) | |
}) | |
}) | |
} | |
const refresh = ()=>{ | |
return new Promise((Ok, fail)=>{ | |
getUser().then((user)=>{ | |
let events = [], | |
grades = [], | |
expectedCallbacks = 0 | |
const finish = ()=>{ | |
Ok({user, events: events.concat(il2.events), grades: grades.concat(il2.grades)}) | |
// Ok({user, events, grades}) | |
} | |
getEnrollments().then((enrollments)=>{ | |
enrollments.forEach((enrollment)=>{ | |
let course = enrollment.course | |
orgUnitId = enrollment.orgUnitId | |
name = enrollment.name | |
expectedCallbacks += 2 | |
getEvents(orgUnitId).then((newEvents)=>{ | |
events = events.concat(newEvents) | |
if (--expectedCallbacks === 0) finish() | |
}) | |
getGrades(orgUnitId).then((newGrades)=>{ | |
grades.push(newGrades) | |
if (--expectedCallbacks === 0) finish() | |
}) | |
}) | |
}) | |
}) | |
}) | |
} | |
const save = (data)=>{ | |
return new Promise((Ok, fail)=>{ | |
storage.set(`${ opts.storagePrefix }-${data.user.id}`, data) | |
storage.set(`${ opts.storagePrefix }-user`, data.user) | |
getLevels(data) | |
.then((levels)=>{ | |
drawIcon(levels.urgent) | |
}) | |
Ok(data) | |
}) | |
} | |
const pull = ()=>{ | |
return new Promise((Ok, fail)=>{ | |
const user = storage.get(`${ opts.storagePrefix }-user`) || null | |
if (user === null) { | |
Ok(null) | |
return | |
} | |
Ok(storage.get(`${ opts.storagePrefix }-${user.id}`) || null) | |
}) | |
} | |
const scrape = ()=>{ | |
return new Promise((Ok, fail)=>{ | |
refresh() | |
.then((data)=>{ return merge(data) }) | |
.then((data)=>{ return save(data) }) | |
.then(()=>{Ok(data)}) | |
}) | |
} | |
const merge = (current)=>{ | |
return new Promise((Ok, fail)=>{ | |
pull().then((previous)=>{ | |
if (previous === null) { | |
Ok(Object.assign({}, current)) | |
return | |
} | |
const events = current.events.map((currentEvent)=>{ | |
const previousEvent = previous.events.find((previousEvent)=>{ | |
return (previousEvent.eventId === currentEvent.eventId) | |
}) || {} | |
return Object.assign({}, previousEvent, currentEvent) | |
}) | |
Ok(Object.assign({}, current, { events })) | |
}) | |
}) | |
} | |
const bind = (data)=>{ | |
let state = Object.assign({}, data) | |
return new Promise((Ok, fail)=>{ | |
let events = state.events.map((currentEvent)=>{ | |
return new Event(currentEvent, (event)=>{ | |
let fndEvent = state.events.find((event)=>{ return event.eventId === currentEvent.eventId }) | |
Object.assign(fndEvent,event) | |
save(state) | |
}) | |
}) | |
Ok(Object.assign({}, data, { events })) | |
}) | |
} | |
const getLevels = (data)=>{ | |
return new Promise((Ok, fail)=>{ | |
let now = new Date(), | |
levels = { | |
panic: 0, | |
urgent: 0 | |
} | |
data.events.forEach((event)=>{ | |
let date = new Date(event.endDate) | |
if (date >= now) { | |
let urgent = date - now < 86400000 * 2 // 2 days | |
// disclude finished events | |
if (urgent && event.finished) urgent = false | |
if (urgent) { | |
levels.urgent++ | |
let difference = (date - now) / 3600000 // hour | |
if (difference < 2) { | |
levels.panic += 2 - difference | |
if (difference < 0.5) { | |
levels.panic += 1 | |
} | |
if (difference < 0.25) { | |
levels.panic += 1 | |
} | |
if (difference < 0.125) { | |
levels.panic += 1 | |
} | |
} | |
} | |
} | |
}) | |
if (Number.isNaN(levels.panic)) levels.panic = 5 | |
if (levels.panic > 5) levels.panic = 5 | |
if (levels.panic < 0) levels.panic = 0 | |
Ok(levels) | |
}) | |
} | |
const drawIcon = (number)=>{ | |
return new Promise((Ok, fail)=>{ | |
var canvas = $('<canvas>')[0] | |
var cxt = canvas.getContext('2d') | |
canvas.width = 40 | |
canvas.height = 40 | |
cxt.textAlign = 'center' | |
cxt.textBaseline = 'middle' | |
cxt.font = '30px "Lucida Console", Monaco, monospace' | |
cxt.fillStyle = '#333' | |
cxt.strokeStyle = '#ececec' | |
cxt.lineWidth = 4 | |
cxt.strokeText(number, canvas.width/2, canvas.height/2) | |
cxt.fillText(number, canvas.width/2, canvas.height/2) | |
// Both are the same size. Chrome doesn't care. *troll face* | |
chrome.browserAction.setIcon({ | |
path: { | |
"19": canvas.toDataURL(), | |
"38": canvas.toDataURL() | |
} | |
}) | |
Ok() | |
}) | |
} | |
const getActivities = ()=>{ | |
return new Promise((Ok, fail)=>{ | |
let clean = (input)=>{ | |
return input.replace("<![CDATA[", "").replace("]]>", "") | |
} | |
let activities = [] | |
$.get(this.url,function(res){ | |
$(res).find('item').each((i,ele)=>{ | |
let title = clean($(ele).find('title').html()), | |
text = clean($(ele).find('description').html()), | |
date = new Date(Date.parse(clean($(ele).find('pubDate').html()))), | |
href = clean($(ele).find('link').html()) | |
activities.push(new Item({ | |
title: title, | |
text: text, | |
date: date, | |
href: href | |
})) | |
}) | |
station.trigger('refresh_progress') | |
Ok(activities) | |
}).fail(()=>{ | |
fail() | |
}) | |
}) | |
} | |
class Event { | |
constructor(data, commit) { | |
this.data = data | |
this.commit = commit | |
} | |
toggle() { | |
this.data.finished = !this.data.finished | |
this.commit(this.valueOf()) | |
} | |
valueOf() { | |
return this.data | |
} | |
} | |
il2.scrape() | |
.then(()=>{ return refresh() }) | |
.then((data)=>{ return merge(data) }) | |
.then((data)=>{ return save(data) }) | |
.then(()=>{ return pull() }) | |
.then((data)=>{ | |
getLevels(data) | |
.then((levels)=>{ | |
drawIcon(levels.urgent) | |
}) | |
return bind(data) | |
}) | |
.then((data)=>{ | |
console.log(data) | |
data.events[0].toggle() | |
}) | |
// if (module !== undefined) | |
// module.exports = { pull, scrape } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment