Greasy Fork is available in English.

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



})();