Greasy Fork

Greasy Fork is available in English.

[银河奶牛]食用工具

开箱记录、箱子期望、离线统计

当前为 2024-07-09 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         [银河奶牛]食用工具
// @namespace    http://tampermonkey.net/
// @version      0.34
// @description  开箱记录、箱子期望、离线统计
// @author       Truth_Light
// @license      Truth_Light
// @match        https://www.milkywayidle.com/*
// @match        https://test.milkywayidle.com/*
// ==/UserScript==

(function() {
    'use strict';

    const itemSelector = '.ItemDictionary_drop__24I5f';
    const iconSelector = '.Icon_icon__2LtL_ use';
    const chestNameSelector = '#root > div > div > div.Modal_modalContainer__3B80m > div.Modal_modal__1Jiep > div.ItemDictionary_modalContent__WvEBY > div.ItemDictionary_itemAndDescription__28_he > div.Item_itemContainer__x7kH1 > div > div > div > div > svg > use';
    const resultDisplaySelector = '.ItemDictionary_openToLoot__1krnv';
    const marketDataStr = localStorage.getItem('MWITools_marketAPI_json');
    const marketData = JSON.parse(marketDataStr);
    let timer = null;
    let chestList = {};

    function getSpecialItemPrice(itemName, priceType) {
        if (marketDataStr && marketData) {
            if (marketData.market && marketData.market[itemName]) {
                const itemPrice = marketData.market[itemName][priceType];
                if (itemPrice !== undefined && itemPrice !== -1) {
                    return itemPrice;
                }
            }
        }
        console.error(`未找到物品 ${itemName} 的 ${priceType} 价格信息`);
        return null; // 或者返回默认值,视情况而定
    }

    let specialItemPrices = {
        'Coin': { ask: 1, bid: 1 }, // 默认的特殊物品价值,包括ask和bid价值
        'Cowbell': {
            ask: getSpecialItemPrice('Bag Of 10 Cowbells', 'ask')/10,
            bid: getSpecialItemPrice('Bag Of 10 Cowbells', 'bid')/10
        },
        'Chimerical Token': {
            ask: getSpecialItemPrice('Chimerical Essence', 'ask'),
            bid: getSpecialItemPrice('Chimerical Essence', 'bid')
        },
        'Sinister Token': {
            ask: getSpecialItemPrice('Sinister Essence', 'ask'),
            bid: getSpecialItemPrice('Sinister Essence', 'bid')
        },
        'Enchanted Token': {
            ask: getSpecialItemPrice('Enchanted Essence', 'ask'),
            bid: getSpecialItemPrice('Enchanted Essence', 'bid')
        },
    };

    function saveChestList() {
        localStorage.setItem('chestList', JSON.stringify(chestList));
    }

    function loadChestList() {
        const savedChestList = localStorage.getItem('chestList');
        chestList = savedChestList ? JSON.parse(savedChestList) : {};
    }

    function getItemNameFromElement(element) {
        const itemNameRaw = element.getAttribute('href').split('#').pop();
        return formatItemName(itemNameRaw);
    }

    function handleNaNValues(itemName,values) {
        if (isNaN(values)) {
            console.error(`物品 ${itemName} 的值 为 NaN`);
            return -1;
        }
        return values;
    }

    function getItemPrice(itemName) {
        let itemAskValue = 0;
        let itemBidValue = 0;
        let priceColor = '#E7E7E7';

        if (chestList[itemName] && chestList[itemName].totalAskValue) {
            // 如果是箱子,直接使用chestList中的价格信息
            itemAskValue = chestList[itemName].totalAskValue;
            itemBidValue = chestList[itemName].totalBidValue;
        } else {
            if (specialItemPrices[itemName]) {
                itemAskValue = specialItemPrices[itemName].ask;
                itemBidValue = specialItemPrices[itemName].bid;
            } else {
                if (marketDataStr) {
                    try {
                        if (marketData && marketData.market && marketData.market[itemName]) {
                            itemAskValue = marketData.market[itemName].ask;
                            itemBidValue = marketData.market[itemName].bid;

                            if (itemAskValue === -1 && itemBidValue === -1) {
                                priceColor = 'yellow';
                            } else if (itemAskValue === -1) {
                                priceColor = '#D95961';
                            } else if (itemBidValue === -1) {
                                priceColor = '#2FC4A7';
                            }

                            if (itemAskValue === -1 && itemBidValue !== -1) {
                                itemAskValue = itemBidValue;
                            }
                        } else {
                            console.error(`未找到物品 ${itemName} 的价格信息`);
                            priceColor = 'yellow';
                        }

                    } catch (error) {
                        console.error(`解析 MWITools_marketAPI_json 数据时出错:`, error);
                    }
                } else {
                    console.error('未找到 MWITools_marketAPI_json 的本地存储数据');
                }
            }
        }

        return { ask: itemAskValue, bid: itemBidValue, priceColor };
    }

    function formatItemName(itemNameRaw) {
        let formattedName = itemNameRaw.replace('#', '').replace(/_/g, ' ').replace(/\b\w/g, char => char.toUpperCase());

        if (formattedName.includes(' ')) {
            const words = formattedName.split(' ');
            let firstWord = words[0];
            const restOfName = words.slice(1).join(' ');
            if (firstWord.endsWith('s') && !firstWord.endsWith("'s")) {
                firstWord = `${firstWord.slice(0, -1)}'${firstWord.slice(-1)}`;
            }
            formattedName = `${firstWord}${restOfName ? " " + restOfName : ""}`;
        }

        return formattedName;
    }

    function parseQuantityRange(rangeText) {
        const parts = rangeText.split('-').map(str => parseInt(str.trim().replace(',', ''), 10));
        if (parts.length === 1) {
            return { min: parts[0], max: parts[0] };
        } else {
            return { min: parts[0], max: parts[1] };
        }
    }

    function parseProbability(probabilityText) {
        const probPercentage = parseFloat(probabilityText.replace('%', ''));
        return probPercentage / 100;
    }

    function formatPrice(value) {
        const isNegative = value < 0;
        value = Math.abs(value);

        if (value >= 1000000) {
            return (isNegative ? '-' : '') + (value / 1000000).toFixed(1) + 'M';
        } else if (value >= 1000) {
            return (isNegative ? '-' : '') + (value / 1000).toFixed(1) + 'K';
        } else {
            return (isNegative ? '-' : '') + value.toString();
        }
    }


    function parseQuantityString(quantityStr) {
        const suffix = quantityStr.slice(-1);
        const base = parseFloat(quantityStr.slice(0, -1));
        if (suffix === 'K') {
            return base * 1000;
        } else if (suffix === 'M') {
            return base * 1000000;
        } else if (suffix === 'B') {
            return base * 1000000000;
        } else {
            return parseFloat(quantityStr);
        }
    }

    function displayResult(container, totalExpectedOutputASK, totalExpectedOutputBID) {
        const formattedASK = formatPrice(totalExpectedOutputASK);
        const formattedBID = formatPrice(totalExpectedOutputBID);

        const dropListContainer = container.querySelector(resultDisplaySelector);

        // 继续执行其他操作
        const previousResults = dropListContainer.querySelectorAll('.resultDiv');
        previousResults.forEach(result => result.remove());

        // 创建期望产出(最低买入价计算)元素
        const minPriceOutput = document.createElement('div');
        minPriceOutput.className = 'resultDiv';
        minPriceOutput.textContent = `期望产出 (最低买入价计算): ${formattedASK}`;
        minPriceOutput.style.color = 'gold';
        minPriceOutput.style.fontSize = '14px';
        minPriceOutput.style.fontWeight = '400';
        minPriceOutput.style.paddingTop = '10px';

        // 创建期望产出(最高收购价计算)元素
        const maxPriceOutput = document.createElement('div');
        maxPriceOutput.className = 'resultDiv';
        maxPriceOutput.textContent = `期望产出 (最高收购价计算): ${formattedBID}`;
        maxPriceOutput.style.color = 'gold';
        maxPriceOutput.style.fontSize = '14px';
        maxPriceOutput.style.fontWeight = '400';
        maxPriceOutput.style.paddingTop = '10px';

        // 插入新创建的元素到掉落物表的最后一个物品后面
        dropListContainer.appendChild(minPriceOutput);
        dropListContainer.appendChild(maxPriceOutput);
    }

    function displayChestStatistics(chestName) {
        const elementA = document.querySelector('.Inventory_modalContent__3ObSx');

        if (elementA) {
            // 获取总计开箱次数和开箱价值(ask/bid)
            const chestData = chestList[chestName];
            const totalOpened = chestData ? chestData.totalOpened : 0;
            const lastOpenAskValue = chestData ? chestData.lastOpenAskValue : 0;
            const lastOpenBidValue = chestData ? chestData.lastOpenBidValue : 0;
            const openTotalAskValue = chestData ? chestData.OpenTotalAskValue : 0;
            const openTotalBidValue = chestData ? chestData.OpenTotalBidValue : 0;

            // 创建显示内容
            const displayElement = document.createElement('div');
            displayElement.classList.add('ChestStatistics'); // 自定义类名,用于样式控制
            displayElement.style.position = 'absolute';
            displayElement.style.left = `${elementA.offsetLeft}px`;
            displayElement.style.top = `${elementA.offsetTop}px`;
            displayElement.style.fontSize = '12px';
            displayElement.innerHTML = `
                总计开箱次数: ${totalOpened}<br>
                本次开箱价值:<br>
                ${formatPrice(lastOpenAskValue)}/${formatPrice(lastOpenBidValue)}<br>
                总计开箱价值:<br>
                ${formatPrice(openTotalAskValue)}/${formatPrice(openTotalBidValue)}<br>
            `;

            // 插入到元素A内部
            elementA.appendChild(displayElement);

            // 返回显示元素,以便后续删除
            return displayElement;
        } else {
            console.error('未找到窗体元素');
            return null;
        }
    }


    function processItems() {
        const modalContainer = document.querySelector(".Modal_modalContainer__3B80m");
        if (!modalContainer) return; // 如果不存在 Modal_modalContainer__3B80m 元素,则直接返回

        const chestNameElem = document.querySelector(chestNameSelector);
        if (!chestNameElem) return;

        const chestName = getItemNameFromElement(chestNameElem);

        const items = document.querySelectorAll(itemSelector);

        const itemDataList = [];
        let totalAskValue = 0;
        let totalBidValue = 0;

        items.forEach(item => {
            const quantityRangeElem = item.querySelector('div:first-child');
            const quantityRangeText = quantityRangeElem.textContent.trim();
            const quantityRange = parseQuantityRange(quantityRangeText);

            const itemName = getItemNameFromElement(item.querySelector(iconSelector));

            let probabilityElem = item.querySelector('div:nth-child(3)');//提取物品的概率
            let probabilityText = probabilityElem ? probabilityElem.textContent.trim() : '';
            probabilityText = probabilityText.replace('~', '');

            let probability;
            if (probabilityText === '') {
                probability = 1.0; // 如果概率文本为空,则假定掉落率为100%
            } else {
                probability = parseProbability(probabilityText);
            }

            let expectedOutput = 0;
            if (quantityRange.min === quantityRange.max) {
                expectedOutput = quantityRange.min * probability;
            } else {
                const average = (quantityRange.min + quantityRange.max) / 2;
                expectedOutput = average * probability;
            }

            let { ask: itemAskValue, bid: itemBidValue, priceColor } = getItemPrice(itemName);

            const itemTotalAskValue = expectedOutput * itemAskValue;
            const itemTotalBidValue = expectedOutput * itemBidValue;

            totalAskValue += itemTotalAskValue;
            totalBidValue += itemTotalBidValue;

            const itemData = {
                itemName,
                quantityRange: `${quantityRange.min}-${quantityRange.max}`,
                probability: probability * 100,
                expectedOutput: expectedOutput.toFixed(2),
                itemAskValue,
                itemBidValue,
                itemTotalAskValue: itemTotalAskValue.toFixed(2),
                itemTotalBidValue: itemTotalBidValue.toFixed(2),
                priceColor
            };

            itemDataList.push(itemData);

            const itemNameElem = item.querySelector('.Item_name__2C42x');
            if (itemNameElem) {
                if (priceColor) {
                    itemNameElem.style.color = priceColor;
                }
            }

        });

        if (itemDataList.length > 0) {
            chestList[chestName] = {
                items: itemDataList,
                totalAskValue: totalAskValue.toFixed(2),
                totalBidValue: totalBidValue.toFixed(2)
            };
            saveChestList();
            displayResult(document.body, totalAskValue, totalBidValue);
        }
    }

    function recordChestOpening(modalElement) {

        const chestNameElement = modalElement.querySelector("div.Modal_modal__1Jiep > div.Inventory_modalContent__3ObSx > div.Item_itemContainer__x7kH1 > div > div > div.Item_iconContainer__5z7j4 > div > svg > use");
        const chestCountElement = modalElement.querySelector("div.Modal_modal__1Jiep > div.Inventory_modalContent__3ObSx > div.Item_itemContainer__x7kH1 > div > div > div.Item_count__1HVvv");

        if (chestNameElement && chestCountElement) {
            const chestName = getItemNameFromElement(chestNameElement);
            const chestCount = parseQuantityString(chestCountElement.textContent.trim());

            const itemsContainer = modalElement.querySelector('.Inventory_gainedItems___e9t9');
            const itemElements = itemsContainer.querySelectorAll('.Item_itemContainer__x7kH1');

            let totalAskValue = 0;
            let totalBidValue = 0;
            const items = [];

            itemElements.forEach(itemElement => {
                const itemNameElement = itemElement.querySelector('.Item_iconContainer__5z7j4 use');
                const itemQuantityElement = itemElement.querySelector('.Item_count__1HVvv');

                if (itemNameElement && itemQuantityElement) {
                    const itemName = getItemNameFromElement(itemNameElement);
                    const itemQuantity = parseQuantityString(itemQuantityElement.textContent.trim());
                    const { ask: itemAskValue, bid: itemBidValue, priceColor } = getItemPrice(itemName);

                    const itemOpenTotalAskValue = itemAskValue * itemQuantity;
                    const itemOpenTotalBidValue = itemBidValue * itemQuantity;

                    items.push({
                        itemName,
                        itemAskValue,
                        itemBidValue,
                        itemOpenTotalAskValue,
                        itemOpenTotalBidValue,
                        quantity: itemQuantity,
                        priceColor
                    });

                    totalAskValue += itemOpenTotalAskValue;
                    totalBidValue += itemOpenTotalBidValue;
                }
            });

            if (!chestList[chestName]) {
                chestList[chestName] = {
                    items: [],
                    OpenTotalAskValue: 0,
                    OpenTotalBidValue: 0,
                    totalOpened: 0,
                    lastOpenAskValue: 0,
                    lastOpenBidValue: 0
                };
            } else {
                const chestData = chestList[chestName];

                chestData.OpenTotalAskValue = chestData.OpenTotalAskValue || 0;
                chestData.OpenTotalBidValue = chestData.OpenTotalBidValue || 0;
                chestData.totalOpened = chestData.totalOpened || 0;
                chestData.lastOpenAskValue = chestData.lastOpenAskValue || 0;
                chestData.lastOpenBidValue = chestData.lastOpenBidValue || 0;
            }

            const chestData = chestList[chestName];

            chestData.lastOpenAskValue = totalAskValue;
            chestData.lastOpenBidValue = totalBidValue;
            chestData.OpenTotalAskValue += totalAskValue;
            chestData.OpenTotalBidValue += totalBidValue;
            chestData.totalOpened += chestCount;

            items.forEach(item => {
                const existingItem = chestData.items.find(i => i.itemName === item.itemName);
                if (existingItem) {
                    existingItem.quantity += item.quantity;
                    existingItem.itemOpenTotalAskValue += item.itemOpenTotalAskValue;
                    existingItem.itemOpenTotalBidValue += item.itemOpenTotalBidValue;
                } else {
                    chestData.items.push(item);
                }
            });

            saveChestList();
            displayChestStatistics(chestName);

        }
    }


    function calculateTotalValues(itemElements) {
        let totalAskValue = 0;
        let totalBidValue = 0;
        console.log("1")
        itemElements.forEach(itemElement => {
            const itemNameElement = itemElement.querySelector('.Item_iconContainer__5z7j4 use');
            const itemQuantityElement = itemElement.querySelector('.Item_count__1HVvv');
            console.log("2")
            if (itemNameElement && itemQuantityElement) {
                const itemName = getItemNameFromElement(itemNameElement);
                const itemQuantity = parseQuantityString(itemQuantityElement.textContent.trim());
                const { ask: itemAskValue, bid: itemBidValue, priceColor } = getItemPrice(itemName);

                const itemTotalAskValue = itemAskValue * itemQuantity;
                const itemTotalBidValue = itemBidValue * itemQuantity;
                console.log("3")
                totalAskValue += itemTotalAskValue;
                totalBidValue += itemTotalBidValue;
            }
        });
        console.log("4")
        console.log(totalAskValue)
        return { totalAskValue, totalBidValue };
    }


    function OfflineStatistics(modalElement) {
        const itemsContainer = modalElement.querySelectorAll(".OfflineProgressModal_itemList__26h-Y");
        const itemsContainer_0 = itemsContainer[0];
        const itemsContainer_1 = itemsContainer[1];
        let itemsContainer_2 = null;
        let spenditemtotalAskValue = 0;
        let spenditemtotalBidValue = 0;
        if (itemsContainer[2]) {
            itemsContainer_2 = itemsContainer[2];
            const spenditemElements = itemsContainer_2.querySelectorAll('.Item_itemContainer__x7kH1');
            const { totalAskValue, totalBidValue } = calculateTotalValues(spenditemElements);
            spenditemtotalAskValue = totalAskValue;
            spenditemtotalBidValue = totalBidValue;
        }
        let TotalSec = null;
        if (itemsContainer_0) {
            const textContent = itemsContainer_0.textContent;
            const match = textContent.match(/(?:(\d+)d\s*)?(?:(\d+)h\s*)?(?:(\d+)m\s*)?(?:(\d+)s)/);
            if (match) {
                let days = parseInt(match[1], 10) || 0;
                let hours = parseInt(match[2], 10) || 0;
                let minutes = parseInt(match[3], 10) || 0;
                let seconds = parseInt(match[4], 10) || 0;
                TotalSec = days * 86400 + hours * 3600 + minutes * 60 + seconds;

            }
        }

        const getitemElements = itemsContainer_1.querySelectorAll('.Item_itemContainer__x7kH1');
        const { totalAskValue:getitemtotalAskValue, totalBidValue:getitemtotalBidValue } = calculateTotalValues(getitemElements);




        if (itemsContainer_0) {
            const newElement = document.createElement('span');
            newElement.textContent = `利润:${formatPrice(getitemtotalBidValue-spenditemtotalAskValue)}[${formatPrice((getitemtotalBidValue-spenditemtotalAskValue)/(TotalSec/3600)*24)}/天]`;
            newElement.style.float = 'right';
            newElement.style.color = 'gold';
            itemsContainer_0.querySelector(':first-child').appendChild(newElement);
        }
        if (itemsContainer_1) {
            const newElement = document.createElement('span');
            newElement.textContent = `产出:[${formatPrice(getitemtotalAskValue)}/${formatPrice(getitemtotalBidValue)}]`;
            newElement.style.float = 'right';
            newElement.style.color = 'gold';
            itemsContainer_1.querySelector(':first-child').appendChild(newElement);
        }
        if (itemsContainer_2) {
            const newElement = document.createElement('span');
            newElement.textContent = `成本:[${formatPrice(spenditemtotalAskValue)}/${formatPrice(spenditemtotalBidValue)}]`;
            newElement.style.float = 'right';
            newElement.style.color = 'gold';
            itemsContainer_2.querySelector(':first-child').appendChild(newElement);
        }
    }

    // 初始化时加载已保存的箱子列表
    loadChestList();
    console.log(chestList);
    // 初始化


    function initObserver() {
        // 选择要观察的目标节点
        const targetNode = document.body;

        // 观察器的配置(需要观察子节点的变化)
        const config = { childList: true, subtree: true };

        // 创建一个观察器实例并传入回调函数
        const observer = new MutationObserver(mutationsList => {
            for (let mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    // 监听到子节点变化
                    mutation.addedNodes.forEach(addedNode => {
                        // 检查是否是我们关注的 Modal_modalContainer__3B80m 元素被添加
                        if (addedNode.classList && addedNode.classList.contains('Modal_modalContainer__3B80m')) {
                            // Modal_modalContainer__3B80m 元素被添加,执行处理函数
                            processItems();
                            recordChestOpening(addedNode);

                            // 开始监听箱子图标的变化
                            startIconObserver();
                        }
                        if (addedNode.classList && addedNode.classList.contains('OfflineProgressModal_modalContainer__knnk7')) {
                            OfflineStatistics(addedNode);
                            console.log("离线报告已创建!")
                        }
                    });

                    mutation.removedNodes.forEach(removedNode => {
                        // 检查是否是 Modal_modalContainer__3B80m 元素被移除
                        if (removedNode.classList && removedNode.classList.contains('Modal_modalContainer__3B80m')) {
                            // Modal_modalContainer__3B80m 元素被移除,停止监听箱子图标的变化
                            stopIconObserver();
                        }
                    });
                }
            }
        });

        // 以上述配置开始观察目标节点
        observer.observe(targetNode, config);

        // 定义箱子图标变化的观察器
        let iconObserver = null;

        // 开始监听箱子图标的变化
        function startIconObserver() {
            const chestNameElem = document.querySelector(chestNameSelector);
            if (!chestNameElem) return;

            // 创建一个观察器实例来监听图标的变化
            iconObserver = new MutationObserver(() => {
                // 当箱子图标变化时,执行处理函数
                processItems();
            });

            // 配置观察器的选项
            const iconConfig = { attributes: true, attributeFilter: ['href'] };

            // 以上述配置开始观察箱子图标节点
            iconObserver.observe(chestNameElem, iconConfig);
        }

        // 停止监听箱子图标的变化
        function stopIconObserver() {
            if (iconObserver) {
                iconObserver.disconnect();
                iconObserver = null;
            }
        }
    }


    initObserver();

})();