TypingClub layout switcher

Auto-switch keyboard layouts on TypingClub website

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==UserScript==
// @name         TypingClub layout switcher
// @namespace    Winand
// @version      25.0
// @description  Auto-switch keyboard layouts on TypingClub website
// @homepageURL  https://github.com/Winand/tc_layoutswitcher
// @author       Winand
// @license      MIT
// @match        https://www.typingclub.com/*
// @match        https://www.edclub.com/sportal/*
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-start
// ==/UserScript==


(function() {
    'use strict';

    const url_api = window.location.origin + "/api/v1.1/";
    const url_student = url_api + "student/";
    const url_tokens = window.location.origin + "/auth/refresh_tokens/";
    const url_program = url_api + "program2/"; // e.g. https://www.edclub.com/api/v1.1/program2/3/?edclub=true&_=1239959050370
    const url_program_page = (program_id) => `${window.location.origin}/sportal/program-${program_id}.game`; //https://stackoverflow.com/a/75611091
    // in case there's no saved layout and current program doesn't define one either
    const default_layout = "en,british-pc";

    var token; // authorization token is needed to change layout
    var student_id; // current user numeric id
    var program_id; // current program numeric id
    var program_kbd; // keyboard layout defined in current program
    var keyboard; // current layout
    var keyboard_pending; // layout is being set

    (function(open, send) {
        // https://stackoverflow.com/a/56499250
        XMLHttpRequest.prototype.open = function(method, url) {
            this._url = window.location.origin + url
            this.addEventListener("load", function() {
                var url = this.responseURL;
                if(url == url_tokens && this.status == 200) {
                    if(this.responseText) {
                        const resp = JSON.parse(this.responseText)[0];
                        token = resp.token;
                        console.log("TOKEN REFRESHED", token);
                    } else console.log("TOKEN NOT REFRESHED");
                } else if(url.startsWith(url_program) && !url.includes("/game/") && this.status == 200) {
                    const resp = JSON.parse(this.responseText);
                    program_id = resp.id;
                    console.log("LESSON PLANS LOADED FOR PROGRAM", program_id);
                    program_kbd = resp.keyboard == null ? default_layout : resp.keyboard;
                    var target_kbd = GM_getValue("lang." + program_id);
                    if(target_kbd == undefined) {
                        // set program's default layout
                        GM_setValue("lang." + program_id, program_kbd);
                        target_kbd = program_kbd;
                    }
                    console.log("PROGRAM KBD", program_kbd, "CURRENT", keyboard, "TARGET", target_kbd);
                    if(target_kbd !== keyboard) {
                        console.log("SWITCH TO", target_kbd, "TOKEN", token);

                        fetch(url_student + student_id + "/", {
                            method: 'POST',
                            body: '{"keyboard":"' + target_kbd + '"}',
                            headers: {
                                'authorization': 'Token ' + token,
                                'Content-type': 'application/json',
                                'x-http-method-override': 'PATCH'
                            }
                        }).then(response => {
                            if(response.status == 202) {
                                //https://stackoverflow.com/q/1226714#comment60232404_1226718
                                window.location = url_program_page(program_id)
                            } else console.log("SWITCH FAILED WITH STATUS", response.status);
                        });
                    }
                } else if(url.startsWith(url_student + "me/") && this.status == 200) {
                    const resp = JSON.parse(this.responseText);
                    student_id = resp.id;
                    keyboard = resp.keyboard;
                } else if(url == url_student + student_id + "/" && keyboard_pending !== undefined) {
                    if(this.status == 202) {
                        keyboard = keyboard_pending;
                        GM_setValue("lang." + program_id, keyboard);
                        console.log("MANUAL SWITCH TO", keyboard, "FOR PROGRAM", program_id);
                    } else console.log("MANUAL SWITCH FROM", keyboard, "TO", keyboard_pending, "FOR PROGRAM", program_id, "FAILED");
                }
            }, false);
            open.apply(this, arguments);
        };

        XMLHttpRequest.prototype.send = function(body) {
            if(this._url == url_student + student_id + "/") {
                // FIXME: 'keyboard' may be undefined if other property is being changed
                keyboard_pending = JSON.parse(body).keyboard || keyboard_pending;
            }
            send.apply(this, arguments);
        };

        if("prototype" in send) {
            console.log("LOADED TOO LATE, TRYING TO REFRESH TOKEN");
            var poll_timer = setInterval(() => {
                if(!token) {
                    console.log("RESTART...");
                    start(); // TypingClub entry point
                } else clearTimeout(poll_timer);
            }, 1000);
        };
    })(XMLHttpRequest.prototype.open, XMLHttpRequest.prototype.send);
})();