Greasy Fork is available in English.

Pronote Dark Mode

Un thème sombre pour Pronote avec quelques petites améliorations

// ==UserScript==
// @name         Pronote Dark Mode
// @namespace    http://tampermonkey.net/
// @version      0.3.6
// @description  Un thème sombre pour Pronote avec quelques petites améliorations
// @author       Dood Corp.
// @match        https://*/pronote/*.html
// @match        https://*.pronote.toutatice.fr/pronote/eleve.html
// @run-at       document-idle
// @grant        GM_setValue
// @grant        GM_getValue
// @icon         https://cdn.drawception.com/drawings/1005959/Kxtt6kLMgv.png
// ==/UserScript==

if (localStorage.getItem("badConnection") == null) {
    localStorage.setItem("badConnection", false);
}

var tryLimit = 50 //Change this line to the number of attempts before stopping the homepage/loginpage detecting. Lower is better for good and reliable connections (you can see the number of tries before load in the console). A higher one will spam the console with errors if you fail your login.
var tryTiming = 100 //Change this line to the amount of time between two attempts. A low number will spam the console with errors (if you use a low one and the page is not detected, up the tryLimit), a high one will cause a delay between the load of the page and the detectiong (it's in ms).

function isInViewport(element) { //Check if element is visible
    var rect = element.getBoundingClientRect();
    return (
        rect.top >= 0 &&
        rect.left >= 0 &&
        rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
        rect.right <= (window.innerWidth || document.documentElement.clientWidth)
    );
}




function checkLoadHome() { //Check if home page is visible (loaded)
        var tries = 0
        var counter = setInterval( function() {
        tries++ //add one to the number of tries
        console.log("Home page not detected at try " + (tries - 1) + ", retrying..."); //had to put it here since the if (isInViewport [...]) causes an error
        if (tries >= tryLimit) { //stop the attempts after a number of failed tries
            clearInterval(counter); //stop the counter
            console.log("failed to load homepage, retrying stopped")
        } else {
            if (isInViewport(document.querySelector("#GInterface\\.Instances\\[2\\]"))) { //detect if homepage is visible
                console.log("home page loaded successfully after " + (tries * tryTiming) / 1000 + " s (try " + tries + ")");
                launchJS();
                clearInterval(counter); //stop both counters
                clearInterval(counterBis);
            }
        }
    }, tryTiming);
}

checkLoadHome(); //since checkLoadHome is a defined function (unlike the login one), this is needed to fire it at launch

var loginTries = 0
var counterBis = setInterval(function () {
    loginTries++
    console.log("Login page not detected at try " + (loginTries - 1) + " , retrying ...");
    if (loginTries >= tryLimit) {
        console.log("failed to detect login page, retrying stopped");
        clearInterval(counterBis);
    } else {
        if (isInViewport(document.querySelector("#id_42 > div.InlineBlock.Texte10"))) { //detect if homepage is fully loaded (when using autologin)
            console.log("Login page loaded successfully after " + (loginTries * tryTiming) / 1000 + " s (try " + (loginTries + 1) + ")")
            clearInterval(counter);
            clearInterval(counterBis);
            document.querySelector("#id_39").addEventListener('click', function a() { clearInterval(counter); checkLoadHome()}, false); //fire checkLoadHome at click on the "connect" button
            document.querySelector("#id_50").addEventListener('keyup', function a() { if (event.keyCode === 13) { clearInterval(counter); checkLoadHome() }; }, false); //fire checkLoadHome when hitting Enter
        }
    }
}, tryTiming);

