Torn Inventory & Display Case Market Value

Combined script for Inventory and Display Case market value

// ==UserScript==
// @name        Torn Inventory & Display Case Market Value
// @namespace   Violentmonkey Scripts
// @match       https://www.torn.com/*
// @grant       GM_xmlhttpRequest
// @version     1.0
// @license     MIT
// @author      BillyBourbon/Bilbosaggings[2323763]
// @description Combined script for Inventory and Display Case market value
// ==/UserScript==

// ================================
// Input your apikey in between the quote marks ""
const apikey = "";
// ================================

(async () => {
  // Function to format numbers to currency format (USD)
  function formatToCurrency(n) {
    n = Number(n);
    return new Intl.NumberFormat("en-US", {
      style: "currency",
      currency: "USD",
      minimumFractionDigits: 0,
      maximumFractionDigits: 0
    }).format(n);
  }

  // Function to load Torn items with caching
  async function loadTornItems() {
    const cacheKey = "tornItemsCache";
    const cacheExpiryKey = "tornItemsCacheExpiry";
    const cacheDuration = 60 * 60 * 1000; // 1 hour in milliseconds

    // Check for cached data
    const cachedData = localStorage.getItem(cacheKey);
    const cachedExpiry = localStorage.getItem(cacheExpiryKey);

    if (cachedData && cachedExpiry && Date.now() < cachedExpiry) {
      console.log("Using cached data");
      return JSON.parse(cachedData);
    }

    let attempt = 0;
    let jsonResponse = null;

    // Retry logic for API request
    while (attempt < 3) {
      try {
        jsonResponse = await new Promise((resolve, reject) => {
          GM_xmlhttpRequest({
            method: "GET",
            url: `https://api.torn.com/v2/torn/items`,
            headers: {
              "Authorization": `ApiKey ${apikey}`,
            },
            onload: function (response) {
              if (response.status >= 200 && response.status < 300) {
                try {
                  const responseData = JSON.parse(response.responseText);
                  resolve(responseData);
                } catch (error) {
                  reject(new Error("Failed to parse JSON"));
                }
              } else {
                reject(new Error(`API request failed with status: ${response.status}`));
              }
            },
            onerror: function (error) {
              reject(new Error(`API request failed with error: ${error}`));
            }
          });
        });

        console.log(jsonResponse);

        // Cache the API response
        localStorage.setItem(cacheKey, JSON.stringify(jsonResponse));
        localStorage.setItem(cacheExpiryKey, Date.now() + cacheDuration);

        return jsonResponse;
      } catch (error) {
        attempt++;
        console.error(`Attempt ${attempt} failed: ${error.message}`);

        if (attempt < 3) {
          await new Promise(resolve => setTimeout(resolve, 2000)); // Delay before retrying
        }
      }
    }
  }

  // Function to find a Torn item by its ID
  function findTornItem(itemId, tornItems) {
    const item = tornItems.find(o => o.id.toString() === itemId.toString());

    // Return null if item is not found
    return item || null;
  }

  // ========================
  // Inventory Market Value
  // ========================
	async function insertMarketValues(itemList) {
		let counter = 0;

		const {
			items: tornItems
		} = await loadTornItems();

		for (let child of itemList.querySelectorAll("li")) {
			const itemId = child.getAttribute("data-item");
			if (itemId !== null && itemId > 0 && child.querySelector(".name-wrap .name") !== null) {
				const itemNameSpan = child.querySelector(".name-wrap .name");
				const itemQuantitySpan = child.querySelector(".name-wrap .qty");
				const itemQuantity = itemQuantitySpan.innerHTML.length === 0 ? 1 : Number(itemQuantitySpan.innerHTML.substring(1));
				const {
					value: {
						market_price
					}
				} = findTornItem(itemId, tornItems);
				itemNameSpan.innerHTML += ` (${formatToCurrency(market_price * itemQuantity)})`;

				counter++;
			}
		}

		return counter;
	}

	const callback = async (mutationList, observer) => {
		console.log("mutation observed");
		for (const mutation of mutationList) {
			if (mutation.type === "childList") {
				if (mutation.addedNodes.length === 0) return;
				const editedElementCount = await insertMarketValues(mutation.target);

				if (editedElementCount > 0) observer.disconnect();
			}
		}
	}

  // ========================
  // Display Case Market Value
  // ========================
  async function updateDisplayCaseMarketValue() {
    // Wait for display case page to load
    while (document.querySelector(".display-cabinet") === null) {
      await new Promise(resolve => setTimeout(resolve, 500)); // Delay before retrying
    }

    const displayCaseContainer = document.querySelector(".display-cabinet");

    const { items: tornItems } = await loadTornItems();

    displayCaseContainer.querySelectorAll("li").forEach(li => {
      const temp = li.querySelector(".item-hover");

      if (temp === null || temp.getAttribute("data-userscript") === "true") return; // Skip if there's no item

      const itemId = temp.getAttribute("itemId");
      const ammountField = li.querySelector(".b-item-amount");
      const ammount = ammountField.innerHTML.trim().substring(1);

      const item = findTornItem(itemId, tornItems);

      // Check if item was found
      if (item && item.value && item.value.market_price) {
        const marketPrice = item.value.market_price;
        const totalValue = ammount * marketPrice
        if(totalValue > 0) ammountField.innerHTML += `(${formatToCurrency(totalValue)})`;
      } else {
        console.error(`Item with ID ${itemId} not found or missing market price.`);
      }

      temp.setAttribute("data-userscript", "true")
    });
  }

  // ========================
  // Main Logic
  // ========================
  const currentUrl = window.location.href;

  if (currentUrl.includes("item.php")) {
    console.log("Updating Inventory Market Value...");
    // Observe changes in the inventory page
    document.querySelectorAll(".items-cont").forEach(container => {
      const observer = new MutationObserver(callback);
      observer.observe(container, {
        attributes: true,
        childList: true,
        subtree: true
      });
    })
  }
  else if (currentUrl.includes("displaycase.php")) {
     console.log("Updating Display Case Market Value...");
     await updateDisplayCaseMarketValue();
  }
})();