您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
An average calculator for IUT Lyon 1 students, semester 1, 2018
// ==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); })(); })();