Seishiin / SteamWorkshopSearch

// ==UserScript==
// @namespace   https://openuserjs.org/users/Seishiin
// @name        SteamWorkshopSearch
// @description Allows you to search for specific mods in your list of subscribed items
// @author      Seishiin
// @version     1.5
// @copyright   2019, Seishiin
// @license     MIT
// @include     /https:\/\/steamcommunity\.com\/id\/.*\/myworkshopfiles\/\?appid=\d+.*&browsefilter=mysubscriptions.*/
// @icon        https://img.icons8.com/color/48/000000/steam.png
// @grant       none
// @noframes
// ==/UserScript==
(function () {
    let iframeNextPage = null;
    let iframeAllMods = null;
    let currentPage = 1;
    const leftContents = document.getElementById("leftContents");
    let modList = null;
    const css = `
    #searchBar {
        width: 251px;
        padding-left: 8px;
        padding-right: 8px;
        background: #101822;
        border-color: #30485c;
    }
    #loader {
        width: 16px;
        height: 16px;
        position: relative;
        bottom: 24px;
        right: 3px;
        float: right;
        border: 4px solid;
        border-radius: 50%;
        border-color: rgb(160, 220, 240) rgb(65, 95, 125) rgb(65, 95, 125);
        animation: spin 2s linear infinite;
    }
    @keyframes spin {
        0% {transform: rotate(0deg);}
        100% {transform: rotate(360deg);}
    }
`;

    ///////////////////////
    // Utility functions //
    ///////////////////////

    RegExp.escape = function (s) {
        return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
    };

    function getTotalPages() {
        let pageinfo = document.getElementsByClassName("workshopBrowsePagingInfo")[0].innerText;
        pageinfo = pageinfo.substring(pageinfo.indexOf("of") + 3, pageinfo.length);
        pageinfo = pageinfo.substring(0, pageinfo.indexOf("entries") - 1);
        // totalPages = Math.ceil(pageinfo / 30);
        return Math.ceil(pageinfo / 30);
    }

    //////////////////////
    // Search functions //
    //////////////////////

    function searchMods(e) {
        let searchTerm = e.srcElement.value;
        let modsFound = 0;
        // escape special characters so it doesn't break the search
        searchTerm = RegExp.escape(searchTerm);
        // hide all mods currently displayed
        for (let i = 0; i < leftContents.childElementCount; i++) {
            if (leftContents.children[i].className.includes("workshopItemSubscription")) {
                leftContents.children[i].style.display = "none";
            }
        }
        // add mods matching the search string
        for (let i = 0; i < modList.length; i++) {
            modsFound = getSearchedMods(modList[i], searchTerm, modsFound);
        }
        // update the page info to display the amount of items shown correctly
        document.getElementsByClassName("workshopBrowsePagingInfo")[0].innerText = "Showing " + modsFound + " of " + modList.length + " entries";
        modsFound = 0;
    }

    function getSearchedMods(mod, searchTerm, modsFound) {
        // get the title of the mod
        let modTitle = mod.children[1].children[2].children[0].children[0].innerText;
        // show only divs containing the search term
        let regex = new RegExp(searchTerm, "i");
        if (regex.test(modTitle)) {
            document.getElementById(mod.id).style.display = "";
            modsFound++;
        }
        return modsFound;
    }

    //////////////////////////////////////
    // Modlist initialization functions //
    //////////////////////////////////////

    function initializeModList(totalPages) {
        // create an iframe to load the next page with mods
        iframeNextPage = document.createElement("iframe");
        iframeNextPage.style.display = "none";

        // go through each page and insert all mods into "iframeAllMods"
        iframeNextPage.addEventListener("load", function () {
            let currentModList = this.contentWindow.document.getElementsByClassName("workshopItemSubscription");
            for (let i = 0; i < currentModList.length; i++) {
                iframeAllMods.contentWindow.document.body.append(currentModList[i]);
            }
            // append the modlist after it has beend completely initialized
            if (currentPage > totalPages) {
                appendModList();
            }
            if (currentPage <= totalPages) {
                updateIFrameSource();
            }
        });
        document.body.appendChild(iframeNextPage);

        // create an iframe to store all mods loaded in "iframeNextPage"
        iframeAllMods = document.createElement("iframe");
        iframeAllMods.style.display = "none";
        iframeAllMods.src = "about:blank";
        document.body.appendChild(iframeAllMods);
    }

    // change the url of "iframeNextPage" to the next page
    function updateIFrameSource() {
        let baseURL
        if (iframeNextPage.baseURI.includes("&p="))
            baseURL = iframeNextPage.baseURI.substring(0, iframeNextPage.baseURI.indexOf("&p="));
        else
            baseURL = iframeNextPage.baseURI;

        iframeNextPage.src = baseURL + "&p=" + currentPage + "&numperpage=30";
        currentPage++;
    }

    function appendModList() {
        // load mods into the list
        modList = iframeAllMods.contentWindow.document.getElementsByClassName("workshopItemSubscription");
        // remove all mods from the list
        for (let i = 0; i < leftContents.childElementCount; i++) {
            if (leftContents.children[i].className.includes("workshopItemSubscription")) {
                // if we do it only once it doesn't work
                leftContents.children[i].remove();
                leftContents.children[i].remove();
            }
        }
        // insert all mods from modList into the website
        // modList = iframeAllMods.contentWindow.document.getElementsByClassName("workshopItemSubscription");
        for (let i = 0; i < modList.length; i++) {
            // if we don't clone this, the corresponding element gets removed from modList
            let clone = modList[i].cloneNode(true);
            leftContents.append(clone);
        }
        // update the paging info
        document.getElementsByClassName("workshopBrowsePagingInfo")[0].innerText = "Showing " + modList.length + " of " + modList.length + " entries";
        // activate the searchbar and hide the loader after the modlist is loaded
        document.getElementById("searchBar").disabled = false;
        document.getElementById("loader").style.display = "none";
    }

    ////////////////////////////////
    // DOM modification functions //
    ////////////////////////////////

    function hideMods() {
        for (let i = 0; i < leftContents.childElementCount; i++) {
            if (leftContents.children[i].className.includes("workshopItemSubscription")) {
                leftContents.children[i].style.display = "none"
            }
        }
    }

    // remove the workshop banner and paginators + hrs, update the paging info
    function modifyDivs() {
        document.getElementsByClassName("workshopBrowsePagingControls")[0].style.display = "none";
        document.getElementsByClassName("workshopBrowsePaging")[0].remove();
        document.getElementsByClassName("greyHR2")[1].remove();
        document.getElementsByClassName("greyHR2")[0].remove();
        document.getElementsByClassName("workshopBrowsePagingWithBG")[0].style.marginBottom = "11px";
        document.getElementsByClassName("workshopBrowsePagingInfo")[0].innerText = "Loading modlist...";
    }

    function addSearchbar() {
        let searchBar = document.createElement("input");
        searchBar.className = "searchTextContainer";
        searchBar.id = "searchBar"
        searchBar.disabled = true;
        searchBar.addEventListener("input", searchMods);

        document.getElementsByClassName("rightSectionHolder")[4].appendChild(searchBar);
    }

    function addLoader() {
        // keyframe animation
        let style = document.createElement('style');
        style.type = 'text/css';
        style.appendChild(document.createTextNode(css));
        document.head.appendChild(style);

        // loader
        let loader = document.createElement("div");
        loader.id = "loader";
        document.getElementsByClassName("workshopBrowsePagingWithBG")[0].appendChild(loader);
    }

    //////////////////
    // Start script //
    //////////////////

    function init() {
        // get the total amount of page if showing 30 items per page
        let totalPages = getTotalPages();
        // remove and update some divs
        modifyDivs();
        // hide mods before loading the modlist to avoid confusion
        hideMods();
        document.getElementsByClassName("rightSectionTopTitle")[0].innerText = "Search for mods:";
        document.getElementsByClassName("rightDetailsBlock")[3].remove();
        addSearchbar();
        // inform the user that the modlist is still loading
        addLoader();
        // after all that is done start loading the mods
        initializeModList(totalPages);
    }

    window.addEventListener('load', function () {
        init()
    }, false);
})();