Inventory_Unstack_Helper

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

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

You will need to install an extension such as Tampermonkey to install this script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

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