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