Ranged Way Idle

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

Version au 21/09/2025. Voir la dernière version.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Ranged Way Idle
// @namespace    http://tampermonkey.net/
// @version      2.18
// @description  一些超级有用的MWI的QoL功能
// @author       AlphB
// @match        https://www.milkywayidle.com/*
// @match        https://test.milkywayidle.com/*
// @grant        GM_notification
// @grant        GM_getValue
// @grant        GM_setValue
// @icon         https://www.google.com/s2/favicons?sz=64&domain=milkywayidle.com
// @grant        none
// @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: "显示购买预付金/出售可获金/待领取金额"},
        showTaskValue: {enable: true, desc: "显示任务期望奖励和任务代币的价值(依赖食用工具)"},
        showMarketAPITime: {enable: true, desc: "显示商店物品的API更新时间(依赖MWITools)"},
        showListingBestPrice: {enable: true, desc: "显示你的挂单的物品的左一/右一价格(依赖MWITools)"},
        forceUpdateAPIButton: {enable: true, desc: "添加强制刷新市场数据API的按钮(在 我的挂牌 界面)"},
        notifyListingFilled: {enable: false, desc: "当你的挂单完成时播放提醒音"},
        doNotTellMeUpgrade: {enable: false, desc: "禁用升级行动队列的按钮(点击时也不会跳转商店)"},
        mournForMagicWayIdle: {enable: true, desc: "在控制台默哀法师助手"},
        debugPrintMessages: {enable: false, desc: "控制台打印所有消息(不推荐打开)"},
        keywords: [],
    }
    const globalVariable = {
        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,
            sellListings: null,
            buyListings: null
        },
        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;
        let observer = new MutationObserver(function () {
            if (config.showMarketListingsFunds.enable) showMarketListingsFunds();
            if (config.autoTaskSort.enable) autoClickTaskSortButton();
            if (config.showTaskValue.enable) {
                showTaskValue();
                showTaskShopItemValue();
            }
            if (config.showMarketAPITime.enable) {
                showMarketAPITime();
            }
            if (config.showListingBestPrice.enable) {
                showListingBestPrice();
            }
            if (config.forceUpdateAPIButton.enable) {
                forceUpdateAPIButton();
            }
            if (config.doNotTellMeUpgrade.enable) {
                doNotTellMeUpgrade();
            }
            showConfigMenu();
        });
        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) {
            }
            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) {
            }
            return originalSend.call(this, message);
        };

    }

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

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

    function showConfigMenu() {
        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 (let key in config) {
                    if (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.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.sellListings = {};
                globalVariable.market.buyListings = {};
                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 (let 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 (let 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 (let 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 (let 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 (let 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() {
        // 点击MWI TaskManager的任务排序按钮
        const targetElement = document.querySelector('#TaskSort');
        if (targetElement && targetElement.textContent !== '手动排序') {
            targetElement.click();
            targetElement.textContent = '手动排序';
        }
    }

    function formatCoinValue(num) {
        if (!num) return num;
        if (Math.abs(num) >= 1e13) {
            return Math.floor(num / 1e12) + "T";
        } else if (Math.abs(num) >= 1e10) {
            return Math.floor(num / 1e9) + "B";
        } else if (Math.abs(num) >= 1e7) {
            return Math.floor(num / 1e6) + "M";
        } else if (Math.abs(num) >= 1e4) {
            return Math.floor(num / 1e3) + "K";
        }
        try {
            return num.toString();
        } catch (err) {
        }
        return num;
    }

    function updateMarketListings(obj) {
        // 更新市场价格
        for (let listing of obj) {
            if (listing.status === "/market_listing_status/cancelled") {
                delete globalVariable.market[listing.isSell ? "sellListings" : "buyListings"][listing.id];
                continue
            } else if (listing.status === "/market_listing_status/filled") {
                if (config.notifyListingFilled.enable && (listing.unclaimedCoinCount || listing.unclaimedItemCount)) {
                    globalVariable.marketListingAudio.play();
                }
            }
            const tax = (listing.itemHrid === "/items/bag_of_10_cowbells") ? 0.82 : 0.98;
            globalVariable.market[listing.isSell ? "sellListings" : "buyListings"][listing.id] = {
                itemHrid: listing.itemHrid,
                price: (listing.orderQuantity - listing.filledQuantity) * (listing.isSell ? Math.floor(listing.price * tax) : listing.price),
                unclaimedCoinCount: listing.unclaimedCoinCount,
            }
        }
        globalVariable.market.buyValue = 0;
        globalVariable.market.sellValue = 0;
        globalVariable.market.unclaimedValue = 0;
        for (let id in globalVariable.market.buyListings) {
            const listing = globalVariable.market.buyListings[id];
            globalVariable.market.buyValue += listing.price || 0;
            globalVariable.market.unclaimedValue += listing.unclaimedCoinCount || 0;
        }
        for (let id in globalVariable.market.sellListings) {
            const listing = globalVariable.market.sellListings[id];
            globalVariable.market.sellValue += listing.price || 0;
            globalVariable.market.unclaimedValue += listing.unclaimedCoinCount || 0;
        }
        globalVariable.market.hasFundsElement = false;
    }

    function showMarketListingsFunds() {
        // 如果已经存在节点,不必更新
        if (globalVariable.market.hasFundsElement) return;
        const coinStackElement = document.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) {
            let 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() {
        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() {
        globalVariable.task.taskShopElement = document.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() {
        const root = document.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 showListingBestPrice() {
        const head = document.querySelector("div.MarketplacePanel_myListingsTableContainer__2s6pm > table > thead > tr");
        if (head && !head.querySelector(".bestPriceHead")) {
            const cloneNode = head.childNodes[3].cloneNode();
            cloneNode.textContent = "左一/右一价格";
            cloneNode.title = '绿色表示最佳价格(左一/右一),红色表示已经被压价';
            cloneNode.classList.add("bestPriceHead");
            head.insertBefore(cloneNode, head.childNodes[4]);
        }

        const body = document.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"));
        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;
            try {
                marketAPIPrice = Number(marketJSON.marketData[itemHrid][enhanceLevel][mode]);
            } catch (e) {
                marketAPIPrice = null;
            }
            const listingPriceStr = row.querySelector('.MarketplacePanel_price__hIzrY').firstChild.textContent;

            if (row.querySelector(".bestPrice") && parseStringPrice(row.querySelector(".bestPrice").textContent) === marketAPIPrice) continue;
            if (row.querySelector(".bestPrice")) {
                row.querySelector(".bestPrice").remove();
            }

            const newNode = document.createElement("td");
            newNode.classList.add("bestPrice");
            newNode.textContent = parseIntPrice(marketAPIPrice);
            if (marketAPIPrice) {
                if (mode === 'a') {
                    // 左
                    newNode.style.color = marketAPIPrice < parseStringPrice(listingPriceStr) ? "#FC1200" : "#12F355";
                } else {
                    // 右
                    newNode.style.color = marketAPIPrice > parseStringPrice(listingPriceStr) ? "#FC1200" : "#12F355";
                }
            } else {
                newNode.style.color = "#004FFF";
            }
            row.insertBefore(newNode, row.childNodes[4]);
        }

        function parseIntPrice(price) {
            if (price === null) {
                return "NULL";
            }
            if (price < 1e5) {
                return price.toString();
            }
            if (price < 1e7) {
                return (price / 1e3) + "K";
            }
            if (price < 1e10) {
                return (price / 1e6) + "M";
            }
            if (price < 1e13) {
                return (price / 1e9) + "B";
            }
            if (price < 1e16) {
                return (price / 1e9) + "T";
            }
        }

        function parseStringPrice(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 forceUpdateAPIButton() {
        const root = document.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 () {
            const resp = await fetch("https://www.milkywayidle.com/game_data/marketplace.json");
            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() {
        const buttons = document.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);
            }
        }
        let 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;
    }


})();