function launchJS() {
    document.getElementById('GInterface.Instances[0].Instances[3]_Combo0').addEventListener('click', function a() { checkLoadHome(); }, false);
    if (document.getElementById('customBtn') == null) {
        var customInput = document.createElement("DIV"); //create the main custom div
        customInput.id = "customInput"; // give an id to the div
        customInput.style = "display: none; position: relative;top: -1.1rem; left: 38rem;"; // hide the div
        customInput.innerHTML = '<input type="text" id="url" placeholder="Entrez une URL" style="border-radius: 4px; margin-left: 0.5rem;"><input type="text" id="name" placeholder="Entrez votre nom" style="border-radius: 4px; margin-left: 0.5rem;"><input type="checkbox" id="keepCheck"style="margin-left: 0.5rem;"><input type="checkbox" id="badConnection" style="margin-left: 0.5rem;">';
        //<input type="text" id="backgroundURL" placeholder ="background URL" style="border-radius: 4px; margin-left: 0.5rem"> (this line is for a future update)
        document.getElementById("GInterface.Instances[0].Instances[0]").firstElementChild.appendChild(customInput); //makes the div a children of the navbar
        document.getElementById('keepCheck').addEventListener('click', function a() { store("keep", document.getElementById('keepCheck').checked) }, false); //changes if the name/url is kept
        document.getElementById('badConnection').addEventListener('click', function a() { store("badConnection", document.getElementById('badConnection').checked); }, false);
        //document.getElementById('backgroundURL').onchange = changeBackground
        document.getElementById('url').addEventListener('change', function a() { store("profilePic", document.getElementById("url").value); changeImage(); }, false); //changes image on url input
        document.getElementById('name').addEventListener('change', function b() { store("name", document.getElementById("name").value); changeName(); }, false);
        var mainDiv = document.getElementsByClassName("ibe_util").item(0); //get the buttons div
        var customButton = document.createElement("DIV"); //create div
        customButton.class = "ibe_iconebtn ibe_actif"; //set class, title etc
        customButton.tabindex = "0";
        customButton.title = "Cacher les options de customisation";
        customButton.innerHTML = '<button id="customBtn" style="height: 22px; width: 22px; margin-left: 0.5rem; cursor: pointer; outline: none; background-color: #232325; border: none;"><img src="https://cdn.onlinewebfonts.com/svg/img_142231.png" style="width: 22px; height: 22px; transform: translateX(-5px) translateY(-3px);"></button>'; //add the button
        var customBtn = document.getElementById('customBtn');
        mainDiv.appendChild(customButton); //append div to buttons
        document.getElementById('customBtn').onclick = showCustom; //detect click on the button, an attribute in the button won't work cauz the button search the function in the website
    }
    function store(name, value) {
        localStorage.setItem(name, value);
        console.log('"' + value + '" successfully stored in "' + name + '"');
    }

    var strTheme = document.querySelector("#div").className;
    var theme = strTheme.substr(33);

    function changeImage() {
        var urls = localStorage.getItem("profilePic");
        document.getElementsByClassName("ibe_util_photo ibe_actif").item(0).firstElementChild.src = urls
    }

    changeNameStartup(); //set the image/name stored in localStorage at the launch of the page

    function changeNameStartup() {
        if (localStorage.getItem("keep") == "true") { //only change the image/name if the "keep" var is set to true
            document.getElementsByClassName("ibe_util_texte ibe_actif").item(0).innerText = localStorage.getItem("name");
            document.getElementsByClassName("ibe_util_photo ibe_actif").item(0).firstElementChild.src = localStorage.getItem("profilePic");
            document.getElementById("keepCheck").checked = true; //tick the checkbox
        }
        if (localStorage.getItem("badConnection") == "true") {
            document.getElementById("badConnection").checked = true;
        }
    }

    function changeName() {
        var names = localStorage.getItem("name");
        document.getElementsByClassName("ibe_util_texte ibe_actif").item(0).innerText = names
    }

    /*function changeBackground() {
          var bckgrnd = document.getElementById("backgroundURL").value
          localStorage.setItem("bckgrnd", bckgrnd);
     }*/ //this will maybe be added in next update

    function showCustom() { //show the input div
        var c = document.getElementById("customInput")
        if (c.style.display === "none") {
            c.style.display = "block";
            console.log("Customisation options displayed");
        } else {
            c.style.display = "none";
            console.log("Customisation options hidden");
        }
    }

    //So I tried to use IndexedDB but I'm too dumb to do it, the following code doesn't work (the JS is loading indefinitly)... If someone knows how to use it, post a comment or email me at whitebowoo@gmail.com

    /*var openRequest = indexedDB.open("store", 1);
    console.log(openRequest);
    var db
    openRequest.onupdateneeded = function(event) {
        db = event.target.result;
        console.log(db);
        var objectStore
        if (!db.objectStoreNames.contains('note')) {
            objectStore = db.createObjectStore('note', { keyPath: 'id' });
            objectStore.createIndex('content', 'content', { unique: true });
        }
    }
    function storeNotepadDB() {
        var request = db.transaction(['note'], 'readwrite')
            .objectStore('note')
            .add({ id: 1, content: 'yeet' });

        request.onsuccess = function (event) {
            console.log('The data has been written successfully');
        };

        request.onerror = function (event) {
            console.log('The data has been written failed');
        }
        db = openRequest.result;
        var notes = document.getElementById("noteInput").value
        var transactions = indexedDB.open("store", 1).transaction('store', 'readwrite');
        var noteObject = transactions.objectStore("store");
        var note = {
            id: 'note',
            content: notes,
        };
        request = noteObject.add(note);
        request.onsuccess = function() {
            console.log('note succesfully stored !' + request.result);
        }
        request.onerror = function() {
            console.log("Error", request.error);
        }
    }

    storeNotepadDB();

    function addNotepad() {
        function autoGrow(element) {
            element.style.height = 'auto';
            element.style.height = (element.scrollHeight) + "px";
        }
        if (document.getElementById('notepad') == null) {
            var column = document.getElementById('GInterface.Instances[2]_colonne_0');
            var notepad = document.createElement("article");
            notepad.id = "notepad";
            notepad.class = "widget notepad collapsible"
            notepad.style = "heigth: auto;"
            notepad.innerHTML = "<button id='saveNote' style='margin-bottom: 5px;'>Save</button><textarea id='noteInput' style='width: 100%; resize: none; min-height: 40px; max-height: 500px; overflow: hidden;'></textarea>"
            column.appendChild(notepad);
            notepad.addEventListener('keyup', function a() { autoGrow(document.getElementById('noteInput')); }, false);
            document.getElementById('saveNote').addEventListener('click', function a() {storeNotepadDB();}, false);
        }
    }

    addNotepad();

    function read() {
        db = openRequest.result;
        var transactions = db.transaction('store', 'readwrite');
        var noteObject = transactions.objectStore("store");
        var request = noteObject.get("note");
        if (request.result) {
            console.log(request.result.content);
        } else {
            console.log("couldn't find data !");
        }
    }*/


    console.log("JS was successfully loaded");
}

