3rdUnknown / Cursed Knowledge (9¾)

// ==UserScript==
// @name         Cursed Knowledge (9¾)
// @namespace    off2class
// @version      14.41
// @description  «I work to support my hobby. So If you asked me which I’d choose, my job or my hobby, my hobby takes priority.»
// @author       3rdUnknown
// @homepageURL  https://openuserjs.org/scripts/3rdUnknown/Cursed_Knowledge_(9%C2%BE)
// @copyright    2021
// @license      MIT
// @match        https://*.off2class.com/student/*
// @match        https://*.off2class.com/student
// @require      https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.3.1/chart.min.js
// @require      https://ics-ikeda.github.io/shuffle-text/build/shuffle-text.js
// @grant        GM_getResourceText
// @grant        GM_addStyle
// @run-at       document-idle
// @updateURL    https://openuserjs.org/meta/3rdUnknown/Cursed_Knowledge_(9¾).meta.js
// ==/UserScript==

(function() {
    'use strict';

    //“Did you know? If a colony of ants gets rid of the laziest one, another one will just start being lazy instead?”

    if ((window.location.pathname.indexOf('student/lesson') !== -1) || (window.location.pathname.indexOf('student/placement') !== -1)) {
        enableCopy();
    } else if (window.location.pathname.indexOf('student/homework') !== -1) {
        let lessonInfo = document.querySelector(".mt-2");
        let lessonInfoContainer = createLessonInfo(lessonInfo);

        enableCopy();

        const created = o2c.page.homework.created_at;
        const updated = o2c.page.homework.updated_at;
        const lesson = o2c.page.homework.lesson_id;

        const dueDate = o2c.page.studentHomework.due_date;
        const createdAt = o2c.page.studentHomework.created_at;
        const submitted = o2c.page.studentHomework.latest_student_answer.answer.submitted;
        const submittedAt = o2c.page.studentHomework.latest_student_answer.answer.submitted_at;

        addElement('gif','https://7355608.fun/images/cat.gif', lessonInfoContainer);
        addElement('title', `Lesson ID: ${lesson}`, lessonInfoContainer);
        addElement('data', `Test was created: ${created}`, lessonInfoContainer);
        addElement('data', `Test was updated: ${updated}`, lessonInfoContainer);
        addElement('data', `Homework was assigned: ${createdAt}`, lessonInfoContainer);
        addElement('data', `Due date: ${dueDate}`, lessonInfoContainer);
        addElement('title', diffDate(dueDate, (new Date()), 'Test will be closed after: '), lessonInfoContainer);
        if (submitted) {
            addElement('title', diffDate(submittedAt, (new Date(createdAt)), 'Test was done after: '), lessonInfoContainer);
        }
    } else {
        // FixMe: chart size dirty hack
        GM_addStyle ( `
canvas {
  width:900px !important;
  height:600px !important;
}

.pt-4 {
    padding-top: 3.5rem!important;
}

#student-dashboard .homework-table {
    background-image: url(https://7355608.fun/images/board2.png)!important;
}
` );

        let pwndLogo = 'https://7355608.fun/images/glitch_logo.gif';
        document.querySelector('#site_logo').src=pwndLogo;

        let pwndTeacher = `https://7355608.fun/images/teacher${Math.floor(Math.random() * 5) + 1}.png`;
        document.querySelector('.teacher').src=pwndTeacher;

        // Shuffle Text
        window.addEventListener('load', init);
        function init() {
            var effectList = [];
            var elementList1 = [document.querySelector("#navbarSupportedContent span")];
            var elementList2 = [];
            var elementList = elementList1.concat(elementList2);

            for (var i = 0; i < elementList.length; i++) {

                var element = elementList[i];
                element.dataset.index = i;

                effectList[i] = new ShuffleText(element);
                element.addEventListener('mouseenter', function () {
                    effectList[+this.dataset.index].start();
                });

                element.addEventListener('mouseleave', function () {
                    effectList[+this.dataset.index].start();
                });

                effectList[i].start();
            }
        }

        let colors = [{
            main: 'rgb(255, 99, 132)',
            light: 'rgb(255, 99, 132, 0.2)'
        },{
            main: 'rgb(75, 192, 192)',
            light: 'rgb(75, 192, 192, 0.2)'
        },{
            main: 'rgb(255, 205, 86)',
            light: 'rgb(255, 205, 86, 0.2)'
        }];

        const x = [];
        Object.entries(o2c.page.completedTests.data).forEach(([key, value]) => {
            console.log(`${key}: ${value.id}`)
            console.log(`${key}: ${value.assigned_on}`)
            console.log(`${key}: ${value.isCompleted}`)

            const xxx = fetch(`https://${window.location.hostname}/student/placement/${value.id}/result`)
            .then(function(response) {
                // When the page is loaded convert it to text
                return response.text()
            })
            .then(function(html) {
                // Initialize the DOM parser
                let parser = new DOMParser();

                // Parse the text
                let doc = parser.parseFromString(html, "text/html");

                // You can now even select part of that html as you would in the regular DOM
                let parsed = doc.querySelectorAll('script')[4];
                parsed = parsed.innerText;
                let parsedStats = parsed.split('o2c.page')[4];
                parsedStats = parsedStats.split('.stats = ')[1];
                parsedStats = parsedStats.slice(0, -10);
                parsedStats = JSON.parse(parsedStats);

                console.log(parsedStats["grading-1"]);

                let datasetTemplate = {
                    label: '',
                    data: [],
                    fill: true,
                    backgroundColor: colors[key].light,
                    borderColor: colors[key].main,
                    pointBackgroundColor: colors[key].main,
                    pointBorderColor: '#fff',
                    pointHoverBackgroundColor: '#fff',
                    pointHoverBorderColor: colors[key].main,
                };

                let score = '';
                Object.entries(parsedStats["grading-1"]).forEach(([key, value]) => {
                    console.log(`${key}: ${value.correct}`);
                    score = `${score}  ${grading('grade', key)}:${grading('score', value.correct)}`;
                    datasetTemplate.data.push(parseInt(value.correct));
                });

                let parsedLevel = parsed.split('o2c.page')[6];
                parsedLevel = parsedLevel.split('.proficiency = ')[1];
                parsedLevel = parsedLevel.slice(0, -10);
                parsedLevel = JSON.parse(parsedLevel);

                let label = `${parsedLevel.name} ${parsedLevel.shortName} (${value.assigned_on})`;
                console.log(`${label}`);

                // Updating "PLACEMENT TEST RESULTS" dashboard
                let placementTestData = `${value.assigned_on.split(" ")[1]} ${value.assigned_on.split(" ")[0]}th`;
                let cells = document.querySelectorAll("#completedTests table td");
                cells.forEach(function(item, index, arr) {
                    if (item.innerText == placementTestData) {
                        arr[index-1].innerHTML = `📚 ${parsedLevel.shortName}:  ${score}`;
                    }
                });
                //

                datasetTemplate.label = label;
                return datasetTemplate
            })
            .catch(function(err) {
                console.log('Failed to fetch page: ', err);
            });
            x.push(xxx)
        });

        Promise.all(x).then(datasets => {
            datasets.reverse();
            console.log(datasets);

            // FixMe: add optional chart by click
            // block = document.querySelector("div.teacher-image-box");
            // container = createChart(block);
            var popCanvas = document.getElementById("popChart");
            var barChart = new Chart(popCanvas, {
                type: 'bar',
                data: {
                    labels: ["A1", "A2", "B1", "B2", "C1"],
                    datasets: datasets,
                },
                options: {
                    elements: {
                        line: {
                            borderWidth: 3,
                        }
                    }
                }
            });
        });

        let block = document.querySelector("#navbarSupportedContent");
        let container = createDiv(block);
        startTime();

        //fixed size of blocks
        let div = document.getElementsByClassName('col-md-2 col-lg-2')[0];
        div.classList.remove("col-md-2", "col-lg-2");
        div.classList.add("col-md-3", "col-lg-3");

        div = document.getElementsByClassName('col-12 col-sm-12 col-md-10 col-lg-10')[0];
        div.classList.remove("col-12", "col-sm-12", "col-md-10", "col-lg-10");
        div.classList.add("col-9", "col-sm-9", "col-md-9", "col-lg-9");
    }
})();

