Skip to content

Instantly share code, notes, and snippets.

@Synvox
Created June 15, 2016 02:02
Show Gist options
  • Save Synvox/7d9095ebc2eb5adb3cb96dc2477f0b9b to your computer and use it in GitHub Desktop.
Save Synvox/7d9095ebc2eb5adb3cb96dc2477f0b9b to your computer and use it in GitHub Desktop.
// 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