Greasy Fork is available in English.

Fast_Add_Cart

Add to cart without redirect to cart page, also provide import/export cart feature.

ติดตั้งสคริปต์นี้?
สคริปต์ที่แนะนำของผู้เขียน

คุณอาจชื่นชอบ Hidden_DLC_Helper

ติดตั้งสคริปต์นี้
// ==UserScript==
// @name:zh-CN      Steam快速添加购物车
// @name            Fast_Add_Cart
// @namespace       https://blog.chrxw.com
// @supportURL      https://blog.chrxw.com/scripts.html
// @contributionURL https://afdian.net/@chr233
// @version         4.3
// @description:zh-CN  超级方便的添加购物车体验, 不用跳转商店页, 附带导入导出购物车功能.
// @description     Add to cart without redirect to cart page, also provide import/export cart feature.
// @author          Chr_
// @match           https://store.steampowered.com/*
// @license         AGPL-3.0
// @icon            https://blog.chrxw.com/favicon.ico
// @grant           GM_addStyle
// @grant           GM_setClipboard
// @grant           GM_setValue
// @grant           GM_getValue
// @grant           GM_registerMenuCommand
// ==/UserScript==

(async () => {
    "use strict";

    // 多语言
    const LANG = {
        ZH: {
            langName: "中文",
            changeLang: "修改插件语言",
            facInputBoxPlaceHolder:
                "一行一条, 自动忽略【#】后面的内容, 支持的格式如下: (自动保存)",
            storeLink: "商店链接",
            steamDBLink: "DB链接",
            import: "导入",
            importDesc: "从文本框批量添加购物车(从上到下导入)",
            importDesc2: "当前页面无法导入购物车",
            export: "导出",
            exportDesc: "将购物车内容导出至文本框",
            exportConfirm: "输入框中含有内容, 请选择操作?",
            exportConfirmReplace: "覆盖原有内容",
            exportConfirmAppend: "添加到最后",
            copy: "复制",
            copyDesc: "复制文本框中的内容",
            copyDone: "复制到剪贴板成功",
            reset: "清除",
            resetDesc: "清除文本框和已保存的数据",
            resetConfirm: "您确定要清除文本框和已保存的数据吗?",
            history: "历史",
            historyDesc: "查看购物车历史记录",
            reload: "刷新",
            reloadDesc: "重新读取保存的购物车内容",
            reloadConfirm: "您确定要重新读取保存的购物车数据吗?",
            goBack: "返回",
            goBackDesc: "返回你当前的购物车",
            clear: "清空购物车",
            clearDesc: "清空购物车",
            clearConfirm: "您确定要移除所有您购物车中的物品吗?",
            help: "帮助",
            helpDesc: "显示帮助",
            helpTitle: "插件版本",
            formatError: "格式有误",
            chooseSub: "请选择SUB",
            operation: "操作中……",
            operationDone: "操作完成",
            addCart: "添加购物车",
            addCartTips: "添加到购物车……",
            addCartErrorSubNotFount: "未识别到SubID",
            noSubDesc: "可能尚未发行或者是免费游戏",
            inCart: "在购物车中",
            importingTitle: "正在导入购物车……",
            add: "添加",
            toCart: "到购物车",
            tips: "提示",
            ok: "是",
            no: "否",
            fetchingSubs: "读取可用SUB",
            noSubFound: "未找到可用SUB",
            networkError: "网络错误",
            addCartSuccess: "添加购物车成功",
            addCartError: "添加购物车失败",
            networkRequestError: "网络请求失败",
            unknownError: "未知错误",
            unrecognizedResult: "返回了未知结果",
            batchExtract: "批量提取",
            batchExtractDone: "批量提取完成",
            batchDesc: "AppID已提取, 可以在购物车页批量导入",
            onlyOnsale: " 仅打折",
            onlyOnsaleDesc: "勾选后批量导入时仅导入正在打折的游戏.",
            onlyOnsaleDesc2: "勾选后批量导出时仅导出正在打折的游戏.",
            notOnSale: "尚未打折, 跳过",
        },
        EN: {
            langName: "English",
            changeLang: "Change plugin language",
            facInputBoxPlaceHolder:
                "One line one item, ignore the content after #, support format: (auto save)",
            storeLink: "Store link",
            steamDBLink: "DB link",
            import: "Import",
            importDesc: "Batch add cart from textbox (from top to bottom)",
            importDesc2: "Current page can't import cart",
            export: "Export",
            exportDesc: "Export cart content to textbox",
            exportConfirm: "Textbox contains content, please choose operation?",
            exportConfirmReplace: "Replace original content",
            exportConfirmAppend: "Append to the end",
            copy: "Copy",
            copyDesc: "Copy textbox content",
            copyDone: "Copy to clipboard success",
            reset: "Reset",
            resetDesc: "Clear textbox and saved data",
            resetConfirm: "Are you sure to clear textbox and saved cart data?",
            history: "History",
            historyDesc: "View cart history",
            reload: "Reload",
            reloadDesc: "Reload saved cart date",
            reloadConfirm: "Are you sure to reload saved cart data?",
            goBack: "Back",
            goBackDesc: "Back to your cart",
            clear: "Clear",
            clearDesc: "Clear cart",
            clearConfirm: "Are you sure to remove all items in your cart?",
            help: "Help",
            helpDesc: "Show help",
            helpTitle: "Plugin Version",
            formatError: "Format error",
            chooseSub: "Please choose SUB",
            operation: "Operation in progress……",
            operationDone: "Operation done",
            addCart: "Add cart",
            addCartTips: "Adding to cart……",
            addCartErrorSubNotFount: "Unrecognized SubID",
            noSubDesc: "Maybe not released or free game",
            inCart: "In cart",
            importingTitle: "Importing cart……",
            add: "Add",
            toCart: "To cart",
            tips: "Tips",
            ok: "OK",
            no: "No",
            fetchingSubs: "Fetching available SUB",
            noSubFound: "No available SUB",
            networkError: "Network error",
            addCartSuccess: "Add cart success",
            addCartError: "Add cart failed",
            networkRequestError: "Network request failed",
            unknownError: "Unknown error",
            unrecognizedResult: "Returned unrecognized result",
            batchExtract: "Extract Items",
            batchExtractDone: "Batch Extract Done",
            batchDesc: "AppID list now saved, goto cart page to use batch import.",
            onlyOnsale: " Only on sale",
            onlyOnsaleDesc:
                "If checked, script will ignore games that is not on sale when import cart.",
            onlyOnsaleDesc2:
                "If checked, script will ignore games that is not on sale when export cart.",
            notOnSale: "Not on sale, skip",
        },
    };

    // 判断语言
    let language = GM_getValue("lang", "ZH");
    if (!language in LANG) {
        language = "ZH";
        GM_setValue("lang", language);
    }
    // 获取翻译文本
    const t = (key) => LANG[language][key] || key;

    {
        // 自动弹出提示
        const languageTips = GM_getValue("languageTips", true);
        if (languageTips && language === "ZH") {
            if (!document.querySelector("html").lang.startsWith("zh")) {
                ShowConfirmDialog(
                    "tips",
                    "Fast add cart now support English, switch?",
                    "Using English",
                    "Don't show again"
                )
                    .done(() => {
                        GM_setValue("lang", "EN");
                        GM_setValue("languageTips", false);
                        window.location.reload();
                    })
                    .fail((bool) => {
                        if (bool) {
                            showAlert(
                                "",
                                "You can switch the plugin's language using TamperMonkey's menu."
                            );
                            GM_setValue("languageTips", false);
                        }
                    });
            }
        }
        //注册菜单
        GM_registerMenuCommand(`${t("changeLang")} (${t("langName")})`, () => {
            switch (language) {
                case "EN":
                    language = "ZH";
                    break;
                case "ZH":
                    language = "EN";
                    break;
            }
            GM_setValue("lang", language);
            window.location.reload();
        });
    }

    //获取商店语言和区域
    const { LANGUAGE: storeLanguage, COUNTRY: userCountry } = JSON.parse(document.querySelector("#application_config")?.getAttribute("data-config") ?? "");
    const { webapi_token: accessToken } = JSON.parse(document.querySelector("#application_config")?.getAttribute("data-store_user_config") ?? "");

    const G_Objs = {};

    //初始化
    const pathname = window.location.pathname;
    if (
        pathname === "/search/" ||
        pathname === "/" ||
        pathname.startsWith("/tags/")
    ) {
        //搜索页,主页,标签页
        return;

        // let timer = setInterval(() => {
        //     let containers = document.querySelectorAll(
        //         [
        //             "#search_resultsRows",
        //             "#tab_newreleases_content",
        //             "#tab_topsellers_content",
        //             "#tab_upcoming_content",
        //             "#tab_specials_content",
        //             "#NewReleasesRows",
        //             "#TopSellersRows",
        //             "#ConcurrentUsersRows",
        //             "#TopRatedRows",
        //             "#ComingSoonRows",
        //         ].join(",")
        //     );
        //     if (containers.length > 0) {
        //         for (let container of containers) {
        //             clearInterval(timer);
        //             for (let ele of container.children) {
        //                 addButton(ele);
        //             }
        //             container.addEventListener("DOMNodeInserted", ({ relatedNode }) => {
        //                 if (relatedNode.parentElement === container) {
        //                     addButton(relatedNode);
        //                 }
        //             });
        //         }

        //         const searchBar = document.querySelector(".searchbar>.searchbar_left");
        //         if (searchBar !== null) {
        //             let btn = document.createElement("button");
        //             btn.addEventListener(
        //                 "click",
        //                 (e) => {
        //                     e.preventDefault();
        //                     const savedCart =
        //                         GM_getValue("btnv6_blue_hoverfade btn_small") ?? "";
        //                     const cartItems = savedCart.split("\n");
        //                     const regFull = new RegExp(/((app|a|bundle|b|sub|s)\/(\d+))/);
        //                     const regShort = new RegExp(/^(([\s]*|)(\d+))/);
        //                     const dataMap = new Set();

        //                     for (let line of cartItems) {
        //                         let match = line.match(regFull) ?? line.match(regShort);
        //                         if (match) {
        //                             let [_, link, _1, _2] = match;
        //                             dataMap.add(link);
        //                         }
        //                     }

        //                     const now = new Date().toLocaleString();
        //                     cartItems.push(`========【${now}】=========`);

        //                     const rows = document.querySelectorAll("#search_resultsRows>a");
        //                     for (let row of rows) {
        //                         if (
        //                             row.className.includes("ds_owned") ||
        //                             row.className.includes("ds_ignored")
        //                         ) {
        //                             continue;
        //                         }

        //                         const url = row.href;
        //                         const title =
        //                             row.querySelector("span.title")?.textContent ?? "null";

        //                         let match = url.match(regFull);
        //                         if (match) {
        //                             let [_, link, _1, _2] = match;

        //                             if (!dataMap.has(link)) {
        //                                 cartItems.push(`${link} #${title}`);
        //                             }
        //                         }
        //                     }
        //                     GM_setValue("fac_cart", cartItems.join("\n"));
        //                     const dialog = showAlert(
        //                         t("batchExtractDone"),
        //                         t("batchDesc"),
        //                         true
        //                     );
        //                     setTimeout(() => {
        //                         dialog.Dismiss();
        //                     }, 1500);
        //                 },
        //                 false
        //             );
        //             btn.className = "btnv6_blue_hoverfade btn_small";
        //             btn.innerHTML = `<span>${t("batchExtract")}</span>`;
        //             searchBar.appendChild(btn);
        //         }
        //     }
        // }, 500);
    } else if (
        pathname.startsWith("/publisher/") ||
        pathname.startsWith("/franchise/") ||
        pathname.startsWith("/developer/")
    ) {
        //发行商主页
        return;

        // let timer = setInterval(() => {
        //     let container = document.getElementById("RecommendationsRows");
        //     if (container != null) {
        //         clearInterval(timer);
        //         for (let ele of container.querySelectorAll("a.recommendation_link")) {
        //             addButton(ele);
        //         }
        //         container.addEventListener("DOMNodeInserted", ({ relatedNode }) => {
        //             if (relatedNode.nodeName === "DIV") {
        //                 for (let ele of relatedNode.querySelectorAll(
        //                     "a.recommendation_link"
        //                 )) {
        //                     addButton(ele);
        //                 }
        //             }
        //         });
        //     }
        // }, 500);
    } else if (
        pathname.startsWith("/app/") ||
        pathname.startsWith("/sub/") ||
        pathname.startsWith("/bundle/")
    ) {
        //商店详情页
        return;

        // let timer = setInterval(() => {
        //     let container = document.getElementById("game_area_purchase");
        //     if (container != null) {
        //         clearInterval(timer);
        //         for (let ele of container.querySelectorAll(
        //             "div.game_area_purchase_game"
        //         )) {
        //             addButton2(ele);
        //         }
        //     }
        // }, 500);
    } else if (pathname.startsWith("/wishlist/")) {
        //愿望单页
        return;

        // let timer = setInterval(() => {
        //     let container = document.getElementById("wishlist_ctn");
        //     if (container != null) {
        //         clearInterval(timer);

        //         for (let ele of container.querySelectorAll("div.wishlist_row")) {
        //             addButton3(ele);
        //         }
        //         container.addEventListener("DOMNodeInserted", ({ relatedNode }) => {
        //             if (relatedNode.nodeName === "DIV") {
        //                 for (let ele of relatedNode.querySelectorAll("div.wishlist_row")) {
        //                     addButton3(ele);
        //                 }
        //             }
        //         });
        //     }
        // }, 500);
    } else if (pathname.startsWith("/cart")) {
        //购物车页

        function genBr() {
            return document.createElement("br");
        }
        function genBtn(text, title, onclick) {
            let btn = document.createElement("button");
            btn.textContent = text;
            btn.title = title;
            btn.className = "btn_medium btnv6_blue_hoverfade fac_cartbtns";
            btn.addEventListener("click", onclick);
            return btn;
        }
        function genSpan(text) {
            let span = document.createElement("span");
            span.textContent = text;
            return span;
        }
        function genTxt(value, placeholder) {
            const t = document.createElement("textarea");
            t.className = "fac_inputbox";
            t.placeholder = placeholder;
            t.value = value;
            return t;
        }
        function genChk(name, title, checked = false) {
            const l = document.createElement("label");
            const i = document.createElement("input");
            const s = genSpan(name);
            i.textContent = name;
            i.title = title;
            i.type = "checkbox";
            i.className = "fac_checkbox";
            i.checked = checked;
            l.title = title;
            l.appendChild(i);
            l.appendChild(s);
            return [l, i];
        }

        const savedCart = GM_getValue("fac_cart") ?? "";
        const placeHolder = [
            t("facInputBoxPlaceHolder"),
            `1. ${t("storeLink")}: https://store.steampowered.com/app/xxx`,
            `2. ${t("steamDBLink")}:  https://steamdb.info/app/xxx`,
            "3. appID:       xxx a/xxx app/xxx",
            "4. subID:       s/xxx sub/xxx",
            "5. bundleID:    b/xxx bundle/xxx",
        ].join("\n");

        const inputBox = genTxt(savedCart, placeHolder);

        function fitInputBox() {
            inputBox.style.height =
                Math.min(inputBox.value.split("\n").length * 20 + 20, 900).toString() +
                "px";
        }

        inputBox.addEventListener("input", fitInputBox);
        G_Objs.inputBox = inputBox;
        fitInputBox();

        const originResetBtn = document.querySelector("div.remove_ctn");
        if (originResetBtn != null) {
            originResetBtn.style.display = "none";
        }

        const [lblDiscount, chkDiscount] = genChk(
            t("onlyOnsale"),
            t("onlyOnsaleDesc"),
            GM_getValue("fac_discount") ?? false
        );
        G_Objs.chkDiscount = chkDiscount;

        const btnImport = genBtn(`🔼${t("import")}`, t("importDesc"), async () => {
            inputBox.value = await importCart(
                inputBox.value,
                chkDiscount.checked
            );
        });

        const histryPage = pathname.search("history") !== -1;
        if (histryPage) {
            btnImport.disabled = true;
            btnImport.title = t("importDesc2");
            // btnImport2.disabled = true;
            // btnImport2.title = t("importDesc2");
        }

        const [lblDiscount2, chkDiscount2] = genChk(
            t("onlyOnsale"),
            t("onlyOnsaleDesc2"),
            GM_getValue("fac_discount2") ?? false
        );
        G_Objs.chkDiscount2 = chkDiscount2;

        const btnExport = genBtn(`🔽${t("export")}`, t("exportDesc"), async () => {
            let currentValue = inputBox.value.trim();
            const now = new Date().toLocaleString();

            var data = await exportCart(chkDiscount2.checked);

            if (currentValue !== "") {
                ShowConfirmDialog(
                    "",
                    t("exportConfirm"),
                    t("exportConfirmReplace"),
                    t("exportConfirmAppend")
                )
                    .done(() => {
                        inputBox.value = `========【${now}】=========\n` + data;
                        fitInputBox();
                    })
                    .fail((bool) => {
                        if (bool) {
                            inputBox.value = currentValue + `\n========【${now}】=========\n` + data;
                            fitInputBox();
                        }
                    });
            } else {
                inputBox.value =
                    `========【${now}】=========\n` + exportCart(chkDiscount2.checked);
                fitInputBox();
            }
        });

        const btnCopy = genBtn(`📋${t("copy")}`, t("copyDesc"), () => {
            GM_setClipboard(inputBox.value, "text");
            showAlert(t("tips"), t("copyDone"), true);
        });
        const btnClear = genBtn(`🗑️${t("reset")}`, t("resetDesc"), () => {
            ShowConfirmDialog("", t("resetConfirm"), t("ok"), t("no")).done(() => {
                inputBox.value = "";
                GM_setValue("fac_cart", "");
                fitInputBox();
            });
        });
        const btnReload = genBtn(`🔃${t("reload")}`, t("reloadDesc"), () => {
            ShowConfirmDialog("", t("reloadConfirm"), t("ok"), t("no")).done(() => {
                const s = GM_getValue("fac_cart") ?? "";
                inputBox.value = s;
                fitInputBox();
            });
        });
        const btnHistory = genBtn(`📜${t("history")}`, t("historyDesc"), () => {
            window.location.href =
                "https://help.steampowered.com/zh-cn/accountdata/ShoppingCartHistory";
        });
        const btnBack = genBtn(`↩️${t("goBack")}`, t("goBackDesc"), () => {
            window.location.href = "https://store.steampowered.com/cart/";
        });
        const btnForget = genBtn(`⚠️${t("clear")}`, t("clearDesc"), () => {
            ShowConfirmDialog("", t("clearConfirm"), t("ok"), t("no")).done(() => {
                deleteAccountCart()
                    .then(() => {
                        location.reload();
                    })
                    .catch((err) => {
                        console.error(err);
                        showAlert("出错", err, false);
                    });
            });
        });
        const btnHelp = genBtn(`🔣${t("help")}`, t("helpDesc"), () => {
            const {
                script: { version },
            } = GM_info;
            showAlert(
                `${t("helpTitle")} ${version}`,
                [
                    `<p>【🔼${t("import")}】${t("importDesc")}</p>`,
                    `<p>【✅${t("onlyOnsale")}】${t("onlyOnsaleDesc")}</p>`,
                    `<p>【🔽${t("export")}】${t("exportDesc")}</p>`,
                    `<p>【✅${t("onlyOnsale")}】${t("onlyOnsaleDesc2")}</p>`,
                    `<p>【📋${t("copy")}】${t("copyDesc")}</p>`,
                    `<p>【🗑️${t("reset")}】${t("resetDesc")}。</p>`,
                    `<p>【📜${t("history")}】${t("historyDesc")}</p>`,
                    `<p>【↩️${t("goBack")}】${t("goBackDesc")}</p>`,
                    `<p>【⚠️${t("clear")}】${t("clearDesc")}</p>`,
                    `<p>【🔣${t("help")}】${t("helpDesc")}</p>`,
                    `<p>【<a href="https://keylol.com/t747892-1-1" target="_blank">发布帖</a>】 【<a href="https://blog.chrxw.com/scripts.html" target="_blank">脚本反馈</a>】 【Developed by <a href="https://steamcommunity.com/id/Chr_" target="_blank">Chr_</a>】</p>`,
                ].join("<br>"),
                true
            );
        });

        const btnArea = document.createElement("div");
        btnArea.appendChild(btnImport);
        // btnArea.appendChild(btnImport2);
        btnArea.appendChild(lblDiscount);
        btnArea.appendChild(genSpan(" | "));
        btnArea.appendChild(btnExport);
        btnArea.appendChild(lblDiscount2);
        btnArea.appendChild(genSpan(" | "));
        btnArea.appendChild(btnHelp);

        const btnArea2 = document.createElement("div");
        btnArea2.appendChild(btnCopy);
        btnArea2.appendChild(btnClear);
        btnArea2.appendChild(btnReload);
        btnArea2.appendChild(genSpan(" | "));
        btnArea2.appendChild(histryPage ? btnBack : btnHistory);
        btnArea2.appendChild(genSpan(" | "));
        btnArea2.appendChild(btnForget);

        window.addEventListener("beforeunload", () => {
            GM_setValue("fac_cart", inputBox.value);
            GM_setValue("fac_discount", chkDiscount.checked);
            GM_setValue("fac_discount2", chkDiscount2.checked);
        });

        //等待购物车加载完毕, 显示额外面板
        const timer = setInterval(() => {
            const continer = document.querySelector("div[data-featuretarget='react-root']>div>div:last-child>div:last-child>div:first-child>div:last-child");
            if (continer) {
                clearInterval(timer);
                continer.appendChild(btnArea);
                continer.appendChild(genBr());
                continer.appendChild(inputBox);
                continer.appendChild(genBr());
                continer.appendChild(btnArea2);
            }
        }, 500);

    }

    // getStoreItem([730], null, null).then((data) => console.log(data)).catch(err => console.error(err))
    // getAccountCart().then((data) => console.log(data)).catch(err => console.error(err))
    // addItemsToAccountCart(null, [28627]).then((data) => console.log(data)).catch(err => console.error(err))

    //始终在右上角显示购物车按钮
    const cart_btn = document.getElementById("store_header_cart_btn");
    if (cart_btn !== null) {
        cart_btn.style.display = "";
    }

    //导入购物车
    function importCart(text, onlyOnSale = false) {
        const regFull = new RegExp(/(app|a|bundle|b|sub|s)\/(\d+)/);
        const regShort = new RegExp(/^([\s]*|)(\d+)/);

        return new Promise(async (resolve, reject) => {
            const dialog = showAlert(
                "导入购物车",
                `<h2 id="fac_diag" class="fac_diag">${t("operation")}</h2>`,
                true
            );

            const timer = setInterval(async () => {
                let txt = document.getElementById("fac_diag");
                if (txt) {
                    clearInterval(timer);

                    const txts = text.split("\n");

                    const result = [];

                    const appIds = [];
                    const subIds = [];
                    const bundleIds = [];

                    const targetSubIds = [];
                    const targetBundleIds = [];

                    try {
                        txt.textContent = "0/4 开始读取输入信息";

                        for (let line of txts) {
                            if (line.trim() === "") {
                                continue;
                            }
                            const tmp = line.split("#")[0];

                            const match = line.match(regFull) ?? line.match(regShort);
                            if (!match) {
                                if (line.search("=====") === -1) {
                                    result.push(`${tmp} #${t("formatError")}`);
                                } else {
                                    result.push(line);
                                }
                                continue;
                            }

                            let [_, type, subID] = match;
                            subID = parseInt(subID);
                            if (subID !== subID) {
                                result.push(`${tmp} #${t("formatError")}`);
                                continue;
                            }

                            switch (type.toLowerCase()) {
                                case "":
                                case "a":
                                case "app":
                                    type = "app";
                                    appIds.push(subID);
                                    break;
                                case "s":
                                case "sub":
                                    type = "sub";
                                    subIds.push(subID);
                                    break;
                                case "b":
                                case "bundle":
                                    type = "bundle";
                                    bundleIds.push(subID);
                                    break;
                                default:
                                    result.push(`${tmp} #${t("formatError")}`);
                                    continue;
                            }
                        }

                        const count = appIds.length + subIds.length + bundleIds.length;
                        txt.textContent = `1/4 成功读取 ${count} 个输入内容`;

                        if (count > 0) {
                            txt.textContent = "1/4 开始读取游戏信息";
                            const store_items = await getStoreItem(appIds, subIds, bundleIds);

                            console.log(store_items);

                            txt.textContent = "2/4 读取游戏信息成功";

                            for (let { appid, purchase_options } of store_items) {
                                if (!purchase_options) { continue; }

                                //输入值包含AppId, 解析可购买项
                                if (appIds.includes(appid)) {
                                    if (purchase_options.length >= 1) {
                                        const { packageid, bundleid, purchase_option_name: name, discount_pct: discount, formatted_final_price: price } = purchase_options[0];
                                        if (discount) {
                                            if (packageid) {
                                                result.push(`sub/${packageid} #app/${appid} #${name} 💳 ${price} 🔖 ${discount}`);
                                                targetSubIds.push(packageid);
                                            } else if (bundleid) {
                                                result.push(`bundle/${bundleid} #app/${appid} #${name} 💳 ${price} 🔖 ${discount}`);
                                                targetBundleIds.push(bundleid);
                                            }
                                        } else {
                                            if (packageid) {
                                                if (!onlyOnSale) {
                                                    result.push(`sub/${packageid} #app/${appid} #${name} 💳 ${price}`);
                                                    targetSubIds.push(packageid);
                                                } else {
                                                    result.push(`sub/${packageid} #app/${appid} #排除 #${name} 💳 ${price}`);
                                                }
                                            } else if (bundleid) {
                                                if (!onlyOnSale) {
                                                    result.push(`bundle/${bundleid} #app/${appid} #${name} 💳 ${price}`);
                                                    targetBundleIds.push(bundleid);
                                                } else {
                                                    result.push(`bundle/${bundleid} #app/${appid} #排除 #${name} 💳 ${price}`);
                                                }
                                            }
                                        }
                                    } else {
                                        result.push(`${tmp} #无可购买项目`);
                                    }
                                }

                                for (let { packageid, bundleid, purchase_option_name: name, discount_pct: discount, formatted_final_price: price } of purchase_options) {
                                    if (discount) {
                                        if (packageid && subIds.includes(packageid)) {
                                            result.push(`sub/${packageid} #${name} 💳 ${price} 🔖 ${discount}`);
                                            targetSubIds.push(packageid);
                                        } else if (bundleid && bundleIds.includes(bundleid)) {
                                            result.push(`bundle/${bundleid} #${name} 💳 ${price} 🔖 ${discount}`);
                                            targetBundleIds.push(bundleid);
                                        }
                                    } else {
                                        if (packageid && subIds.includes(packageid)) {
                                            if (!onlyOnSale) {
                                                result.push(`sub/${packageid} #${name} 💳 ${price}`);
                                                targetSubIds.push(packageid);
                                            } else {
                                                result.push(`sub/${packageid} #排除 #${name} 💳 ${price}`);

                                            }
                                        } else if (bundleid && bundleIds.includes(bundleid)) {
                                            if (!onlyOnSale) {
                                                result.push(`bundle/${bundleid} #${name} 💳 ${price}`);
                                                targetBundleIds.push(bundleid);
                                            } else {
                                                result.push(`bundle/${bundleid} #排除 #${name} 💳 ${price}`);
                                            }
                                        }
                                    }
                                }
                            }

                            txt.textContent = "3/4 解析游戏信息成功";

                            const data = await addItemsToAccountCart(targetSubIds, targetBundleIds, false);
                            console.log(data);

                            txt.textContent = "4/4 导入购物车成功";

                            dialog.Dismiss();

                            resolve(result.join("\n"));

                            setTimeout(() => {
                                window.location.reload();
                            }, 1000);

                        } else {
                            txt.textContent = "4/4 尚未输入有效内容";
                            resolve(result.join("\n"));
                        }

                    } catch (err) {
                        txt.textContent = "导出购物车失败";
                        console.error(err);
                        resolve(result.join("\n"));
                    }
                }
            }, 200);
        });
    }
    //导出购物车
    async function exportCart(onlyOnsale = false) {
        return new Promise(async (resolve, reject) => {
            const dialog = showAlert(
                "导出购物车",
                `<h2 id="fac_diag" class="fac_diag">${t("operation")}</h2>`,
                true
            );

            const timer = setInterval(async () => {
                let txt = document.getElementById("fac_diag");
                if (txt) {
                    clearInterval(timer);

                    const result = [];

                    const subIds = [];
                    const bundleIds = [];
                    const gameNames = {};

                    try {
                        txt.textContent = "0/4 开始读取账号购物车";

                        const { line_items } = await getAccountCart();

                        if (line_items) {
                            for (let { packageid, bundleid } of line_items) {
                                if (packageid) {
                                    subIds.push(packageid);
                                }
                                else if (bundleid) {
                                    bundleIds.push(bundleid);
                                }
                            }
                        }

                        const count = subIds.length + bundleIds.length;
                        txt.textContent = `1/4 成功读取 ${count} 个购物车内容`;

                        if (count > 0) {
                            txt.textContent = "1/4 开始读取游戏信息";
                            const store_items = await getStoreItem(null, subIds, bundleIds);
                            txt.textContent = "2/4 读取游戏信息成功";

                            for (let { purchase_options } of store_items) {
                                if (!purchase_options) { continue; }

                                for (let { packageid, bundleid, purchase_option_name, discount_pct } of purchase_options) {
                                    let key;
                                    if (packageid) {
                                        key = `sub/${packageid}`;
                                    } else if (bundleid) {
                                        key = `bundle/${bundleid}`;
                                    }
                                    else {
                                        continue;
                                    }
                                    gameNames[key] = [`${purchase_option_name}`, discount_pct];
                                }
                            }

                            txt.textContent = "3/4 解析游戏信息成功";
                            txt.textContent = "3/4 开始导出购物车信息";
                            if (line_items) {
                                for (let { packageid, bundleid, price_when_added: { formatted_amount } } of line_items) {
                                    let key;
                                    if (packageid) {
                                        key = `sub/${packageid}`;
                                    } else if (bundleid) {
                                        key = `bundle/${bundleid}`;
                                    }
                                    const [name, discount] = gameNames[key] ?? "_";
                                    if (discount) {
                                        result.push(`${key} #${name} 💳 ${formatted_amount} 🔖 ${discount}`)
                                    } else if (!onlyOnsale) {
                                        result.push(`${key} #${name} 💳 ${formatted_amount}`)
                                    }
                                }
                            }

                            txt.textContent = "3/4 导出购物车信息成功";
                            dialog.Dismiss();

                            resolve(result.join("\n"));

                        } else {
                            txt.textContent = "4/4 购物车内容为空";
                            resolve(result.join("\n"));
                        }

                    } catch (err) {
                        txt.textContent = "读取账号购物车失败";
                        console.error(err);
                        resolve(result.join("\n"));
                    }
                }
            }, 200);
        });
    }

    // 获取游戏详情
    function getStoreItem(appIds = null, subIds = null, bundleIds = null) {
        return new Promise((resolve, reject) => {
            const ids = [];
            if (appIds) {
                for (let x of appIds) {
                    ids.push({ appid: x });
                }
            }
            if (subIds) {
                for (let x of subIds) {
                    ids.push({ packageid: x });
                }
            }
            if (bundleIds) {
                for (let x of bundleIds) {
                    ids.push({ bundleid: x });
                }
            }

            if (ids.length === 0) {
                reject([false, "未提供有效ID"]);
            }

            const payload = {
                ids,
                context: {
                    language: storeLanguage,
                    country_code: userCountry,
                    steam_realm: "1"
                },
                data_request: {
                    include_all_purchase_options: true
                }
            };
            const json = encodeURI(JSON.stringify(payload));
            fetch(
                `https://api.steampowered.com/IStoreBrowseService/GetItems/v1/?input_json=${json}`,
                {
                    method: "GET",
                }
            )
                .then(async (response) => {
                    if (response.ok) {
                        const { response: { store_items } } = await response.json();
                        resolve(store_items);
                    } else {
                        reject(t("networkRequestError"));
                    }
                })
                .catch((err) => {
                    reject(err);
                });
        });
    }

    //读取购物车
    function getAccountCart() {
        return new Promise((resolve, reject) => {
            fetch(
                `https://api.steampowered.com/IAccountCartService/GetCart/v1/?access_token=${accessToken}`,
                {
                    method: "GET",
                }
            )
                .then(async (response) => {
                    if (response.ok) {
                        const { response: { cart } } = await response.json();
                        resolve(cart);
                    } else {
                        reject(t("networkRequestError"));
                    }
                })
                .catch((err) => {
                    reject(err);
                });
        });
    }

    //添加购物车
    function addItemsToAccountCart(subIds = null, bundleIds = null, isPrivate = false) {
        return new Promise((resolve, reject) => {
            const items = [];
            if (subIds) {
                for (let x of subIds) {
                    items.push({ packageid: x });
                }
            }
            if (bundleIds) {
                for (let x of bundleIds) {
                    items.push({ bundleid: x });
                }
            }
            if (items.length === 0) {
                reject([false, "未提供有效ID"]);
            }

            for (let x of items) {
                x["gift_info"] = null; //giftInfo;
                x["flags"] = {
                    is_gift: false,
                    is_private: isPrivate == true
                };
            }

            const payload = {
                user_country: userCountry,
                items,
                navdata: {
                    domain: "store.steampowered.com",
                    controller: "default",
                    method: "default",
                    submethod: "",
                    feature: "spotlight",
                    depth: 1,
                    countrycode: userCountry,
                    webkey: 0,
                    is_client: false,
                    curator_data: {
                        clanid: null,
                        listid: null
                    },
                    is_likely_bot: false,
                    is_utm: false
                }
            };
            const json = JSON.stringify(payload);

            fetch(
                `https://api.steampowered.com/IAccountCartService/AddItemsToCart/v1/?access_token=${accessToken}`,
                {
                    method: "POST",
                    body: `input_json=${json}`,
                    headers: {
                        "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
                    },
                }
            )
                .then(async (response) => {
                    if (response.ok) {
                        const { response: { cart } } = await response.json();
                        resolve(cart);
                    } else {
                        reject(t("networkRequestError"));
                    }
                })
                .catch((err) => {
                    reject(err);
                });
        });
    }

    //删除购物车
    function deleteAccountCart() {
        return new Promise((resolve, reject) => {
            fetch(
                `https://api.steampowered.com/IAccountCartService/DeleteCart/v1/?access_token=${accessToken}`,
                {
                    method: "POST",
                }
            )
                .then(async (response) => {
                    if (response.ok) {
                        const { response: { line_item_ids, cart } } = await response.json();
                        resolve([line_item_ids, cart]);
                    } else {
                        reject(t("networkRequestError"));
                    }
                })
                .catch((err) => {
                    reject(err);
                });
        });
    }

    //显示提示
    function showAlert(title, text, succ = true) {
        return ShowAlertDialog(`${succ ? "✅" : "❌"}${title}`, text);
    }
})();