function createDiv(container) {
    let newDiv = document.createElement("div");
    newDiv.id = "clock";
    insertAfter(newDiv, container);
    return newDiv;
}

function createChart(container) {
    let newCanvas = document.createElement("canvas");
    newCanvas.id = "popChart";
    insertAfter(newCanvas, container);
    return newCanvas;
}

function createLessonInfo(container) {
    var newDiv = document.createElement("div");
    newDiv.id = "knowledge";
    newDiv.setAttribute("style", "position: absolute; top: 25px; left: 200px; width: max-content;");

    insertAfter(newDiv, container);
    return newDiv;
}

function addElement(key, value, container) {
    let newEl;
    if (key === 'title') {
        newEl = document.createElement("p");
        newEl.className = "homework-header";
        newEl.setAttribute("style", "margin-bottom: 0.5em;");
        newEl.innerHTML = `<br><b> ${value} </b>`;
    } else if (key === 'gif') {
        newEl = document.createElement("img");
        newEl.src = value;
    } else {
        newEl = document.createElement("p");
        newEl.setAttribute("style", "margin-bottom: 0.5em;");
        newEl.innerHTML = `${value}`;
    }
    container.appendChild(newEl);
}

function insertAfter(newNode, existingNode) {
    existingNode.parentNode.insertBefore(newNode, existingNode.nextSibling);
}

