Skip to content
Snippets Groups Projects

estErn

  • Clone with SSH
  • Clone with HTTPS
  • Embed
  • Share
    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

    Edited
    ernest_notes.js 22.82 KiB
    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();
    0% or .
    You are about to add 0 people to the discussion. Proceed with caution.
    Finish editing this message first!
    Please register or to comment