[银河奶牛]食用工具

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

Tính đến 09-08-2024. Xem phiên bản mới nhất.

  1. // ==UserScript==
  2. // @name [银河奶牛]食用工具
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.391
  5. // @description 开箱记录、箱子期望、离线统计、公会钉钉
  6. // @author Truth_Light
  7. // @license Truth_Light
  8. // @match https://www.milkywayidle.com/*
  9. // @match https://test.milkywayidle.com/*
  10. // @icon https://www.google.com/s2/favicons?sz=64&domain=milkywayidle.com
  11. // @grant GM.xmlHttpRequest
  12. // ==/UserScript==
  13.  
  14. (async function() {
  15. 'use strict';
  16.  
  17. const itemSelector = '.ItemDictionary_drop__24I5f';
  18. const iconSelector = '.Icon_icon__2LtL_ use';
  19. 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';
  20. const MARKET_API_URL = "https://raw.githubusercontent.com/holychikenz/MWIApi/main/medianmarket.json";
  21. let marketData = null;
  22. let timer = null;
  23. let chestList = {};
  24. let formattedChestDropData = {};
  25.  
  26. async function fetchMarketData() {
  27. return new Promise((resolve, reject) => {
  28. GM.xmlHttpRequest({
  29. method: 'GET',
  30. url: MARKET_API_URL,
  31. responseType: 'json',
  32. timeout: 5000,
  33. onload: function(response) {
  34. if (response.status === 200) {
  35. const data = JSON.parse(response.responseText);
  36. console.log('从API获取到的数据:', data);
  37. resolve(data);
  38. } else {
  39. console.error('获取数据失败。状态码:', response.status);
  40. reject(new Error('数据获取失败'));
  41. }
  42. },
  43. ontimeout: function() {
  44. console.error('请求超时:超过5秒未能获取到数据');
  45. reject(new Error('请求超时'));
  46. },
  47. onerror: function(error) {
  48. console.error('获取数据时发生错误:', error);
  49. reject(error);
  50. }
  51. });
  52. });
  53. }
  54.  
  55. try {
  56. // 尝试从 API 获取数据
  57. marketData = await fetchMarketData();
  58. } catch (error) {
  59. console.error('从 API 获取数据失败,尝试从本地存储获取数据。', error);
  60.  
  61. // 从本地存储获取数据
  62. const marketDataStr = localStorage.getItem('MWITools_marketAPI_json');
  63. marketData = JSON.parse(marketDataStr);
  64.  
  65. if (!marketData) {
  66. alert('无法获取 market 数据');
  67. } else {
  68. console.log('从本地存储获取到的数据:', marketData);
  69. }
  70. }
  71.  
  72. function getSpecialItemPrice(itemName,priceType) {
  73. if (marketData?.market?.[itemName]) {
  74. const itemPrice = marketData.market[itemName][priceType];
  75. if (itemPrice !== undefined && itemPrice !== -1) {
  76. return itemPrice;
  77. }
  78. }
  79. console.error(`未找到物品 ${itemName} ${priceType} 价格信息`);
  80. return null;
  81. }
  82.  
  83. let specialItemPrices = {
  84. 'Coin': { ask: 1, bid: 1 }, // 默认的特殊物品价值,包括 ask 和 bid 价值
  85. 'Cowbell': {
  86. ask: getSpecialItemPrice('Bag Of 10 Cowbells', 'ask') / 10 || 30000,
  87. bid: getSpecialItemPrice('Bag Of 10 Cowbells', 'bid') / 10 || 25000
  88. },
  89. 'Chimerical Token': {
  90. ask: getSpecialItemPrice('Chimerical Essence', 'ask') || 1300,
  91. bid: getSpecialItemPrice('Chimerical Essence', 'bid') || 1100
  92. },
  93. 'Sinister Token': {
  94. ask: getSpecialItemPrice('Sinister Essence', 'ask') || 1400,
  95. bid: getSpecialItemPrice('Sinister Essence', 'bid') || 1200
  96. },
  97. 'Enchanted Token': {
  98. ask: getSpecialItemPrice('Enchanted Essence', 'ask') || 3900,
  99. bid: getSpecialItemPrice('Enchanted Essence', 'bid') || 3500
  100. },
  101. };
  102.  
  103. function getItemNameFromElement(element) {
  104. const itemNameRaw = element.getAttribute('href').split('#').pop();
  105. return formatItemName(itemNameRaw);
  106. }
  107.  
  108. function getItemPrice(itemName) {
  109. let itemAskValue = 0;
  110. let itemBidValue = 0;
  111. let priceColor = '#E7E7E7';
  112.  
  113. if (marketData) {
  114. try {
  115. if (marketData && marketData.market && marketData.market[itemName]) {
  116. itemAskValue = marketData.market[itemName].ask;
  117. itemBidValue = marketData.market[itemName].bid;
  118.  
  119. if (itemAskValue === -1 && itemBidValue === -1) {
  120. priceColor = 'yellow';
  121. } else if (itemAskValue === -1) {
  122. priceColor = '#D95961';
  123. } else if (itemBidValue === -1) {
  124. priceColor = '#2FC4A7';
  125. }
  126.  
  127. if (itemAskValue === -1 && itemBidValue !== -1) {
  128. itemAskValue = itemBidValue;
  129. }
  130. } else {
  131. console.error(`未找到物品 ${itemName} 的价格信息`);
  132. priceColor = 'yellow';
  133. }
  134.  
  135. } catch (error) {
  136. console.error(`解析 MWITools_marketAPI_json 数据时出错:`, error);
  137. }
  138. } else {
  139. console.error('未找到 MWITools_marketAPI_json 的本地存储数据');
  140. }
  141.  
  142.  
  143.  
  144. return { ask: itemAskValue, bid: itemBidValue, priceColor };
  145. }
  146.  
  147. function formatItemName(itemNameRaw) {
  148. let formattedName = itemNameRaw.replace('#', '').replace(/_/g, ' ').replace(/\b\w/g, char => char.toUpperCase());
  149. const specialStrings = {
  150. 'Artisans': 'Artisan\'s',
  151. };
  152. if (formattedName.includes(' ')) {
  153. const words = formattedName.split(' ');
  154. let firstWord = words[0];
  155. const restOfName = words.slice(1).join(' ');
  156. if (firstWord.endsWith('s') && !firstWord.endsWith("'s")) {
  157. firstWord = `${firstWord.slice(0, -1)}'${firstWord.slice(-1)}`;
  158. }
  159. formattedName = `${firstWord}${restOfName ? " " + restOfName : ""}`;
  160. }
  161.  
  162. return formattedName.split(' ').map(word => specialStrings[word] || word).join(' ');
  163. }
  164.  
  165.  
  166. function formatPrice(value) {
  167. const isNegative = value < 0;
  168. value = Math.abs(value);
  169.  
  170. if (value >= 1000000) {
  171. return (isNegative ? '-' : '') + (value / 1000000).toFixed(1) + 'M';
  172. } else if (value >= 1000) {
  173. return (isNegative ? '-' : '') + (value / 1000).toFixed(1) + 'K';
  174. } else {
  175. return (isNegative ? '-' : '') + value.toFixed(1);
  176. }
  177. }
  178.  
  179.  
  180. function parseQuantityString(quantityStr) {
  181. const suffix = quantityStr.slice(-1);
  182. const base = parseFloat(quantityStr.slice(0, -1));
  183. if (suffix === 'K') {
  184. return base * 1000;
  185. } else if (suffix === 'M') {
  186. return base * 1000000;
  187. } else if (suffix === 'B') {
  188. return base * 1000000000;
  189. } else {
  190. return parseFloat(quantityStr);
  191. }
  192. }
  193.  
  194. function recordChestOpening(modalElement) {
  195.  
  196. if (document.querySelector('.ChestStatistics')) {
  197. return;
  198. }
  199.  
  200. // 从本地存储读取数据
  201. let edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
  202. edibleTools.Chest_Open_Data = edibleTools.Chest_Open_Data || {};
  203.  
  204. let chestOpenData = edibleTools.Chest_Open_Data;
  205. const chestDropData = edibleTools.Chest_Drop_Data;
  206.  
  207. 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");
  208. const chestCountElement = modalElement.querySelector("div.Modal_modal__1Jiep > div.Inventory_modalContent__3ObSx > div.Item_itemContainer__x7kH1 > div > div > div.Item_count__1HVvv");
  209.  
  210. if (chestNameElement && chestCountElement) {
  211.  
  212. const chestName = getItemNameFromElement(chestNameElement);
  213. chestOpenData[chestName] = chestOpenData[chestName] || {};
  214. let chestData = chestOpenData[chestName];
  215. const chestCount = parseQuantityString(chestCountElement.textContent.trim());
  216. chestData["总计开箱数量"] = (chestData["总计开箱数量"] || 0) + chestCount;
  217. chestData["获得物品"] = chestData["获得物品"] || {};
  218. const itemsContainer = modalElement.querySelector('.Inventory_gainedItems___e9t9');
  219. const itemElements = itemsContainer.querySelectorAll('.Item_itemContainer__x7kH1');
  220.  
  221. let totalAskValue = 0;
  222. let totalBidValue = 0;
  223.  
  224. itemElements.forEach(itemElement => {
  225. const itemNameElement = itemElement.querySelector('.Item_iconContainer__5z7j4 use');
  226. const itemQuantityElement = itemElement.querySelector('.Item_count__1HVvv');
  227.  
  228. if (itemNameElement && itemQuantityElement) {
  229. const itemName = getItemNameFromElement(itemNameElement);
  230. const itemQuantity = parseQuantityString(itemQuantityElement.textContent.trim());
  231.  
  232. const itemData = chestDropData[chestName].item[itemName] || {};
  233. const itemAskValue = itemData["出售单价"] || 0;
  234. const itemBidValue = itemData["收购单价"] || 0;
  235. const color = itemData.Color || '';
  236.  
  237. itemQuantityElement.style.color = color;
  238.  
  239. const itemOpenTotalAskValue = itemAskValue * itemQuantity;
  240. const itemOpenTotalBidValue = itemBidValue * itemQuantity;
  241.  
  242. chestData["获得物品"][itemName] = chestData["获得物品"][itemName] || {};
  243. chestData["获得物品"][itemName]["数量"] = (chestData["获得物品"][itemName]["数量"] || 0) + itemQuantity;
  244. chestData["获得物品"][itemName]["总计Ask价值"] = (chestData["获得物品"][itemName]["总计Ask价值"] || 0) + itemOpenTotalAskValue;
  245. chestData["获得物品"][itemName]["总计Bid价值"] = (chestData["获得物品"][itemName]["总计Bid价值"] || 0) + itemOpenTotalBidValue;
  246.  
  247. totalAskValue += itemOpenTotalAskValue;
  248. totalBidValue += itemOpenTotalBidValue;
  249. }
  250. });
  251.  
  252. chestData["总计开箱Ask"] = (chestData["总计开箱Ask"] || 0) + totalAskValue;
  253. chestData["总计开箱Bid"] = (chestData["总计开箱Bid"] || 0) + totalBidValue;
  254.  
  255. //显示
  256. const openChestElement = document.querySelector('.Inventory_modalContent__3ObSx');
  257.  
  258. const displayElement = document.createElement('div');
  259. displayElement.classList.add('ChestStatistics'); // 自定义类名,用于样式控制
  260. displayElement.style.position = 'absolute';
  261. displayElement.style.left = `${openChestElement.offsetLeft}px`;
  262. displayElement.style.top = `${openChestElement.offsetTop}px`;
  263. displayElement.style.fontSize = '12px';
  264. displayElement.innerHTML = `
  265. 总计开箱次数:<br>
  266. ${chestData["总计开箱数量"]}<br>
  267. 本次开箱价值:<br>
  268. ${formatPrice(totalAskValue)}/${formatPrice(totalBidValue)}<br>
  269. 总计开箱价值:<br>
  270. ${formatPrice(chestData["总计开箱Ask"])}/${formatPrice(chestData["总计开箱Bid"])}<br>
  271. `;
  272.  
  273. const expectedOutputElement = document.createElement('div');
  274. expectedOutputElement.classList.add('ExpectedOutput');
  275. expectedOutputElement.style.position = 'absolute';
  276. expectedOutputElement.style.left = `${openChestElement.offsetLeft}px`;
  277. expectedOutputElement.style.bottom = `${openChestElement.offsetTop}px`;
  278. expectedOutputElement.style.fontSize = '12px';
  279. expectedOutputElement.innerHTML = `
  280. 预计产出价值:<br>
  281. ${formatPrice(chestDropData[chestName]["期望产出Ask"]*chestCount)}/${formatPrice(chestDropData[chestName]["期望产出Bid"]*chestCount)}<br>
  282. `;
  283.  
  284. openChestElement.appendChild(displayElement);
  285. openChestElement.appendChild(expectedOutputElement);
  286.  
  287. // 保存更新的数据到本地存储
  288. localStorage.setItem('Edible_Tools', JSON.stringify(edibleTools));
  289. }
  290. }
  291.  
  292.  
  293. function calculateTotalValues(itemElements) {
  294. let totalAskValue = 0;
  295. let totalBidValue = 0;
  296.  
  297. itemElements.forEach(itemElement => {
  298. const itemNameElement = itemElement.querySelector('.Item_iconContainer__5z7j4 use');
  299. const itemQuantityElement = itemElement.querySelector('.Item_count__1HVvv');
  300.  
  301. if (itemNameElement && itemQuantityElement) {
  302. const itemName = getItemNameFromElement(itemNameElement);
  303. const itemQuantity = parseQuantityString(itemQuantityElement.textContent.trim());
  304.  
  305. let askPrice = 0;
  306. let bidPrice = 0;
  307. let priceColor = '';
  308.  
  309. // 获取价格
  310. if (specialItemPrices[itemName] && specialItemPrices[itemName].ask) {
  311. askPrice = parseFloat(specialItemPrices[itemName].ask);
  312. bidPrice = parseFloat(specialItemPrices[itemName].bid);
  313. priceColor = '';
  314. } else if (marketData?.market?.[itemName]) {
  315. bidPrice = marketData.market[itemName].bid;
  316. askPrice = marketData.market[itemName].ask;
  317. } else {
  318. console.log(`${itemName} 的价格未找到`);
  319. }
  320.  
  321. const itemTotalAskValue = askPrice * itemQuantity;
  322. const itemTotalBidValue = bidPrice * itemQuantity;
  323. totalAskValue += itemTotalAskValue;
  324. totalBidValue += itemTotalBidValue;
  325. }
  326. });
  327.  
  328. console.log(totalAskValue);
  329. return { totalAskValue, totalBidValue };
  330. }
  331.  
  332.  
  333.  
  334. function OfflineStatistics(modalElement) {
  335. const itemsContainer = modalElement.querySelectorAll(".OfflineProgressModal_itemList__26h-Y");
  336.  
  337. let timeContainer = null;
  338. let getItemContainer = null;
  339. let spendItemContainer = null;
  340.  
  341.  
  342. itemsContainer.forEach(container => {
  343. const labelElement = container.querySelector('.OfflineProgressModal_label__2HwFG');
  344. if (labelElement) {
  345. const textContent = labelElement.textContent.trim();
  346. if (textContent.startsWith("You were offline for") || textContent.startsWith("你离线了")) {
  347. timeContainer = container;
  348. } else if (textContent.startsWith("Items gained:") || textContent.startsWith("获得物品:")) {
  349. getItemContainer = container;
  350. } else if (textContent.startsWith("You consumed:") || textContent.startsWith("你消耗了:")) {
  351. spendItemContainer = container;
  352. }
  353. }
  354. });
  355.  
  356. let TotalSec = null;
  357. if (timeContainer) {
  358. const textContent = timeContainer.textContent;
  359. const match = textContent.match(/(?:(\d+)d\s*)?(?:(\d+)h\s*)?(?:(\d+)m\s*)?(?:(\d+)s)/);
  360. if (match) {
  361. let days = parseInt(match[1], 10) || 0;
  362. let hours = parseInt(match[2], 10) || 0;
  363. let minutes = parseInt(match[3], 10) || 0;
  364. let seconds = parseInt(match[4], 10) || 0;
  365. TotalSec = days * 86400 + hours * 3600 + minutes * 60 + seconds;
  366. }
  367. }
  368.  
  369. let getitemtotalAskValue = 0;
  370. let getitemtotalBidValue = 0;
  371. if (getItemContainer) {
  372. const getitemElements = getItemContainer.querySelectorAll('.Item_itemContainer__x7kH1');
  373. const { totalAskValue, totalBidValue } = calculateTotalValues(getitemElements);
  374. getitemtotalAskValue = totalAskValue;
  375. getitemtotalBidValue = totalBidValue;
  376. }
  377.  
  378.  
  379. let spenditemtotalAskValue = 0;
  380. let spenditemtotalBidValue = 0;
  381. if (spendItemContainer) {
  382. const spenditemElements = spendItemContainer.querySelectorAll('.Item_itemContainer__x7kH1');
  383. const { totalAskValue, totalBidValue } = calculateTotalValues(spenditemElements);
  384. spenditemtotalAskValue = totalAskValue;
  385. spenditemtotalBidValue = totalBidValue;
  386. }
  387.  
  388. if (timeContainer) {
  389. const newElement = document.createElement('span');
  390. newElement.textContent = `利润:[${formatPrice(getitemtotalBidValue - spenditemtotalAskValue)}/${formatPrice((getitemtotalBidValue - spenditemtotalAskValue) / (TotalSec / 3600) * 24)}]/天`;
  391. newElement.style.float = 'right';
  392. newElement.style.color = 'gold';
  393. timeContainer.querySelector(':first-child').appendChild(newElement);
  394. }
  395. if (getItemContainer) {
  396. const newElement = document.createElement('span');
  397. newElement.textContent = `产出:[${formatPrice(getitemtotalAskValue)}/${formatPrice(getitemtotalBidValue)}]`;
  398. newElement.style.float = 'right';
  399. newElement.style.color = 'gold';
  400. getItemContainer.querySelector(':first-child').appendChild(newElement);
  401. }
  402. if (spendItemContainer) {
  403. const newElement = document.createElement('span');
  404. newElement.textContent = `成本:[${formatPrice(spenditemtotalAskValue)}/${formatPrice(spenditemtotalBidValue)}]`;
  405. newElement.style.float = 'right';
  406. newElement.style.color = 'gold';
  407. spendItemContainer.querySelector(':first-child').appendChild(newElement);
  408. }
  409. }
  410.  
  411.  
  412. function initObserver() {
  413. // 选择要观察的目标节点
  414. const targetNode = document.body;
  415.  
  416. // 观察器的配置(需要观察子节点的变化)
  417. const config = { childList: true, subtree: true };
  418.  
  419. // 创建一个观察器实例并传入回调函数
  420. const observer = new MutationObserver(mutationsList => {
  421. for (let mutation of mutationsList) {
  422. if (mutation.type === 'childList') {
  423. // 监听到子节点变化
  424. mutation.addedNodes.forEach(addedNode => {
  425. // 检查是否是我们关注的 Modal_modalContainer__3B80m 元素被添加
  426. if (addedNode.classList && addedNode.classList.contains('Modal_modalContainer__3B80m')) {
  427. // Modal_modalContainer__3B80m 元素被添加,执行处理函数
  428. //
  429. //processItems();
  430. ShowChestPrice();
  431. recordChestOpening(addedNode);
  432.  
  433. // 开始监听箱子图标的变化
  434. startIconObserver();
  435. }
  436. if (addedNode.classList && addedNode.classList.contains('OfflineProgressModal_modalContainer__knnk7')) {
  437. OfflineStatistics(addedNode);
  438. console.log("离线报告已创建!")
  439. }
  440. });
  441.  
  442. mutation.removedNodes.forEach(removedNode => {
  443. // 检查是否是 Modal_modalContainer__3B80m 元素被移除
  444. if (removedNode.classList && removedNode.classList.contains('Modal_modalContainer__3B80m')) {
  445. // Modal_modalContainer__3B80m 元素被移除,停止监听箱子图标的变化
  446. stopIconObserver();
  447. }
  448. });
  449. }
  450. }
  451. });
  452.  
  453. // 以上述配置开始观察目标节点
  454. observer.observe(targetNode, config);
  455.  
  456. // 定义箱子图标变化的观察器
  457. let iconObserver = null;
  458.  
  459. // 开始监听箱子图标的变化
  460. function startIconObserver() {
  461. const chestNameElem = document.querySelector(chestNameSelector);
  462. if (!chestNameElem) return;
  463.  
  464. // 创建一个观察器实例来监听图标的变化
  465. iconObserver = new MutationObserver(() => {
  466. // 当箱子图标变化时,执行处理函数
  467. //processItems();
  468. ShowChestPrice();
  469. });
  470.  
  471. // 配置观察器的选项
  472. const iconConfig = { attributes: true, attributeFilter: ['href'] };
  473.  
  474. // 以上述配置开始观察箱子图标节点
  475. iconObserver.observe(chestNameElem, iconConfig);
  476. }
  477.  
  478. // 停止监听箱子图标的变化
  479. function stopIconObserver() {
  480. if (iconObserver) {
  481. iconObserver.disconnect();
  482. iconObserver = null;
  483. }
  484. }
  485. }
  486.  
  487.  
  488.  
  489.  
  490. initObserver();
  491.  
  492.  
  493. //公会部分代码
  494. const userLanguage = navigator.language || navigator.userLanguage;
  495. const isZH = userLanguage.startsWith("zh");
  496. const updataDealy = 24*60*60*1000; //数据更新时限
  497. let rateXPDayMap = {};
  498.  
  499. function hookWS() {
  500. const dataProperty = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data");
  501. const oriGet = dataProperty.get;
  502.  
  503. dataProperty.get = hookedGet;
  504. Object.defineProperty(MessageEvent.prototype, "data", dataProperty);
  505.  
  506. function hookedGet() {
  507. const socket = this.currentTarget;
  508. if (!(socket instanceof WebSocket)) {
  509. return oriGet.call(this);
  510. }
  511. if (socket.url.indexOf("api.milkywayidle.com/ws") <= -1 && socket.url.indexOf("api-test.milkywayidle.com/ws") <= -1) {
  512. return oriGet.call(this);
  513. }
  514.  
  515. const message = oriGet.call(this);
  516. Object.defineProperty(this, "data", { value: message }); // Anti-loop
  517.  
  518. return handleMessage(message);
  519. }
  520. }
  521.  
  522.  
  523.  
  524. //奶牛钉钉
  525. function handleMessage(message) {
  526. try {
  527. let obj = JSON.parse(message);
  528. if (obj && obj.type === "init_client_data") {
  529. processAndPrintData(obj,marketData);
  530. }
  531. if (obj && obj.type === "guild_updated") {
  532. const Guild_ID = obj.guild.id;
  533. const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
  534. edibleTools.Guild_Data = edibleTools.Guild_Data || {};
  535. let storedData = edibleTools.Guild_Data || {};
  536.  
  537. // 判断是否已经存在旧数据
  538. if (storedData[Guild_ID] && storedData[Guild_ID].guild_updated && storedData[Guild_ID].guild_updated.old.updatedAt) {
  539. const oldUpdatedAt = new Date(storedData[Guild_ID].guild_updated.new.updatedAt);
  540. const newUpdatedAt = new Date(obj.guild.updatedAt);
  541.  
  542. // 计算时间差(单位:毫秒)
  543. const timeDifference = newUpdatedAt - oldUpdatedAt;
  544.  
  545. if (timeDifference >= updataDealy) {
  546. // 更新老数据为新数据
  547. storedData[Guild_ID].guild_updated.old = storedData[Guild_ID].guild_updated.new;
  548. // 更新新数据为当前数据
  549. storedData[Guild_ID].guild_updated.new = {
  550. experience: obj.guild.experience,
  551. level: obj.guild.level,
  552. updatedAt: obj.guild.updatedAt
  553. };
  554. } else {
  555. // 仅更新新数据
  556. storedData[Guild_ID].guild_updated.new = {
  557. experience: obj.guild.experience,
  558. level: obj.guild.level,
  559. updatedAt: obj.guild.updatedAt
  560. };
  561. }
  562. //计算Δ
  563. const Delta = {
  564. Delta_Xp: storedData[Guild_ID].guild_updated.new.experience - storedData[Guild_ID].guild_updated.old.experience,
  565. Delta_Level: storedData[Guild_ID].guild_updated.new.level - storedData[Guild_ID].guild_updated.old.level,
  566. Delta_Time: (newUpdatedAt - new Date(storedData[Guild_ID].guild_updated.old.updatedAt)) / 1000, // 转换为秒
  567. 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)
  568. };
  569. storedData[Guild_ID].guild_updated.Delta = Delta;
  570.  
  571. const Guild_TotalXp_div = document.querySelectorAll(".GuildPanel_value__Hm2I9")[1];
  572. if (Guild_TotalXp_div) {
  573. const xpText = isZH ? "经验值 / 小时" : "XP / Hour";
  574.  
  575. Guild_TotalXp_div.insertAdjacentHTML(
  576. "afterend",
  577. `<div>${formatPrice(Delta.Rate_XP_Hours)} ${xpText}</div>`
  578. );
  579. const Guild_NeedXp_div = document.querySelectorAll(".GuildPanel_value__Hm2I9")[2];
  580. if (Guild_NeedXp_div) {
  581. const Guild_NeedXp = document.querySelectorAll(".GuildPanel_value__Hm2I9")[2].textContent.replace(/,/g, '');
  582. const Time = TimeReset(Guild_NeedXp/Delta.Rate_XP_Hours);
  583. Guild_NeedXp_div.insertAdjacentHTML(
  584. "afterend", // 使用 "afterend" 在元素的后面插入内容
  585. `<div>${Time}</div>`
  586. );
  587. }
  588. }
  589. } else {
  590. // 如果没有旧数据,则直接添加新数据
  591. storedData[Guild_ID] = {
  592. guild_name: obj.guild.name,
  593. guild_updated: {
  594. old: {
  595. experience: obj.guild.experience,
  596. level: obj.guild.level,
  597. updatedAt: obj.guild.updatedAt
  598. },
  599. new: {},
  600. }
  601. };
  602. }
  603.  
  604. // 存储更新后的数据到 localStorage
  605. edibleTools.Guild_Data = storedData;
  606. localStorage.setItem('Edible_Tools', JSON.stringify(edibleTools));
  607. } else if (obj && obj.type === "guild_characters_updated") {
  608. const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
  609. edibleTools.Guild_Data = edibleTools.Guild_Data || {};
  610. let storedData = edibleTools.Guild_Data || {};
  611. for (const key in obj.guildSharableCharacterMap) {
  612. if (obj.guildSharableCharacterMap.hasOwnProperty(key)) {
  613. const Guild_ID = obj.guildCharacterMap[key].guildID;
  614. const name = obj.guildSharableCharacterMap[key].name;
  615. const newUpdatedAt = new Date();
  616. storedData[Guild_ID].guild_player = storedData[Guild_ID].guild_player || {};
  617. 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) {
  618. const oldUpdatedAt = storedData[Guild_ID].guild_player[name].old.updatedAt
  619. const timeDifference = newUpdatedAt - oldUpdatedAt
  620. if (timeDifference >= updataDealy) {
  621. // 更新老数据为新数据
  622. storedData[Guild_ID].guild_player[name].old = storedData[Guild_ID].guild_player[name].new;
  623. // 更新新数据为当前数据
  624. storedData[Guild_ID].guild_player[name].new = {
  625. id: key,
  626. gameMode: obj.guildSharableCharacterMap[key].gameMode,
  627. guildExperience: obj.guildCharacterMap[key].guildExperience,
  628. updatedAt: newUpdatedAt,
  629. };
  630. } else {
  631. // 仅更新新数据
  632. storedData[Guild_ID].guild_player[name].new = {
  633. id: key,
  634. gameMode: obj.guildSharableCharacterMap[key].gameMode,
  635. guildExperience: obj.guildCharacterMap[key].guildExperience,
  636. updatedAt: newUpdatedAt,
  637. };
  638. }
  639. //计算Δ
  640. const Delta = {
  641. Delta_Time:(newUpdatedAt - new Date(storedData[Guild_ID].guild_player[name].old.updatedAt)) / 1000,
  642. Delta_Xp: storedData[Guild_ID].guild_player[name].new.guildExperience - storedData[Guild_ID].guild_player[name].old.guildExperience,
  643. 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)
  644. };
  645. storedData[Guild_ID].guild_player[name].Delta = Delta;
  646. rateXPDayMap[name] = Delta.Rate_XP_Day;
  647. }else {
  648. storedData[Guild_ID].guild_player[name] = {
  649. old: {
  650. id: key,
  651. gameMode: obj.guildSharableCharacterMap[key].gameMode,
  652. guildExperience: obj.guildCharacterMap[key].guildExperience,
  653. updatedAt: newUpdatedAt,
  654. },
  655. new:{}
  656. };
  657. }
  658. }
  659.  
  660. }
  661. //console.log("测试数据",storedData);
  662. //console.log("guild_characters_updated", obj);
  663. updateExperienceDisplay(rateXPDayMap);
  664. edibleTools.Guild_Data = storedData;
  665. localStorage.setItem('Edible_Tools', JSON.stringify(edibleTools));
  666. }
  667.  
  668.  
  669.  
  670.  
  671. } catch (error) {
  672. console.error("Error processing message:", error);
  673. }
  674. return message;
  675. }
  676.  
  677.  
  678. function TimeReset(hours) {
  679. const totalMinutes = hours * 60;
  680. const days = Math.floor(totalMinutes / (24 * 60));
  681. const yudays = totalMinutes % (24 * 60);
  682. const hrs = Math.floor(yudays / 60);
  683. const minutes = Math.floor(yudays % 60);
  684. const dtext = isZH ? "天" : "d";
  685. const htext = isZH ? "时" : "h";
  686. const mtext = isZH ? "分" : "m";
  687. return `${days}${dtext} ${hrs}${htext} ${minutes}${mtext}`;
  688. }
  689.  
  690. function updateExperienceDisplay(rateXPDayMap) {
  691. const trElements = document.querySelectorAll(".GuildPanel_membersTable__1NwIX tbody tr");
  692. const idleuser_list = [];
  693. const dtext = isZH ? "天" : "d";
  694.  
  695. // 将 rateXPDayMap 转换为数组并排序
  696. const sortedMembers = Object.entries(rateXPDayMap)
  697. .map(([name, XPdata]) => ({ name, XPdata }))
  698. .sort((a, b) => b.XPdata - a.XPdata);
  699.  
  700. sortedMembers.forEach(({ name, XPdata }) => {
  701. trElements.forEach(tr => {
  702. const nameElement = tr.querySelector(".CharacterName_name__1amXp");
  703. const experienceElement = tr.querySelector("td:nth-child(3) > div");
  704. const activityElement = tr.querySelector('.GuildPanel_activity__9vshh');
  705.  
  706. if (nameElement && nameElement.textContent.trim() === name) {
  707. if (activityElement.childElementCount === 0) {
  708. idleuser_list.push(nameElement.textContent.trim());
  709. }
  710.  
  711. if (experienceElement) {
  712. const newDiv = document.createElement('div');
  713. newDiv.textContent = `${formatPrice(XPdata)}/${dtext}`;
  714.  
  715. // 计算颜色
  716. const rank = sortedMembers.findIndex(member => member.name === name);
  717. const hue = 120 - (rank * (120 / (sortedMembers.length - 1)));
  718. newDiv.style.color = `hsl(${hue}, 100%, 50%)`;
  719.  
  720. experienceElement.insertAdjacentElement('afterend', newDiv);
  721. }
  722. return;
  723. }
  724. });
  725. });
  726.  
  727. update_idleuser_tb(idleuser_list);
  728. }
  729.  
  730. function update_idleuser_tb(idleuser_list) {
  731. const targetElement = document.querySelector('.GuildPanel_noticeMessage__3Txji');
  732. if (!targetElement) {
  733. console.error('公会标语元素未找到!');
  734. return;
  735. }
  736. const clonedElement = targetElement.cloneNode(true);
  737.  
  738. const namesText = idleuser_list.join(', ');
  739. clonedElement.innerHTML = '';
  740. clonedElement.textContent = isZH ? `闲置的成员:${namesText}` : `Idle User : ${namesText}`;
  741. clonedElement.style.color = '#ffcc00';
  742.  
  743. // 设置复制元素的高度为原元素的25%
  744. const originalStyle = window.getComputedStyle(targetElement);
  745. const originalHeight = originalStyle.height;
  746. const originalMinHeight = originalStyle.minHeight;
  747. clonedElement.style.height = `25%`;
  748. clonedElement.style.minHeight = `25%`; // 也设置最小高度
  749. targetElement.parentElement.appendChild(clonedElement);
  750. }
  751.  
  752.  
  753. hookWS();
  754.  
  755.  
  756. //箱子数据获取
  757. function processAndPrintData(obj) {
  758. let formattedShopData = {};
  759.  
  760. // 处理商店数据
  761. for (let [key, details] of Object.entries(obj.shopItemDetailMap)) {
  762. const { itemHrid, costs } = details;
  763. const itemName = formatItemName(itemHrid.split('/').pop());
  764.  
  765. costs.forEach(cost => {
  766. const costItemName = formatItemName(cost.itemHrid.split('/').pop());
  767.  
  768. if (costItemName === "Coin") return;
  769.  
  770. const costCount = cost.count;
  771.  
  772. if (!formattedShopData[costItemName]) {
  773. formattedShopData[costItemName] = { items: {}, 最挣钱: '', BID单价: 0 };
  774. }
  775.  
  776. // 计算每种代币购买每个物品的收益
  777. let bidValue = marketData?.market?.[itemName]?.bid || 0;
  778. let profit = bidValue / costCount;
  779.  
  780. formattedShopData[costItemName].items[itemName] = {
  781. 花费: costCount
  782. };
  783.  
  784. // 更新最赚钱的物品信息
  785. if (profit > formattedShopData[costItemName].BID单价) {
  786. formattedShopData[costItemName].最挣钱 = itemName;
  787. formattedShopData[costItemName].BID单价 = profit;
  788. specialItemPrices[costItemName].ask = profit;
  789. specialItemPrices[costItemName].bid = profit;
  790. }
  791. });
  792. }
  793. const mostProfitableItems = Object.values(formattedShopData).map(item => item.最挣钱).filter(Boolean);
  794. console.log(mostProfitableItems)
  795. // 处理箱子掉落物数据
  796.  
  797. for (let iteration = 0; iteration < 4; iteration++) {
  798. for (let [key, items] of Object.entries(obj.openableLootDropMap)) {
  799. const boxName = formatItemName(key.split('/').pop());
  800.  
  801. if (!formattedChestDropData[boxName]) {
  802. formattedChestDropData[boxName] = { item: {} };
  803. }
  804. let TotalAsk = 0;
  805. let TotalBid = 0;
  806. let awa = 0;
  807. items.forEach(item => {
  808. const { itemHrid, dropRate, minCount, maxCount } = item;
  809. const itemName = formatItemName(itemHrid.split('/').pop());
  810. const expectedYield = ((minCount + maxCount) / 2) * dropRate;
  811. let bidPrice = -1;
  812. let askPrice = -1;
  813. let priceColor = '';
  814.  
  815. if (specialItemPrices[itemName] && specialItemPrices[itemName].ask) {
  816. askPrice = parseFloat(specialItemPrices[itemName].ask);
  817. bidPrice = parseFloat(specialItemPrices[itemName].bid);
  818. priceColor = '';
  819. } else if (marketData?.market?.[itemName]) {
  820. bidPrice = marketData.market[itemName].bid;
  821. askPrice = marketData.market[itemName].ask;
  822. } else {
  823. console.log(`${itemName} 的价格未找到`);
  824. }
  825.  
  826. if (formattedChestDropData[boxName].item[itemName] && iteration === 0) {
  827. // 如果物品已存在,更新期望掉落和相关价格
  828. const existingItem = formattedChestDropData[boxName].item[itemName];
  829. existingItem.期望掉落 += expectedYield;
  830. } else if (iteration === 0) {
  831. formattedChestDropData[boxName].item[itemName] = {
  832. 期望掉落: expectedYield,
  833. };
  834. }
  835.  
  836. // 判断 itemName 是否在最挣钱物品列表中,并执行相应代码
  837. if (mostProfitableItems.includes(itemName)) {
  838. priceColor = '#FFb3E6';
  839. } else if (askPrice === -1 && bidPrice === -1) {
  840. priceColor = 'yellow';
  841. } else if (askPrice === -1) {
  842. askPrice = bidPrice;
  843. priceColor = '#D95961';
  844. } else if (bidPrice === -1) {
  845. priceColor = '#2FC4A7';
  846. }
  847.  
  848. const existingItem = formattedChestDropData[boxName].item[itemName];
  849. existingItem.出售单价 = askPrice;
  850. existingItem.收购单价 = bidPrice;
  851. existingItem.出售总价 = (existingItem.出售单价 * existingItem.期望掉落).toFixed(2);
  852. existingItem.收购总价 = (existingItem.收购单价 * existingItem.期望掉落).toFixed(2);
  853. existingItem.Color = priceColor;
  854.  
  855. // 累计总价
  856. TotalAsk += (askPrice * expectedYield);
  857. TotalBid += (bidPrice * expectedYield);
  858. });
  859.  
  860. formattedChestDropData[boxName] = {
  861. ...formattedChestDropData[boxName],
  862. 期望产出Ask: TotalAsk.toFixed(2),
  863. 期望产出Bid: TotalBid.toFixed(2),
  864. };
  865.  
  866. if (!specialItemPrices[boxName]) {
  867. specialItemPrices[boxName] = {}
  868. }
  869.  
  870. specialItemPrices[boxName].ask = formattedChestDropData[boxName].期望产出Ask;
  871. specialItemPrices[boxName].bid = formattedChestDropData[boxName].期望产出Bid;
  872. }
  873. }
  874.  
  875. for (let itemName in specialItemPrices) {
  876. if (specialItemPrices.hasOwnProperty(itemName)) {
  877. marketData.market[itemName] = {
  878. ask: specialItemPrices[itemName].ask,
  879. bid: specialItemPrices[itemName].bid
  880. };
  881. }
  882. }
  883.  
  884. localStorage.setItem('MWITools_marketAPI_json', JSON.stringify(marketData));
  885. let edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
  886. edibleTools.Chest_Drop_Data = formattedChestDropData;
  887. localStorage.setItem('Edible_Tools', JSON.stringify(edibleTools));
  888. // 打印结果
  889. console.log("特殊物品价格表:",specialItemPrices)
  890. console.log("箱子掉落物列表:", formattedChestDropData);
  891. console.log("地牢商店列表:", formattedShopData);
  892. }
  893.  
  894. function ShowChestPrice() {
  895. const modalContainer = document.querySelector(".Modal_modalContainer__3B80m");
  896. if (!modalContainer) return; // 如果不存在 Modal_modalContainer__3B80m 元素,则直接返回
  897.  
  898. const chestNameElem = document.querySelector(chestNameSelector);
  899. if (!chestNameElem) return;
  900.  
  901. const chestName = getItemNameFromElement(chestNameElem);
  902. const items = document.querySelectorAll(itemSelector);
  903.  
  904. const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools'))
  905. const formattedChestDropData = edibleTools.Chest_Drop_Data;
  906.  
  907. items.forEach(item => {
  908. const itemName = getItemNameFromElement(item.querySelector(iconSelector));
  909. if (!itemName) return; // 检查 itemName 是否存在
  910.  
  911. const itemData = formattedChestDropData[chestName].item[itemName];
  912. if (!itemData) return; // 检查 itemData 是否存在
  913.  
  914. const itemColor = itemData.Color;
  915. const itemNameElem = item.querySelector('.Item_name__2C42x');
  916. if (itemNameElem && itemColor) {
  917. itemNameElem.style.color = itemColor;
  918. }
  919. });
  920.  
  921. const askPrice = formattedChestDropData[chestName]["期望产出Ask"];
  922. const bidPrice = formattedChestDropData[chestName]["期望产出Bid"];
  923. if (askPrice && bidPrice) {
  924. const dropListContainer = document.querySelector('.ItemDictionary_openToLoot__1krnv');
  925. if (!dropListContainer) return; // 检查 dropListContainer 是否存在
  926.  
  927. const previousResults = dropListContainer.querySelectorAll('.resultDiv');
  928. previousResults.forEach(result => result.remove());
  929.  
  930. const createPriceOutput = (label, price) => {
  931. const priceOutput = document.createElement('div');
  932. priceOutput.className = 'resultDiv';
  933. priceOutput.textContent = `${label}: ${formatPrice(price)}`;
  934. priceOutput.style.color = 'gold';
  935. priceOutput.style.fontSize = '14px';
  936. priceOutput.style.fontWeight = '400';
  937. priceOutput.style.paddingTop = '10px';
  938. return priceOutput;
  939. };
  940.  
  941. const minPriceOutput = createPriceOutput('期望产出 (最低买入价计算)', askPrice);
  942. const maxPriceOutput = createPriceOutput('期望产出 (最高收购价计算)', bidPrice);
  943.  
  944. dropListContainer.appendChild(minPriceOutput);
  945. dropListContainer.appendChild(maxPriceOutput);
  946. }
  947. }
  948. })();