Greasy Fork is available in English.

Auction House Price Viewer

Shows you the lowest market price of an item while in the auction house.

// ==UserScript==
// @name         Auction House Price Viewer
// @namespace    LordBusiness.AHPV
// @version      1.1.1
// @description  Shows you the lowest market price of an item while in the auction house.
// @author       LordBusiness [2052465]
// @match        https://www.torn.com/amarket.php
// @run-at       document-end
// @connect      api.torn.com
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// ==/UserScript==

const APIkey = 'Mx1ByrR3oCGFwfx8';

GM_addStyle(`
    .items-list > li > .c-bid-wrap {
        cursor: pointer;
    }
`);

(function() {
    'use strict';


    // Saves values that are retreived from that API
    let lowestPrices = {}

    // This rendition of fetch can bypass the CSP.
    const unsafeFetch = url => new Promise((resolve, reject) => GM_xmlhttpRequest({
              method: 'GET',
              url: url,
              responseType: 'json',
              onload: response => resolve(response.response),
              onerror: err => reject(err)
          })),

          // Display value to user
          displayValue = (value, placeholder) => {
              // If integer, check if max integer (this means there were no items for sale). Else just return a properly formatted string.
              if(Number.isInteger(value)) value = value == Number.MAX_SAFE_INTEGER ? 'Not on the market yet' : `Price: \$${value.toLocaleString()}`

              // JQUERY BELOW! EXTREMELY TOXIC! KEEP OUT!
              $( placeholder )
                  .trigger('mouseleave')
                  .prop('title', value)
                  .trigger('mouseenter');
              // JQUERY ABOVE! EXTREMELY TOXIC! KEEP OUT!
          },

          // Get the lowest price when given an object with market costs
          getlowestPrice = market => {
              // return biggest value possible if there is nothing for sale
              if (market === null) return Number.MAX_SAFE_INTEGER;

              // Push all costs into an array
              let costs = [];
              for(const { cost } of Object.values(market)) costs.push(cost);
              return Math.min(...costs);
          },

          // Get lowest price from API
          checkPriceOnHover = event => {

              // Get the target hover elemnt and item ID
              const hoverDiv = event.target,
                    itemID = hoverDiv.parentNode.querySelector('[item]').getAttribute('item');

              // if click, redirect to relevant item page
              if(event.type == 'click') {
                  location.href = `https://www.torn.com/imarket.php#/p=shop&type=${itemID}`;
                  return;
              }

              // Memoization
              if(itemID in lowestPrices) {
                  displayValue(lowestPrices[itemID], hoverDiv);
                  return;
              }

              // Get prices from Torn API
              unsafeFetch(`https://api.torn.com/market/${itemID}?selections=bazaar,itemmarket&key=${APIkey}`)
              .then(async response => {
                  if(response.error) return response.error.error;

                  const lowestBazaarPrice = getlowestPrice(response.bazaar),
                        lowestItemmarketPrice = getlowestPrice(response.itemmarket);

                  return Math.min(lowestBazaarPrice, lowestItemmarketPrice);
              })
              .then(lowestPrice => {
                  lowestPrices[itemID] = lowestPrice;
                  displayValue(lowestPrice, hoverDiv);
              })
              .catch(err => console.log(err));
          },

          // Mutation Observer config. We only want to observe children, i.e., the respective items.
          mutationConfig = {
              childList: true
          },

          // Event Listener Config. We want the listener to be invoked at most once.
          eventListenerConfig = {
              once: true
          },

          // Mutation Observer
          auctionObserver = new MutationObserver(mutationList => {
              for(const mutationRecord of mutationList) {
                  for(const addedNode of mutationRecord.addedNodes) {
                      if(addedNode.id && !addedNode.classList.contains('itemPriceHover')) {
                          addedNode.querySelector('.c-bid-wrap').addEventListener('click', checkPriceOnHover);
                          addedNode.querySelector('.c-bid-wrap').addEventListener('mouseenter', checkPriceOnHover, eventListenerConfig);
                          addedNode.classList.add('itemPriceHover');
                      }
                  }
              }
          }),

          // Get item lists (there are 5 -- one for each rarity)
          itemsLists = document.querySelectorAll('.items-list');

    // Loop through item lists and add a MutationObserver to each one.
    for(const itemsList of itemsLists) {
        auctionObserver.observe(itemsList, mutationConfig);
    }

})();