var cssLoad = 0
function addGlobalStyle(css) { //Apply the CSS
    cssLoad++
    var head, style;
    head = document.getElementsByTagName('head')[0];
    if (!head) { return; }
    style = document.createElement('style');
    style.type = 'text/css';
    style.innerHTML = css;
    head.appendChild(style);
    console.log("-CSS line number " + cssLoad + " : " + css + " was successfully loaded")
}

localStorage.setItem("etatAffichageFooter_3", true); //Get rid of the useless footer
console.log("footer removed successfully");

//Good luck to read the css bro
if (localStorage.getItem("badConnection") == 'true') {
    addGlobalStyle('#div { background-image : linear-gradient(#4e4e4e, #212121) !important;}'); //change the loading & login background image
    addGlobalStyle('.dotty, .interface_affV_client {background-image : radial-gradient(circle, #4e4e4e 5%, transparent 10%), radial-gradient(circle, #6d6d6e 5%, transparent 10%); width:100%; height: 100%; user-select: text; background-color: #121212 !important;}'); //change main background
    console.log('mauvaise co');
} if (localStorage.getItem("badConnection") == 'false') {
    addGlobalStyle('#div { background-image : url("https://s3-eu-west-1.amazonaws.com/sales-i-wordpress/wp-content/uploads/2015/12/17112848/black-background.jpg") !important; }'); //change the loading & login background image
    console.log('bonne co');
    addGlobalStyle('.dotty, .interface_affV_client { background-image: url("https://wallpaperaccess.com/full/1811424.png"); background-size: auto; width:100%; height: 100%; background-color: #121212 !important; user-select: text; }'); //change main background
}