function enableCopy() {
    let allowText = function(e){
        e.stopImmediatePropagation();
        return true;
    };
    document.addEventListener('copy', allowText, true);
    document.addEventListener('cut', allowText, true);
}

function startTime() {
    var h1,h2,m1,m2;
    var today = new Date();
    var h = today.getHours();
    var m = today.getMinutes();
    h1 = Math.trunc(h/10);
    h2 = h%10;
    m1 = Math.trunc(m/10);
    m2 = m%10;
    document.getElementById('clock').innerHTML =
        '<img src="https://7355608.fun/images/'+h1+'.gif" alt="'+h1+'" width="20%" border="0">'+
        '<img src="https://7355608.fun/images/'+h2+'.gif" alt="'+h2+'" width="20%" border="0">'+
        ' : '+
        '<img src="https://7355608.fun/images/'+m1+'.gif" alt="'+m1+'" width="20%" border="0">'+
        '<img src="https://7355608.fun/images/'+m2+'.gif" alt="'+m2+'" width="20%" border="0">';
    var t = setTimeout(startTime, 10000);
}

function diffDate(dueDate, nowDate, text) {
    const dateNow = nowDate;
    const dateFuture = new Date(dueDate);

    if (dateNow > dateFuture) {
        return `${text} closed`
    }

    let diffInMilliSeconds = Math.abs(dateFuture - dateNow) / 1000;

    // calculate days
    const days = Math.floor(diffInMilliSeconds / 86400);
    diffInMilliSeconds -= days * 86400;
    console.log('Days', days);

    // calculate hours
    const hours = Math.floor(diffInMilliSeconds / 3600) % 24;
    diffInMilliSeconds -= hours * 3600;
    console.log('Hours', hours);

    // calculate minutes
    const minutes = Math.floor(diffInMilliSeconds / 60) % 60;
    diffInMilliSeconds -= minutes * 60;
    console.log('Minutes', minutes);

    return `${text} ${days} days ${hours} hours ${minutes} minutes`;
}

function grading(type, text) {
    console.log(`grading: type=${type} | text=${text}`);
    if (type == 'grade') {
        switch (text) {
            case '10':
                return '<b>A1</b>';
                break;
            case '20':
                return '<b>A2</b>';
                break;
            case '30':
                return '<b>B1</b>';
                break;
            case '50':
                return '<b>B2</b>';
                break;
            case '60':
                return '<b>C1</b>';
                break;
            default:
                return text
        }
    } else if (type == 'score') {
        if (text <= 10) {
            return `<b><font color="red">${text}</font></b>`
        } else if (text <= 15) {
            return `<b><font color="orange">${text}</font></b>`
        } else if (text > 15) {
            return `<b><font color="green">${text}</font></b>`
        } else {
            return text
        }
    }
}