GM_addStyle(`
button.fac_listbtns {
    display: none;
    position: relative;
    z-index: 100;
    padding: 1px;
  }
  a.search_result_row > button.fac_listbtns {
    top: -25px;
    left: 300px;
  }
  a.tab_item > button.fac_listbtns {
    top: -40px;
    left: 330px;
  }
  a.recommendation_link > button.fac_listbtns {
    bottom: 10px;
    right: 10px;
    position: absolute;
  }
  div.wishlist_row > button.fac_listbtns {
    top: 35%;
    right: 30%;
    position: absolute;
  }
  div.game_purchase_action > button.fac_listbtns {
    right: 8px;
    bottom: 8px;
  }
  button.fac_cartbtns {
    padding: 5px 8px;
  }
  button.fac_cartbtns:not(:last-child) {
    margin-right: 4px;
  }
  button.fac_cartbtns:not(:first-child) {
    margin-left: 4px;
  }
  a.tab_item:hover button.fac_listbtns,
  a.search_result_row:hover button.fac_listbtns,
  div.recommendation:hover button.fac_listbtns,
  div.wishlist_row:hover button.fac_listbtns {
    display: block;
  }
  div.game_purchase_action:hover > button.fac_listbtns {
    display: inline;
  }
  button.fac_choose {
    padding: 1px;
    margin: 2px 5px;
  }
  textarea.fac_inputbox {
    resize: vertical;
    font-size: 12px;
    min-height: 130px;
    width: 98%;
    background: #3d4450;
    color: #fff;
    padding: 1%;
    border: gray;
    border-radius: 5px;
  }
`);