Inventory_Unstack_Helper

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

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==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;
      }
    `);
})();