Neopets Inventory Overhauls

Adds quick stock features directly into the inventory screen, including on stacked items, and removes the forced refresh when performing actions on items.

// ==UserScript==
// @name         Neopets Inventory Overhauls
// @namespace    http://neopat.ch
// @license      GNU GPLv3
// @version      2.02
// @description  Adds quick stock features directly into the inventory screen, including on stacked items, and removes the forced refresh when performing actions on items.
// @author       You
// @match        https://www.neopets.com/inventory.phtml*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=neopets.com
// @grant        none
// ==/UserScript==

(function() {

    // Function to check for grid items
    function checkForGridItems() {
        const gridItems = document.querySelectorAll('.grid-item');
        if (gridItems.length > 0) {

            //Check if inventory is in stacked mode or not
            var parentDiv = document.getElementById('invStack');
            var firstChildDiv = parentDiv.getElementsByClassName('stack-icon-container')[0];
            var isStacked = firstChildDiv.classList.contains('invfilter-active');
            if (isStacked) {
                //Stacked inventory
                var arrayindex = 1;
                for (let i = 0; i < gridItems.length; i++) {
                    // Find the first div with class 'item-img' inside the current grid-item
                    const itemImg = gridItems[i].querySelector('.item-img');

                    //Get Item name
                    const itemName = itemImg.getAttribute('data-itemname');


                    //Check radio input element count
                    if (gridItems[i].querySelector('input[type="radio"]')) {
                        radios = gridItems[i].querySelector('input[type="radio"]').length
                    } else {
                        radios = 0;
                    }
                    //Stop adding radios if already added
                    if (radios < window.itemsById[itemName].length) {
                        //Loop over unstacked item data by item name
                        for (let j = 0; j < window.itemsById[itemName].length; j++) {
                            dataObjId = window.itemsById[itemName][j];
                            //build first set of radio inputs
                            if (j < 1) {
                                document.getElementsByClassName('item-subname')[i].innerHTML += `<input type="hidden" name="id_arr[` + arrayindex + `]" value="` + dataObjId + `">
            <center>Quick Stock:<br>
            <img src=https://lel.wtf/shop.png width=15 height=15><input onclick="document.querySelectorAll('input[type=radio].` + itemName.replace(/\s+/g, '-') + `-stock').forEach(radio => radio.checked = true);" class="` + itemName.replace(/\s+/g, '-') + `-stock"  type="radio" name="radio_arr[` + arrayindex + `]" value="stock" ondblclick="this.checked = false; document.querySelectorAll('input[type=radio].` + itemName.replace(/\s+/g, '-') + `-stock').forEach(radio => radio.checked = false);">
            <img src=https://lel.wtf/sdb.png width=15 height=15><input onclick="document.querySelectorAll('input[type=radio].` + itemName.replace(/\s+/g, '-') + `-deposit').forEach(radio => radio.checked = true);" class="` + itemName.replace(/\s+/g, '-') + `-deposit" type="radio" name="radio_arr[` + arrayindex + `]" value="deposit" ondblclick="this.checked = false;  document.querySelectorAll('input[type=radio].` + itemName.replace(/\s+/g, '-') + `-deposit').forEach(radio => radio.checked = false);">
            <img src=https://lel.wtf/discard.png width=15 height=15><input onclick="document.querySelectorAll('input[type=radio].` + itemName.replace(/\s+/g, '-') + `-discard').forEach(radio => radio.checked = true);" class="` + itemName.replace(/\s+/g, '-') + `-discard" type="radio" name="radio_arr[` + arrayindex + `]" value="discard" ondblclick="this.checked = false; document.querySelectorAll('input[type=radio].` + itemName.replace(/\s+/g, '-') + `-discard').forEach(radio => radio.checked = false);">
            </center>`


                                ;

                            } else {
                                //build all other hidden radio inputs
                                document.getElementsByClassName('item-subname')[i].innerHTML += `<input type="hidden" name="id_arr[` + arrayindex + `]" value="` + dataObjId + `">

            <input style="display:none" class="` + itemName.replace(/\s+/g, '-') + `-stock" type="radio" name="radio_arr[` + arrayindex + `]" value="stock" ondblclick="this.checked = false; checkall[0].checked = false;">
            <input style="display:none" class="` + itemName.replace(/\s+/g, '-') + `-deposit" type="radio" name="radio_arr[` + arrayindex + `]" value="deposit" ondblclick="this.checked = false; checkall[0].checked = false;">
            <input style="display:none" class="` + itemName.replace(/\s+/g, '-') + `-discard" type="radio" name="radio_arr[` + arrayindex + `]" value="discard" ondblclick="this.checked = false; checkall[0].checked = false;">
           `;



                            }

                            arrayindex += 1;
                        }
                    }

                }
            } else {

                //Non stacked inventory
                for (let i = 0; i < gridItems.length; i++) {
                    // Find the first div with class 'item-img' inside the current grid-item
                    const itemImg = gridItems[i].querySelector('.item-img');


                    const dataObjId = itemImg.getAttribute('data-objid');

                    arrayindex = i + 1;
                    if (!gridItems[i].querySelector('input[type="radio"]')) {
                        document.getElementsByClassName('item-subname')[i].innerHTML += `<input type="hidden" name="id_arr[` + arrayindex + `]" value="` + dataObjId + `">
            <center>Quick Stock:<br>
            <img src=https://lel.wtf/shop.png width=15 height=15><input type="radio" name="radio_arr[` + arrayindex + `]" value="stock" ondblclick="this.checked = false; checkall[0].checked = false;">
            <img src=https://lel.wtf/sdb.png width=15 height=15><input type="radio" name="radio_arr[` + arrayindex + `]" value="deposit" ondblclick="this.checked = false; checkall[0].checked = false;">
            <img src=https://lel.wtf/discard.png width=15 height=15><input type="radio" name="radio_arr[` + arrayindex + `]" value="discard" ondblclick="this.checked = false; checkall[0].checked = false;">
            </center>`;
                    }

                }




            }




        }
    }

    function quickerstock() {
        //Check if inventory is stacked or unstacked
        var parentDiv = document.getElementById('invStack');
        var firstChildDiv = parentDiv.getElementsByClassName('stack-icon-container')[0];
        var isStacked = firstChildDiv.classList.contains('invfilter-active');
        if (isStacked) {

            //Ajax request for unstacked inventory data pulled from https://images.neopets.com/themes/h5/common/js/inventory.js with modified success function
            //Initiated once per click of "quicker stock" to populate needed stack data
            $.ajax({
                type: "POST",
                url: "https://www.neopets.com/np-templates/ajax/inventory.php?itemType=np&alpha=&itemStack=0&action=",
                success: function(response) {

                    var htmlString = response;


                    // Turn HTML string into traversable DOM
                    var parser = new DOMParser();
                    var doc = parser.parseFromString(htmlString, 'text/html');

                    // Find all div elements with the class 'item-img'
                    var items = doc.querySelectorAll('.item-img');

                    // Initialize an empty object
                    window.itemsById = {};

                    // Loop through each item and extract data
                    items.forEach(item => {
                        var itemName = item.getAttribute('data-itemname');
                        var objId = item.getAttribute('data-objid');

                        // Check if the item name key already exists
                        if (!window.itemsById[itemName]) {
                            window.itemsById[itemName] = [];
                        }

                        // Push the objId into the array for the corresponding item name
                        window.itemsById[itemName].push(objId);
                    });

                    //If invisible frames and submit button not already added
                    if (!document.getElementById('updateframe')) {
                        //Add invisible frame for form to POST to and submit button
                        document.getElementsByClassName('inv-items')[0].innerHTML = `
                                <iframe id="updateframe" name="updateframe" style="display:none"></iframe><form name="update" id="update" action="inventory.phtml" target="updateframe"><input type=hidden name=refresh></input></form><form name="quickstock" action="process_quickstock.phtml" method="post" target="refreshframe"><input type="hidden" name="buyitem" value="0">
                                <input class="button-default__2020 button-yellow__2020 btn-single__2020" type="submit" value="Submit">` + document.getElementsByClassName('inv-items')[0].innerHTML;
                    }


                    // Set an interval to repeatedly check for the elements
                    intervalId = setInterval(checkForGridItems, 500); // checks every 500 milliseconds (half a second)
                }
            });

        } else {
            //If invisible frames and submit button not already added
            if (!document.getElementById('updateframe')) {
                //Add invisible frame to POST to and submit button
                document.getElementsByClassName('inv-items')[0].innerHTML = `
                                <iframe id="updateframe" name="updateframe" style="display:none"></iframe><form name="update" id="update" action="inventory.phtml" target="updateframe"><input type=hidden name=refresh></input></form><form name="quickstock" action="process_quickstock.phtml" method="post" target="refreshframe"><input type="hidden" name="buyitem" value="0">
                                <input class="button-default__2020 button-yellow__2020 btn-single__2020" type="submit" value="Submit">` + document.getElementsByClassName('inv-items')[0].innerHTML;
            }


            // Set an interval to repeatedly check for the elements
            intervalId = setInterval(checkForGridItems, 500); // checks every 1000 milliseconds (1 second)
        }
    }



    //Minor style fix so that the three radio buttons all fit on one line
    var style = document.createElement("style");
    style.type = "text/css";
    style.innerHTML = `
       .grid-item {
    width: 130px;
    }

      `;

    document.getElementsByTagName("head")[0].appendChild(style);




    //If running in top window
    if (window.self === window.top) {
        //Create Iframe
        var refreshframe = document.createElement("iframe");
        refreshframe.name = "refreshframe";
        refreshframe.id = "refreshframe";
        refreshframe.src = "https://none"
        refreshframe.style.display = 'none';
        document.getElementsByTagName("body")[0].appendChild(refreshframe);

        document.getElementsByClassName('inv-menulinks')[0].innerHTML = `
          <li>
			<a id="quickerstock" style="cursor: pointer;">Quicker Stock</a>
		</li>` + document.getElementsByClassName('inv-menulinks')[0].innerHTML;



        document.getElementById('quickerstock').addEventListener('click', function() {
            quickerstock();
        });



        // Function to attach the load event listener to the iframe
        function attachLoadListener() {
            refreshframe = document.getElementById('refreshframe');
            // Ensure the iframe is provided and it's not null
            if (!refreshframe) return;
            // Attach the load event listener to the iframe
            refreshframe.addEventListener('load', function() {
                //If iframe is in the inventory page
                if (document.getElementById('refreshframe').contentWindow.document.location != "https://none") {
                    //Update inventory when iframe has loaded
                    if (document.getElementById('refreshframe').contentWindow.document.location == "https://www.neopets.com/inventory.phtml") {

                        setTimeout(updateInvTab2, 1000);
                        //Then check for REs
                        setTimeout(findre, 1200);
                        intervalId = setInterval(checkForGridItems, 1000); // checks every 1000 milliseconds (1 second)

                    } else {

                        document.getElementById('update').submit();
                        setTimeout(updateInvTab2, 1000);
                        //Then check for REs
                        setTimeout(findre, 1200);
                        intervalId = setInterval(checkForGridItems, 500); // checks every 1000 milliseconds (1 second)
                    }

                    document.getElementById('refreshframe').contentWindow.document.location = 'https://none';
                }
            });




        }
        //Attach event listener to iframe
        refreshframe = document.getElementById('refreshframe');
        attachLoadListener(refreshframe);
    }
    //Point existing refresh links to iframe to avoid the use of fetch();
    document.querySelector("#refreshshade__2020").target = "refreshframe";
    document.querySelector("#invResult > div.popup-header__2020 > a").target = "refreshframe";
    //Function to populate item div background images since the built-in function likes to fail for NC item tabs
    function genimages() {
        var divs = document.querySelectorAll('div.item-img');
        //Loop over items
        divs.forEach(function(div) {
            //If it doesn't have a gif set as the background image
            if (!div.style.backgroundImage.includes('.gif')) {
                //set one based on the data-src attribute
                var dataSrc = div.getAttribute('data-src');
                div.setAttribute('style', "background-image: url('" + dataSrc + "');");
            }
        });
    }
    //Function to detect REs in the iframe and display them on the top page
    function findre() {
        //if RE is found
        if (document.getElementById('refreshframe').contentWindow.document.getElementsByClassName("randomEvent")[0] || document.getElementById('refreshframe').contentWindow.document.getElementById("shh_prem_bg") || document.getElementById('refreshframe').contentWindow.document.getElementById("shh_prem_bg")) {
            //Grab all HTML associated with the RE
            var restylesheet = document.getElementById('refreshframe').contentWindow.document.getElementById('navsub-buffer__2020').nextElementSibling;
            var restyle = restylesheet.nextElementSibling;
            var rescript = restyle.nextElementSibling;
            var replaceholder = rescript.nextElementSibling;
            var remain = replaceholder.nextElementSibling;
            var rehtml = restylesheet.outerHTML + restyle.outerHTML + rescript.outerHTML + replaceholder.outerHTML + remain.outerHTML;
            //Inject the RE into the top page
            document.getElementById('navsub-buffer__2020').insertAdjacentHTML('afterend', '<div id="injectedre">' + rehtml + '</div>');
        }
        //if RE is found
        if (document.getElementById('updateframe').contentWindow.document.getElementsByClassName("randomEvent")[0] || document.getElementById('updateframe').contentWindow.document.getElementById("shh_prem_bg") || document.getElementById('updateframe').contentWindow.document.getElementById("shh_prem_bg")) {
            //Grab all HTML associated with the RE
            var restylesheet = document.getElementById('refreshframe').contentWindow.document.getElementById('navsub-buffer__2020').nextElementSibling;
            var restyle = restylesheet.nextElementSibling;
            var rescript = restyle.nextElementSibling;
            var replaceholder = rescript.nextElementSibling;
            var remain = replaceholder.nextElementSibling;
            var rehtml = restylesheet.outerHTML + restyle.outerHTML + rescript.outerHTML + replaceholder.outerHTML + remain.outerHTML;
            //Inject the RE into the top page
            document.getElementById('navsub-buffer__2020').insertAdjacentHTML('afterend', '<div id="injectedre">' + rehtml + '</div>');
        }
    }
    //Cleanup stuff
    function cleanup() {
        //Remove any previously injected REs
        if (document.querySelector("#injectedre")) {
            document.querySelector("#injectedre").remove();
        }
        //Remove modal and shade overlay
        document.querySelector("#invResult").style.display = 'none';
        document.querySelector("#refreshshade__2020 > div").style.visibility = 'hidden';
    }
    //Call the cleanup function when the close button or shade is clicked
    document.querySelector("#refreshshade__2020").addEventListener('click', function() {
        cleanup();
    });
    document.getElementsByClassName('inv-popup-exit')[1].addEventListener('click', function() {
        cleanup();
    });
    //Check item images for proper background images 5 times per second
    setInterval(genimages, 200);



})();