estErn
The snippet can be accessed without any authentication.
Authored by
FOULON VALENTIN
Bot qui récupère les notes d'Ernest et envoie les nouvelles notes sur Discord
Installation de la dépendance : npm i node-html-parser
Configuration du script : changer les 3 premières constantes, et éventuellement la constante newGrades si vous voulez recevoir toutes les notes
Utilisation : node ernest_notes.js
const username = 'A changer';
const password = 'A changer aussi';
const webhookURL = 'A changer à nouveau';
const newGrades = true; // Changer à false pour avoir toutes les notes à chaque fois
const htmlparser = require('node-html-parser');
const fs = require('fs');
var execValue = null;
var routedJSESSIONID = null;
var debug = false;
const logfile = 'log.txt';
fs.unlinkSync(logfile);
const _log = console.log;
console.log = function(title, ...args) {
fs.appendFileSync(logfile, `[${(new Date()).toISOString()}] ${title}${args.length > 0 ? `\n${args}` : ''}\n`);
_log.apply(console, debug ? [`\x1b[34m${title}\x1b[0m`, args].flat() : [`\x1b[34m${title}\x1b[0m`].flat());
}
function processChunkedResponse(response) {
let text = '';
let reader = response.body.getReader()
let decoder = new TextDecoder();
return readChunk();
function readChunk() {
return reader.read().then(appendChunks);
}
function appendChunks(result) {
const chunk = decoder.decode(result.value || new Uint8Array, {stream: !result.done});
text += chunk;
if (result.done) return text;
return readChunk();
}
}
function getExecutionValue(html) {
if (html == null) return;
const root = htmlparser.parse(html)
const exec = root.querySelector('input[name=execution]');
execValue = exec._attrs.value;
return;
}
function exchangeJSESSIONID(url, routedJSESSIONID) {
return new Promise((resolve, reject) => {
const headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br',
'Referer': 'https://cas.unistra.fr',
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': 'https://cas.unistra.fr',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-site',
'Sec-Fetch-User': '?1',
'sec-ch-ua-platform': '"Linux"',
'sec-ch-ua': '"Not=A?Brand";v="99", "Chromium";v="118"',
'sec-ch-ua-mobile': '?0',
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
'Upgrade-insecure-requests': '1',
'Cookie': `JSESSIONID=${routedJSESSIONID}; ROUTEID=.${routedJSESSIONID.split('.')[1]}`
};
fetch(url, {
headers: headers,
redirect: 'manual',
})
.then(res => {
if (res.status != 302) {
reject(res);
return;
}
if (res.headers.get('set-cookie') != null) {
parseJSESSIONIDCookie(res.headers.get('set-cookie'))
.then(cookie => {
console.log("GOT NEW JSESSIONID", cookie);
resolve(cookie);
})
.catch(reject);
} else {
console.log("INSTRUCTED TO RE-USE OLD JSESSIONID", routedJSESSIONID);
resolve(routedJSESSIONID);
}
})
.catch(reject);
}, url, routedJSESSIONID);
}
function parseJSESSIONIDCookie(setcookie) {
return new Promise((resolve, reject) => {
const cookies = setcookie.split(', ');
const cookieOptions = cookies.map(x => x.split('; ')[0]);
const kv = cookieOptions.map(x => x.split('='));
const field = kv.filter(x => x[0] == 'JSESSIONID')[0] || null;
if (field != null) {
resolve(field[1]);
return;
} else {
reject("NO COOKIE");
return;
}
});
}
function logout() {
return new Promise((resolve, reject) => {
fetch('https://cas.unistra.fr/cas/logout')
.then(res => {
console.log("LOG OUT SUCCESFUL");
resolve();
})
.catch(reject)
});
}
function login() {
return new Promise((resolve, reject) => {
const body = {
username: username,
password: password,
execution: execValue,
_eventId: 'submit',
geolocation: ''
};
const headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br',
'Referer': 'https://cas.unistra.fr/cas/login?service=https%3A%2F%2Fdossieretu.unistra.fr%2Fmdw%2Flogin%2Fcas',
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': 'https://cas.unistra.fr',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'same-origin',
'Sec-Fetch-User': '?1',
'sec-ch-ua-platform': '"Linux"',
'sec-ch-ua': '"Not=A?Brand";v="99", "Chromium";v="118"',
'sec-ch-ua-mobile': '?0',
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
};
fetch('https://cas.unistra.fr/cas/login', {
redirect: 'manual',
method: 'POST',
body: new URLSearchParams(body),
headers: headers,
})
.then(res => {
if (res.status != 302) {
reject(res);
return;
}
console.log("SUCCESSFULLY LOGGED IN");
parseJSESSIONIDCookie(res.headers.get('Set-Cookie'))
.then(cookie => {
console.log("GOT NEW JSESSIONID", cookie);
exchangeJSESSIONID(res.headers.get('Location'), routedJSESSIONID)
.then(cookie => {
console.log("SUCCESSFULLY EXCHANGED JSESSIONID", cookie);
resolve(cookie);
})
.catch(reject);
})
.catch(reject);
})
.catch(reject);
});
}
function getLoginPage() {
return new Promise((resolve, reject) => {
fetch('https://cas.unistra.fr/cas/login?service=https%3A%2F%2Fdossieretu.unistra.fr%2Fmdw%2Flogin%2Fcas', {
redirect: 'manual',
method: 'GET',
})
.then(processChunkedResponse)
.then(body => {
getExecutionValue(body);
console.log("GOT EXECUTION VALUE", execValue);
login()
.then(cookie => {
console.log("MADE FINAL STEP");
resolve(cookie);
})
.catch(reject);
})
.catch(reject);
});
}
function catchVaadinKey(body) {
const uidl = body['uidl'];
const reparse = JSON.parse(uidl);
const key = reparse['Vaadin-Security-Key'];
console.log("GOT VAADIN SECURITY KEY", key);
return key;
}
function getSecurityKey(routedJSESSIONID) {
return new Promise((resolve, reject) => {
var date = new Date();
const tzo1 = date.getTimezoneOffset();
var dstDiff = 0;
var rtzo = tzo1;
for (var m=12;m>0;m--) {
date.setUTCMonth(m);
const tzo2 = date.getTimezoneOffset();
if (tzo1 != tzo2) {
dstDiff = (tzo1 > tzo2 ? tzo1-tzo2 : tzo2-tzo1);
rtzo = (tzo1 > tzo2 ? tzo1 : tzo2);
break;
}
}
const body = {
'v-browserDetails': '1',
'theme': 'valo-ul',
'v-appId': 'mdw-107968',
'v-sh': '1080',
'v-sw': '1920',
'v-cw': '1920',
'v-ch': '1080',
'v-curdate': `${date.getTime()}`,
'v-tzo': `-120`,
'v-dstd': `${dstDiff}`,
'v-rtzo': `${rtzo}`,
'v-dston': `${tzo1 != rtzo}`,
'v-vw': '1920',
'v-vh': '1080',
'v-loc': 'https://dossieretu.unistra.fr/mdw/#!notesView',
'v-wn': `mwd-107969-${Math.random()}`,
}
const headers = {
'Cookie': `JSESSIONID=${routedJSESSIONID}; ROUTEID=.${routedJSESSIONID.split('.')[1]}`,
'sec-ch-ua-platform': '"Windows"',
'sec-ch-ua': '"Google Chrome";v="113", "Chromium";v="113", "Not=A?Brand";v="24"',
'sec-ch-ua-mobile': '?0',
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5666.197 Safari/537.36',
'Accept': '*/*',
'Accept-Language': 'en-US,en;q=0.5',
'Accept-Encoding': 'gzip, deflate, br',
'Referer': 'https://dossieretu.unistra.fr/mdw/',
'Origin': 'https://dossieretu.unistra.fr',
'Connection': 'keep-alive',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
};
fetch(`https://dossieretu.unistra.fr/mdw/?v-${date.getTime()}`, {
method: 'POST',
headers: headers,
body: new URLSearchParams(body),
})
.then(res => {
if (res.status != 200) {
reject(res);
return;
}
return res;
})
.then(res => {
processChunkedResponse(res)
.then(body => {
const k = catchVaadinKey(JSON.parse(body));
resolve(k);
});
})
.catch(reject);
}, routedJSESSIONID);
}
function getRoutedJSESSIONID() {
return new Promise((resolve, reject) => {
const headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'en-US,en;q=0.9',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Pragma': 'no-cache',
'Sec-Ch-Ua': '"Not=A?Brand";v="99", "Chromium";v="118"',
'Sec-Ch-Ua-Mobile': '?0',
'Sec-Ch-Ua-Platform': '"Linux"',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'none',
'Sec-Fetch-User': '?1',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'node',
};
fetch('https://dossieretu.unistra.fr/mdw', {
headers: headers,
redirect: 'manual'
})
.then(res => {
if (res.status != 302) {
reject(res);
return;
}
parseJSESSIONIDCookie(res.headers.get('set-cookie'))
.then(cookie => {
resolve(cookie);
})
.catch(reject);
});
});
}
function getText(html) {
const htmlNode = htmlparser.parse(html);
return htmlNode.innerText;
}
function getNotesV2(vaadinKey, gradesArray) {
return new Promise((resolve, reject) => {
const headers = {
'Accept': '*/*',
'Accept-Language': 'en-US,en;q=0.9',
'Connection': 'keep-alive',
'Content-Type': 'application/json; charset=UTF-8',
'Cookie': `JSESSIONID=${routedJSESSIONID}; ROUTEID=.${routedJSESSIONID.split('.')[1]}`,
'Origin': 'https://dossieretu.unistra.fr',
'Referer': 'https://dossieretu.unistra.fr/mdw/',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
'sec-ch-ua': '"Not=A?Brand";v="99", "Chromium";v="118"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Linux"'
};
const body = {
'csrfToken': vaadinKey,
'rpc': [["60","v","v",["positionx",["i",100]]],["60","v","v",["positiony",["i",23]]],["67","v","v",["pagelength",["i",19]]],["143","v","v",["positionx",["i",51]]],["143","v","v",["positiony",["i",386]]],["67","v","v",["firstToBeRendered",["i",0]]],["67","v","v",["lastToBeRendered",["i",44]]],["67","v","v",["reqfirstrow",["i",15]]],["67","v","v",["reqrows",["i",30]]]],
'syncId': 1,
'clientId': 1
};
fetch('https://dossieretu.unistra.fr/mdw/UIDL/?v-uiId=0', {
method: 'POST',
headers: headers,
body: JSON.stringify(body),
})
.then(res => {
if (res.status != 200) {
reject();
return;
}
processChunkedResponse(res)
.then(body => {
const events = JSON.parse(body.substring(8))[0]['state'];
const contents = Object.values(events).filter(x => x.contentMode == 'HTML');
const newarr = contents.reduce(function(result, value, index, array) {
if (index % 4 === 0)
result.push(array.slice(index + 1, index + 3));
return result;
}, []);
const filtered = newarr.filter(x => x.length != 0);
const cleanOut = filtered.map(x => ({title: getText(x[0].text.split(/[;]+/).pop()), note: 'text' in x[1] ? getText(x[1].text) : "No grade"}));
resolve(gradesArray.concat(cleanOut));
})
})
.catch(reject);
});
}
function getNotes(vaadinKey) {
return new Promise((resolve, reject) => {
const headers = {
'Accept': '*/*',
'Accept-Language': 'en-US,en;q=0.9',
'Connection': 'keep-alive',
'Content-Type': 'application/json; charset=UTF-8',
'Cookie': `JSESSIONID=${routedJSESSIONID}; ROUTEID=.${routedJSESSIONID.split('.')[1]}`,
'Origin': 'https://dossieretu.unistra.fr',
'Referer': 'https://dossieretu.unistra.fr/mdw/',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
'sec-ch-ua': '"Not=A?Brand";v="99", "Chromium";v="118"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Linux"'
};
const body = {
'csrfToken': vaadinKey,
'rpc': [["0","com.vaadin.shared.ui.ui.UIServerRpc","resize",[931,1003,1003,931]],["55","com.vaadin.shared.ui.button.ButtonServerRpc","click",[{"altKey":false,"button":"LEFT","clientX":789,"clientY":328,"ctrlKey":false,"metaKey":false,"relativeX":301,"relativeY":17,"shiftKey":false,"type":1}]]],
'syncId': 0,
'clientId': 0,
'wsver': '7.7.35',
}
fetch('https://dossieretu.unistra.fr/mdw/UIDL/?v-uiId=0', {
method: 'POST',
headers: headers,
body: JSON.stringify(body),
})
.then(res => {
if (res.status != 200) {
reject();
return;
}
processChunkedResponse(res)
.then(body => {
const events = JSON.parse(body.substring(8))[0]['state'];
const contents = Object.values(events).filter(x => x.contentMode == 'HTML');
const newarr = contents.reduce(function(result, value, index, array) {
if (index % 4 === 0)
result.push(array.slice(index + 2, index + 4));
return result;
}, []);
const filtered = newarr.filter(x => x.length != 0);
const cleanOut = filtered.map(x => ({title: getText(x[0].text.split(/[;]+/).pop()), note: 'text' in x[1] ? getText(x[1].text) : "No grade"}));
const gradesArray = cleanOut.slice(2);
getNotesV2(vaadinKey, gradesArray)
.then(resolve)
.catch(reject);
});
})
.catch(reject);
});
}
function getAllGrades(gradesArray) {
var graded = gradesArray.filter(x => x.note != 'No grade');
while (graded.length > 0) {
const outJSON = {
"username": "estErn",
"embeds": graded.slice(0, 10).map(x => ({title: x.title, description: x.note})),
};
fetch(webhookURL, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(outJSON),
})
.then(res => {
if (res.status != 204) throw "Failed to send message";
})
.catch(console.log);
graded = graded.slice(10);
}
}
function getNewGrades(oldGradesArray, gradesArray) {
var graded = gradesArray.filter((x, i) => x.note != oldGradesArray[i].note);
console.log("GOT NEW GRADES", graded);
while (graded.length > 0) {
const outJSON = {
"username": "estErn",
"embeds": graded.slice(0, 10).map(x => ({title: x.title, description: x.note})),
};
fetch(webhookURL, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(outJSON),
})
.then(res => {
if (res.status != 204) throw "Failed to send message";
})
.catch(console.log);
graded = graded.slice(10);
}
}
async function app() {
try {
const oldGradesData = fs.readFileSync('data.json');
const oldGradesArray = JSON.parse(oldGradesData);
routedJSESSIONID = await getRoutedJSESSIONID();
console.log('GOT ROUTEDJSESSIONID', routedJSESSIONID);
routedJSESSIONID = await getLoginPage();
const key = await getSecurityKey(routedJSESSIONID);
const gradesArray = await getNotes(key);
fs.writeFileSync('data.json', JSON.stringify(gradesArray, null, 4));
if (newGrades) {
getNewGrades(oldGradesArray, gradesArray);
} else {
getAllGrades(gradesArray);
}
await logout();
} catch(e) {
console.log("GOT AN ERROR SOMEWHERE", e);
await logout();
return;
}
}
app();
Please register or sign in to comment