MWI-Equipment-Diff

Make life easier

// ==UserScript==
// @name         MWI-Equipment-Diff
// @namespace    http://tampermonkey.net/
// @version      0.8
// @description  Make life easier
// @author       BKN46
// @match        https://*.milkywayidle.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=milkywayidle.com
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
  'use strict';

  let selfData = {};

  const colors = {
    info: 'rgb(0, 108, 158)',
    smaller: 'rgb(199, 21, 21)',
    greater: 'rgb(23, 151, 12)',
  };
  let equipmentToDiff = {};
  const isZHInGameSetting = localStorage.getItem("i18nextLng")?.toLowerCase()?.startsWith("zh");
  let isZH = isZHInGameSetting;

  function parseEquipmentModal(element) {
    const equipmentDetail = {};
    const detailLines = element.querySelectorAll('.EquipmentStatsText_stat__27Sus');
    for (const line of detailLines) {
      if (line.querySelector('.EquipmentStatsText_uniqueStat__2xvqX')) continue;
      const data = line.textContent.split(':');
      if (data.length === 2) {
        const key = data[0].trim();
        const value = data[1].split('(')[0].trim();
        if (value === 'N/A') continue;
        equipmentDetail[key] = value;
      }
    }

    if (!element.querySelector('.diff-tooltip')) {
      const diffTooltip = document.createElement('div');
      diffTooltip.className = 'diff-tooltip';
      diffTooltip.textContent = isZH ? '按\'d\'来添加作为对比对象' : 'Press "d" to add to quick diff';
      diffTooltip.style = `color: ${colors.info}; font-weight: bold;`;
      element.appendChild(diffTooltip);

      if (equipmentToDiff.data) {
        const diffRemoveTooltip = document.createElement('div');
        diffRemoveTooltip.className = 'diff-remove-tooltip';
        diffRemoveTooltip.textContent = isZH ? '按\'r\'来移除当前对比' : 'Press "r" to remove present quick diff';
        diffRemoveTooltip.style = `color: ${colors.info}; font-weight: bold;`;
        element.appendChild(diffRemoveTooltip);
      }
    }
    
    return equipmentDetail;
  }

  function addDiffToModal(element, data, price) {
    if (element.querySelector('.diff-value') || element.querySelector('.diff-header')) return;
    const parentArea = element.querySelector('.EquipmentStatsText_equipmentStatsText__djKBS');
    const TextArea = parentArea.firstChild;

    const detailLines = element.querySelectorAll('.EquipmentStatsText_stat__27Sus');
    for (const line of detailLines) {
      const key = line.textContent.split(':')[0].trim();
      const valueElement = line.querySelectorAll('span')[1];
      const diffSpan = document.createElement('span');
      diffSpan.className = 'diff-value';
      if (key in equipmentToDiff.data) {
        const diffValue = equipmentToDiff.data[key];
        if (data[key] === diffValue) {
          continue;
        }
        const diff = strDiff(diffValue, data[key]);
        diffSpan.textContent = ` (${diff})`;
        const color = diff.startsWith('-') ? colors.smaller : colors.greater;
        diffSpan.style = `color: ${color}; font-weight: bold;`;
        valueElement.appendChild(diffSpan);
      } else if (valueElement) {
        diffSpan.textContent = ` (N/A)`;
        const color = valueElement.textContent.startsWith('-') ? colors.greater : colors.smaller;
        diffSpan.style = `color: ${color}; font-weight: bold;`;
        valueElement.appendChild(diffSpan);
      }
    }
    for (const key in equipmentToDiff.data) {
      if (!(key in data)) {
        const newLine = document.createElement('div');
        newLine.className = 'EquipmentStatsText_stat__27Sus';

        const keySpan = document.createElement('span');
        keySpan.textContent = `${key}: `;
        newLine.appendChild(keySpan);

        const valueSpan = document.createElement('span');
        valueSpan.textContent = 'N/A';
        newLine.appendChild(valueSpan);

        const diffSpan = document.createElement('span');
        diffSpan.className = 'diff-value';
        diffSpan.textContent = ` (${equipmentToDiff.data[key]})`;
        const color = equipmentToDiff.data[key].startsWith('-') ? colors.smaller : colors.greater;
        diffSpan.style = `color: ${color}; font-weight: bold;`;
        valueSpan.appendChild(diffSpan);
        TextArea.appendChild(newLine);
      }
    }

    if (equipmentToDiff.name) {
      const newLine = document.createElement('div');
      newLine.className = 'diff-header';
      newLine.style = 'display: flex; grid-gap: 6px; gap: 6px; justify-content: space-between;'

      const keySpan = document.createElement('span');
      keySpan.textContent = isZH ? '对比对象: ' : 'Compares to: ';  
      keySpan.style = `color: ${colors.info}; font-weight: bold;`;
      newLine.appendChild(keySpan);
  
      const diffTitle = document.createElement('span');
      diffTitle.textContent = equipmentToDiff.name;
      diffTitle.style = `color: ${colors.info}; font-weight: bold;`;
      newLine.appendChild(diffTitle);
      parentArea.insertBefore(newLine, TextArea);
    }
    if (price >= 0 && equipmentToDiff.price >= 0) {
      const newLine = document.createElement('div');
      newLine.className = 'diff-header';
      newLine.style = 'display: flex; grid-gap: 6px; gap: 6px; justify-content: space-between;'

      const keySpan = document.createElement('span');
      keySpan.textContent = isZH ? `价格差: ` : `Price diff: `;  
      keySpan.style = `color: ${colors.info}; font-weight: bold;`;
      newLine.appendChild(keySpan);
  
      const priceSpan = document.createElement('span');
      priceSpan.className = 'diff-price';
      const priceDiff = priceParse(price - equipmentToDiff.price);
      priceSpan.textContent = `${priceDiff}`;
      const priceColor = priceDiff.startsWith('-') ? colors.greater : colors.smaller;
      priceSpan.style = `color: ${priceColor}; font-weight: bold;`;
      newLine.appendChild(priceSpan);

      parentArea.insertBefore(newLine, TextArea);
    }
  }

  function strDiff(s1, s2) {
    if (s1 === s2) return '';
    if (!s1 || !s2) return s1 || s2;
    let postfix = '';
    let isNeg = false;
    if (s1.endsWith('%')) postfix = '%';
    if (s1.startsWith('-')) isNeg = true;
    const proc = (t) => parseFloat(t.replace('%', '').replace(',', '').replace(' ', '').replace('+', '').replace('_', ''));
    const diff = proc(s2) - proc(s1);
    if (isNaN(diff)) return s2;
    if (diff === 0) return '';
    if (diff > 0) return `${isNeg ? '-' : '+'}${parseFloat(diff.toFixed(3))}${postfix}`;
    return `${isNeg ? '+' : '-'}${parseFloat(Math.abs(diff).toFixed(3))}${postfix}`;
  }

  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

        try {
            return handleMessage(message);
        } catch (error) {
            console.log("Error in handleMessage:", error);
            return message;
        }
    }
  }
  hookWS();

  function handleMessage(message) {
    if (typeof message !== "string") {
        return message;
    }
    try {
        const parsedMessage = JSON.parse(message);
        if (parsedMessage && parsedMessage.type === "init_character_data") {
            selfData = parsedMessage;
        }
    } catch (error) {
        console.log("Error parsing message:", error);
    }
    return message;
  }

  function parsePrice(costText) {
    if (costText.endsWith('M')) {
      return parseFloat(costText.replace('M', '').replace(',', '')) * 1e6
    } else if (costText.endsWith('k')) {
      return parseFloat(costText.replace('K', '').replace(',', '')) * 1e3
    } else if (costText.endsWith('B')) {
      return parseFloat(costText.replace('B', '').replace(',', '')) * 1e9
    } else if (costText.endsWith('T')) {
      return parseFloat(costText.replace('T', '').replace(',', '')) * 1e12
    } else {
      return parseFloat(costText.replace(',', ''))
    }
  }

  function priceParse(priceNum) {
    const isNegative = priceNum < 0;
    const absValue = Math.abs(priceNum);
    let result;
    
    if (absValue >= 1e10) {
      result = parseFloat((absValue / 1e9).toFixed(3)) + 'B';
    } else if (absValue >= 1e7) {
      result = parseFloat((absValue / 1e6).toFixed(3)) + 'M';
    } else if (absValue >= 1e4) {
      result = parseFloat((absValue / 1e3).toFixed(3)) + 'K';
    } else {
      result = parseFloat(absValue.toFixed(3));
    }
    
    return isNegative ? '-' + result : '+' + result;
  }

  function getMWIToolsPrice(modal) {
    const enhancedPriceText = isZH ? '总成本' : 'Total cost';
    let costNodes = Array.from(modal.querySelectorAll('*')).filter(el => {
      if (!el.textContent || !el.textContent.includes(enhancedPriceText)) return false;
      return Array.from(el.childNodes).every(node => node.nodeType === Node.TEXT_NODE);
    });
    if (costNodes.length > 0) {
      const node = costNodes[0];
      const costText = node.textContent.replace(enhancedPriceText, '').trim();
      return parsePrice(costText);
    }

    const normalPriceText = isZH ? '日均价' : 'Daily average price';
    costNodes = Array.from(modal.querySelectorAll('*')).filter(el => {
      if (!el.textContent || !el.textContent.includes(normalPriceText)) return false;
      return Array.from(el.childNodes).every(node => node.nodeType === Node.TEXT_NODE);
    });
    if (costNodes.length > 0) {
      const node = costNodes[0];
      const costText = node.textContent.split('/')[0].split(' ')[1].trim();
      return parsePrice(costText);
    }

    return -1;
  }

  setInterval(() => {
    const modals = document.querySelectorAll('.MuiPopper-root');
    if (!modals) return;
    // compatibility with chat-enhance addon
    let equipmentDetail = null;
    let modal = null;
    for (const modal_ of modals) {
      equipmentDetail = modal_.querySelector('.ItemTooltipText_equipmentDetail__3sIHT');
      if (equipmentDetail) {
        modal = modal_;
        break
      };
    }
    if (!equipmentDetail) return;

    const equipmentName = modal.querySelector('.ItemTooltipText_name__2JAHA').textContent.trim();  
    const equipmentData = parseEquipmentModal(equipmentDetail);

    const price = getMWIToolsPrice(modal);

    if (equipmentToDiff.data) {
      addDiffToModal(equipmentDetail, equipmentData, price);
    }

    document.onkeydown = (event) => {
      if (event.key === 'd') {
        // event.preventDefault();
        console.log(`Added to quick diff: ${equipmentName}`);
        equipmentToDiff = {
          data: equipmentData,
          name: equipmentName,
          price: price,
        };
        equipmentDetail.querySelector('.diff-tooltip').textContent = isZH ? '已添加作为对比' : 'Added to quick diff';
      } else if (event.key === 'r') {
        // event.preventDefault();
        console.log(`Removed from quick diff: ${equipmentName}`);
        equipmentToDiff = {};
        equipmentDetail.querySelector('.diff-tooltip').textContent = isZH ? '已移除对比' : 'Removed from quick diff';
      }
    };

  }, 500)

})();