Leetcode Contest Language reavealer

The primary aim of this script is to identify in what coding language the user has written the code. For v1, I only consider the first result.

Versão de: 04/12/2018. Veja: a última versão.

// ==UserScript==
// @name         Leetcode Contest Language reavealer
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  The primary aim of this script is to identify in what coding language the user has written the code. For v1, I only consider the first result.
// @author       Rupesh Dabbir
// @license      MIT
// @namespace    https://github.com/rupeshdabbir
// @include      https://leetcode.com/contest/*/ranking/*
// @include      https://leetcode.com/contest/*
// @match        https://leetcode.com/contest/weekly-contest-*/ranking*/*
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// ==/UserScript==
(function() {

    let firstLoad = false;

    const pageURLCheckTimer = setInterval(
        function() {
            if (this.lastPathStr !== location.pathname ||
                this.lastQueryStr !== location.search ||
                this.lastPathStr === null ||
                this.lastQueryStr === null
            ) {
                this.lastPathStr = location.pathname;
                this.lastQueryStr = location.search;
                gmMain();
            }
        }, 222
    );

    function gmMain() {
        const isRanking = window.location.pathname.split('ranking').length > 1;
        if (isRanking && !firstLoad) {
            firstLoad = true;
            initMagic();
        }
    }

    function initMagic() {
        /* Promisified Helper method that waits for a particular DOM elem to be loaded */
        const waitFor = (...selectors) => new Promise(resolve => {
            const delay = 500
            const f = () => {
                const elements = selectors.map(selector => document.querySelector(selector))
                if (elements.every(element => element != null)) {
                    resolve(elements)
                } else {
                    setTimeout(f, delay)
                }
            }
            f()
        })

        /* When Pagination has been clicked */
        waitFor('.pagination').then(([table]) => {
            $('.pagination').click(function() {
                $('.td-lang-data').remove();
                $('.td-my-data').remove();

                $('.rupesh-loader').css('display', 'block');
                setTimeout(() => { // Time for window.location.href to update.
                    doAjax();
                }, 1000);
            });
        });

        /*
         * When Table has been loaded, add the language barrier! ;-)
         */

        waitFor('thead > tr').then(([table]) => {
            $('thead > tr').append('<th class="language-bar"> Language</th>');
            addLoader();
            doAjax();
        });

        /*
         * Helper to add Spinner on page.
         * Note: Once this component has been initialized, it'll stay on the page with
         * CSS display being 'none'. Thus, can be re-used without further DOM manipulation.
         * TODO: Refactor and move all the inline styling to CSS class.
         */

        function addLoader() {
            $('tbody > tr').each(function(i) {
                if (i > 0) {
                    const img = document.createElement('img');
                    img.src = "https://image.ibb.co/bLqPSf/Facebook-1s-200px.gif";
                    img.width = "50";
                    img.height = "50";
                    img.style = "margin-left: 10px";
                    img.classList.add("rupesh-loader");
                    $('tbody > tr')[i].append(img);
                }
            });
        }

        function getLastUrlPath() {
            let locationLastPart = window.location.pathname
            if (locationLastPart.substring(locationLastPart.length - 1) == "/") {
                locationLastPart = locationLastPart.substring(0, locationLastPart.length - 1);
            }
            locationLastPart = locationLastPart.substr(locationLastPart.lastIndexOf('/') + 1);

            if (isNaN(+locationLastPart)) return "1";

            return +locationLastPart;
        }

        /*
         * @param AJAX with url.
         * @returns JSON that contains ID's that can be further fetched to find Lang metadata.
         */
        function doAjax() {
            const contextSuffix = window.location.href.split('/').filter((val) => val.indexOf('weekly-contest') > -1)[0];
            const page = getLastUrlPath();
            const url = `https://leetcode.com/contest/api/ranking/${contextSuffix}/?pagination=${page}`;
            // console.log("You are on page:", page);
            // console.info("The Url before making req:", url);

            fetch(url)
                .then((resp) => resp.json())
                .then(function(data) {
                    //console.log("Ranking API data return", data);
                    fetchSubmissionIDS(data);
                });
        }

        function fetchSubmissionIDS(data) {
            const ids = [];

            data.submissions.forEach((submission) => {
                const one = Object.values(submission)[0];
                const id = one.submission_id;
                ids.push(id);
            });
            //console.log("Submission ID's are: ", ids);
            fetchLanguageInfo(ids);
        }

        function doSyncCall(id) {
            const url = 'https://leetcode.com/api/submissions/';

            return new Promise((resolve, reject) => {
                fetch(url + id)
                    .then(resp => resp.json())
                    .then((data) => resolve(data.lang))
                    .catch((err) => resolve('N/A')); // TODO: Maybe retry promise after xx ms?
                                                    // Currently we are resolving the promise && proceeding.
            });
        }

        /*
         * At this point, this function does the following:
         * 1. It takes an array of ID's (leetcode submission ID)
         * 2. Fetch respective metadata from the leetcode submission API.
         * 3. Delegate the results to anther fxn to append to the DOM.
         *
         * Note: This is a promisified function that heavily relies on fetching data in sequence.
         *       This is because the appropriate ID needs to be shown the correct language choice.
         * TODO: Further optimization could be fetch everything async and maintain a running ID,
         *       And append their respective results to the DOM.
         */

        function fetchLanguageInfo(ids) {
            const languageArr = [];

            ids.reduce((promise, id) => {
                return promise.then((result) => {
                    return doSyncCall(id).then(val => {
                        languageArr.push(val);
                    });
                });
            }, Promise.resolve()).then(() => { // TODDO: Optimize with promise.all/race
                //console.log("Final Language Array: ", languageArr);
                appendLangToDOM(languageArr);
            });
        }

        /*
         * This method is currently deprecated. Thought of making this a helper method for something else.
         */

        function makeCallAndFetchLanguage(id, arr) {
            const url = `https://leetcode.com/api/submissions/${id}`;

            fetch(url)
                .then((resp) => resp.json())
                .then(function(data) {
                    //console.log("Language Results", data);
                    // Data is a JSON.
                    arr.push(Promise.resolve(data.lang));
                });
        }

        /*
         * It does exactly what it sounds like it does ;-)
         */

        function appendLangToDOM(arr) {
            let j = 0;

            // Hide the spinner.
            $('.rupesh-loader').css('display', 'none');

            // Append my submission.
            const myTd = document.createElement('td');
            myTd.innerHTML = "? (hehe)"; // TODO: Make it dynamic.
            myTd.classList.add('td-my-data');
            $('.success').append(myTd);

            $('tbody > tr').each(function(i) {
                if (i > 0) {
                    const td = document.createElement('td');
                    //console.log("array JAKE", arr[j]);
                    td.innerHTML = arr[j];
                    td.classList.add('td-lang-data');
                    $('tbody > tr')[i].append(td);
                    j++;
                }
            });
        }
    }


})();