addGlobalStyle('.liste-nested > li > h4 {color: #a478d4 !important;}');
addGlobalStyle('h4, fieldset, td.AlignementMilieu, a > span:not(.InlineBlock), legend.Texte10.Legende, td.AlignementDroit.AlignementHaut { color: #fff !important; }');
addGlobalStyle('legend > div > div.jiehint.ie-ellipsis { background-color: #fff; }');
addGlobalStyle('::-webkit-scrollbar { display: none; } !important; }'); //get rid of the useless scrollbar
addGlobalStyle('.widget, fieldset, legend.Texte10.Legende, td.AlignementDroit.AlignementHaut, .AvecSelectionTexte.utilMess_visu_message.AvecMain > legend > div.Gras > div.jiehint.ie-ellipsis { background-color: #303030 !important; }');
addGlobalStyle('.widget.travailafaire h3, .widget.ressourcepedagogique h3 { background-color: #ebdbff; border-radius: 0.5rem 0.5rem 0.5rem 0.5rem; padding: 0.3rem !important; }');
addGlobalStyle('.widget .content-container > div[id^=id_] .liste-nested h5, .widget .content-container > div[id^=id_] .liste-groups h5, .widget .content-container > div[id^=id_] .liste-ressources h5, .widget .content-container > div[id^=id_] h4,  { color: #9ca0a0 !important; }');
addGlobalStyle('.widget, .AlignementDroit.PetitEspaceDroit, #id_114 > ul > li:nth-child(1) > ul > li:nth-child(2) > div > div.descriptif.done, .objetBandeauEntete_secondmenu .objetBandeauEntete_fullsize .precedenteConnexion  { color: #f0f0f0 !important; }');
addGlobalStyle('.widget.travailafaire .content-container > div[id^=id_] .liste-nested .sub-liste li .liste-docs a, .widget.travailafaire .content-container > div[id^=id_] .liste-groups .sub-liste li .liste-docs a, .widget.travailafaire .content-container > div[id^=id_] .liste-ressources .sub-liste li .liste-docs a { color: #ebdbff !important; }');
addGlobalStyle('.widget.ressourcepedagogique .content-container > div[id^=id_] li .file-name, .widget.ressourcepedagogique .content-container > div[id^=id_] li .file-contain.icon::before { color: #ebdbff !important; }');
addGlobalStyle('.as-input.actif.ie-ellipsis {color: #303030 !important; }');
addGlobalStyle('.as-button { background-color: #b5bbbb !important; }');
addGlobalStyle('.EspaceIndex #div > .interface_affV > div.interface_affV_client.no-tactile { overflow: auto !important;  }');
addGlobalStyle('.ibp-bloc-left, .host-france-container, .knowledge-container, .footer-toggler, .ibe_gauche, .Image_Logo_PronoteBarreHaut, .icon_uniF2C3 { display:none !important; }'); //get rid of the useless images
addGlobalStyle('.ObjetBandeauPied { transform: translateY(-5px) !important; }'); //set the footer a little bit higher because there is a gab between the main background and the end of the page
addGlobalStyle('.ObjetBandeauEspace, .item-menu_niveau0:hover, .item-menu_niveau0.focused-in, .item-menu_niveau0.item-selected { background-color: #232325 !important; color: #eef0f5 !important; }');
addGlobalStyle('.ibe_centre { position: fixed !important; }'); //set the name in the navbar fixed so it doesn't change place when showing the custom div
addGlobalStyle('.item-menu_niveau0, .label-submenu { color: #b7b7b7 !important;}');
addGlobalStyle('.objetBandeauEntete_secondmenu { background-color: #757575 !important;}');
addGlobalStyle('.objetBandeauEntete_secondmenu, .menu-principal_niveau1, .EtiquetteCours { background-color : #626469 !important }');
addGlobalStyle('.objetbandeauentete_global, .objetBandeauEntete_thirdmenu { background-color: #a2a0a0 !important;}');
addGlobalStyle('.item-menu_niveau1.selected, .ObjetMenuContexutel { background-color: #46484d !important}');
addGlobalStyle('::placeholder { color: #5d5e61 !important }');
addGlobalStyle('#id_137fond { background-color: #707070 !important; }');
addGlobalStyle('.widget .content-container > div[id^=id_] .liste-nested h5, .widget .content-container > div[id^=id_] .liste-groups h5, .widget .content-container > div[id^=id_] .liste-ressources h5, .underline { color:#a6b5b5 !important }');
addGlobalStyle('.Texte10:not(.c_7):not(.ieBoutonDefautSansImage):not(.Calendrier_Jour):not(.Calendrier_Mois):not(#breadcrumbBandeauPerso):not(.EspaceGauche.EspaceDroit) { color: #d2d2d2 !important }');
addGlobalStyle('.Fenetre_Cadre { background-color: #cccccc !important}');
addGlobalStyle('.Calendrier_Jour_Selection { background-color: #626262 !important}');
addGlobalStyle('.liste_contenu_cellule_contenu, .divCellule, .liste_gridTitre_cel { background-color: #5c5c5c; color: #cdcdd8 !important}');
addGlobalStyle('.MaClasseBordure, .CarteCompteZoneGenerique { border-radius: 0 4px 0 4px !important;}');
addGlobalStyle('#id_95 { user-select:none !important}');
addGlobalStyle('#conteneur-page > div > div.jspPane > div > div:nth-child(1), #conteneur-page > div > div.jspPane > div > div:nth-child(2), #conteneur-page > div > div.jspPane > div > div:nth-child(3), #conteneur-page > div > div.jspPane > div > div:nth-child(4), #conteneur-page > div > div.jspPane > div > div:nth-child(5) { background-color: #7f7f7f !important;}');
addGlobalStyle('.ObjetTimeline_classScrollPanel { border-left: 1px solid #1E1414 !important; border-right: 1px solid #1E1414; border-bottom: 1px solid #1E1414 !important; }');
//addGlobalStyle('.PetitEspaceHaut { border-left: 1px solid #1E1414 !important; border-top: 1px solid #1E1414 !important; border-right: 1px solid #1E1414 !important; }');
addGlobalStyle('#conteneur-page > div > div.jspPane > div > div:nth-child(1) > div.PetitEspaceHaut { border-top: 1px solid #1E1414 !important; }');
addGlobalStyle('#id_174 { background-color: #a2a0a0 !important }');
addGlobalStyle('.EtiquetteCours, .AlignementMilieu.Insecable { color: #f0f0f0 !important; }');
addGlobalStyle('.liste_zoneFils { border-top: 4px solid #C5C5C5 !important; border-bottom: 4px solid #C5C5C5 !important; border-left: 4px solid #C5C5C5 !important; border-right: 4px solid #C5C5C5 !important; backdrop-filter: blur(7px); }');