// ==UserScript==
// @name [银河奶牛]食用工具
// @namespace http://tampermonkey.net/
// @version 0.38
// @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
// ==/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}<br>
本次开箱价值:<br>
${formatPrice(lastOpenAskValue)}/${formatPrice(lastOpenBidValue)}<br>
总计开箱价值:<br>
${formatPrice(openTotalAskValue)}/${formatPrice(openTotalBidValue)}<br>
`;
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 = `
期望产出:<br>
${formatPrice(expectedAskOutput)}/${formatPrice(expectedBidOutput)}<br>
`;
//调整位置
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 userLanguage = navigator.language || navigator.userLanguage;
const isZH = userLanguage.startsWith("zh");
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);
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);
const newUpdatedAt = new Date(obj.guild.updatedAt);
// 计算时间差(单位:毫秒)
const 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) {
const xpText = isZH ? "经验值 / 小时" : "XP / Hour";
Guild_TotalXp_div.insertAdjacentHTML(
"afterend",
`<div>${formatPrice(Delta.Rate_XP_Hours)} ${xpText}</div>`
);
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" 在元素的后面插入内容
`<div>${Time}</div>`
);
}
}
} 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;
const newUpdatedAt = new Date();
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 && storedData[Guild_ID].guild_player[name].old.updatedAt) {
const oldUpdatedAt = storedData[Guild_ID].guild_player[name].old.updatedAt
const timeDifference = newUpdatedAt - oldUpdatedAt
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,
updatedAt: newUpdatedAt,
};
} else {
// 仅更新新数据
storedData[Guild_ID].guild_player[name].new = {
id: key,
gameMode: obj.guildSharableCharacterMap[key].gameMode,
guildExperience: obj.guildCharacterMap[key].guildExperience,
updatedAt: newUpdatedAt,
};
}
//计算Δ
const Delta = {
Delta_Time:(newUpdatedAt - new Date(storedData[Guild_ID].guild_player[name].old.updatedAt)) / 1000,
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)/((newUpdatedAt - new Date(storedData[Guild_ID].guild_player[name].old.updatedAt)) / 1000)).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,
updatedAt: newUpdatedAt,
},
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);
const dtext = isZH ? "天" : "d";
const htext = isZH ? "时" : "h";
const mtext = isZH ? "分" : "m";
return `${days}${dtext} ${hrs}${htext} ${minutes}${mtext}`;
}
function updateExperienceDisplay(rateXPDayMap) {
const trElements = document.querySelectorAll(".GuildPanel_membersTable__1NwIX tbody tr");
const idleuser_list = [];
const dtext = isZH ? "天" : "d";
// 将 rateXPDayMap 转换为数组并排序
const sortedMembers = Object.entries(rateXPDayMap)
.map(([name, XPdata]) => ({ name, XPdata }))
.sort((a, b) => b.XPdata - a.XPdata);
sortedMembers.forEach(({ name, XPdata }) => {
trElements.forEach(tr => {
const nameElement = tr.querySelector(".CharacterName_name__1amXp");
const experienceElement = tr.querySelector("td:nth-child(3) > div");
const activityElement = tr.querySelector('.GuildPanel_activity__9vshh');
if (nameElement && nameElement.textContent.trim() === name) {
if (activityElement.childElementCount === 0) {
idleuser_list.push(nameElement.textContent.trim());
}
if (experienceElement) {
const newDiv = document.createElement('div');
newDiv.textContent = `${formatPrice(XPdata)}/${dtext}`;
// 计算颜色
const rank = sortedMembers.findIndex(member => member.name === name);
const hue = 120 - (rank * (120 / (sortedMembers.length - 1)));
newDiv.style.color = `hsl(${hue}, 100%, 50%)`;
experienceElement.insertAdjacentElement('afterend', newDiv);
}
return;
}
});
});
update_idleuser_tb(idleuser_list);
}
function update_idleuser_tb(idleuser_list) {
const targetElement = document.querySelector('.GuildPanel_noticeMessage__3Txji');
if (!targetElement) {
console.error('公会标语元素未找到!');
return;
}
const clonedElement = targetElement.cloneNode(true);
const namesText = idleuser_list.join(', ');
clonedElement.innerHTML = '';
clonedElement.textContent = isZH ? `闲置的成员:${namesText}` : `Idle User : ${namesText}`;
clonedElement.style.color = '#ffcc00';
// 设置复制元素的高度为原元素的25%
const originalStyle = window.getComputedStyle(targetElement);
const originalHeight = originalStyle.height;
const originalMinHeight = originalStyle.minHeight;
clonedElement.style.height = `25%`;
clonedElement.style.minHeight = `25%`; // 也设置最小高度
targetElement.parentElement.appendChild(clonedElement);
}
hookWS();
})();