Ranged Way Idle

一些超级有用的MWI的QoL功能

Verzia zo dňa 08.10.2025. Pozri najnovšiu verziu.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name         Ranged Way Idle
// @namespace    http://tampermonkey.net/
// @version      3.0
// @description  一些超级有用的MWI的QoL功能
// @author       AlphB
// @match        https://www.milkywayidle.com/*
// @match        https://test.milkywayidle.com/*
// @grant        GM_notification
// @grant        GM.xmlHttpRequest
// @connect      https://www.milkywayidle.com/*
// @connect      https://test.milkywayidle.com/*
// @icon         https://tupian.li/images/2025/09/30/68dae3cf1fa7e.png
// @license      CC-BY-NC-SA-4.0
// ==/UserScript==


(function () {
    const config = {
        notifyDeath: {enable: true, desc: "战斗中角色死亡时发送通知"},
        forceUpdateMarketPrice: {enable: true, desc: "进入市场时,强制更新MWITools的市场价格(依赖MWITools)"},
        notifyWhisperMessages: {enable: false, desc: "接受到私信时播放提醒音"},
        listenKeywordMessages: {enable: false, desc: "中文频道消息含有关键词时播放提醒音"},
        matchByRegex: {enable: false, desc: "改用正则表达式匹配中文频道消息(依赖上一条功能)"},
        autoTaskSort: {enable: true, desc: "自动点击MWI TaskManager的任务排序按钮(依赖MWI TaskManager)"},
        showMarketListingsFunds: {enable: true, desc: "显示购买预付金/出售可获金/待领取金额"},
        fundsNoRounding: {enable: true, desc: "显示总计购买预付金/出售可获金/待领取金额时,不四舍五入"},
        showTaskValue: {enable: true, desc: "显示任务期望奖励和任务代币的价值(依赖食用工具)"},
        showMarketAPITime: {enable: true, desc: "显示商店物品的API更新时间(依赖MWITools)"},
        showListingPrice: {enable: true, desc: "为每个挂单显示左一/右一价格(依赖MWITools)、出售可获金或购买预付金"},
        showListingCreateTime: {enable: true, desc: "显示挂单创建时间"},
        listingCreateLifespan: {enable: false, desc: "显示挂单已存在时间,而非创建时间(依赖上一条功能)"},
        forceUpdateAPIButton: {enable: true, desc: "添加强制刷新市场数据API的按钮(在 我的挂牌 界面)"},
        notifyListingFilled: {enable: false, desc: "当你的挂单完成时播放提醒音"},
        doNotTellMeUpgrade: {enable: false, desc: "禁用升级行动队列的按钮(点击时也不会跳转商店)"},
        doNotShowQueue: {enable: false, desc: "禁止显示行动队列"},
        mournForMagicWayIdle: {enable: true, desc: "在控制台默哀法师助手"},
        quickLoadRangedWayIdle: {enable: false, desc: "尝试优化观察节点以提升游戏性能(可能有bug,出现问题请关闭)"},
        debugPrintMessages: {enable: false, desc: "控制台打印所有消息(不推荐打开)"},
        priceToFixed: 2,
        keywords: [],
    }
    const globalVariable = {
        marketURL: document.URL.includes("www.milkywayidle.com") ?
            "https://www.milkywayidle.com/game_data/marketplace.json" :
            "https://test.milkywayidle.com/game_data/marketplace.json",
        battleData: {
            players: null,
            lastNotifyTime: 0,
        },
        characterID: null,
        whisperAudio: new Audio(`https://upload.thbwiki.cc/d/d1/se_bonus2.mp3`),
        keywordAudio: new Audio(`https://upload.thbwiki.cc/c/c9/se_pldead00.mp3`),
        marketListingAudio: new Audio(`https://upload.thbwiki.cc/f/ff/se_trophy.mp3`),
        market: {
            hasFundsElement: false,
            sellValue: null,
            buyValue: null,
            unclaimedValue: null,
            allListings: {}
        },
        task: {
            taskListElement: null,
            taskShopElement: null,
            taskTokenValueData: null,
            hasTaskValueElement: false,
            hasTaskShopValueElement: false,
            taskValueElements: [],
            taskShopElements: [],
            tokenValue: {
                Bid: null,
                Ask: null
            }
        },
        marketAPITime: {
            time: null,
            element: null,
        },
        marketBestPriceElement: [],
        disabledUpgradeButtons: []
    };

    init();

    function init() {
        readConfig();
        // 任务代币计算功能需要食用工具
        if (!('Edible_Tools' in localStorage) ||
            !JSON.parse(localStorage.getItem('Edible_Tools')) ||
            (!("Chest_Drop_Data" in JSON.parse(localStorage.getItem('Edible_Tools'))))) {
            config.showTaskValue.enable = false;
            saveConfig({showTaskValue: false});
        }
        // 更新市场价格需要MWITools支持
        if (!('MWITools_marketAPI_json' in localStorage) ||
            !JSON.parse(localStorage.getItem('MWITools_marketAPI_json')) ||
            (!("marketData" in JSON.parse(localStorage.getItem('MWITools_marketAPI_json'))))) {
            config.forceUpdateMarketPrice.enable = false;
            saveConfig({forceUpdateMarketPrice: false});
        }
        globalVariable.whisperAudio.volume = 0.4;
        globalVariable.keywordAudio.volume = 0.4;
        globalVariable.marketListingAudio.volume = 0.4;
        const listingTimeController = createIntervalController(showListingCreateTime);
        const observer = new MutationObserver(function (mutations, observer) {
            const rootNode = config.quickLoadRangedWayIdle.enable ? mutations[0].target : document;
            if (config.quickLoadRangedWayIdle.enable) {
                const loadingElement = rootNode.querySelector("div.GamePage_gamePanel__3uNKN");
                if (loadingElement && loadingElement.classList.contains("GamePage_connectionMessage__1ZU5B")) {
                    return;
                }
            }
            if (config.showMarketListingsFunds.enable) showMarketListingsFunds(rootNode);
            if (config.autoTaskSort.enable) autoClickTaskSortButton(rootNode);
            if (config.showTaskValue.enable) {
                showTaskValue(rootNode);
                showTaskShopItemValue(rootNode);
            }
            if (config.showMarketAPITime.enable) {
                showMarketAPITime(rootNode);
            }
            if (config.showListingPrice.enable) {
                showListingPrice(rootNode);
            }
            if (config.showListingCreateTime.enable) {
                showListingCreateTime();
                if (config.listingCreateLifespan.enable) {
                    listingTimeController.start();
                } else {
                    listingTimeController.stop();
                }
            }
            if (config.forceUpdateAPIButton.enable) {
                forceUpdateAPIButton(rootNode);
            }
            if (config.doNotTellMeUpgrade.enable) {
                doNotTellMeUpgrade(rootNode);
            }
            if(config.doNotShowQueue.enable){
                doNotShowQueue(rootNode);
            }
            showConfigMenu(rootNode);
        });
        observer.observe(document, {childList: true, subtree: true});
        if (config.showTaskValue.enable) {
            globalVariable.task.taskTokenValueData = getTaskTokenValue();
        }
        if (config.mournForMagicWayIdle.enable) {
            console.log("为法师助手默哀");
        }

        // hook WS
        const oriGet = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data").get;

        function hookedGet() {
            const socket = this.currentTarget;
            if (!(socket instanceof WebSocket) || !socket.url) {
                return oriGet.call(this);
            }
            const message = oriGet.call(this);
            try {
                handleMessage(message, 'get')
            } catch (err) {
                console.error(err);
            }
            return message;
        }

        Object.defineProperty(MessageEvent.prototype, "data", {
            get: hookedGet,
            configurable: true,
            enumerable: true
        });

        const originalSend = WebSocket.prototype.send;

        WebSocket.prototype.send = function (message) {
            try {
                handleMessage(message, 'send');
            } catch (err) {
                console.error(err);
            }
            return originalSend.call(this, message);
        };

    }

    function readConfig() {
        const localConfig = localStorage.getItem("ranged_way_idle_config");
        if (localConfig) {
            const localConfigObj = JSON.parse(localConfig);
            for (const key in localConfigObj) {
                if (config.hasOwnProperty(key) && key !== 'keywords' && key !== 'priceToFixed') {
                    config[key].enable = localConfigObj[key];
                }
            }
            config.priceToFixed = localConfigObj.priceToFixed;
            config.keywords = localConfigObj.keywords;
        }
    }

    function saveConfig(obj) {
        // 仅保存enable开关和priceToFixed, keywords
        const saveConfigObj = {};
        const configMenu = document.querySelectorAll("div#ranged_way_idle_config_menu input");
        if (configMenu.length === 0) return;
        for (const checkbox of configMenu) {
            if (checkbox.type === "checkbox") {
                config[checkbox.id].enable = checkbox.checked;
                saveConfigObj[checkbox.id] = checkbox.checked;
            } else if (checkbox.type === "number" && checkbox.id === 'priceToFixed') {
                const num = parseInt(checkbox.value);
                config.priceToFixed = num;
                saveConfigObj.priceToFixed = num;
            }
        }
        for (const key in obj) {
            saveConfigObj[key] = obj[key];
        }
        saveConfigObj.keywords = config.keywords;
        localStorage.setItem("ranged_way_idle_config", JSON.stringify(saveConfigObj));
    }

    function showConfigMenu(rootNode) {
        const targetNode = document.querySelector("div.SettingsPanel_profileTab__214Bj");
        if (targetNode) {
            if (!targetNode.querySelector("#ranged_way_idle_config_menu")) {
                // enable开关部分
                targetNode.insertAdjacentHTML("beforeend", `<div id="ranged_way_idle_config_menu"></div>`);
                const insertElem = targetNode.querySelector("div#ranged_way_idle_config_menu");
                insertElem.insertAdjacentHTML(
                    "beforeend",
                    `<div style="float: left;" id="ranged_way_idle_config">${
                        "Ranged Way Idle 设置(刷新后生效)"
                    }</div></br>`
                );
                insertElem.insertAdjacentHTML(
                    "beforeend",
                    `<div style="float: left;" id="ranged_way_idle_config">${
                        "若刷新后选项变化或仍不生效,说明插件不兼容,可能是因为未安装插件或版本过旧"
                    }</div></br>`
                );
                for (const key in config) {
                    if (key === 'priceToFixed' || key === 'keywords') continue;
                    insertElem.insertAdjacentHTML(
                        "beforeend",
                        `<div style="float: left;">
                                   <input type="checkbox" id="${key}" ${config[key].enable ? "checked" : ""}>${config[key].desc}
                               </div></br>`
                    );
                }

                // 挂单出售可获金/购买预付金价格显示精度
                insertElem.insertAdjacentHTML(
                    "beforeend",
                    `<div style="float: left;">
                                   <label for="priceToFixed">挂单出售可获金/购买预付金价格显示精度:</label>
                                   <input type="number" id="priceToFixed" value="${config.priceToFixed}" min="0" max="10" step="1">
                               </div></br>`
                );

                insertElem.addEventListener("change", saveConfig);


                // 控制 keywords 列表
                const container = document.createElement('div');
                container.style.marginTop = '20px';
                container.classList.add("ranged_way_idle_keywords_config_menu")
                const input = document.createElement('input');
                input.type = 'text';
                input.style.width = '200px';
                input.placeholder = 'Ranged Way Idle 监听' + (config.matchByRegex.enable ? '正则' : '关键词');
                const button = document.createElement('button');
                button.textContent = '添加';
                const listContainer = document.createElement('div');
                listContainer.style.marginTop = '10px';
                container.appendChild(input);
                container.appendChild(button);
                container.appendChild(listContainer);
                targetNode.insertBefore(container, targetNode.nextSibling);

                function renderList() {
                    listContainer.innerHTML = '';
                    config.keywords.forEach((item, index) => {
                        const itemDiv = document.createElement('div');
                        itemDiv.textContent = item;
                        itemDiv.style.margin = 'auto';
                        itemDiv.style.width = '200px';
                        itemDiv.style.cursor = 'pointer';
                        itemDiv.addEventListener('click', () => {
                            config.keywords.splice(index, 1);
                            renderList();
                        });
                        listContainer.appendChild(itemDiv);
                    });
                    saveConfig();
                }

                renderList();
                button.addEventListener('click', () => {
                    const newItem = input.value.trim();
                    if (newItem) {
                        config.keywords.push(newItem);
                        input.value = '';
                        saveConfig();
                        renderList();
                    }
                });
            }
        }
    }

    function handleMessage(data, type) {
        const obj = JSON.parse(data);
        if (config.debugPrintMessages.enable) console.log(type, obj);
        // 我们无权处理上传的数据
        if (type !== 'get' || !obj) return;
        switch (obj.type) {
            case "init_character_data":
                globalVariable.market.allListings = {};
                updateMarketListings(obj.myMarketListings);
                globalVariable.characterID = obj.character.id;
                break;
            case "market_listings_updated":
                updateMarketListings(obj.endMarketListings);
                break;
            case "new_battle":
                if (config.notifyDeath.enable) initBattle(obj);
                break;
            case "battle_updated":
                if (config.notifyDeath.enable) checkDeath(obj);
                break;
            case "market_item_order_books_updated":
                if (config.forceUpdateMarketPrice.enable) marketPriceUpdate(obj);
                break;
            case "quests_updated":
                for (const e of globalVariable.task.taskValueElements) {
                    e.remove();
                }
                globalVariable.task.taskValueElements = [];
                globalVariable.task.hasTaskValueElement = false;
                break;
            case "chat_message_received":
                handleChatMessage(obj);
                break;
        }
    }

    function notifyDeath(name) {
        // 如果间隔小于60秒,强制不播报
        const nowTime = Date.now();
        if (nowTime - globalVariable.battleData.lastNotifyTime < 60000) return;
        globalVariable.battleData.lastNotifyTime = nowTime;
        new Notification('战斗提醒', {body: `${name} 死了!`});
    }

    function initBattle(obj) {
        // 处理战斗中各个玩家的角色名,供播报死亡信息
        globalVariable.battleData.players = [];
        for (const player of obj.players) {
            globalVariable.battleData.players.push({
                name: player.name, isAlive: player.currentHitpoints > 0,
            });
            if (player.currentHitpoints === 0) {
                notifyDeath(player.name);
            }
        }
    }

    function checkDeath(obj) {
        // 检查玩家是否死亡
        if (!globalVariable.battleData.players) return;
        for (const key in obj.pMap) {
            const index = parseInt(key);
            if (globalVariable.battleData.players[index].isAlive && obj.pMap[key].cHP === 0) {
                // 角色 活->死 时发送提醒
                globalVariable.battleData.players[index].isAlive = false;
                notifyDeath(globalVariable.battleData.players[index].name);
            } else if (obj.pMap[key].cHP > 0) {
                globalVariable.battleData.players[index].isAlive = true;
            }
        }
    }

    function marketPriceUpdate(obj) {
        // 强制刷新MWITools的市场价格数据
        if (config.showTaskValue.enable) {
            globalVariable.task.taskTokenValueData = getTaskTokenValue();
        }
        const marketAPIjson = JSON.parse(localStorage.getItem('MWITools_marketAPI_json'));
        if (!('MWITools_marketAPI_json' in localStorage) ||
            !JSON.parse(localStorage.getItem('MWITools_marketAPI_json')) ||
            (!("marketData" in JSON.parse(localStorage.getItem('MWITools_marketAPI_json'))))) return;
        const itemHrid = obj.marketItemOrderBooks.itemHrid;
        if (!(itemHrid in marketAPIjson.marketData)) return;
        const orderBooks = obj.marketItemOrderBooks.orderBooks;
        for (const enhanceLevel in orderBooks) {
            if (!(enhanceLevel in marketAPIjson.marketData[itemHrid])) {
                marketAPIjson.marketData[itemHrid][enhanceLevel] = {a: 0, b: 0};
            }
            const ask = orderBooks[enhanceLevel].asks;
            if (ask && ask.length) {
                marketAPIjson.marketData[itemHrid][enhanceLevel].a = Math.min(...ask.map(listing => listing.price));
            }
            const bid = orderBooks[enhanceLevel].bids;
            if (bid && bid.length) {
                marketAPIjson.marketData[itemHrid][enhanceLevel].b = Math.max(...bid.map(listing => listing.price));
            }
        }
        // 将修改后结果写回marketAPI缓存,完成对marketAPI价格的强制修改
        localStorage.setItem("MWITools_marketAPI_json", JSON.stringify(marketAPIjson));
    }

    function handleChatMessage(obj) {
        // 处理聊天信息
        if (obj.message.chan === "/chat_channel_types/whisper") {
            if (config.notifyWhisperMessages.enable && obj.message.rId === globalVariable.characterID) {
                globalVariable.whisperAudio.play();
            }
        } else if (obj.message.chan === "/chat_channel_types/chinese") {
            if (config.listenKeywordMessages.enable) {
                for (const keyword of config.keywords) {
                    if (!config.matchByRegex.enable && obj.message.m.includes(keyword)) {
                        globalVariable.keywordAudio.play();
                    } else if (config.matchByRegex.enable) {
                        const regex = new RegExp(keyword, "g");
                        if (regex.test(obj.message.m)) {
                            globalVariable.keywordAudio.play();
                        }
                    }
                }

            }
        }
    }

    function autoClickTaskSortButton(rootNode) {
        // 点击MWI TaskManager的任务排序按钮
        const targetElement = document.querySelector('#TaskSort');
        if (targetElement && targetElement.textContent !== '手动排序') {
            targetElement.click();
            targetElement.textContent = '手动排序';
        }
    }

    function formatCoinValue(num) {
        const fixed = config.fundsNoRounding.enable ? 0 : config.priceToFixed;
        if (Math.abs(num) >= 1e13) {
            return (num / 1e12).toFixed(fixed) + "T";
        } else if (Math.abs(num) >= 1e10) {
            return (num / 1e9).toFixed(fixed) + "B";
        } else if (Math.abs(num) >= 1e7) {
            return (num / 1e6).toFixed(fixed) + "M";
        } else if (Math.abs(num) >= 1e4) {
            return (num / 1e3).toFixed(fixed) + "K";
        }
        if (num.toFixed) {
            return num.toFixed(fixed);
        }
        return num;
    }

    function updateMarketListings(obj) {
        // 更新全局变量存储的挂单价格
        for (const listing of obj) {
            if (listing.status === "/market_listing_status/cancelled") {
                // 挂单手动取消
                delete globalVariable.market.allListings[listing.id];
                continue
            } else if (listing.status === "/market_listing_status/filled") {
                if (config.notifyListingFilled.enable && (listing.unclaimedCoinCount || listing.unclaimedItemCount)) {
                    globalVariable.marketListingAudio.play();
                }
                if (!listing.unclaimedCoinCount && !listing.unclaimedItemCount) {
                    // 挂单正常交易完毕(领取所有金币或物品)
                    delete globalVariable.market.allListings[listing.id];
                    continue
                }
            }
            const tax = (listing.itemHrid === "/items/bag_of_10_cowbells") ? 0.82 : 0.98;
            const buyValue = (listing.orderQuantity - listing.filledQuantity) * listing.price;
            const sellValue = (listing.orderQuantity - listing.filledQuantity) * Math.floor(listing.price * tax);
            globalVariable.market.allListings[listing.id] = {
                itemHrid: listing.itemHrid,
                orderQuantity: listing.orderQuantity,
                filledQuantity: listing.filledQuantity,
                isSell: listing.isSell,
                price: listing.price,
                buyValue: buyValue,
                sellValue: sellValue,
                unclaimedCoinCount: listing.unclaimedCoinCount,
                createdTimestamp: listing.createdTimestamp,
            }
        }
        globalVariable.market.buyValue = 0.0;
        globalVariable.market.sellValue = 0.0;
        globalVariable.market.unclaimedValue = 0.0;
        for (const listingID in globalVariable.market.allListings) {
            const listing = globalVariable.market.allListings[listingID];
            if (listing.isSell) {
                globalVariable.market.sellValue += listing.sellValue;
            } else {
                globalVariable.market.buyValue += listing.buyValue;
            }
            globalVariable.market.unclaimedValue += listing.unclaimedCoinCount;
        }
        globalVariable.market.hasFundsElement = false;
    }

    function showMarketListingsFunds(rootNode) {
        // 如果已经存在节点,不必更新
        if (globalVariable.market.hasFundsElement) return;
        const coinStackElement = rootNode.querySelector("div.MarketplacePanel_coinStack__1l0UD");
        // 不在市场面板,不必更新
        if (coinStackElement) {
            coinStackElement.style.top = "0px";
            coinStackElement.style.left = "0px";
            let fundsElement = coinStackElement.parentNode.querySelector("div.fundsElement");
            while (fundsElement) {
                fundsElement.remove();
                fundsElement = coinStackElement.parentNode.querySelector("div.fundsElement");
            }
            makeNode("购买预付金", globalVariable.market.buyValue, ["125px", "0px"]);
            makeNode("出售可获金", globalVariable.market.sellValue, ["125px", "22px"]);
            makeNode("待领取金额", globalVariable.market.unclaimedValue, ["0px", "22px"]);
            globalVariable.market.hasFundsElement = true;
        }

        function makeNode(text, value, style) {
            const node = coinStackElement.cloneNode(true);
            node.classList.add("fundsElement");
            const countNode = node.querySelector("div.Item_count__1HVvv");
            const textNode = node.querySelector("div.Item_name__2C42x");
            if (countNode) countNode.textContent = formatCoinValue(value);
            if (textNode) textNode.innerHTML = `<span style="color: rgb(102,204,255); font-weight: bold;">${text}</span>`;
            node.style.left = style[0];
            node.style.top = style[1];
            coinStackElement.parentNode.insertBefore(node, coinStackElement.nextSibling);
        }
    }

    function getTaskTokenValue() {
        const chestDropData = JSON.parse(localStorage.getItem("Edible_Tools")).Chest_Drop_Data;
        const lootsName = ["大陨石舱", "大工匠匣", "大宝箱"];
        const bidValueList = [
            parseFloat(chestDropData["Large Meteorite Cache"]["期望产出Bid"]),
            parseFloat(chestDropData["Large Artisan's Crate"]["期望产出Bid"]),
            parseFloat(chestDropData["Large Treasure Chest"]["期望产出Bid"]),
        ];
        const askValueList = [
            parseFloat(chestDropData["Large Meteorite Cache"]["期望产出Ask"]),
            parseFloat(chestDropData["Large Artisan's Crate"]["期望产出Ask"]),
            parseFloat(chestDropData["Large Treasure Chest"]["期望产出Ask"]),
        ];
        const res = {
            bidValue: Math.max(...bidValueList),
            askValue: Math.max(...askValueList)
        };
        // bid和ask的最佳兑换选项
        res.bidLoots = lootsName[bidValueList.indexOf(res.bidValue)];
        res.askLoots = lootsName[askValueList.indexOf(res.askValue)];
        // bid和ask的任务代币价值
        res.bidValue = Math.round(res.bidValue / 30);
        res.askValue = Math.round(res.askValue / 30);
        // 小紫牛的礼物的额外价值计算
        res.giftValueBid = Math.round(parseFloat(chestDropData["Purple's Gift"]["期望产出Bid"]));
        res.giftValueAsk = Math.round(parseFloat(chestDropData["Purple's Gift"]["期望产出Ask"]));
        if (config.forceUpdateMarketPrice.enable) {
            const marketJSON = JSON.parse(localStorage.getItem("MWITools_marketAPI_json"));
            marketJSON.marketData["/items/task_token"] = {"0": {a: res.askValue, b: res.bidValue}};
            localStorage.setItem("MWITools_marketAPI_json", JSON.stringify(marketJSON));
        }
        res.rewardValueBid = res.bidValue + res.giftValueBid / 50;
        res.rewardValueAsk = res.askValue + res.giftValueAsk / 50;
        return res;
    }

    function showTaskValue(rootNode) {
        globalVariable.task.taskListElement = document.querySelector("div.TasksPanel_taskList__2xh4k");
        // 如果不在任务面板,则销毁显示任务价值的元素
        if (!globalVariable.task.taskListElement) {
            globalVariable.task.taskValueElements = [];
            globalVariable.task.hasTaskValueElement = false;
            globalVariable.task.taskListElement = null;
            return;
        }
        // 如果已经存在任务价值的元素,不再更新
        if (globalVariable.task.hasTaskValueElement) return;
        globalVariable.task.hasTaskValueElement = true;
        const taskNodes = [...globalVariable.task.taskListElement.querySelectorAll("div.RandomTask_randomTask__3B9fA")];

        function convertKEndStringToNumber(str) {
            if (str.endsWith('K') || str.endsWith('k')) {
                return Number(str.slice(0, -1)) * 1000;
            } else {
                return Number(str);
            }
        }

        taskNodes.forEach(function (node) {
            const reward = node.querySelector("div.RandomTask_rewards__YZk7D");
            const coin = convertKEndStringToNumber(reward.querySelectorAll("div.Item_count__1HVvv")[0].innerText);
            const tokenCount = Number(reward.querySelectorAll("div.Item_count__1HVvv")[1].innerText);
            const newDiv = document.createElement("div");
            newDiv.textContent = `奖励期望收益: 
            ${formatCoinValue(coin + tokenCount * globalVariable.task.taskTokenValueData.rewardValueAsk)} / 
            ${formatCoinValue(coin + tokenCount * globalVariable.task.taskTokenValueData.rewardValueBid)}`;
            newDiv.style.color = "rgb(248,0,248)";
            newDiv.classList.add("rewardValue");
            node.querySelector("div.RandomTask_action__3eC6o").appendChild(newDiv);
            globalVariable.task.taskValueElements.push(newDiv);
        });
    }

    function showTaskShopItemValue(rootNode) {
        globalVariable.task.taskShopElement = rootNode.querySelector("div.TasksPanel_buyableGrid__2Ua51");
        // 如果不在商店面板,则销毁显示价值的元素
        if (!globalVariable.task.taskShopElement) {
            globalVariable.task.taskShopValueElements = [];
            globalVariable.task.hasTaskShopValueElement = false;
            globalVariable.task.taskShopElement = null;
            return;
        }
        // 如果已经存在价值的元素,不再更新
        if (globalVariable.task.hasTaskShopValueElement) return;
        globalVariable.task.hasTaskShopValueElement = true;
        const taskNodes = [...globalVariable.task.taskShopElement.querySelectorAll("div.TasksPanel_item__DWSpv")];

        function convertKEndStringToNumber(str) {
            if (str.endsWith('K') || str.endsWith('k')) {
                return Number(str.slice(0, -1)) * 1000;
            } else {
                return Number(str);
            }
        }

        const chestDropData = JSON.parse(localStorage.getItem("Edible_Tools")).Chest_Drop_Data;
        taskNodes.forEach(function (node) {
            if (node.childNodes[2].textContent !== "30") return;
            const newDiv = document.createElement("div");
            if (node.childNodes[1].childNodes[0].childNodes[0].href.baseVal.endsWith("large_meteorite_cache")) {
                newDiv.textContent = `
            ${formatCoinValue(parseFloat(chestDropData["Large Meteorite Cache"]["期望产出Ask"]))} / 
            ${formatCoinValue(parseFloat(chestDropData["Large Meteorite Cache"]["期望产出Bid"]))}`;
            } else if (node.childNodes[1].childNodes[0].childNodes[0].href.baseVal.endsWith("large_artisans_crate")) {
                newDiv.textContent = `
            ${formatCoinValue(parseFloat(chestDropData["Large Artisan's Crate"]["期望产出Ask"]))} / 
            ${formatCoinValue(parseFloat(chestDropData["Large Artisan's Crate"]["期望产出Bid"]))}`;
            } else if (node.childNodes[1].childNodes[0].childNodes[0].href.baseVal.endsWith("large_treasure_chest")) {
                newDiv.textContent = `
            ${formatCoinValue(parseFloat(chestDropData["Large Treasure Chest"]["期望产出Ask"]))} / 
            ${formatCoinValue(parseFloat(chestDropData["Large Treasure Chest"]["期望产出Bid"]))}`;
            }
            newDiv.style.color = "rgb(248,0,248)";
            newDiv.classList.add("taskShopValue");
            node.childNodes[2].insertAdjacentElement('beforebegin', newDiv);
            globalVariable.task.taskShopValueElements.push(newDiv);
        });
    }

    function showMarketAPITime(rootNode) {
        const root = rootNode.querySelector("div.MarketplacePanel_buttonContainer__vJQud");
        if (!root) return;
        const nowTime = JSON.parse(localStorage.getItem('MWITools_marketAPI_json')).timestamp;
        if (nowTime === globalVariable.marketAPIUpdateTime) return;
        globalVariable.marketAPIUpdateTime = nowTime;
        if (globalVariable.marketAPITimeElement) {
            globalVariable.marketAPITimeElement.remove();
        }
        globalVariable.marketAPITimeElement = document.createElement("div");
        globalVariable.marketAPITimeElement.textContent = "API更新时间: " + new Date(nowTime * 1000).toLocaleString();
        globalVariable.marketAPITimeElement.style.color = "rgb(102,204,255)";
        globalVariable.marketAPITimeElement.classList.add("marketAPIUpdateTime");
        root.insertBefore(globalVariable.marketAPITimeElement, root.lastChild);
    }

    function showListingPrice(rootNode) {
        const head = rootNode.querySelector("div.MarketplacePanel_myListingsTableContainer__2s6pm > table > thead > tr");
        if (head && !head.querySelector(".bestPriceHead")) {
            const cloneNode1 = head.childNodes[3].cloneNode();
            cloneNode1.textContent = "左一/右一价格";
            cloneNode1.title = '绿色表示最佳价格(左一/右一),红色表示已经被压价';
            cloneNode1.classList.add("bestPriceHead");
            head.insertBefore(cloneNode1, head.childNodes[4]);

            const cloneNode2 = head.childNodes[3].cloneNode();
            cloneNode2.textContent = "出售可获金/购买预付金";
            head.insertBefore(cloneNode2, head.childNodes[5]);
        }

        const body = rootNode.querySelector("div.MarketplacePanel_myListingsTableContainer__2s6pm > table > tbody");
        if (!body) return;
        const rows = body.querySelectorAll("tr");
        if (!rows) return;
        const marketJSON = JSON.parse(localStorage.getItem("MWITools_marketAPI_json"));
        const listingKeys = Object.keys(globalVariable.market.allListings).reverse();
        const listingCountAssertion = body.querySelectorAll("tr").length === listingKeys.length;
        let index = 0;

        for (const row of body.querySelectorAll("tr")) {
            const mode = row.childNodes[1].classList[1].includes("sell") ? "a" : "b";
            const itemHrid = '/items/' + row.childNodes[2].firstChild.firstChild.firstChild.firstChild.firstChild.firstChild.firstChild.href.baseVal.split('#')[1];
            const enhanceLevelElement = row.querySelector('.Item_enhancementLevel__19g-e');
            const enhanceLevel = enhanceLevelElement ? Number(enhanceLevelElement.textContent) : 0;
            let marketAPIPrice;
            const itemPrice = marketJSON.marketData[itemHrid];
            if (!itemPrice || !itemPrice[enhanceLevel] || !itemPrice[enhanceLevel][mode] || itemPrice[enhanceLevel][mode] === -1) {
                marketAPIPrice = null;
            } else {
                marketAPIPrice = Number(itemPrice[enhanceLevel][mode]);
            }
            const listingPriceStr = row.querySelector('.MarketplacePanel_price__hIzrY').firstChild.textContent;

            if (!row.querySelector(".bestPrice") || row.querySelector(".bestPrice").textContent !== priceIntToString(marketAPIPrice, false)) {
                if (row.querySelector(".bestPrice")) {
                    row.querySelector(".bestPrice").remove();
                }

                const bestPriceNode = document.createElement("td");
                bestPriceNode.classList.add("bestPrice");
                if (marketAPIPrice === null) {
                    bestPriceNode.textContent = "NULL";
                    bestPriceNode.style.color = "#004FFF";
                } else {
                    bestPriceNode.textContent = priceIntToString(marketAPIPrice, false) || "NULL";
                    if (mode === 'a') {
                        // 左
                        bestPriceNode.style.color = marketAPIPrice < priceStringToInt(listingPriceStr) ? "#FC1200" : "#12F355";
                    } else {
                        // 右
                        bestPriceNode.style.color = marketAPIPrice > priceStringToInt(listingPriceStr) ? "#FC1200" : "#12F355";
                    }
                }
                console.log(itemHrid, enhanceLevel, marketAPIPrice, itemPrice);
                row.insertBefore(bestPriceNode, row.childNodes[4]);
            }

            if (listingCountAssertion) {
                const oldNode = row.querySelector(".buySellValue");
                let shouldUpdateBuySellValue = !oldNode;
                const listing = globalVariable.market.allListings[listingKeys[index]];
                const price = parseInt(listing.isSell ? listing.sellValue : listing.buyValue);
                const newPriceString = priceIntToString(price, true);
                if (oldNode) {
                    const oldPriceString = oldNode.textContent;
                    shouldUpdateBuySellValue |= newPriceString !== oldPriceString;
                }
                if (shouldUpdateBuySellValue) {
                    if (oldNode) oldNode.remove();
                    const buySellValueNode = document.createElement("td");
                    buySellValueNode.classList.add("buySellValue");
                    buySellValueNode.textContent = newPriceString;
                    buySellValueNode.style.color = getPriceColor(price);
                    row.insertBefore(buySellValueNode, row.childNodes[5]);
                }
                index++;
            } else {
                const oldNode = row.querySelector(".buySellValue");
                if (oldNode) {
                    oldNode.remove();
                }
                const buySellValueNode = document.createElement("td");
                buySellValueNode.classList.add("buySellValue");
                buySellValueNode.textContent = "Error";
                buySellValueNode.style.color = "#FF0000";
            }
        }

        function priceIntToString(price, useFixed) {
            if (useFixed) {
                const fixed = config.priceToFixed;
                if (price < 1e5) {
                    return price.toFixed(fixed);
                }
                if (price < 1e7) {
                    return (price / 1e3).toFixed(fixed) + "K";
                }
                if (price < 1e10) {
                    return (price / 1e6).toFixed(fixed) + "M";
                }
                if (price < 1e13) {
                    return (price / 1e9).toFixed(fixed) + "B";
                }
                return (price / 1e12).toFixed(fixed) + "T";
            }
            if (price < 1e5) {
                return price.toString();
            }
            if (price < 1e7) {
                return (price / 1e3).toString() + "K";
            }
            if (price < 1e10) {
                return (price / 1e6).toString() + "M";
            }
            if (price < 1e13) {
                return (price / 1e9).toString() + "B";
            }
            return (price / 1e12).toString() + "T";
        }

        function priceStringToInt(price) {
            if (price === "NULL") {
                return null;
            }
            if (price.endsWith('K') || price.endsWith('k')) {
                return Number(price.slice(0, -1)) * 1e3;
            }
            if (price.endsWith('M')) {
                return Number(price.slice(0, -1)) * 1e6;
            }
            if (price.endsWith('B')) {
                return Number(price.slice(0, -1)) * 1e9;
            }
            if (price.endsWith('T')) {
                return Number(price.slice(0, -1)) * 1e12;
            }
            return Number(price);
        }

        function getPriceColor(price) {
            if (price < 1e5) {
                return "#FFFFFF";
            }
            if (price < 1e7) {
                return "#FDDAA5";
            }
            if (price < 1e10) {
                return "#82DCCA";
            }
            if (price < 1e13) {
                return "#77BAEC";
            }
            if (price < 1e16) {
                return "#AC8FD4";
            }
            return "#F800F8";
        }
    }

    function showListingCreateTime() {
        const body = document.querySelector("div.MarketplacePanel_myListingsTableContainer__2s6pm > table > tbody");
        if (!body) return;
        const listingKeys = Object.keys(globalVariable.market.allListings).reverse();
        const listingCountAssertion = body.querySelectorAll("tr").length === listingKeys.length;
        if (!listingCountAssertion) return;
        let index = 0;
        for (const row of body.querySelectorAll("tr")) {
            const oldNode = row.querySelector(".listingCreateTime")
            let shouldUpdateTime = !oldNode;
            const listing = globalVariable.market.allListings[listingKeys[index]];
            const newTimeString = config.listingCreateLifespan.enable ?
                ("已存在" + formatLifespan(listing.createdTimestamp)) :
                ("创建于" + formatUTCTime(listing.createdTimestamp));
            if (oldNode) {
                const oldTimeString = oldNode.textContent;
                shouldUpdateTime |= oldTimeString !== newTimeString;
            }
            if (shouldUpdateTime) {
                if (oldNode) oldNode.remove();
                const timeNode = document.createElement("div");
                timeNode.classList.add("listingCreateTime");
                timeNode.textContent = newTimeString;
                timeNode.style.color = "gray";
                timeNode.style.fontSize = "12px";
                row.firstChild.appendChild(timeNode);
            }
            index++;
        }


        function formatUTCTime(utcString) {
            const date = new Date(utcString);

            return date.toLocaleString('en-US', {
                month: '2-digit',
                day: '2-digit',
                hour: '2-digit',
                minute: '2-digit',
                hour12: false
            }).replace(/\//g, '-').replace(',', '');
        }

        function formatLifespan(utcString) {
            // 解析UTC时间字符串
            const targetDate = new Date(utcString);
            const now = new Date();

            // 计算时间差(毫秒)
            const diffMs = now - targetDate;

            // 如果目标时间是未来时间,返回0
            if (diffMs < 0) {
                return "0秒";
            }

            // 计算各个时间单位
            const seconds = Math.floor(diffMs / 1000);
            const minutes = Math.floor(seconds / 60);
            const hours = Math.floor(minutes / 60);
            const days = Math.floor(hours / 24);

            // 计算剩余时间
            const remainingSeconds = seconds % 60;
            const remainingMinutes = minutes % 60;
            const remainingHours = hours % 24;

            // 构建结果数组
            const parts = [];

            if (days > 0) {
                parts.push(`${days}天`);
            }
            if (remainingHours > 0) {
                parts.push(`${remainingHours}时`);
            }
            if (remainingMinutes > 0) {
                parts.push(`${remainingMinutes}分`);
            }
            if (remainingSeconds > 0 || parts.length === 0) {
                parts.push(`${remainingSeconds}秒`);
            }

            return parts.join('');
        }
    }

    function createIntervalController(fn) {
        let intervalId = null;
        let isRunning = false;
        return {
            start() {
                if (isRunning) return;
                isRunning = true;
                fn();
                intervalId = setInterval(fn, 500);
            },
            stop() {
                if (!isRunning) return;
                isRunning = false;
                clearInterval(intervalId);
                intervalId = null;
            },
        };
    }

    function forceUpdateAPIButton(rootNode) {
        const root = rootNode.querySelector("div.MarketplacePanel_listingCount__3nVY_");
        if (!root || root.querySelector(".forceUpdateAPIButton") || !root.querySelector("button:nth-child(1)")) return;
        const button = root.querySelector("button:nth-child(1)").cloneNode(true);
        button.textContent = "强制刷新API";
        button.classList.add("forceUpdateAPIButton");
        button.addEventListener("click", async function () {
            if (GM && GM.xmlHttpRequest) {
                GM.xmlHttpRequest({
                    method: 'GET',
                    url: globalVariable.marketURL,
                    onload: function (response) {
                        const text = response.responseText;
                        localStorage.setItem("MWITools_marketAPI_json", text);
                        alert("强制刷新API成功,市场数据更新于" + new Date(JSON.parse(text).timestamp * 1000).toLocaleString());
                    },
                    onerror: function (err) {
                        console.error(err);
                    },
                    ontimeout: function () {
                        console.error('timeout');
                    }
                });
            } else {
                const resp = await fetch(globalVariable.marketURL);
                const text = await resp.text();
                localStorage.setItem("MWITools_marketAPI_json", text);
                alert("强制刷新API成功,市场数据更新于" + new Date(JSON.parse(text).timestamp * 1000).toLocaleString());
            }
        });
        root.appendChild(button);
    }

    function doNotTellMeUpgrade(rootNode) {
        const buttons = rootNode.querySelectorAll("button");
        for (const button of buttons) {
            if ((button.textContent === "Upgrade Queue Capacity" || button.textContent === "升级行动队列") && button.disabled === false) {
                button.disabled = true;
                globalVariable.disabledUpgradeButtons.push(button);
            }
        }
        const newList = [];
        for (const button of globalVariable.disabledUpgradeButtons) {
            if (button.textContent !== "Upgrade Queue Capacity" && button.textContent !== "升级行动队列") {
                button.disabled = false;
            } else {
                newList.push(button);
            }
        }
        globalVariable.disabledUpgradeButtons = newList;
    }

    function doNotShowQueue(rootNode){
        // 关闭行动队列的面板
        const queueNode = document.querySelector(".MuiTooltip-popperInteractive");
        if (!queueNode || !queueNode.querySelector(".QueuedActions_queuedActionsEditMenu__3OoQH")) return;
        document.querySelector(".QueuedActions_queuedActions__2xerL").click();
    }
})();