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();