Miagui / Retro Enhanced

// ==UserScript==
// @name         Retro Enhanced
// @namespace    https://github.com/miagui
// @version      1.4.3
// @description  Additional functionality for retro gaming related sites.
// @author       Miagui
// @match        *://retroachievements.org/*
// @license      MIT
// @updateURL    https://openuserjs.org/meta/Miagui/Retro_Enhanced.meta.js
// @downloadURL  https://openuserjs.org/install/Miagui/Retro_Enhanced.user.js
// @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.meg
// @connect      speedrun.com
// @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 = $(".brand-top 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 prioritizeEmuparadise = await GM_getValue("prioritizeEmuparadise", false);
    var enableGlassEffect = await await GM_getValue("enableGlassEffect", true);

    var RAConsole = {
        ARCADE: "Arcade",
        SNES: "SNES/Super Famicom",
        NES: "NES/Famicom",
        GAMEBOY: "Game Boy",
        GBC: "Game Boy Color",
        GBA: "Game Boy Advance",
        N64: "Nintendo 64",
        GCN: "GameCube",
        NDS: "Nintendo DS",
        A2600: "Atari 2600",
        A7800: "Atari 7800",
        PCENGINE: "PC Engine/TurboGrafx-16",
        PCENGINECD: "PC Engine CD/TurboGrafx-CD",
        MASTERSYSTEM: "Master System",
        GG: "Game Gear",
        GENESIS: "Mega Drive",
        SEGA32X: "32X",
        SEGACD: "Sega CD",
        SATURN: "Saturn",
        DREAMCAST: "Dreamcast",
        PS1: "PlayStation",
        PS2: "PlayStation 2",
        PSP: "PlayStation Portable",
        PANASONIC3DO: "3DO Interactive Multiplayer",
        NEOGEOCD: "Neo Geo CD",
        NEOPOCKET: "Neo Geo Pocket",
        POKEMINI: "Pokemon Mini",
        VIRTUALBOY: "Virtual Boy",
        SG1000: "SG-1000",
        COLECO: "ColecoVision",
        MSX: "MSX",
        WONDERSWAN: "WonderSwan",
        VECTREX: "Vectrex",
        NEC8800: "PC-8000/8800",
        APPLEII: "Apple II"
    };

    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(page)
    // GM_log(sectionName)

    if (page == "/controlpanel.php") {
        $("article > .detaillist > .component:eq(1)").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: <div style="font-size: 0.8em;">(For Chrome users: <a href="https://experienceleague.adobe.com/docs/target/using/experiences/vec/troubleshoot-composer/mixed-content.html?lang=en#task_FF297A08F66E47A588C14FD67C037B3A">enable this</a>; For all browsers: must click on "Add Exception and Get my File!" for the first time)</div></td>
                                    <td><input id="enhanced-epromsearch" type="checkbox" ${enableEmuparadise ? "checked='checked'" : ""}></td>
                                </tr>
                                <tr>
                                    <td>Prioritize Emuparadise for ROMs search (must have "Add Emuparadise to ROMs search" enabled):</td>
                                    <td><input id="enhanced-prioritize_ep" type="checkbox" ${prioritizeEmuparadise ? "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 on the game page:</td>
                                    <td><input id="enhanced-gameplayvideo" type="checkbox" ${enableGameplayVideo ? "checked='checked'" : ""}></td>
                                </tr>
                                <tr>
                                    <td>Enable custom game page background:</td>
                                    <td><input id="enhanced-custombg" type="checkbox" ${enableCustomBG ? "checked='checked'" : ""}></td>
                                </tr>
                                <tr>
                                    <td>Enable glass background effect:</td>
                                    <td><input id="enhanced-glassEffect" type="checkbox" ${enableGlassEffect ? "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-prioritize_ep', function () {
            GM_setValue("prioritizeEmuparadise", $("#enhanced-prioritize_ep").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"))
        })
        $(document).on('change', '#enhanced-glassEffect', function () {
            GM_setValue("enableGlassEffect", $("#enhanced-glassEffect").is(":checked"))
        })



    }

    // =========================================
    //               Game Set Page
    // =========================================
    // Regex: 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 div:eq(2) > img").attr("src")
        var tag = "";
        var gameImg = $("#achievement img[alt=\"In-game screenshot\"]").attr("src");
        var rgxTag = /\~(.*?)\~/g;

        // GM_log("Icon:" + icon)

        // 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 = [];
        var resultsDlcs = [];

        // 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

        // =========================================
        //              Divs Insertion
        // =========================================

        // Set custom background
        if (gameImg != "https://media.retroachievements.org/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"
            })
        }

        // Change to glass background
        if (enableGlassEffect) {
            $(":root").attr("style", "--box-bg-color: rgba(35, 35, 35, 0.95);")
            $("#leftcontainer").attr("style", "background: var(--box-bg-color);")   
            $("#rightcontainer").attr("style", "background: var(--box-bg-color);")   
        }

        // Change top nav to body color
        $(".brand-top").addClass("bg-body")
        // Change header background to transparent
        // $(".brand-top + nav").removeClass("bg-body")

        // Prepare divs
        $("aside > .gamealts").first().before("<div id='romsdl'></div>")
        $("aside > .gamealts").first().before("<div id='speedruncom'></div>")
        var divRoms = $("#romsdl");
        var divSpeedruncom = $("#speedruncom");
        divRoms.css("margin-top", "1em")
        divSpeedruncom.css("margin", "1em 0em");

        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 ((isAvailable && tag == "") || (console == RAConsole.ARCADE && tag != "")) {
                var promise;
                
                // Deprecated feature to find roms for hacks and homebrews.
                // if (tag != "" && console != RAConsole.ARCADE) {
                //     promise = searchCustom();
                //     collection.name = "Custom";
                // } 


                // Search through Emuparadise
                if (enableEmuparadise && prioritizeEmuparadise) {
                    collection.name = "Emuparadise";
                    collection.url = "https://www.emuparadise.me/roms-isos-games.php"
                    promise = searchEmuparadise();
                    
                } else {
                    // resolve the promise so the function don't stop working
                    promise = Promise.resolve()
                }

                // Search through archives.org
                promise.then(() => {
                    if (results.length == 0) {

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

                        } else if (console == RAConsole.PS1) {
                            collection.name = "[REDUMP] Disc Image Collection: Sony - Sony PlayStation";
                            collection.url = "https://archive.org/details/redump.psx"
                            return searchArchive("https://archive.org/download/redump.psx")
                                .then(() => {
                                    if (results.length == 0) return searchArchive("https://archive.org/download/redump.psx.p2");
                                    else return;
                                })
                                .then(() => {
                                    if (results.length == 0) return searchArchive("https://archive.org/download/redump.psx.p3");
                                    else return;
                                })
                                .then(() => {
                                    if (results.length == 0) return searchArchive("https://archive.org/download/redump.psx.p4");
                                    else return;
                                });
        
                        } else if (console == RAConsole.PS2) {
                            collection.name = "PS2 Redump USA CHD";
                            collection.url = "https://archive.org/details/ps2-redump-usa-chd-part-A"
                            // PS2 Redump USA CHD is separated per alphabet, so create a link from it instead of checking each page
                            let ps2link = "https://archive.org/download/ps2-redump-usa-chd-part-" + simplify_title(game).charAt(0).toUpperCase()
                            return searchArchive(ps2link)
                                .then(() => {
                                    if (results.length == 0) return searchArchive("https://archive.org/download/ps2chd1");
                                    else return;
                                })
                                .then(() => {
                                    if (results.length == 0) return searchArchive("https://archive.org/download/ps2chd2");
                                    else return;
                                })
                                .then(() => {
                                    if (results.length == 0) return searchArchive("https://archive.org/download/ps2chd3");
                                    else return;
                                })
                                .then(() => {
                                    if (results.length == 0) return searchArchive("https://archive.org/download/ps2chd4");
                                    else return;
                                })
                                .then(() => {
                                    if (results.length == 0) return searchArchive("https://archive.org/download/ps2chd5");
                                    else return;
                                })
                        }
                        
                        else if (console == RAConsole.PSP) {
                            collection.name = "[REDUMP] Disc Image Collection: 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) {
                                        collection.name = "PSN Collection By Ghostware"
                                        collection.url = "https://archive.org/download/PSNCollectionByGhostware"
                                        return searchArchive("https://archive.org/download/PSNCollectionByGhostware");
                                    }
                                    else return;
                                })

                        } else if (console == RAConsole.SATURN) {
                            collection.name = "Redump Sega Saturn 2018";
                            collection.url = "https://archive.org/download/SegaSaturn2018July10"
                            return searchArchive("https://archive.org/download/SegaSaturn2018July10");
        
                        } else if (console == RAConsole.DREAMCAST) {
                            collection.name = "[REDUMP] Disc Image Collection: Sega  - Sega 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 Mega CD & Sega CD";
                            collection.url = "https://archive.org/download/redump.sega_megacd-segacd"
                            return searchArchive("https://archive.org/download/redump.sega_megacd-segacd");
        
                        } else if (console == RAConsole.NEOGEOCD) {
                            collection.name = "[REDUMP] Disc Image Collection: SNK - Neo Geo CD";
                            collection.url = "https://archive.org/download/redump.ngcd.revival"
                            return searchArchive("https://archive.org/download/redump.ngcd.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.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.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 = "Apple 2 TOSEC 2012"
                            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 2016"
                            collection.url = "https://archive.org/download/No-Intro-Collection_2016-01-03_Fixed"
                            return searchNoIntro2016();
                        }
                    // exit if already has results
                    } else return;
                    
                })

                .then(() => {
                    if (enableEmuparadise && results.length == 0) {
                        collection.name = "Emuparadise";
                        collection.url = "https://www.emuparadise.me/roms-isos-games.php"
                        return searchEmuparadise();
                    }
                })

                .then(() => {
                    if (console == RAConsole.PSP) 
                    return searchArchiveDlc("https://archive.org/download/PSP-DLC/%5BNo-Intro%5D%20PSP%20DLC/")
                })

                .then(() => {
                    // GM_log(results);
                    if (results.length > 0) createDownloads()
                    if (resultsDlcs.length > 0) createDlcs()
                })
                
                
            } else {
                // divRoms.append("<b>Searching roms for this system not supported.</b>");
                GM_log("Searching roms for this system not supported.")
            }
        }

        // =========================================
        //       Create Content to the Page
        // =========================================

        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 + ">" + removeExt(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 createDlcs() {
            divRoms.append(`<h3 style="margin-top: 1em;">DLCs</h3>`);

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

            for (var i = 0; i < resultsDlcs.length; i++) {
                let dlLink = (resultsDlcs[i].url).replace(/ /g, "%20");
                divRoms.append("<a class='dl-links' href=" + dlLink + ">" + removeExt(resultsDlcs[i].name) + "</a>");
            }
            if (collection.url != "") divRoms.append(`<div style="margin-top:1em;">From <a href=https://archive.org/download/PSP-DLC/%5BNo-Intro%5D%20PSP%20DLC>PSP-DLC (No-Intro)</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; margin: 1em 0em 1em 0em"
            //                                  src=${srLogo}></img>
            //                         </a>`)
            // GM_log(srRuns)
            // GM_log(srRuns.length)
            if (srRuns.length > 0) {
                srRuns.forEach((runsData) => {
                    // GM_log(runsData.link)
                    divSpeedruncom.append(`<div><a href=${runsData.link}>${runsData.category}:</a> ${runsData.time} by ${runsData.runner}</div>`)
                });
            } else {
                divSpeedruncom.append("<div>Couldn't find this game on Speedrun.com</div>")
            }
        }

        function createVideo() {
            if (srVideoUrl != "")
                GM_log("Creating video with URL: " + srVideoUrl)
                $("#achievement").children().eq(3)
                .after($(`<iframe style="display: block; width: 100%; height:315px; padding-bottom: 1em"
                                    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
            // GM_log(srSearchUrl)
            return new Promise((resolve, reject) => {

                GM_xmlhttpRequest({
                    method: "GET",
                    url: srSearchUrl,
                    onload: function (gamesResponse) {
                        let gamesData = JSON.parse(gamesResponse.response).data
                        // GM_log("Games: " + gamesData)
                        if (gamesData.length > 0) {
                            srGamelink = gamesData[0].weblink;
                            srGameId = gamesData[0].id;
                            // srLogo = gamesData[0].assets.logo.uri; don't work as of now, possibly due to API changes on Speedrun.com
                            resolve(gamesData[0].links[3].uri);
                        } else {
                            throw `Couldn't find this game on Speedrun.com (${srSearchUrl}).`;
                        }
                    }
                })
            })

            .then((link) => {
                return new Promise((resolve, reject) => {
                    GM_xmlhttpRequest({
                        method: "GET",
                        url: link,
                        onload: function (response) {
                            let categoriesData = JSON.parse(response.response).data
                            resolve(categoriesData)
                        }
                    })
                    
                })
            }, err => {
                throw (err)
            })



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

                    // Runs
                    return new Promise((resolve, reject) => {
                        GM_xmlhttpRequest({
                            method: "GET",
                            url: srRoot + `runs?game=${srGameId}&category=${category.id}&status=verified`,
                            onload: function (runsResponse) {
                                let runsData = JSON.parse(runsResponse.response).data[0];
                                if (runsData != undefined && runsData.status.status != "rejected") {
                                    // Pick a eligible speedrun video and use it as the video showcase on the page
                                    // GM_log(runsData.videos)
                                    if (srVideoUrl == "" && runsData.videos)
                                        srVideoUrl = toEmbedUrl(runsData.videos.links[0].uri);
                                    // GM_log(isVideoUrl(runsData.videos.links[0].uri))
                                    // if (isVideoUrl(runsData.videos.links[0].uri))
                                    totalRuns++;
                                }
                                resolve(runsData)
                            }
                        })
                    })
                    .then((runsData) => {
                        if (runsData == undefined) return false;
                        // TODO: Resolve useless request if user is a guest.
                        let isGuest = runsData.players[0].rel == "guest" ? true : false;

                        return new Promise((resolve, reject) => {
                            GM_xmlhttpRequest({
                                method: "GET",
                                url: srRoot + "users/" + runsData.players[0].id,
                                onload: function (userRes) {
                                    userData = JSON.parse(userRes.response).data
                                    srRuns.push({
                                        category: category.name,
                                        time: parseIso8601(runsData.times.primary),
                                        runner: isGuest ? runsData.players[0].name : userData.names.international,
                                        link: runsData.videos ? runsData.videos.links[0].uri : ""
                                    })
                                    // GM_log(srRuns.length)
                                    // GM_log(totalRuns)
                                    resolve(true)
                                }
                            })
                        }) 
                    })
                }))
                .then(() => {
                    // GM_log("Last step from promises")
                    // GM_log(srRuns)
                    if (enableSpeedrun) createSpeedrun();
                    if (enableGameplayVideo) createVideo();
                })
            })
        }



        // Functional
        //     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((runsResponse) => {
        //                             let runsData = runsResponse.data[0];
        //                             if (runsData != undefined && runsData.status.status != "rejected") {
        //                                 // GM_log(runsData.videos)
        //                                 if (srVideoUrl == "" && runsData.videos)
        //                                     srVideoUrl = toEmbedUrl(runsData.videos.links[0].uri);
        //                                 totalRuns++;
        //                                 return runsData;
        //                             }
        //                         })

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

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

        
        // Broken??
        //     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((runsResponse) => {
        //                             let runsData = runsResponse.data[0];
        //                             if (runsData != undefined && runsData.status.status != "rejected") {
        //                                 // GM_log(runsData.videos)
        //                                 if (srVideoUrl == "" && runsData.videos)
        //                                     srVideoUrl = toEmbedUrl(runsData.videos.links[0].uri);
        //                                 totalRuns++;
        //                                 return runsData;
        //                             }
        //                         })

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


        // }
        
        // =========================================
        //      Archives.org (Arcade) Function
        // =========================================
        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.toLowerCase().includes("hack")) return;
                            if (tag == "Hack" && !name.toLowerCase().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.toLowerCase().includes("hack")) return;
                                if (tag == "Hack" && !name.toLowerCase().includes("hack")) return;
                                if (compare(name, game)) {
                                    results.push({
                                        name: name,
                                        url: mainDir + $(this).attr('name') + ".zip"
                                    });
                                }
                            })
                        }
                        resolve(true);
                    }
                })
            });
        }

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

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

            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";
                secondaryConsoleDir = "SNK - Neo Geo Pocket Color";
            }

            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);
                                }
                            })
                        })
                    }
                })
        }

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

                        // 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);
                    }
                })
            });
        }

        // =========================================
        //      Archives.org (DLC) Function
        // =========================================
        function searchArchiveDlc(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)) {
                                resultsDlcs.push({
                                    name: title,
                                    url: fullUrl
                                });
                            }
                        });

                        // Normal search
                        if (resultsDlcs.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)) {
                                    resultsDlcs.push({
                                        name: title,
                                        url: fullUrl
                                    });
                                }
                            });
                        }
                        return resolve(true);
                    }
                })
            });
        }

        // =========================================
        //           Emuparadise Function
        // =========================================
        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.GCN) {
                consoleUrl = "Nintendo_Gamecube_ISOs/List-All-Titles/42"
            }

            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.PS2) {
                consoleUrl = "Sony_Playstation_2_ISOs/List-All-Titles/41";
            }

            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);
                    }
                })
            });
        }

        // =========================================
        //             Utility Functions
        // =========================================

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

        function compare(a, b) {
            // GM_log(a)
            let str1 = simplify_title(a);
            let str2 = simplify_title(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 simplify_title(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(/\.(zip|7z)$/,"")
                .replace(/^The /g, '')
                .replace(", The", '')
                .replace(/'s/gs, '')
                .replace('&', 'and')
                .replace(/:|-| |\.|!|\?|\/|'/gs, '')
                .replace(/(\r\n|\n|\r)/gs, "") // remove line breaks
                .split('|')[0]
                .replace(',', "")
                
                // remove headers (old)
                // .replace(/\([\w\s]+\)/gs, "")
                // .replace(/\[[\w\s]+\]/gs, "")
                // remove headers (everything inside parenthesis)
                .replace(/\(.+\)/gs, "")
                .replace(/\[.+\]/gs, "")
                .toLowerCase()
        }

        function removeExt(str) {
            return str.replace(/\.(zip|7z|chd)$/,"");
        }

        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 isVideoUrl(url) {
            return (url.includes("www.youtu") || url.includes("www.twitch.tv"))
        }

        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 "";
        }
    }
})();