Raw Source
pinchanzo / Retro Enhanced: Pinchanzo Edition

// ==UserScript==
// @name         Retro Enhanced: Pinchanzo Edition
// @namespace    pinchanzo
// @version      0.1.1
// @description  Additional functionality for retro gaming related sites.
// @author       Miagui
// @match        *://retroachievements.org/*
// @license      MIT
// @updateURL    https://openuserjs.org/meta/pinchanzo/Retro_Enhanced_Pinchanzo_Edition.meta.js
// @downloadURL  https://openuserjs.org/install/pinchanzo/RAE
// @grant        GM_xmlhttpRequest
// @grant        GM_log
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_listValues
// @grant        GM_deleteValue
// @connect      archive.org
// @connect      the-eye.eu
// @connect      raw.githubusercontent.com
// @connect      sheets.googleapis.com
// @connect      emuparadise.me
// @require http://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js
// ==/UserScript==

(async function () {
    var pageWithParams = (location.pathname + location.search).substr(1);
    var page = location.pathname;
    var loggedUser = $(".login strong > a").text();
    var sectionName = window.location.hash.slice(1);

    // Configs
    var enableSpeedrun = await GM_getValue("enableSpeedrun", false);
    var enableRomSearch = await GM_getValue("enableRomSearch", true);
    var enableCustomBG = await GM_getValue("enableCustomBG", true);
    var enableGameplayVideo = await GM_getValue("enableGameplayVideo", true);
    var enableEmuparadise = await GM_getValue("enableEmuparadise", false);
    var playLaterList = await GM_getValue("playLaterGames", []);
    playLaterList = playLaterList.length != 0 ? JSON.parse(playLaterList) : [];

    var RAConsole = {
        GAMEBOY: "Game Boy",
        GBC: "Game Boy Color",
        GBA: "Game Boy Advance",
        NES: "NES",
        SNES: "SNES",
        N64: "Nintendo 64",
        NDS: "Nintendo DS",
        POKEMINI: "Pokemon Mini",
        VIRTUALBOY: "Virtual Boy",
        A2600: "Atari 2600",
        A7800: "Atari 7800",

        PCENGINE: "PC Engine",
        MASTERSYSTEM: "Master System",
        GG: "Game Gear",
        GENESIS: "Mega Drive",
        SEGA32X: "32X",
        SEGACD: "Sega CD",
        SATURN: "Saturn",
        DREAMCAST: "Dreamcast",
        PS1: "PlayStation",
        PSP: "PlayStation Portable",
        PANASONIC3DO: "3DO Interactive Multiplayer",
        NEOPOCKET: "Neo Geo Pocket",
        SG1000: "SG-1000",
        COLECO: "ColecoVision",
        MSX: "MSX",
        WONDERSWAN: "WonderSwan",
        VECTREX: "Vectrex",
        NEC8800: "PC-8000/8800",
        APPLEII: "Apple II",
        ARCADE: "Arcade"
    };

    var SRConsole = {
        PC: "8gej2n93",
        APPLEII: "w89ryw6l",
        ATARI2600: "o0644863",
        ARCADE: "vm9vn63k",
        NEC8800: "7g6mw8er",
        COLECOVISION: "wxeo8d6r",
        COMMODORE64: "gz9qox60",
        MSX: "jm950z6o",
        NES: "jm95z9ol",
        MSX2: "83exkk6l",
        MASTERSYSTEM: "83exwk6l",
        ATARI7800: "gde33gek",
        FAMICOMDISKSYSTEM: "mr6k409z",
        PCENGINE: "5negxk6y",
        GENESIS: "mr6k0ezw",
        GAMEBOY: "n5683oev",
        NEOGEOAES: "mx6p4w63",
        GG: "w89r3w9l",
        SNES: "83exk6l5",
        PHILIPSCDI: "w89rjw6l",
        SEGACD: "31670d9q",
        PANASONIC3D0: "8gejmne3",
        NEOGEOCD: "kz9w7mep",
        PCFX: "p36n8568",
        PS1: "wxeod9rn",
        SEGA32X: "kz9wrn6p",
        SEGASATURN: "lq60l642",
        VIRTUALBOY: "7g6mk8er",
        N64: "w89rwelk",
        GAMEBOYCOLOR: "gde3g9k1",
        NEOGEOPOCKETCOLOR: "7m6ydw6p",
        TURBOGRAFX16CD: "p36nlxe8",
        DREAMCAST: "v06d394z",
        WONDERSWAN: "vm9v8n63",
        PLAYSTATION2: "n5e17e27",
        WONDERSWANCOLOUR: "n568kz6v",
        GAMEBOYADVANCE: "3167d6q2",
        GAMECUBE: "4p9z06rn",
        POKÉMONMINI: "vm9vr1e3",
        NDS: "7g6m8erk",
        PLAYSTATIONPORTABLE: "5negk9y7",
        WII: "v06dk3e4"
    }

    // GM_log(enableSpeedrun)
    // GM_log(enableRomSearch)
    // GM_log(enableCustomBG)
    // GM_log(playLaterList)
    // GM_log(page)
    // GM_log(sectionName)

    // Add Play Later List to nav
    $("#menuholder > li").eq(4).find(".divider")
        .eq(0).before(` <li>
                        <a href="/setRequestList.php?u=${loggedUser}#playlater">
                            Play Later
                        </a>
                    </li>`)

    if (page == "/controlpanel.php") {
        $("#leftcontainer > .component").eq(3).after("<div id='enhanced-settings' class='component'></div>")
        let settingsDiv = $("#enhanced-settings");
        settingsDiv.append("<h3>Retro Enhanced</h3>")
        settingsDiv.append(`<table>
                                <tbody>
                                <tr>
                                    <td>Enable roms search:</td>
                                    <td><input id="enhanced-romsearch" type="checkbox" ${enableRomSearch ? "checked='checked'" : ""}></td>
                                </tr>
                                <tr>
                                    <td>Add Emuparadise to roms search (For Chrome need
                                        <a href="https://experienceleague.adobe.com/docs/target/using/experiences/vec/troubleshoot-composer/mixed-content.html?lang=en#task_FF297A08F66E47A588C14FD67C037B3A">
                                        'Insecure Content' set to 'Allow'
                                        </a>
                                        on site settings):
                                    </td>
                                    <td><input id="enhanced-epromsearch" type="checkbox" ${enableEmuparadise ? "checked='checked'" : ""}></td>
                                </tr>
                                <tr>
                                    <td>Enable Speedrun.com stats:</td>
                                    <td><input id="enhanced-speedrun" type="checkbox" ${enableSpeedrun ? "checked='checked'" : ""}></td>
                                </tr>
                                <tr>
                                    <td>Enable gameplay video:</td>
                                    <td><input id="enhanced-gameplayvideo" type="checkbox" ${enableCustomBG ? "checked='checked'" : ""}></td>
                                </tr>
                                <tr>
                                    <td>Enable custom game page background:</td>
                                    <td><input id="enhanced-custombg" type="checkbox" ${enableCustomBG ? "checked='checked'" : ""}></td>
                                </tr>
                                </tbody>
                        </table>`)

        $(document).on('change', '#enhanced-romsearch', function () {
            GM_setValue("enableRomSearch", $("#enhanced-romsearch").is(":checked"))
        })

        $(document).on('change', '#enhanced-epromsearch', function () {
            GM_setValue("enableEmuparadise", $("#enhanced-epromsearch").is(":checked"))
        })

        $(document).on('change', '#enhanced-speedrun', function () {
            GM_setValue("enableSpeedrun", $("#enhanced-speedrun").is(":checked"))
        })

        $(document).on('change', '#enhanced-custombg', function () {
            GM_setValue("enableCustomBG", $("#enhanced-custombg").is(":checked"))
        })

        $(document).on('change', '#enhanced-gameplayvideo', function () {
            GM_setValue("enableGameplayVideo", $("#enhanced-gameplayvideo").is(":checked"))
        })



    }
    // setRequestList.php?u=UserName && verify if user is the one logged
    else if (page == "/setRequestList.php" &&
        //https://regex101.com/r/X0UMEo/1
        /u=([^&]*)/g.exec(pageWithParams)[1] == loggedUser) {

        if (sectionName == "playlater") {
            $('html, body').animate({
                scrollTop: 850
            }, 100);
        }

        playLaterList.sort((a, b) => (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0));

        function list() {
            var list = "";
            for (const prop in RAConsole) {
                if (RAConsole.hasOwnProperty(prop)) {
                    const element = RAConsole[prop];
                    if (playLaterList.filter(playGame => playGame.console == element).length > 0)
                        list = list.concat(`<h4 style="margin-top:1em;">${element}</h4>
                                            <table>
                                            <tbody>
                                            <tr>
                                                <th>Game</th>
                                                <th>Points</th>
                                            </tr>
                                            ${playLaterList.map(playGame => {
                                                if (playGame.console == element)
                                                    return `
                                                            <tr>
                                                                <td>
                                                                    <div class="bb_inline" style='display:flex;width:500px;'>
                                                                        <a href="/Game/${playGame.id}">
                                                                            <img loading="lazy" alt="" title="${playGame.name}" src="${playGame.icon}" width="32" height="32" class="badgeimg">
                                                                            ${playGame.tag? `~${playGame.tag}~` : ""} ${playGame.name}
                                                                        </a>
                                                                    </div>
                                                                </td>
                                                                <td>${playGame.points || 0} points</td>
                                                            </tr>`}).join(' ')}
                                            </tbody>
                                            </table>`)

                }
            }
            return list;
        }
        $("table").after(`</br></br>
                            <h3 id="playlater">Play Later</h3>
                            ${(list())}`)

    }
    // game/13918
    else if (page.match(/game\/[0-9]/g) != null ||
        page == "/gameInfo.php") {

        // General
        var console = $(".navpath").children().eq(1).text();
        var game = $(".navpath").children().eq(2).text();
        var gameId = /([^\/]+)\/?$/g.exec($("meta[property='og:url']").attr("content"))[0];
        var points = $("#achievement .TrueRatio").eq(0).prev().text();
        var icon = $("#achievement table img").attr("src")
        var tag = "";
        var gameImg = $("#achievement .gamescreenshots img").eq(1).attr("src");
        var isOnPlayLater = playLaterList.filter(playGame => playGame.id == gameId).length > 0;
        var rgxTag = /\~(.*?)\~/g;
        // Check for tags
        // if (game.match(rgxTag) != undefined) {
            // tag = rgxTag.exec(game)[1];
            // game = game.replace(game.match(rgxTag) + " ", "");
        // }
        // GM_log(gameId)

        var isAvailable = false;
        var collection = {
            name: "",
            url: ""
        };
        var results = [];

        // Speedrun.com API resources
        var srRoot = "https://www.speedrun.com/api/v1/";
        var srLogo = "";
        var srVideoUrl = "";
        var srGamelink = "";
        var srGameId = "";
        var srRuns = [];
        // GM_log(game)
        // GM_log(console)

        // Avoid unwanted exceptions for hubs pages.
        if (console == "") return


        if (gameImg != "/Images/000002.png" && enableCustomBG) {
            $("body").append(`<style>
                            body:before {
                                content: "";
                                position: fixed;
                                width : 110%;
                                height: 110%;
                                background: inherit;
                                background-size: cover;
                                z-index: -1;
                                overflow: hidden;

                                filter        : blur(8px);
                                -moz-filter   : blur(8px);
                                -webkit-filter: blur(8px);
                                -o-filter     : blur(8px);
                            }
                             </style>`);
            $("body").css({
                "background-image": `url(${gameImg})`,
                "background-attachment": "fixed",
                "background-size": "90%",
                "background-position": "center",
                "background-repeat": "no-repeat"
            })
        }

        // Update local data in case it changes.
        var index = playLaterList.findIndex(playGame => playGame.id == gameId);
        playLaterList[index] = {
            name: game,
            id: gameId,
            console: console,
            icon: icon,
            points: points,
            tag: tag
        };
        // Clean if there is any undefined id.
        playLaterList = playLaterList.filter(playGame => playGame.id != undefined)
        // GM_log(gameId)
        await GM_setValue("playLaterGames", JSON.stringify(playLaterList))

        // Play Later button
        $("#leftcontainer #achievement h4").eq(1).before(`<a style='display:block;float:right;
                                                                    cursor:pointer;margin-top:5px;'
                                                             id='enhanced-playlater'>
                                                            <h4 style="font-size: 1.25em;">
                                                                ${isOnPlayLater ? '✘ Remove' : '+ Add to' } Play Later
                                                            </h4>
                                                        </a>`);
        $("#leftcontainer #achievement h4").eq(1).parent().parent().css("margin-top", "2em")

        // Play Later Click
        $(document).on('click', '#enhanced-playlater', async function () {
            // getValue again incase has been updated on another window
            playLaterList = await GM_getValue("playLaterGames", []);
            playLaterList = playLaterList.length != 0 ? JSON.parse(playLaterList) : [];

            if (!isOnPlayLater) {
                playLaterList.push({
                    name: game,
                    id: gameId,
                    console: console,
                    icon: icon,
                    points: points,
                    tag: tag
                })
            } else {
                playLaterList = playLaterList.filter(playGame => playGame.id != gameId)
            }

            await GM_setValue("playLaterGames", JSON.stringify(playLaterList));

            isOnPlayLater = playLaterList.filter(playGame => playGame.id == gameId).length > 0;
            $('#enhanced-playlater > h4').text(`${isOnPlayLater ? '✘ Remove' : '+ Add to' } Play Later`)
        })

        // Prepare divs
        $("#rightcontainer > .gamescreenshots").append("<div id='roms'></div>")
        $("#rightcontainer > .gamescreenshots").append("<div id='speedruncom'></div>")
        var divRoms = $("#roms");
        var divSpeedruncom = $("#speedruncom");
        divRoms.css("margin-top", "1em")
        divSpeedruncom.css("margin-top", "1em");

        if (enableGameplayVideo || enableSpeedrun) getSpeedruns(game)

        // Rom Search
        if (enableRomSearch) {

            // Verify if system is available
            for (const prop in RAConsole) {
                if (RAConsole.hasOwnProperty(prop)) {
                    const element = RAConsole[prop];
                    if (element == console) isAvailable = true;
                }
            }

           if (tag != "" && console != RAConsole.ARCADE) {
                divRoms.append("<b>Searching roms for games with tags not supported.</b>");
            } else if (isAvailable && tag == "") {
                var promise;

                // Deprecated feature to find roms for hacks and homebrews.
                // if (tag != "" && console != RAConsole.ARCADE) {
                //     promise = searchCustom();
                //     collection.name = "Custom";
                // }
                if (enableEmuparadise) {
                    promise = searchEmuparadise();
                    collection.name = "Emuparadise";
                    collection.url = "https://www.emuparadise.me/roms-isos-games.php"
                } else {
                    // resolve the promise so it the function don't stop working
                    promise = Promise.resolve()
                }

                promise.then(() => {
                    if (results.length == 0) {

                        if (console == RAConsole.GAMEBOY) {
                            collection.name = "[No-Intro] Game Boy"
                            collection.url = "https://archive.org/download/nointro.gb"
                            return searchArchive("https://archive.org/download/nointro.gb");

                        } else if (console == RAConsole.GBC) {
                            collection.name = "[No-Intro] Game Boy Color"
                            collection.url = "https://archive.org/download/nointro.gbc"
                            return searchArchive("https://archive.org/download/nointro.gbc");

                        } else if (console == RAConsole.GBA) {
                            collection.name = "[No-Intro] Game Boy Advance"
                            collection.url = "https://archive.org/download/nointro.gba"
                            return searchArchive("https://archive.org/download/nointro.gba");

                        } else if (console == RAConsole.NES) {
                            collection.name = "[No-Intro] Nintendo Entertainment System"
                            collection.url = "https://archive.org/download/nointro.nes"
                            return searchArchive("https://archive.org/download/nointro.nes");

                        } else if (console == RAConsole.SNES) {
                            collection.name = "[No-Intro] Super Nintendo Entertainment System"
                            collection.url = "https://archive.org/download/nointro.snes"

                        } else if (console == RAConsole.N64) {
                            collection.name = "[No-Intro] Nintendo 64"
                            collection.url = "https://archive.org/download/nointro.n64"
                            return searchArchive("https://archive.org/download/nointro.n64")
                                .then(() => {
                                    if (results.length == 0) return searchArchive("https://archive.org/download/nointro.n64dd");
                                    else return;
                                })

                        } else if (console == RAConsole.NDS) {
                            collection.name = "[No-Intro] Nintendo DS Decrypted";
                            collection.url = "https://archive.org/download/noIntroNintendoDsDecrypted2020Jan20"
                            return searchArchive("https://archive.org/download/noIntroNintendoDsDecrypted2020Jan20");

                        } else if (console == RAConsole.POKEMINI) {
                            collection.name = "[No-Intro] Pokémon Mini"
                            collection.url = "http://archive.org/download/nointro.poke-mini"
                            return searchArchive("http://archive.org/download/nointro.poke-mini");

                        } else if (console == RAConsole.VIRTUALBOY) {
                            collection.name = "[No-Intro] Virtual Boy"
                            collection.url = "https://archive.org/download/nointro.vb"
                            return searchArchive("https://archive.org/download/nointro.vb");

                        } else if (console == RAConsole.A2600) {
                            collection.name = "[No-Intro] Atari 2600"
                            collection.url = "https://archive.org/download/nointro2600atarii"
                            return searchArchive("https://archive.org/download/nointro2600atarii");

                        } else if (console == RAConsole.A7800) {
                            collection.name = "[No-Intro] Atari 7800"
                            collection.url = "https://archive.org/download/nointro.atari-7800"
                            return searchArchive("https://archive.org/download/nointro.atari-7800");

                        } else if (console == RAConsole.ATARI7800) {
                            collection.name = "[No-Intro] Atari 7800"
                            collection.url = "https://archive.org/download/nointro.atari-7800"
                            return searchArchive("https://archive.org/download/nointro.atari-7800");

                        } else if (console == RAConsole.PS1) {
                            collection.name = "[REDUMP] PSX";
                            collection.url = "https://archive.org/download/Sony-Playstation-USA-Redump.org-2019-05-27"
                            return searchArchive("https://archive.org/download/Sony-Playstation-USA-Redump.org-2019-05-27")
                                .then(() => {
                                    if (results.length == 0) return searchArchive("https://archive.org/download/Sony-Playstation-EUR-Redump.org");
                                    else return;
                                })
                                .then(() => {
                                    if (results.length == 0) return searchArchive("https://archive.org/download/SonyPlaystation-EUR-Part2-Redump.org2019-06-05");
                                    else return;
                                })
                                .then(() => {
                                    if (results.length == 0) return searchArchive("https://archive.org/download/Sony-Playstation-JP-ASIA-Part-1");
                                    else return;
                                })
                                .then(() => {
                                    if (results.length == 0) return searchArchive("https://archive.org/download/Sony-Playstation-JP-ASIA-Part-2");
                                    else return;
                                })

                        } else if (console == RAConsole.PSP) {
                            collection.name = "[REDUMP & Ghostware] Sony PlayStation Portable"
                            collection.url = "https://archive.org/download/redump.psp"
                            return searchArchive("https://archive.org/download/redump.psp")
                                .then(() => {
                                    if (results.length == 0) return searchArchive("https://archive.org/download/redump.psp.p2");
                                    else return;
                                })
                                .then(() => {
                                    if (results.length == 0) return searchArchive("https://archive.org/download/PSNCollectionByGhostware");
                                    else return;
                                })
                                .then(() => {
                                    if (results.length == 0) return searchArchive("https://archive.org/download/PSP-DLC/PSP%20DLC/");
                                    else return;
                                })

                        } else if (console == RAConsole.SATURN) {
                            collection.name = "[REDUMP] Sega Saturn";
                            collection.url = "https://archive.org/download/redump.ss.revival"
                            return searchArchive("https://archive.org/download/redump.ss.revival");

                        } else if (console == RAConsole.DREAMCAST) {
                            collection.name = "[REDUMP] Dreamcast";
                            collection.url = "https://archive.org/download/redump.dc.revival";
                            return searchArchive("https://archive.org/download/redump.dc.revival");

                        } else if (console == RAConsole.SEGACD) {
                            collection.name = "[REDUMP] Sega CD";
                            collection.url = "https://archive.org/download/redump.mcd.revival"
                            return searchArchive("https://archive.org/download/redump.mcd.revival");

                        } else if (console == RAConsole.ARCADE) {
                            collection.name = "FB Neo Nightly"
                            collection.url = "https://archive.org/download/2020_01_06_fbn/"
                            return searchArcade();

                        } else if (console == RAConsole.NEC8800) {
                            collection.name = "[Neo Kobe] NEC PC-8801/8001"
                            collection.url = "https://archive.org/details/Neo_Kobe_NEC_PC-8001_2016-02-25"
                            return searchArchive("https://ia800202.us.archive.org/view_archive.php?archive=/18/items/Neo_Kobe_NEC_PC-8801_2016-02-25/Neo%20Kobe%20-%20NEC%20PC-8801%20%282016-02-25%29.zip")
                                // Search for 8001 if it finds nothing.
                                .then(() => {
                                    if (results.length == 0) return searchArchive("https://ia600204.us.archive.org/view_archive.php?archive=/18/items/Neo_Kobe_NEC_PC-8001_2016-02-25/Neo%20Kobe%20-%20NEC%20PC-8001%20%282016-02-25%29.zip)");
                                    else return;
                                })

                        } else if (console == RAConsole.APPLEII) {
                            collection.name = "[TOSEC] Apple II"
                            collection.url = "https://archive.org/details/Apple_2_TOSEC_2012_04_23"
                            return searchArchive("https://ia802908.us.archive.org/view_archive.php?archive=/25/items/Apple_2_TOSEC_2012_04_23/Apple_2_TOSEC_2012_04_23.zip");

                        } else {
                            collection.name = "No-Intro 2021"
                            collection.url = "https://archive.org/download/no-intro_romsets/no-intro%20romsets/"
                            return searchNoIntro2016();
                        }
                    // exit if already has results
                    } else return;

                })
                .then(() => createDownloads())

            } else {
                divRoms.append("<b>Searching roms for this system not supported.</b>");
            }
        }

        function createDownloads() {
            divRoms.append("<h3>ROMs</h3>");

            divRoms.append(`<b>Found ${results.length} related result(s)`)
            divRoms.append(``);

            for (var i = 0; i < results.length; i++) {
                let dlLink = (results[i].url).replace(/ /g, "%20");
                divRoms.append("<a class='dl-links' href=" + dlLink + ">" + results[i].name + "</a>");
            }
            if (collection.url != "") divRoms.append(`<div style="margin-top:1em;">From <a href=${collection.url}>${collection.name}</a></div>`);
            $(".dl-links").css("display", "block");
        }

        function createSpeedrun() {
            divSpeedruncom.append("<h3>World Records</h3>");
            divSpeedruncom.append(`<a href=${srGamelink}>
                                        <img style="display: block; width: 100%; object-fit: cover;"
                                             src=${srLogo}></img>
                                    </a>`)
            if (srRuns.length > 0) {
                srRuns.forEach((run) => {
                    divSpeedruncom.append(`<div><a href=${run.link}>${run.category}:</a> ${run.time} by ${run.runner}</div>`)
                });
            } else {
                divSpeedruncom.append("<div>Couldn't find this game on Speedrun.com</div>")
            }
        }

        function createVideo() {
            if (srVideoUrl != "")
                $("#achievement").children().eq(4)
                .after($(`<iframe style="display: block; width: 100%; height:315px;"
                                    src="${srVideoUrl}"
                                    allowfullscreen="allowfullscreen"
                                    autoplay="false">
                                </iframe>`));
        }


        function getSrConsoleId(consoleName) {
            if (consoleName == RAConsole.A2600) {
                return SRConsole.ATARI2600;
            } else if (consoleName == RAConsole.A7800) {
                return SRConsole.ATARI7800;
            } else if (consoleName == RAConsole.APPLEII) {
                return SRConsole.APPLEII;
            } else if (consoleName == RAConsole.ARCADE) {
                return SRConsole.ARCADE;
            } else if (consoleName == RAConsole.COLECO) {
                return SRConsole.COLECOVISION;
            } else if (consoleName == RAConsole.DREAMCAST) {
                return SRConsole.DREAMCAST;
            } else if (consoleName == RAConsole.GAMEBOY) {
                return SRConsole.GAMEBOY;
            } else if (consoleName == RAConsole.GBA) {
                return SRConsole.GAMEBOYADVANCE;
            } else if (consoleName == RAConsole.GBC) {
                return SRConsole.GAMEBOYCOLOR;
            } else if (consoleName == RAConsole.GENESIS) {
                return SRConsole.GENESIS;
            } else if (consoleName == RAConsole.GG) {
                return SRConsole.GG;
            } else if (consoleName == RAConsole.N64) {
                return SRConsole.N64;
            } else if (consoleName == RAConsole.SATURN) {
                return SRConsole.SEGASATURN;
            } else if (consoleName == RAConsole.MASTERSYSTEM) {
                return SRConsole.MASTERSYSTEM;
            } else if (consoleName == RAConsole.NDS) {
                return SRConsole.NDS;
            } else if (consoleName == RAConsole.NEC8800) {
                return SRConsole.NEC8800;
            } else if (consoleName == RAConsole.NEOPOCKET) {
                return SRConsole.NEOGEOPOCKETCOLOR;
            } else if (consoleName == RAConsole.NES) {
                return SRConsole.NES;
            } else if (consoleName == RAConsole.PANASONIC3DO) {
                return SRConsole.PANASONIC3D0;
            } else if (consoleName == RAConsole.PCENGINE) {
                return SRConsole.PCENGINE;
            } else if (consoleName == RAConsole.POKEMINI) {
                return SRConsole.POKÉMONMINI;
            } else if (consoleName == RAConsole.PS1) {
                return SRConsole.PS1;
            } else if (consoleName == RAConsole.PSP) {
                return SRConsole.PLAYSTATIONPORTABLE
            } else if (consoleName == RAConsole.SEGA32X) {
                return SRConsole.SEGA32X;
            } else if (consoleName == RAConsole.SEGACD) {
                return SRConsole.SEGACD;
            } else if (consoleName == RAConsole.SG1000) {
                return SRConsole.MASTERSYSTEM;
            } else if (consoleName == RAConsole.SNES) {
                return SRConsole.SNES;
            } else if (consoleName == RAConsole.VIRTUALBOY) {
                return SRConsole.VIRTUALBOY;
            } else return "";

        }

        function getSpeedruns(gameName) {
            var consoleId = getSrConsoleId(console);
            var srSearchUrl = encodeURI(srRoot + "games?name=" + gameName + "&platform=" + consoleId)
            // Games
            return $.get(srSearchUrl)
                .then((gamesResponse) => {
                    if (gamesResponse.data.length > 0) {
                        srGamelink = gamesResponse.data[0].weblink;
                        srGameId = gamesResponse.data[0].id;
                        srLogo = gamesResponse.data[0].assets.logo.uri;
                        return gamesResponse.data[0].links[3].uri;
                    } else {
                        throw `Couldn't find this game on Speedrun.com (${srSearchUrl}).`;
                    }
                }, err => {
                    throw (err)
                })

                // Categories
                .then((link) => {
                    return $.get(link).then(response => response.data)
                }, err => {
                    throw (err)
                })

                .then((categories) => {
                    // GM_log(categories)
                    let totalRuns = 0;
                    return Promise.all(categories.map(category => {

                            // Runs
                            return $.get(srRoot + `runs?game=${srGameId}&category=${category.id}&status=verified`)
                                .then((runsRes) => {
                                    let run = runsRes.data[0];
                                    if (run != undefined && run.status.status != "rejected") {
                                        // GM_log(run.videos)
                                        if (srVideoUrl == "" && run.videos)
                                            srVideoUrl = toEmbedUrl(run.videos.links[0].uri);
                                        totalRuns++;
                                        return run;
                                    }
                                })

                                .then((run) => {
                                    if (run == undefined) return false;
                                    // TODO: Resolve useless request if user is a guest.
                                    let isGuest = run.players[0].rel == "guest" ? true : false;
                                    return $.get(srRoot + "users/" + run.players[0].id)
                                        .then(userRes => {
                                            srRuns.push({
                                                category: category.name,
                                                time: parseIso8601(run.times.primary),
                                                runner: isGuest ? run.players[0].name : userRes.data.names.international,
                                                link: run.videos ? run.videos.links[0].uri : ""
                                            })

                                            // GM_log(srRuns.length)
                                            // GM_log(totalRuns)
                                            return true;
                                        })
                                })
                        }))
                        .then(() => {
                            if (enableSpeedrun) createSpeedrun();
                            if (enableGameplayVideo) createVideo();
                        })
                }, err => {
                    throw (err)
                })
        }

        // function searchCustom() {
        //     var spreadsheetId = "1IhU4rxn9aZ44HHTttYxTxeGlamQ7KHmFdiAtGjFK8Sg";
        //     var publicKey = "AIzaSyBX7lt6Dzt4NZti7Du_xxAD_qPKb4xKZ-4";
        //     return new Promise((resolve, reject) => {
        //         GM_xmlhttpRequest({
        //             method: "GET",
        //             url: `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${console}!A2:C1000?key=${publicKey}`,
        //             onload: function (response) {
        //                 var sheet = JSON.parse(response.responseText);
        //                 sheet.values.forEach(row => {
        //                     // Stop in case tag is not matching
        //                     if (row[0] != tag) return;

        //                     if (refinedCompare(row[1], game)) {
        //                         // GM_log(row)
        //                         results.push({
        //                             name: `${row[1]} (${row[0]})`,
        //                             url: row[2]
        //                         });
        //                     }
        //                 });
        //                 if (results.length == 0) {
        //                     sheet.values.forEach(row => {
        //                         if (compare(row[1], game)) {
        //                             // GM_log(row)
        //                             results.push({
        //                                 name: `${row[1]} (${tag})`,
        //                                 url: row[2]
        //                             });
        //                         }
        //                     });

        //                 }
        //                 resolve(true);
        //             }
        //         })
        //     })


        // }

        function searchArcade() {
            var mainDir = "//archive.org/download/2020_01_06_fbn/roms/arcade.zip/arcade%2F";
            var datDir = "https://raw.githubusercontent.com/libretro/FBNeo/master/dats/FinalBurn%20Neo%20(ClrMame%20Pro%20XML%2C%20Arcade%20only).dat";

            return new Promise((resolve, reject) => {
                // Find content in .dat file from FB Neo repo.
                GM_xmlhttpRequest({
                    method: "GET",
                    url: datDir,
                    onload: function (response) {
                        // GM_log(response);
                        var xmlDoc = $.parseXML(response.responseText);

                        // Refined search
                        $(xmlDoc).find("game").each(function (index) {
                            let name = $(this).find("description").text();
                            if (tag == "" && name.includes("[Hack]")) return;
                            if (tag == "Hack" && !name.includes("[Hack]")) return;
                            if (refinedCompare(name, game)) {
                                results.push({
                                    name: name,
                                    url: mainDir + $(this).attr('name') + ".zip"
                                });
                            }
                        })

                        // Normal search
                        if (results.length == 0) {
                            $(xmlDoc).find("game").each(function (index) {
                                let name = $(this).find("description").text();
                                if (tag == "" && name.includes("[Hack]")) return;
                                if (tag == "Hack" && !name.includes("[Hack]")) return;
                                if (compare(name, game)) {
                                    results.push({
                                        name: name,
                                        url: mainDir + $(this).attr('name') + ".zip"
                                    });
                                }
                            })
                        }
                        resolve(true);
                    }
                })
            });
        }

        // https://archive.org/download/No-Intro-Collection_2016-01-03_Fixed
        function searchNoIntro2016() {
            var mainDir = "https://archive.org/download/No-Intro-Collection_2016-01-03_Fixed/";
            var consoleDir = "";
            var secondaryConsoleDir = "";

            if (console == RAConsole.NES) {
                consoleDir = "Nintendo - Nintendo Entertainment System";
            }

            if (console == RAConsole.GAMEBOY) {
                consoleDir = "Nintendo - Game Boy";
            }

            if (console == RAConsole.GBC) {
                consoleDir = "Nintendo - Game Boy Color";
            }

            if (console == RAConsole.GBA) {
                consoleDir = "Nintendo - Game Boy Advance";
            }

            if (console == RAConsole.N64) {
                consoleDir = "Nintendo - Nintendo 64";
            }

            if (console == RAConsole.A7800) {
                consoleDir = "Atari - 7800";
            }

            if (console == RAConsole.PCENGINE) {
                consoleDir = "NEC - PC Engine - TurboGrafx 16";
            }

            if (console == RAConsole.GENESIS) {
                consoleDir = "Sega - Mega Drive - Genesis";
            }

            if (console == RAConsole.MASTERSYSTEM) {
                consoleDir = "Sega - Master System - Mark III";
            }

            if (console == RAConsole.GG) {
                consoleDir = "Sega - Game Gear";
            }

            if (console == RAConsole.NEOPOCKET) {
                consoleDir = "SNK - Neo Geo Pocket";
            }

            if (console == RAConsole.POKEMINI) {
                consoleDir = "Nintendo - Pokemon Mini";
            }

            if (console == RAConsole.VIRTUALBOY) {
                consoleDir = "Nintendo - Virtual Boy.zip";
            }

            if (console == RAConsole.SG1000) {
                consoleDir = "Sega - SG-1000";
            }

            if (console == RAConsole.COLECO) {
                consoleDir = "Coleco - ColecoVision";
            }

            if (console == RAConsole.MSX) {
                consoleDir = "Microsoft - MSX";
                secondaryConsoleDir = "Microsoft - MSX 2";
            }

            if (console == RAConsole.WONDERSWAN) {
                consoleDir = "Bandai - WonderSwan Color";
                secondaryConsoleDir = "Bandai - WonderSwan Color"
            }

            if (console == RAConsole.VECTREX) {
                consoleDir = "GCE - Vectrex";
            }

            consoleDir = consoleDir.replace(/ /g, "%20").concat(".zip/");
            secondaryConsoleDir = secondaryConsoleDir.replace(/ /g, "%20").concat(".zip/");

            return new Promise((resolve, reject) => {
                    GM_xmlhttpRequest({
                        method: "GET",
                        url: mainDir + consoleDir,
                        onload: function (response) {
                            // GM_log(response);

                            // Refined search
                            $(response.responseText).find(`td`).children(":first-child").each(function (index) {
                                if (refinedCompare($(this).text(), game)) {
                                    results.push({
                                        name: $(this).text(),
                                        url: $(this).attr('href')
                                    });
                                }
                            });

                            // Normal search
                            if (results.length == 0) {
                                $(response.responseText).find(`td`).children(":first-child").each(function (index) {
                                    if (compare($(this).text(), game)) {
                                        results.push({
                                            name: $(this).text(),
                                            url: $(this).attr('href')
                                        });
                                    }
                                });
                            }
                            resolve(true);
                        }
                    })
                })
                .then(() => {
                    if (secondaryConsoleDir != "") {
                        return new Promise((resolve, reject) => {
                            GM_xmlhttpRequest({
                                method: "GET",
                                url: mainDir + secondaryConsoleDir,
                                onload: function (response) {
                                    // GM_log(response);

                                    // Refined search
                                    $(response.responseText).find(`td`).children(":first-child").each(function (index) {
                                        if (refinedCompare($(this).text(), game)) {
                                            results.push({
                                                name: $(this).text(),
                                                url: $(this).attr('href')
                                            });
                                        }
                                    });

                                    // Normal search
                                    if (results.length == 0) {
                                        $(response.responseText).find(`td`).children(":first-child").each(function (index) {
                                            if (compare($(this).text(), game)) {
                                                results.push({
                                                    name: $(this).text(),
                                                    url: $(this).attr('href')
                                                });
                                            }
                                        });
                                    }
                                    resolve(true);
                                }
                            })
                        })
                    }
                })
        }

        // Generic Archives site
        function searchArchive(mainDir) {
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: "GET",
                    url: mainDir,
                    onload: function (response) {
                        // GM_log(response);

                        // Refined search
                        $(response.responseText).find(`td`).children(":first-child").each(function (index) {
                            let title = /([^\/]+)\/?$/g.exec($(this).text())[1];
                            // Check in case link is already the full url
                            let fullUrl = $(this).attr('href').startsWith("//archive.org/download/") ?
                                $(this).attr('href') : `${mainDir}/${$(this).attr('href')}`
                            // GM_log($(this).attr('href'))
                            if (refinedCompare(title, game)) {
                                results.push({
                                    name: title,
                                    url: fullUrl
                                });
                            }

                        });

                        // Normal search
                        if (results.length == 0) {
                            $(response.responseText).find(`td`).children(":first-child").each(function (index) {
                                let title = /([^\/]+)\/?$/g.exec($(this).text())[1];
                                let fullUrl = $(this).attr('href').startsWith("//archive.org/download/") ?
                                    $(this).attr('href') : `${mainDir}/${$(this).attr('href')}`
                                // GM_log($(this).attr('href'))
                                if (compare(title, game)) {
                                    results.push({
                                        name: title,
                                        url: fullUrl
                                    });
                                }
                            });
                        }
                        return resolve(true);
                    }
                })
            });
        }

        // Generic Archives site
        function searchEmuparadise() {

            var mainDir = "https://www.emuparadise.me/";
            var consoleUrl;

            if (console == RAConsole.SNES) {
                consoleUrl = "Super_Nintendo_Entertainment_System_(SNES)_ROMs/List-All-Titles/5";
            }

            if (console == RAConsole.NES) {
                consoleUrl = "Nintendo_Entertainment_System_ROMs/List-All-Titles/13";
            }

            if (console == RAConsole.GAMEBOY) {
                consoleUrl = "Nintendo_Game_Boy_ROMs/List-All-Titles/12";
            }

            if (console == RAConsole.GBC) {
                consoleUrl = "Nintendo_Game_Boy_Color_ROMs/List-All-Titles/11";
            }

            if (console == RAConsole.GBA) {
                consoleUrl = "Nintendo_Gameboy_Advance_ROMs/List-All-Titles/31";
            }

            if (console == RAConsole.N64) {
                consoleUrl = "Nintendo_64_ROMs/List-All-Titles/9";
            }

            if (console == RAConsole.NDS) {
                consoleUrl = "Nintendo_DS_ROMs/List-All-Titles/32";
            }

            if (console == RAConsole.GENESIS) {
                consoleUrl = "Sega_Genesis_-_Sega_Megadrive_ROMs/List-All-Titles/6";
            }

            if (console == RAConsole.MASTERSYSTEM) {
                consoleUrl = "Sega_Master_System_ROMs/List-All-Titles/15";
            }

            if (console == RAConsole.SEGA32X) {
                consoleUrl = "Sega_32X_ROMs/61";
            }

            if (console == RAConsole.SATURN) {
                consoleUrl = "Sega_Saturn_ISOs/List-All-Titles/3"
            }

            if (console == RAConsole.SEGACD) {
                consoleUrl = "Sega_CD_ISOs/List-All-Titles/10";
            }

            if (console == RAConsole.GG) {
                consoleUrl = "Sega_Game_Gear_ROMs/List-All-Titles/14";
            }

            if (console == RAConsole.NEOPOCKET) {
                consoleUrl = "Neo_Geo_Pocket_-_Neo_Geo_Pocket_Color_(NGPx)_ROMs/38";
            }

            if (console == RAConsole.A2600) {
                consoleUrl = "Atari_2600_ROMs/List-All-Titles/49"
            }

            if (console == RAConsole.A7800) {
                consoleUrl = "Atari_7800_ROMs/47"
            }

            if (console == RAConsole.PCENGINE) {
                consoleUrl = "PC_Engine_-_TurboGrafx16_ROMs/List-All-Titles/16";
            }

            if (console == RAConsole.APPLEII) {
                consoleUrl = "Apple_][_ROMs/List-All-Titles/24";
            }

            if (console == RAConsole.PS1) {
                consoleUrl = "Sony_Playstation_ISOs/List-All-Titles/2";
            }

            if (console == RAConsole.PSP) {
                consoleUrl = "PSP_ISOs/List-All-Titles/44";
            }

            if (console == RAConsole.PANASONIC3DO) {
                consoleUrl = "Panasonic_3DO_(3DO_Interactive_Multiplayer)_ISOs/List-All-Titles/20";
            }

            return new Promise((resolve, reject) => {

                GM_xmlhttpRequest({
                    method: "GET",
                    url: mainDir + consoleUrl,
                    onload: function (response) {
                        // GM_log(response);

                        // Refined search
                        $(response.responseText).find(`.index.gamelist`).each(function (index) {
                            let epGameId = /([^\/]+)\/?$/g.exec($(this).attr('href'))[0];
                            // GM_log($(this).text())
                            if (refinedCompare($(this).text(), game)) {
                                results.push({
                                    name: $(this).text(),
                                    url: `https://www.emuparadise.me/roms/get-download.php?gid=${epGameId}&test=true`
                                });
                            }

                        });

                        // Normal search
                        if (results.length == 0) {
                            $(response.responseText).find(`.index.gamelist`).each(function (index) {
                                let epGameId = /([^\/]+)\/?$/g.exec($(this).attr('href'))[0];
                                // GM_log($(this).text())
                                if (compare($(this).text(), game)) {
                                    results.push({
                                        name: $(this).text(),
                                        url: `https://www.emuparadise.me/roms/get-download.php?gid=${epGameId}&test=true`
                                    });
                                }
                            });
                        }
                        return resolve(true);
                    }
                })
            });
        }

        function refinedCompare(a, b) {
            let str1 = abstractify(a);
            let str2 = abstractify(b);
            // GM_log("Str 1: " + str1)
            // GM_log("Str 2: " + str2)
            if (str1 == str2)
                return true;
            else
                return false;
        }

        function compare(a, b) {
            // GM_log(a)
            let str1 = abstractify(a);
            let str2 = abstractify(b);
            // GM_log("Str 1: " + str1)
            // GM_log("Str 2: " + str2)
            // GM_log(str1.includes(str2))
            if (str1.includes(str2))
                return true;
            else
                return false;
        }
        // Remove unnecessary characters and region headering
        function abstractify(str) {
            // Dreamcast TOSEC set puts versioning into the filename
            if (console == RAConsole.DREAMCAST)
                str = str.replace(/v[0-9].[0-9]{3}/gs, "")

            return str
                .replace(/^The /g, '')
                .replace(", The", '')
                .replace(/'s/gs, '')
                .replace('&', 'and')
                .replace(/:|-| |\.|\+|!|\/|'/gs, '')
                .split('|')[0]
                .replace(',', "")
                // remove headers
                .replace(/\([\w\s]+\)/gs, "")
                .replace(/\[[\w\s]+\]/gs, "")
                .toLowerCase()
        }

        function parseIso8601(time) {
            var parsed = "";
            let regex = /(-)?P(?:([.,\d]+)Y)?(?:([.,\d]+)M)?(?:([.,\d]+)W)?(?:([.,\d]+)D)?T(?:([.,\d]+)H)?(?:([.,\d]+)M)?(?:([.,\d]+)S)?/;
            let groups = regex.exec(time);
            if (groups[6] != undefined) parsed = parsed.concat(`${groups[6]}h `)
            if (groups[7] != undefined) parsed = parsed.concat(`${groups[7]}m `)
            if (groups[8] != undefined) parsed = parsed.concat(`${groups[8]}s `)
            return parsed;
        }

        function toEmbedUrl(url) {
            if (url.includes("twitch") || url.includes("youtu")) {
                // GM_log(url);
                // https://regex101.com/r/jmV6qH/1
                var regexYoutube = /(?:https?:\/{2})?(?:w{3}\.)?youtu(?:be)?\.(?:com|be)(?:\/watch\?v=|\/)?([^\s&]+)/;
                // https://regex101.com/r/0RofWZ/1
                var regexTwitch = /(?:https?:\/{2})?www\.twitch\.tv\/(:?[\S]+\/)?([\]?)?\/([\d]+)/;
                if (url.match(regexYoutube) != undefined) {
                    return "https://www.youtube.com/embed/" + url.match(regexYoutube)[1];
                } else if (url.match(regexTwitch) != undefined) {
                    return "https://player.twitch.tv/?video=" + url.match(regexTwitch)[2] + "&parent=retroachievements.org&autoplay=false";
                }
            }
            return "";
        }
    }
})();