// ==UserScript== // @name [银河奶牛]食用工具 // @namespace http://tampermonkey.net/ // @version 0.37 // @description 开箱记录、箱子期望、离线统计、公会钉钉 // @author Truth_Light // @license Truth_Light // @match https://www.milkywayidle.com/* // @match https://test.milkywayidle.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=milkywayidle.com // @grant GM.xmlHttpRequest // @downloadURL none // ==/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'); let marketData = JSON.parse(marketDataStr); const MARKET_API_URL = "https://raw.githubusercontent.com/holychikenz/MWIApi/main/medianmarket.json"; let timer = null; let chestList = {}; if (!marketData) { GM.xmlHttpRequest({ method: 'GET', url: MARKET_API_URL, responseType: 'json', timeout: 5000, onload: function(response) { if (response.status === 200) { marketData = JSON.parse(response.responseText); console.log('从API获取到的数据:', marketData); } else { console.error('获取数据失败。状态码:', response.status); } }, ontimeout: function() { console.error('请求超时:超过5秒未能获取到数据'); }, onerror: function(error) { console.error('获取数据时发生错误:', error); } }); } function getSpecialItemPrice(itemName, priceType) { if (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 || 30000, bid: getSpecialItemPrice('Bag Of 10 Cowbells', 'bid') / 10 || 25000 }, 'Chimerical Token': { ask: getSpecialItemPrice('Chimerical Essence', 'ask') || 1300, bid: getSpecialItemPrice('Chimerical Essence', 'bid') || 1100 }, 'Sinister Token': { ask: getSpecialItemPrice('Sinister Essence', 'ask') || 1400, bid: getSpecialItemPrice('Sinister Essence', 'bid') || 1200 }, 'Enchanted Token': { ask: getSpecialItemPrice('Enchanted Essence', 'ask') || 3900, bid: getSpecialItemPrice('Enchanted Essence', 'bid') || 3500 }, }; 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 (marketData) { 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,chestCount) { 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}
本次开箱价值:
${formatPrice(lastOpenAskValue)}/${formatPrice(lastOpenBidValue)}
总计开箱价值:
${formatPrice(openTotalAskValue)}/${formatPrice(openTotalBidValue)}
`; elementA.appendChild(displayElement); // 判断是否显示期望产出元素 if (chestData && chestData.totalAskValue && chestData.totalBidValue) { const totalAskValue = parseFloat(chestData.totalAskValue); const totalBidValue = parseFloat(chestData.totalBidValue); const expectedAskOutput = totalAskValue * chestCount; const expectedBidOutput = totalBidValue * chestCount; // 创建新的显示元素 const expectedOutputElement = document.createElement('div'); expectedOutputElement.classList.add('ExpectedOutput'); expectedOutputElement.style.position = 'absolute'; expectedOutputElement.style.left = `${elementA.offsetLeft}px`; expectedOutputElement.style.fontSize = '12px'; expectedOutputElement.innerHTML = ` 期望产出:
${formatPrice(expectedAskOutput)}/${formatPrice(expectedBidOutput)}
`; //调整位置 document.body.appendChild(expectedOutputElement); expectedOutputElement.style.top = `${elementA.offsetTop + elementA.offsetHeight - expectedOutputElement.offsetHeight}px`; elementA.appendChild(expectedOutputElement); } 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) { if (document.querySelector('.ChestStatistics')) { return; } 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,chestCount); } } function calculateTotalValues(itemElements) { let totalAskValue = 0; let totalBidValue = 0; 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 itemTotalAskValue = itemAskValue * itemQuantity; const itemTotalBidValue = itemBidValue * itemQuantity; totalAskValue += itemTotalAskValue; totalBidValue += itemTotalBidValue; } }); console.log(totalAskValue) return { totalAskValue, totalBidValue }; } function OfflineStatistics(modalElement) { const itemsContainer = modalElement.querySelectorAll(".OfflineProgressModal_itemList__26h-Y"); let timeContainer = null; let getItemContainer = null; let spendItemContainer = null; itemsContainer.forEach(container => { const labelElement = container.querySelector('.OfflineProgressModal_label__2HwFG'); if (labelElement) { const textContent = labelElement.textContent.trim(); if (textContent.startsWith("You were offline for") || textContent.startsWith("你离线了")) { timeContainer = container; } else if (textContent.startsWith("Items gained:") || textContent.startsWith("获得物品:")) { getItemContainer = container; } else if (textContent.startsWith("You consumed:") || textContent.startsWith("你消耗了:")) { spendItemContainer = container; } } }); let TotalSec = null; if (timeContainer) { const textContent = timeContainer.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; } } let getitemtotalAskValue = 0; let getitemtotalBidValue = 0; if (getItemContainer) { const getitemElements = getItemContainer.querySelectorAll('.Item_itemContainer__x7kH1'); const { totalAskValue, totalBidValue } = calculateTotalValues(getitemElements); getitemtotalAskValue = totalAskValue; getitemtotalBidValue = totalBidValue; } let spenditemtotalAskValue = 0; let spenditemtotalBidValue = 0; if (spendItemContainer) { const spenditemElements = spendItemContainer.querySelectorAll('.Item_itemContainer__x7kH1'); const { totalAskValue, totalBidValue } = calculateTotalValues(spenditemElements); spenditemtotalAskValue = totalAskValue; spenditemtotalBidValue = totalBidValue; } if (timeContainer) { const newElement = document.createElement('span'); newElement.textContent = `利润:${formatPrice(getitemtotalBidValue - spenditemtotalAskValue)}[${formatPrice((getitemtotalBidValue - spenditemtotalAskValue) / (TotalSec / 3600) * 24)}/天]`; newElement.style.float = 'right'; newElement.style.color = 'gold'; timeContainer.querySelector(':first-child').appendChild(newElement); } if (getItemContainer) { const newElement = document.createElement('span'); newElement.textContent = `产出:[${formatPrice(getitemtotalAskValue)}/${formatPrice(getitemtotalBidValue)}]`; newElement.style.float = 'right'; newElement.style.color = 'gold'; getItemContainer.querySelector(':first-child').appendChild(newElement); } if (spendItemContainer) { const newElement = document.createElement('span'); newElement.textContent = `成本:[${formatPrice(spenditemtotalAskValue)}/${formatPrice(spenditemtotalBidValue)}]`; newElement.style.float = 'right'; newElement.style.color = 'gold'; spendItemContainer.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(); const updataDealy = 24*60*60*1000; //数据更新时限 let rateXPDayMap = {}; function hookWS() { const dataProperty = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data"); const oriGet = dataProperty.get; dataProperty.get = hookedGet; Object.defineProperty(MessageEvent.prototype, "data", dataProperty); function hookedGet() { const socket = this.currentTarget; if (!(socket instanceof WebSocket)) { return oriGet.call(this); } if (socket.url.indexOf("api.milkywayidle.com/ws") <= -1 && socket.url.indexOf("api-test.milkywayidle.com/ws") <= -1) { return oriGet.call(this); } const message = oriGet.call(this); Object.defineProperty(this, "data", { value: message }); // Anti-loop return handleMessage(message); } } //奶牛钉钉 function handleMessage(message) { try { let obj = JSON.parse(message); let timeDifference = null; let newUpdatedAt = null; if (obj && obj.type === "guild_updated") { const Guild_ID = obj.guild.id; let storedData = JSON.parse(localStorage.getItem("Guild_Data")) || {}; // 判断是否已经存在旧数据 if (storedData[Guild_ID] && storedData[Guild_ID].guild_updated && storedData[Guild_ID].guild_updated.old.updatedAt) { const oldUpdatedAt = new Date(storedData[Guild_ID].guild_updated.new.updatedAt); newUpdatedAt = new Date(obj.guild.updatedAt); // 计算时间差(单位:毫秒) timeDifference = newUpdatedAt - oldUpdatedAt; if (timeDifference >= updataDealy) { // 更新老数据为新数据 storedData[Guild_ID].guild_updated.old = storedData[Guild_ID].guild_updated.new; // 更新新数据为当前数据 storedData[Guild_ID].guild_updated.new = { experience: obj.guild.experience, level: obj.guild.level, updatedAt: obj.guild.updatedAt }; } else { // 仅更新新数据 storedData[Guild_ID].guild_updated.new = { experience: obj.guild.experience, level: obj.guild.level, updatedAt: obj.guild.updatedAt }; } //计算Δ const Delta = { Delta_Xp: storedData[Guild_ID].guild_updated.new.experience - storedData[Guild_ID].guild_updated.old.experience, Delta_Level: storedData[Guild_ID].guild_updated.new.level - storedData[Guild_ID].guild_updated.old.level, Delta_Time: (newUpdatedAt - new Date(storedData[Guild_ID].guild_updated.old.updatedAt)) / 1000, // 转换为秒 Rate_XP_Hours: (3600*(obj.guild.experience - storedData[Guild_ID].guild_updated.old.experience)/((newUpdatedAt - new Date(storedData[Guild_ID].guild_updated.old.updatedAt)) / 1000)).toFixed(2) }; storedData[Guild_ID].guild_updated.Delta = Delta; const Guild_TotalXp_div = document.querySelectorAll(".GuildPanel_value__Hm2I9")[1]; if (Guild_TotalXp_div) { Guild_TotalXp_div.insertAdjacentHTML( "afterend", // 使用 "afterend" 在元素的后面插入内容 `
${formatPrice(Delta.Rate_XP_Hours)} 经验值 / 小时
` ); const Guild_NeedXp_div = document.querySelectorAll(".GuildPanel_value__Hm2I9")[2]; if (Guild_NeedXp_div) { const Guild_NeedXp = document.querySelectorAll(".GuildPanel_value__Hm2I9")[2].textContent.replace(/,/g, ''); const Time = TimeReset(Guild_NeedXp/Delta.Rate_XP_Hours); Guild_NeedXp_div.insertAdjacentHTML( "afterend", // 使用 "afterend" 在元素的后面插入内容 `
${Time}
` ); } } } else { // 如果没有旧数据,则直接添加新数据 storedData[Guild_ID] = { guild_name: obj.guild.name, guild_updated: { old: { experience: obj.guild.experience, level: obj.guild.level, updatedAt: obj.guild.updatedAt }, new: {}, } }; } // 存储更新后的数据到 localStorage localStorage.setItem("Guild_Data", JSON.stringify(storedData)); } else if (obj && obj.type === "guild_characters_updated") { let storedData = JSON.parse(localStorage.getItem("Guild_Data")) || {}; for (const key in obj.guildSharableCharacterMap) { if (obj.guildSharableCharacterMap.hasOwnProperty(key)) { const Guild_ID = obj.guildCharacterMap[key].guildID; const name = obj.guildSharableCharacterMap[key].name; storedData[Guild_ID].guild_player = storedData[Guild_ID].guild_player || {}; if (storedData[Guild_ID] && storedData[Guild_ID].guild_player && storedData[Guild_ID].guild_player[name] && storedData[Guild_ID].guild_player[name].old) { if (timeDifference >= updataDealy) { // 更新老数据为新数据 storedData[Guild_ID].guild_player[name].old = storedData[Guild_ID].guild_player[name].new; // 更新新数据为当前数据 storedData[Guild_ID].guild_player[name].new = { id: key, gameMode: obj.guildSharableCharacterMap[key].gameMode, guildExperience: obj.guildCharacterMap[key].guildExperience }; } else { // 仅更新新数据 storedData[Guild_ID].guild_player[name].new = { id: key, gameMode: obj.guildSharableCharacterMap[key].gameMode, guildExperience: obj.guildCharacterMap[key].guildExperience }; } //计算Δ const Delta = { Delta_Xp: storedData[Guild_ID].guild_player[name].new.guildExperience - storedData[Guild_ID].guild_player[name].old.guildExperience, Rate_XP_Day: (24*3600*(obj.guildCharacterMap[key].guildExperience - storedData[Guild_ID].guild_player[name].old.guildExperience)/(storedData[Guild_ID].guild_updated.Delta.Delta_Time)).toFixed(2) }; storedData[Guild_ID].guild_player[name].Delta = Delta; rateXPDayMap[name] = Delta.Rate_XP_Day; }else { storedData[Guild_ID].guild_player[name] = { old: { id: key, gameMode: obj.guildSharableCharacterMap[key].gameMode, guildExperience: obj.guildCharacterMap[key].guildExperience }, new:{} }; } } } //console.log("测试数据",storedData); //console.log("guild_characters_updated", obj); updateExperienceDisplay(rateXPDayMap); localStorage.setItem("Guild_Data", JSON.stringify(storedData)); } } catch (error) { console.error("Error processing message:", error); } return message; } function TimeReset(hours) { const totalMinutes = hours * 60; const days = Math.floor(totalMinutes / (24 * 60)); const yudays = totalMinutes % (24 * 60); const hrs = Math.floor(yudays / 60); const minutes = Math.floor(yudays % 60); return `${days}天 ${hrs}时 ${minutes}分`; } function updateExperienceDisplay(rateXPDayMap) { const trElements = document.querySelectorAll(".GuildPanel_membersTable__1NwIX tbody tr"); // 将 rateXPDayMap 转换为数组,便于排序和排名计算 const sortedMembers = Object.entries(rateXPDayMap) .map(([name, XPdata]) => ({ name, XPdata })) .sort((a, b) => b.XPdata - a.XPdata); // 按 XPdata 降序排序 for (let { name, XPdata } of sortedMembers) { for (let tr of trElements) { const nameElement = tr.querySelector(".CharacterName_name__1amXp"); if (nameElement && nameElement.textContent.trim() === name) { const experienceElement = tr.querySelector("td:nth-child(3) > div"); if (experienceElement) { const newDiv = document.createElement('div'); newDiv.textContent = `${formatPrice(XPdata)}/天`; // 计算颜色:根据排名计算渐变颜色,排名越高越绿,排名越低越红 const hue = 120 - (sortedMembers.findIndex(member => member.name === name) * (120 / (sortedMembers.length - 1))); newDiv.style.color = `hsl(${hue}, 100%, 50%)`; experienceElement.insertAdjacentElement('afterend', newDiv); break; } } } } } hookWS(); })();