// ==UserScript==
// @name Tomuss average
// @namespace http://tampermonkey.net/
// @version 1.0
// @description An average calculator for IUT Lyon 1 students, semester 1, 2018
// @author Codinget (natnat-mc)
// @match https://tomusss.univ-lyon1.fr/*
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @connect dsidev3.univ-lyon1.fr
// ==/UserScript==
(function() {
'use strict';
const regex={
outof: /^([+-]?\d+(?:\.\d+)?)\/(\d+)$/, // grades with a denominator
interval: /^([+-]?\d+(?:\.\d+)?)\[([+-]?\d+(?:\.\d+)?);([+-]?\d+(?:\.\d+)?)\]$/, // grades with an interval
pu: /var\s+_PU_\s*=\s*"([^"]+)"\s*;/ // the _PU_ variable for the other grades' URI
};
// inject the CSS
(() => {
let css='.TooltipParent {\n';
css+='\tposition: relative;\n';
css+='\toverflow: visible;\n';
css+='}\n';
css+='.TooltipParent .Tooltip {\n';
css+='\topacity: 0;\n';
css+='\tdisplay: block;\n';
css+='\ttransition: opacity .5s;\n';
css+='\tposition: absolute;\n';
css+='\twidth: 10em;\n';
css+='\tleft: -1000vh;\n';
css+='\tpadding: 3px 1em;\n';
css+='\tcolor: #aaa;\n';
css+='\tbackground-color: #333;\n';
css+='\tborder-radius: 5px;\n';
css+='\ttext-align: center;\n';
css+='}\n';
css+='.TooltipParent:hover .Tooltip, .TooltipParent .Tooltip:hover {\n';
css+='\topacity: 1;\n';
css+='\tdisplay: block;\n';
css+='\tz-index: 100;\n';
css+='\tleft: -4em;\n';
css+='\tbottom: 2em;\n';
css+='}\n';
css+='.TooltipParent:hover .Tooltip.TooltipRight, .TooltipParent .Tooltip.TooltipRight:hover {\n';
css+='\tleft: 5em;\n';
css+='\tbottom: -1em;\n';
css+='}\n';
css+='.AverageList {\n';
css+='\twidth: 100%;\n';
css+='}';
GM_addStyle(css);
})();
// target all the parts
let parts=Array.from(document.querySelectorAll('.UEGrades'));
// keep only the parts with grades in them
parts=parts.filter(part => {
return Array.from(part.querySelectorAll('.DisplayTypeNote')).map(cell => {
return cell.querySelector('.CellValue');
}).map(cell => {
return cell.innerText;
}).filter(grade => {
return grade.trim();
}).length!=0;
});
// keep only the parts with no nested parts
parts=parts.filter(part => {
return part.querySelectorAll('.UEGrades').length==0;
});
// make part objects
const UEs=parts.map(part => {
let obj={};
// add the element
(() => {
obj.element=part;
})();
// add the source
(() => {
obj.source='Tomuss';
})();
// find the names
(() => {
const previous=part.previousElementSibling;
if(!previous) return;
const title=previous.querySelector('.UETitle');
if(!title) return;
obj.name=title.innerText;
})();
// find the grades
(() => {
obj.grades=Array.from(part.querySelectorAll('.DisplayTypeNote')).map(cell => {
return cell.querySelector('.CellValue');
}).map(cell => {
return cell.innerText;
}).filter(grade => {
return grade.trim();
}).map(grade => {
if(regex.outof.test(grade)) {
let [_, a, b]=regex.outof.exec(grade);
return {
type: 'normal',
value: +a/+b
};
} else if(regex.interval.test(grade)) {
let [_, a, b, c]=regex.interval.exec(grade);
return {
type: 'bonus',
value: +a,
range: [+b, +c]
};
}
return false;
});
})();
// sort the grades between normal and bonuses
(() => {
obj.normalGrades=obj.grades.filter(grade => {
return grade && grade.type=='normal';
}).map(grade => {
return grade.value;
});
obj.bonusGrades=obj.grades.filter(grade => {
return grade && grade.type=='bonus';
}).map(grade => {
return grade.value;
});
})();
// calculate average, bonus and total score
(() => {
obj.average=(obj.normalGrades.reduce((a, b) => a+b, 0)/obj.normalGrades.length)*20;
obj.bonus=obj.bonusGrades.length==0?0:obj.bonusGrades.reduce((a, b) => a+b, 0)/obj.bonusGrades.length;
obj.total=obj.average+obj.bonus;
})();
return obj;
});
// push the grades into the container
UEs.forEach(ue => {
let cell=document.createElement('div');
cell.classList.add('Display', 'DisplayCellBox', 'CellBox', 'DisplayTypeNote', 'CustomAverage', 'TooltipParent');
let h=ue.total<10?0:120;
let s=100;
let l=100-Math.abs(ue.total-10)*5;
cell.style.backgroundColor='hsl('+h+','+s+'%,'+l+'%)';
let title=cell.appendChild(document.createElement('div'));
title.classList.add('Display', 'DisplayCellTitle', 'CellTitle');
title.innerText='Average';
let value=cell.appendChild(document.createElement('div'));
value.classList.add('Display', 'DisplayCellValue', 'CellValue');
value.innerText=ue.total.toFixed(2);
let denominator=value.appendChild(document.createElement('small'));
denominator.style.fontSize='60%';
denominator.innerText='/20';
let tooltip=cell.appendChild(document.createElement('span'));
tooltip.classList.add('Tooltip');
tooltip.innerText="Average: "+ue.average.toFixed(3);
if(ue.bonusGrades.length) tooltip.innerText+="\nBonus: "+ue.bonus.toFixed(3)+"\nTotal: "+ue.total.toFixed(3);
ue.element.prepend(cell);
});
// try loading the other grades
(() => {
// what to do on error
function xhrFail(err) {
console.error(err);
}
// open an XHR as a Promise
function xhrPromise(params) {
let p=Object.assign({
method: 'GET'
}, params);
let _ok, _ko;
let pr=new Promise((ok, ko) => {
_ok=ok;
_ko=ko;
});
if(p.onload) {
let h=p.onload;
p.onload=xhr => {
_ok(xhr);
h(xhr);
};
} else {
p.onload=_ok;
}
if(p.onerror) {
let h=p.onerror;
p.onerror=err => {
_ko(err);
h(err);
};
} else {
p.onerror=_ko;
}
GM_xmlhttpRequest(p);
return pr;
}
// the URL holder
let _url;
// request the first page
xhrPromise({
url: 'https://dsidev3.univ-lyon1.fr/WD210AWP/WD210Awp.exe/CONNECT/IUT_Note_Etudiant'
}).then(xhr => {
// parse it to get the real URL
let match=regex.pu.exec(xhr.responseText);
if(!match) throw new Error("Couldn't extract URL");
_url='https://dsidev3.univ-lyon1.fr'+match[1];
// generate the query string
let name=localStorage.getItem('name');
let email=localStorage.getItem('email');
let id=localStorage.getItem('id');
if(!name) {
let f=prompt('First name');
let s=prompt('Last name');
name=s.toUpperCase()+' '+f.toUpperCase();
localStorage.setItem('name', name);
}
if(!email) {
email=prompt('IUT Email');
localStorage.setItem('email', email);
}
if(!id) {
id=prompt('Student ID');
id=id.replace('p', '1');
localStorage.setItem('id', id);
}
let qs='WD_ACTION_=AJAXPAGE&EXECUTE=47';
qs+='&WD_CONTEXTE_=A33';
qs+='&WD_BUTTON_CLICK_=';
qs+='&A9=1';
qs+='&A9_DEB=1';
qs+='&_A9_OCC=1';
qs+='&A33=3';
qs+='&A7=-1';
qs+='&A7_DEB=1';
qs+='&_A7_OCC=1';
qs+='&A16='+encodeURIComponent(name);
qs+='&A3='+encodeURIComponent(email);
qs+='&A4=2018';
qs+='&A8='+encodeURIComponent(id);
qs+='&A27=-1';
qs+='&A27_DEB=1';
qs+='&_A27_OCC=49'
// send the action to get the first semester
return xhrPromise({
url: _url,
method: 'POST',
data: qs
});
}).then(xhr => {
// send the action to get the averages
return xhrPromise({
url: _url,
method: 'POST',
data: 'WD_ACTION_=AJAXEXECUTE&LIGNESTABLE=A7&0=142'
});
}).then(xhr => {
// parse the XML and read the averages
let xml=xhr.responseXML;
let lines=Array.from(xml.querySelectorAll('LIGNE'));
let parts=lines.map(line => {
let columns=Array.from(line.querySelectorAll('COLONNE')).map(a => a.textContent.trim());
return {
name: columns[1],
type: columns[2],
total: (isNaN(+columns[3])||columns[3]==='')?false:+columns[3],
average: (isNaN(+columns[3])||columns[3]==='')?false:+columns[3],
columns,
source: 'external(IUT)'
};
}).filter(part => {
return part.total!==false;
}).forEach(part => {
UEs.push(part);
});
console.log(parts);
}).then(() => {
// create a container for the averages
const container=document.createElement('table');
container.classList.add('AverageList');
// add the header row
(() => {
const header=container.appendChild(document.createElement('tr'));
header.appendChild(document.createElement('th')).innerText='Subject';
header.appendChild(document.createElement('th')).innerText='Source';
header.appendChild(document.createElement('th')).innerText='Average';
})();
// add the other rows
(() => {
function add(ue) {
const row=container.appendChild(document.createElement('tr'));
let subject=row.appendChild(document.createElement('td'));
subject.innerText=ue.name;
subject.classList.add('TooltipParent');
let subTT=subject.appendChild(document.createElement('span'));
subTT.innerText=ue.name;
subTT.classList.add('Tooltip', 'TooltipRight');
let source=row.appendChild(document.createElement('td'));
source.innerText=ue.source;
source.classList.add('TooltipParent');
let srcTT=source.appendChild(document.createElement('span'));
srcTT.innerText=ue.source;
srcTT.classList.add('Tooltip');
let avg=row.appendChild(document.createElement('td'));
avg.innerText=ue.total.toFixed(2);
avg.classList.add('TooltipParent');
avg.appendChild(document.createElement('small')).innerText='/20';
let avgTT=avg.appendChild(document.createElement('span'));
avgTT.innerText='Average: '+ue.average.toFixed(3);
if(ue.bonus) {
avgTT.innerText+='\nBonus: '+ue.bonus.toFixed(3)+'\nTotal: '+ue.total.toFixed(3);
}
avgTT.classList.add('Tooltip');
}
UEs.forEach(add);
})();
// insert the container somewhere in the DOM
(() => {
const gradeC=document.querySelector('.Display.DisplayGrades.Grades');
gradeC.prepend(container);
gradeC.prepend(document.createElement('hr'));
})();
}).catch(xhrFail);
})();
})();