Inventory_Unstack_Helper

Steam inventory unstack helper (split stacked items into single items)

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name               Inventory_Unstack_Helper
// @description        Steam inventory unstack helper (split stacked items into single items)
// @match              https://steamcommunity.com/profiles/*/inventory*
// @match              https://steamcommunity.com/id/*/inventory*
// @icon               https://blog.chrxw.com/favicon.ico
// @forkFrom           https://greasyfork.org/en/scripts/498326-inventory-stack-helper/
// @version 1.2
// @namespace https://greasyfork.org/users/1542473
// ==/UserScript==

(() => {
    "use strict";

    let GappId = 0;
    let GcontextId = 2;

    const delay = 300;    // delay between API calls in ms
    const amount = 1000;  // how many items to request from inventory API

    // Read WebAPI access token from page
    let token = document
        .querySelector("#application_config")
        ?.getAttribute("data-loyalty_webapi_token");
    if (token) {
        token = token.replace(/"/g, "");
    } else {
        ShowAlertDialog("Info", "Failed to read WebAPI token. Try re-logging into Steam.");
        return;
    }

    const GObjs = addPanel();
    doFitInventory();

    // ---------------------------------------------------------------------

    function genBtn(text, title, onclick) {
        const btn = document.createElement("button");
        btn.textContent = text;
        btn.title = title;
        btn.className = "ish_button";
        btn.addEventListener("click", onclick);
        return btn;
    }

    function genSpan(text) {
        const span = document.createElement("span");
        span.textContent = text;
        return span;
    }

    function addPanel() {
        const btnArea = document.querySelector("div.inventory_links");
        if (!btnArea) {
            console.warn("[Inventory_Unstack_Helper] Cannot find inventory link panel.");
            return {};
        }

        const container = document.createElement("div");
        container.className = "ish_container";
        btnArea.insertBefore(container, btnArea.firstChild);

        const hiddenContainer = document.createElement("div");
        hiddenContainer.style.display = "none";
        hiddenContainer.appendChild(genSpan("Inventory"));
        container.appendChild(hiddenContainer);

        const btnUnstack = genBtn(
            "Unstack",
            "Unstack all stacked items in the currently selected inventory (this may take a while).",
            doUnstack
        );

        const spStatus = genSpan("");

        container.appendChild(btnUnstack);
        container.appendChild(spStatus);

        document
            .querySelectorAll("div.games_list_tabs>a")
            .forEach((tab) => {
                tab.addEventListener("click", doFitInventory);
            });

        document
            .querySelector("#responsive_inventory_select")
            ?.addEventListener("change", doFitInventory);

        return { btnUnstack, spStatus };
    }


    function doFitInventory() {
        const { appid, contextid } = g_ActiveInventory;

        GappId = appid ?? "0";
        GcontextId = contextid ?? "2";

        if (GappId == 753) {
            GcontextId = "6";
        }

        console.log("[Inventory_Unstack_Helper] app/context:", GappId, GcontextId);
    }

    function doUnstack() {
        const { btnUnstack, spStatus } = GObjs;

        spStatus.textContent = "Unstacking… [Loading inventory]";
        if (btnUnstack) btnUnstack.style.display = "none";

        loadInventory(GappId, GcontextId, amount)
            .then(async (inv) => {
                if (!inv) {
                    ShowAlertDialog(
                        "Info",
                        "Failed to read inventory. Please check AppID / ContextID or try again later."
                    );
                    return;
                }

                const { assets } = inv;
                if (!assets) {
                    ShowAlertDialog(
                        "Info",
                        "Inventory response did not contain assets. Please try again later."
                    );
                    return;
                }

                const itemGroup = [];
                let totalReq = 0;

                for (const item of assets) {
                    const { amount } = item;

                    if (GappId === 753) continue;

                    const count = parseInt(amount, 10);
                    if (count > 1) {
                        item.amount = count;
                        itemGroup.push(item);
                        totalReq += count - 1;
                    }
                }

                if (totalReq > 0) {
                    const totalType = itemGroup.length;
                    spStatus.textContent =
                        `Unstacking… [Types 0/${totalType} Requests 0/${totalReq} 0.00%]`;

                    let type = 1;
                    let req = 1;

                    for (const item of itemGroup) {
                        for (let i = 1; i < item.amount; i++) {
                            await unStackItem(GappId, item.assetid, 1);
                            await asyncDelay(delay);
                            const percent = ((100 * req) / totalReq).toFixed(2);
                            spStatus.textContent =
                                `Unstacking… [Types ${type}/${totalType} ` +
                                `Requests ${req++}/${totalReq} ${percent}%]`;
                        }
                        type++;
                    }
                }

                ShowAlertDialog(
                    "Info",
                    totalReq > 0
                        ? "Unstack operation completed."
                        : "No stacked items found to unstack."
                );
            })
            .catch((err) => {
                ShowAlertDialog(
                    "Info",
                    "Error while reading inventory. Details:\r\n" + err
                );
                console.error("[Inventory_Unstack_Helper] loadInventory error:", err);
            })
            .finally(() => {
                spStatus.textContent = "";
                if (btnUnstack) btnUnstack.style.display = null;

                // Reload current inventory so user sees updated items
                if (window.g_ActiveInventory && g_ActiveInventory.m_owner) {
                    try {
                        g_ActiveInventory.m_owner.ReloadInventory(GappId, GcontextId);
                    } catch (e) {
                        console.warn(
                            "[Inventory_Unstack_Helper] Failed to reload inventory:",
                            e
                        );
                    }
                }
            });
    }

    // ---------------------------------------------------------------------

    function asyncDelay(ms) {
        return new Promise((resolve) => setTimeout(resolve, ms));
    }

    function loadInventory(appId, contextId, count) {
        return new Promise((resolve, reject) => {
            const url = `https://steamcommunity.com/inventory/${g_steamID}/${appId}/${contextId}?l=${g_strLanguage}&count=${count}`;

            const attempt = (triesLeft) => {
                fetch(url)
                    .then((response) => response.json())
                    .then((json) => {
                        if (json) {
                            resolve(json);
                        } else if (triesLeft > 1) {
                            attempt(triesLeft - 1);
                        } else {
                            reject("Inventory JSON is empty.");
                        }
                    })
                    .catch((err) => {
                        if (triesLeft > 1) {
                            attempt(triesLeft - 1);
                        } else {
                            console.error(err);
                            reject(`Failed to load inventory: ${err}`);
                        }
                    });
            };

            attempt(3);
        });
    }

    function unStackItem(appId, itemAssetId, quantity) {
        return new Promise((resolve, reject) => {
            fetch("https://api.steampowered.com/IInventoryService/SplitItemStack/v1/", {
                method: "POST",
                body: `access_token=${encodeURIComponent(
                    token
                )}&appid=${encodeURIComponent(
                    appId
                )}&itemid=${encodeURIComponent(
                    itemAssetId
                )}&quantity=${encodeURIComponent(
                    quantity
                )}&steamid=${encodeURIComponent(g_steamID)}`,
                headers: {
                    "content-type":
                        "application/x-www-form-urlencoded; charset=UTF-8",
                },
            })
                .then((response) => response.json())
                .then((json) => {
                    const { success } = json || {};
                    if (!success) {
                        console.warn(
                            "[Inventory_Unstack_Helper] SplitItemStack returned:",
                            json
                        );
                    }
                    resolve(success);
                })
                .catch((err) => {
                    console.error(err);
                    reject(`Failed to unstack item: ${err}`);
                });
        });
    }

    GM_addStyle(`
      div.ish_container {
        display: inline;
      }
      div.ish_container > * {
        margin-right: 5px;
      }
    `);
})();