MWISubscribe

Subscribe Market Item

Install this script?
Author's suggested script

You may also like MWICommon.

Install this script
// ==UserScript==
// @name         MWISubscribe
// @namespace    http://tampermonkey.net/
// @version      0.13
// @description  Subscribe Market Item
// @author       Lhiok
// @license      MIT
// @match        https://www.milkywayidle.com/*
// @match        https://test.milkywayidle.com/*
// @icon         https://www.milkywayidle.com/favicon.svg
// @supportURL   https://github.com/Lhiok/MWIScript/
// @grant        none
// ==/UserScript==

(function() {
    "use strict";

    let mwi_common = null;
    let mwi_subscribe_items = {};

    const storage_key = "mwi_subscribe_items";
    const subscribeItemsStorage = localStorage.getItem(storage_key);
    if (subscribeItemsStorage) {
        const subscribeItems = JSON.parse(subscribeItemsStorage);
        // 兼容旧版本
        if (Array.isArray(subscribeItems)) {
            for (let i = 0; i < subscribeItems.length; i++) {
                mwi_subscribe_items[subscribeItems[i]] = {
                    ask: -2, // -1 代表查询失败 这里使用-2表示旧数据
                    bid: -2,
                };
            }
            localStorage.setItem(storage_key, JSON.stringify(mwi_subscribe_items));
        }
        else {
            mwi_subscribe_items = subscribeItems;
        }
    }

    function formatNumber(value) {
        return value.toString().replace(/\d+/, function (n) {
            return n.replace(/(\d)(?=(?:\d{3})+$)/g, '$1,')
        })
    }

    function fixNumber(value) {
        if (value > 100) return value.toFixed(0);
        if (value > 10) return value.toFixed(1);
        return value.toFixed(2);
    }

    function formatNumberWithUnit(value) {
        if (value >= 1_000_000_000_000_000) return `${fixNumber(value / 1_000_000_000_000_000)}P`;
        if (value >= 1_000_000_000_000) return `${fixNumber(value / 1_000_000_000_000)}T`;
        if (value >= 1_000_000_000) return `${fixNumber(value / 1_000_000_000)}B`;
        if (value >= 1_000_000) return `${fixNumber(value / 1_000_000)}M`;
        if (value >= 1_000) return `${fixNumber(value / 1_000)}K`;
        return value.toFixed(0);
    }

    function updateSubscribePrice(itemHrid, itemLevel) {
        const itemHridLevel = itemLevel > 0? `${itemHrid}::${itemLevel}`: itemHrid;
        if (mwi_subscribe_items[itemHridLevel] == undefined) {
            return;
        }

        mwi_subscribe_items[itemHridLevel] = {
            ask: mwi_common.getItemPriceByHrid(itemHrid, itemLevel, 'ask'),
            bid: mwi_common.getItemPriceByHrid(itemHrid, itemLevel, 'bid'),
        };
        localStorage.setItem(storage_key, JSON.stringify(mwi_subscribe_items));
    }

    function getPricePercentColor(pricePercent) {
        if (pricePercent > 0) {
            return "#ff0000";
        }
        else if (pricePercent < 0) {
            return "#00ff00";
        }
        else {
            return "#aaaaaa";
        }
    }

    function updateSubscribedList() {
        const displayContainer = document.querySelector("div#subscribeDisplayContainer");
        if (!displayContainer) {
            return;
        }
        
        // 移除收藏物品
        displayContainer.innerHTML = "";
        // 创建收藏物品
        for (let itemHridLevel in mwi_subscribe_items) {
            const itemInfo = itemHridLevel.split("::");
            const itemHrid = itemInfo[0];
            const itemLevel = itemInfo[1]? Number(itemInfo[1]): 0;
            if (!itemHrid || itemHrid === "" || itemHrid === "undefined") {
                return;
            }

            const itemCount = mwi_common.getItemNumByHrid(itemHrid, itemLevel);
            
            // 当前价格
            const askPrice = mwi_common.getItemPriceByHrid(itemHrid, itemLevel, 'ask');
            const bidPrice = mwi_common.getItemPriceByHrid(itemHrid, itemLevel, 'bid');

            // 昨日价格
            const askPriceYesterday = mwi_common.getItemPriceYesterdayByHrid(itemHrid, itemLevel, 'ask');
            const bidPriceYesterday = mwi_common.getItemPriceYesterdayByHrid(itemHrid, itemLevel, 'bid');
            const askPricePercentYesterday = askPriceYesterday > 0? (askPrice - askPriceYesterday) / askPriceYesterday * 100: 0;
            const bidPricePercentYesterday = bidPriceYesterday > 0? (bidPrice - bidPriceYesterday) / bidPriceYesterday * 100: 0;

            // 订阅价格
            const subscribePrice = mwi_subscribe_items[itemHridLevel];
            let askPriceSubscribe = subscribePrice.ask;
            let bidPriceSubscribe = subscribePrice.bid;
            if (askPriceSubscribe == -2 && bidPriceSubscribe == -2) {
                askPriceSubscribe = askPrice;
                bidPriceSubscribe = bidPrice;
                updateSubscribePrice(itemHrid, itemLevel);
            }
            const askPricePercentSubscribe = askPriceSubscribe > 0? (askPrice - askPriceSubscribe) / askPriceSubscribe * 100: 0;
            const bidPricePercentSubscribe = bidPriceSubscribe > 0? (bidPrice - bidPriceSubscribe) / bidPriceSubscribe * 100: 0;

            const item = document.createElement("div");
            item.innerHTML = `<div style="position: relative; background: hsl(198, 76.50%, 16.70%); padding: 4px;">
                <div style="display: flex; width: 100%; height: 40px;">
                    <svg role="img" aria-label="${mwi_common.getItemNameByHrid(itemHrid, mwi_common.isZh)}" style="width: 40px; height: 40px;">
                        <use href="/static/media/items_sprite.6d12eb9d.svg#${itemHrid.substr(7)}"></use>
                    </svg>
                    <div style="${mwi_common.isZh? "": "font-size: 13px; "}width: 200px; height: 40px; padding-left: 4px;">
                        <div style="display: flex; gap: 4px; white-space: nowrap;">
                            <div style="color:hsl(202, 41.50%, 71.20%);">${mwi_common.isZh? "名称": "Name"}:</div>
                            <div>${mwi_common.getItemNameByHrid(itemHrid, mwi_common.isZh)}${itemLevel > 0? ` +${itemLevel}`: ""}</div>
                        </div>
                        <div style="display: flex; gap: 4px; white-space: nowrap;">
                            <div style="color:hsl(202, 41.50%, 71.20%);">${mwi_common.isZh? "数量": "Count"}:</div>
                            <div>${formatNumber(itemCount)}</div>
                        </div>
                    </div>
                </div>
                <hr borderColor="hsl(204, 92.60%, 5.30%)" margin="4px 4px">
                <div style="display: flex; gap: 4px; white-space: nowrap;">
                    <div style="color:hsl(202, 41.50%, 71.20%);">${mwi_common.isZh? "今": "T"}:</div>
                    <div>${formatNumberWithUnit(askPrice)} / ${formatNumberWithUnit(bidPrice)}</div>
                    <div>(${formatNumberWithUnit(askPrice * itemCount)} / ${formatNumberWithUnit(bidPrice * itemCount)})</div>
                </div>
                <div style="display: flex; gap: 4px; white-space: nowrap;">
                    <div style="color:hsl(202, 41.50%, 71.20%);">${mwi_common.isZh? "昨": "Y"}:</div>
                    <div>${formatNumberWithUnit(askPriceYesterday)} / ${formatNumberWithUnit(bidPriceYesterday)}</div>
                    <div>(<span style="color: ${getPricePercentColor(askPricePercentYesterday)}">${askPricePercentYesterday.toFixed(2)}%</span> / <span style="color: ${getPricePercentColor(bidPricePercentYesterday)}">${bidPricePercentYesterday.toFixed(2)}%</span>)</div>
                </div>
                <div style="display: flex; gap: 4px; white-space: nowrap;">
                    <div style="color:hsl(202, 41.50%, 71.20%);">${mwi_common.isZh? "订": "S"}:</div>
                    <div>${formatNumberWithUnit(askPriceSubscribe)} / ${formatNumberWithUnit(bidPriceSubscribe)}</div>
                    <div>(<span style="color: ${getPricePercentColor(askPricePercentSubscribe)}">${askPricePercentSubscribe.toFixed(2)}%</span> / <span style="color: ${getPricePercentColor(bidPricePercentSubscribe)}">${bidPricePercentSubscribe.toFixed(2)}%</span>)</div>
                </div>
                <div style="display: flex; justify-content: flex-end; gap: 10px; padding-top: 4px; white-space: nowrap;">
                    <button id="updatePrice" style="background-color: orange; color: black; white-space: nowrap;">${mwi_common.isZh? "更新": "Update"}</button>
                    <button id="goToMarket" style="background-color: orange; color: black; white-space: nowrap;">${mwi_common.isZh? "前往": "Market"}</button>
                </div>
            </div>`;
            displayContainer.appendChild(item);

            // 添加点击事件
            const updatePriceButton = item.querySelector("button#updatePrice");
            const goToMarketButton = item.querySelector("button#goToMarket");
            updatePriceButton && updatePriceButton.addEventListener("click", () => {
                updateSubscribePrice(itemHrid, itemLevel)
                updateSubscribedList();
            });
            goToMarketButton && goToMarketButton.addEventListener("click", () => mwi_common.gotoMarket(itemHrid, itemLevel));
        };
    }

    function createSubscribeButton(marketPanel) {
        const displayContainer = marketPanel.querySelector(".MarketplacePanel_currentItem__3ercC");
        if (!displayContainer) {
            return;
        }

        // 创建收藏按钮
        const subscribeButton = document.createElement("button");
        subscribeButton.setAttribute("id", "subscribeButton");
        subscribeButton.className = "subscribe-btn";
        subscribeButton.style.position = "absolute";
        subscribeButton.style.padding = "0";
        subscribeButton.style.marginLeft = "6px";
        subscribeButton.style.background = "none";
        subscribeButton.style.border = "none";
        subscribeButton.style.outline = "none";
        subscribeButton.style.zIndex = "2"; /** make sure it's on top of the item level div created by MWITools */
        
        // 创建图标
        const svgNS = "http://www.w3.org/2000/svg";
        const svg = document.createElementNS(svgNS, "svg");
        svg.setAttribute("viewBox", "0 0 24 24");
        svg.setAttribute("width", "24");
        svg.setAttribute("height", "24");
        // 未收藏
        const heartUnsubscribed = document.createElementNS(svgNS, "path");
        heartUnsubscribed.setAttribute("d", "M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z");
        heartUnsubscribed.setAttribute("fill", "#aaaaaa");
        heartUnsubscribed.setAttribute("stroke", "#333");
        heartUnsubscribed.setAttribute("transition", "all 0.3s");
        // 已收藏
        const heartSubscribed = document.createElementNS(svgNS, "path");
        heartSubscribed.setAttribute("d", "M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z");
        heartSubscribed.setAttribute("fill", "#ff4d4f");
        heartSubscribed.setAttribute("opacity", "0");
        heartSubscribed.setAttribute("transition", "opacity 0.3s");
        // 添加图标
        svg.appendChild(heartUnsubscribed);
        svg.appendChild(heartSubscribed);
        subscribeButton.appendChild(svg);
        displayContainer.prepend(subscribeButton);
        
        // 物品等级
        let itemHrid = "";
        let itemLevel = 0;
        let itemHridLevel = "";
        let isSubscribed = false;

        const updateSubscribedButton = function() {
            if (isSubscribed) {
                heartUnsubscribed.setAttribute("stroke", "#ff4d4f");
                heartSubscribed.setAttribute("opacity", "1");
            } else {
                heartUnsubscribed.setAttribute("stroke", "#333");
                heartSubscribed.setAttribute("opacity", "0");
            }
        };

        const updateMarketItem = function() {
            itemHrid = "";
            const displayItem = displayContainer.querySelector(".Item_iconContainer__5z7j4");
            if (displayItem && displayItem.firstChild) {
                const itemName = displayItem.firstChild.getAttribute("aria-label");
                if (itemName && itemName !== "") {
                    itemHrid = mwi_common.getItemHridByName(itemName);
                }
            }

            itemLevel = 0;
            const levelDiv = displayContainer.querySelector(".Item_enhancementLevel__19g-e");
            if (levelDiv) {
                itemLevel = Number(levelDiv.textContent.replace("+", ""));
                if (isNaN(itemLevel)) itemLevel = 0;
            }

            itemHridLevel = itemHrid && itemLevel > 0? `${itemHrid}::${itemLevel}`: itemHrid;
            isSubscribed = mwi_subscribe_items[itemHridLevel] != undefined;
            updateSubscribedButton();
        }

        updateMarketItem();
        
        // 绑定点击
        subscribeButton.addEventListener("click", function() {
            if (!itemHridLevel || itemHridLevel === "") {
                return;
            }

            isSubscribed = !isSubscribed;
            updateSubscribedButton();

            if (isSubscribed) {
                console.info("[MWISubscribe] add item " + itemHridLevel);
                mwi_subscribe_items[itemHridLevel] = {
                    ask: mwi_common.getItemPriceByHrid(itemHrid, itemLevel, 'ask'),
                    bid: mwi_common.getItemPriceByHrid(itemHrid, itemLevel, 'bid'),
                }
            }
            else {
                console.info("[MWISubscribe] remove item " + itemHridLevel);
                delete mwi_subscribe_items[itemHridLevel];
            }

            localStorage.setItem(storage_key, JSON.stringify(mwi_subscribe_items));
            updateSubscribedList();
        });

        new MutationObserver(updateMarketItem).observe(displayContainer, { childList: true, subtree: true });
    }
    
    function createDisplayButton(marketPanel) {
        const tabPanelContainer = marketPanel.querySelector(".TabsComponent_tabPanelsContainer__26mzo");
        const filterContainer = marketPanel.querySelector(".MarketplacePanel_itemFilterContainer__3F3td");
        if (!tabPanelContainer || !filterContainer) {
            return;
        }

        const tabList = tabPanelContainer.querySelector("[role=tablist]");
        const panelList = tabPanelContainer.querySelector(".TabsComponent_tabPanelsContainer__26mzo");
        if (!tabList || !panelList) {
            return;
        }
        
        // 创建收藏页签按钮
        const subscribeTabButton = document.createElement("button");
        subscribeTabButton.setAttribute("class", "MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary css-1q2h7u5");
        subscribeTabButton.setAttribute("tabindex", -1);
        subscribeTabButton.setAttribute("role", "tab");
        subscribeTabButton.setAttribute("aria-selected", false);
        subscribeTabButton.setAttribute("id", "subscribeTabButton");
        subscribeTabButton.textContent = mwi_common.isZh? "收藏": "Subscribed";
        tabList.appendChild(subscribeTabButton);

        // 创建收藏面板
        const subscribeDisplayPanel = document.createElement("div");
        subscribeDisplayPanel.setAttribute("class", "TabPanel_tabPanel__tXMJF TabPanel_hidden__26UM3");
        panelList.appendChild(subscribeDisplayPanel);
        // 创建收藏容器
        const subscribeDisplayContainer = document.createElement("div");
        subscribeDisplayContainer.setAttribute("id", "subscribeDisplayContainer");
        subscribeDisplayContainer.style.display = "grid";
        subscribeDisplayContainer.style.gridTemplateColumns = "repeat(auto-fill, 240px)";
        subscribeDisplayContainer.style.gridTemplateRows = "max-content";
        subscribeDisplayContainer.style.gap = "10px";
        subscribeDisplayContainer.style.justifyContent = "center";
        subscribeDisplayContainer.style.overflowY = "auto";
        subscribeDisplayPanel.appendChild(subscribeDisplayContainer);
        updateSubscribedList();
        
        // 创建查看收藏按钮
        const showSubscribeButton = document.createElement("button");
        showSubscribeButton.setAttribute("id", "showSubscribeButton");
        showSubscribeButton.style.position = "absolute";
        showSubscribeButton.style.marginLeft = "20px";
        showSubscribeButton.style.backgroundColor = "orange";
        showSubscribeButton.style.color = "black";
        showSubscribeButton.style.whiteSpace = "nowrap";
        showSubscribeButton.textContent = mwi_common.isZh? "查看收藏": "View Subscribed Items";
        filterContainer.appendChild(showSubscribeButton);

        // 设置按钮点击事件
        tabList.childNodes.forEach((childBtn, btnIdx) => {
            childBtn.addEventListener("click", function() {
                // 按钮样式更改
                tabList.childNodes.forEach(otherBtn => {
                    if (otherBtn === childBtn) {
                        otherBtn.setAttribute("class", "MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary Mui-selected css-1q2h7u5");
                        otherBtn.setAttribute("tabindex", 0);
                        otherBtn.setAttribute("aria-selected", true);
                    }
                    else {
                        otherBtn.setAttribute("class", "MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary css-1q2h7u5");
                        otherBtn.setAttribute("tabindex", -1);
                        otherBtn.setAttribute("aria-selected", false);
                    }
                });
                // 面板样式更改
                panelList.childNodes.forEach((otherPanel, panelIdx) => {
                    if (panelIdx === btnIdx) {
                        otherPanel.setAttribute("class", "TabPanel_tabPanel__tXMJF");
                    }
                    else {
                        otherPanel.setAttribute("class", "TabPanel_tabPanel__tXMJF TabPanel_hidden__26UM3");
                    }
                });
                // 更新收藏列表
                if (childBtn === subscribeTabButton) updateSubscribedList();
            });
        });

        showSubscribeButton.addEventListener("click", function () {
            const filterInput = filterContainer.querySelector(".Input_input__2-t98");
            if (!filterInput) {
                return;
            }
            
            // 取消筛选
            const lastValue = filterInput.value;
            const event = new Event("input", { bubbles: true });
            event.simulated = true;
            filterInput.value = "";
            filterInput._valueTracker && filterInput._valueTracker.setValue(lastValue);
            filterInput.dispatchEvent(new Event("input", { bubbles: true }));

            // 选中收藏页签
            subscribeTabButton.click();
        });
    }

    function addButton() {
        const marketPanel = document.querySelector(".MarketplacePanel_marketplacePanel__21b7o ");
        if (!marketPanel) {
            return;
        }

        const subscribeButton = marketPanel.querySelector("button#subscribeButton");
        subscribeButton || createSubscribeButton(marketPanel);

        const displayButton = marketPanel.querySelector("button#subscribeTabButton");
        displayButton || createDisplayButton(marketPanel);
    }

    function start() {
        console.info("[MWISubscribe] start");
        mwi_common = window.mwi_common;
        if (!mwi_common) {
            console.error("[MWISubscribe] mwi_common not found");
            return;
        }

        setInterval(addButton, 500);
        // 监听市场数据变动
        document.addEventListener(mwi_common.eventNames.marketDataUpdate, updateSubscribedList);
    }

    if (window.mwi_common) start();
    else {
        console.info("[MWISubscribe] waiting for mwi_common");
        document.addEventListener("mwi_common_injected", start);
    }

})();