- // ==UserScript==
- // @name [银河奶牛]食用工具
- // @namespace http://tampermonkey.net/
- // @version 0.33
- // @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) {
- if (value >= 1000000) {
- return (value / 1000000).toFixed(1) + 'M';
- } else if (value >= 1000) {
- return (value / 1000).toFixed(1) + 'K';
- } else {
- return 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();
-
- })();