Fast_Add_Cart

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

Pasang skrip ini?
Sugesti pemilik skrip

Kamu mungkin juga suka Hidden_DLC_Helper.

Pasang skrip ini

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name:zh-CN      Steam快速添加购物车
// @name            Fast_Add_Cart
// @namespace       https://blog.chrxw.com
// @supportURL      https://blog.chrxw.com/scripts.html
// @contributionURL https://afdian.com/@chr233
// @version         4.9
// @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/*
// @match           https://steamcommunity.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: "尚未打折, 跳过",
            showUserCountry: "显示账号区域",
            yes: "是",
        },
        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",
            showUserCountry: "Show user country",
            yes: "Yes",
        },
    };

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

    const showUserCountry = GM_getValue("show_user_country", false);

    {
        // 自动弹出提示
        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();
        });

        GM_registerMenuCommand(
            `${t("showUserCountry")} (${t(showUserCountry ? "yes" : "no")})`,
            () => {
                GM_setValue("show_user_country", !showUserCountry);
                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 = {};

    if (userCountry && showUserCountry) {
        const area = document.querySelector("#header_wallet_ctn");
        if (area) {
            const span = document.createElement("span");
            span.textContent = `[${userCountry}]`;
            area.appendChild(span);
        }
    }

    //初始化
    const pathname = window.location.pathname;
    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");
        }

        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 btnConvertToGift = genBtn("转送礼", "将购物车项目转换为送礼", () => {
            editCart(true, false);
        });

        const btnConvertToSelf = genBtn(
            "转自用",
            "将购物车项目转换为为自己购买",
            () => {
                editCart(false, false);
            }
        );

        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(btnConvertToGift);
        btnArea.appendChild(btnConvertToSelf);
        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);

        const fastAddCartPanel = document.createElement("div");
        fastAddCartPanel.className = "fac_panel";

        fastAddCartPanel.appendChild(btnArea);
        fastAddCartPanel.appendChild(genBr());
        fastAddCartPanel.appendChild(inputBox);
        fastAddCartPanel.appendChild(genBr());
        fastAddCartPanel.appendChild(btnArea2);

        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 container = document.querySelector(
                "div[data-featuretarget='react-root']>div>div:last-child>div:last-child>div:last-child>div:nth-child(1)>div:last-child"
            );
            if (container) {
                clearInterval(timer);

                container.parentElement.insertBefore(fastAddCartPanel, container);
            }
        }, 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);
        });
    }
    //导出购物车
    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);
        });
    }

    //编辑购物车
    async function editCart(setToGift = false, setToPrivate = false) {
        const setGiftInfo = await inputGiftee(setToGift);

        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 lineItemIds = [];

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

                        const { line_items } = await getAccountCart();

                        if (line_items) {
                            for (const {
                                line_item_id,
                                flags: { is_gift, is_private },
                                gift_info,
                            } of line_items) {
                                const accountid_giftee =
                                    gift_info?.accountid_giftee?.toString() ?? "";

                                console.log(
                                    line_item_id,
                                    is_gift,
                                    is_private,
                                    accountid_giftee
                                );

                                //跳过无需处理的id
                                if (setToGift) {
                                    if (
                                        is_gift &&
                                        setGiftInfo?.accountid_giftee == accountid_giftee
                                    ) {
                                        continue;
                                    }
                                } else if (setToPrivate) {
                                    if (is_private) {
                                        continue;
                                    }
                                } else {
                                    if (!is_gift && !is_private) {
                                        continue;
                                    }
                                }

                                lineItemIds.push(line_item_id);
                            }
                        }

                        console.log(lineItemIds);

                        const count = lineItemIds.length;
                        txt.textContent = `1/3 共计 ${count} 个待修改购物车内容`;

                        if (count > 0) {
                            for (let i = 0; i < count; i++) {
                                const itemId = lineItemIds[i];
                                await editAccountCart(
                                    itemId,
                                    setToGift,
                                    setToPrivate,
                                    setGiftInfo
                                );
                                txt.textContent = `2/3 当前进度 ${i + 1} / ${count}`;
                            }

                            txt.textContent = "3/3 批量修改购物车成功";

                            setTimeout(() => dialog.Dismiss(), 1000);
                            resolve();
                        } else {
                            txt.textContent = "3/3 购物车无需修改";

                            setTimeout(() => dialog.Dismiss(), 1000);
                            resolve();
                        }
                    } catch (err) {
                        txt.textContent = "读取账号购物车失败";
                        console.error(err);
                        resolve();
                    }
                }
            }, 200);
        });
    }

    const steamIdConvert = BigInt("0x110000100000000");

    function inputGiftee(isGift = false) {
        const nickname =
            document.querySelector("#account_pulldown")?.textContent?.trim() ??
            "unknown";

        return new Promise((resolve, reject) => {
            if (!isGift) {
                resolve(null);
            } else {
                ShowPromptDialog(
                    "提示",
                    "请输入礼物接收人的 SteamID 或者好友代码, 可以留空",
                    "确认",
                    "跳过"
                )
                    .done((text) => {
                        try {
                            let steamId = BigInt(text.trim());
                            if (steamId == 0) {
                                throw "输入数值有误";
                            }

                            if (steamId > steamIdConvert) {
                                steamId -= steamIdConvert;
                            }
                            const giftInfo = {
                                accountid_giftee: steamId.toString(),
                                gift_message: {
                                    gifteename: steamId.toString(),
                                    message: "Send by Fast_Add_Cart",
                                    sentiment: nickname,
                                    signature: nickname,
                                },
                                time_scheduled_send: 0,
                            };
                            resolve(giftInfo);
                        } catch (err) {
                            ShowAlertDialog("提示", "输入数值有误").then(() => resolve(null));
                        }
                    })
                    .fail(() => {
                        resolve(null);
                    });
            }
        });
    }

    // 获取游戏详情
    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 editAccountCart(itemId, isGift, isPrivate, giftInfo = null) {
        return new Promise((resolve, reject) => {
            const payload = {
                line_item_id: itemId,
                user_country: userCountry,
                gift_info: giftInfo,
                flags: {
                    is_gift: isGift,
                    is_private: isPrivate,
                },
            };
            const json = JSON.stringify(payload);

            console.log(json);

            fetch(
                `https://api.steampowered.com/IAccountCartService/ModifyLineItem/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: { line_item_ids, cart },
                        } = await response.json();
                        resolve([line_item_ids, 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;
  }
`);