Item Market - Remove/Relist All

Adds buttons to remove all / relist all in item market pages. Uses localstorage to save item details when removing, then when relisting compares to those presented and pre-fills their details with saved ones.

// ==UserScript==
// @name         Item Market - Remove/Relist All
// @namespace    Titanic_
// @version      1.0
// @author       Titanic_ [2968477]
// @description  Adds buttons to remove all / relist all in item market pages. Uses localstorage to save item details when removing, then when relisting compares to those presented and pre-fills their details with saved ones.
// @match        https://www.torn.com/page.php?sid=ItemMarket*
// @license      MIT
// @grant        none
// ==/UserScript==

(function () {
  "use strict";

  const INPUT_SETTER = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value").set;
  const CHECKBOX_SETTER = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "checked").set;

  function saveListings() {
    const listings = [...document.querySelectorAll("div[class^=itemRow_]")].map((item) => {
      const props = [...item.querySelectorAll("[class*=properties_] [class*=property_]")];
      return {
        name: item.querySelector("span[class^=name_]")?.textContent,
        price: parseInt(item.querySelector("[class*=priceInputWrapper_] input[class*=money]")?.value?.replace(/,/g, "")) || null,
        quantity: parseInt(item.querySelector("[class*=amountInputWrapper_] input[class*=money]")?.dataset.money) || null,
        checkbox: item.querySelector("[class*=checkboxContainer_] input[id*=itemRow-selectCheckbox]")?.checked || null,
        dmg: props[0]?.textContent || null,
        acc: props[1]?.textContent || null,
        mods: [...item.querySelectorAll("i[class*=modifier_]")].map(m => [...m.classList].filter(c => c.startsWith("bonus-")))
      };
    });
    localStorage.setItem("Saved_Listings", JSON.stringify(listings));

    document.querySelectorAll("div[class^=itemRow_]").forEach((item) => {
      const input = item.querySelector("[class*=amountInputWrapper_] input[class*=money]");
      if (input) {
        INPUT_SETTER.call(input, 0);
        input.dispatchEvent(new Event("input", { bubbles: true }));
      }
      const checkbox = item.querySelector("[class*=checkboxContainer_] input[id*=itemRow-selectCheckbox]");
      if (checkbox) {
        CHECKBOX_SETTER.call(checkbox, false);
        checkbox.dispatchEvent(new Event("click", { bubbles: true }));
      }
    });
  }

  function restoreListings() {
    const savedListings = JSON.parse(localStorage.getItem("Saved_Listings") || "[]");
    if (!savedListings.length) return alert("No saved items. Use Remove All first.");

    document.querySelectorAll("div[class^=itemRow_]").forEach((item) => {
      const name = item.querySelector("span[class^=name_]")?.textContent;
      const mods = [...item.querySelectorAll("i[class*=modifier_]")].map(m => [...m.classList].filter(c => c.startsWith("bonus-")));
      const dmg = item.querySelector("[class*=properties_] [class*=property_]:nth-child(1)")?.textContent || null;
      const acc = item.querySelector("[class*=properties_] [class*=property_]:nth-child(2)")?.textContent || null;

      const listing = savedListings.find(l =>
        l.name === name &&
        JSON.stringify(l.mods) === JSON.stringify(mods) &&
        l.dmg === dmg &&
        l.acc === acc
      );

      if (!listing) return;

      if (listing.quantity !== null) {
        const input = item.querySelector("[class*=amountInputWrapper_] input[class*=money]");
        if (input) { INPUT_SETTER.call(input, listing.quantity); input.dispatchEvent(new Event("input", { bubbles: true })); }
      }
      if (listing.checkbox !== null) {
        const checkbox = item.querySelector("[class*=checkboxContainer_] input[id*=itemRow-selectCheckbox]");
        if (checkbox) { CHECKBOX_SETTER.call(checkbox, listing.checkbox); checkbox.dispatchEvent(new Event("click", { bubbles: true })); }
      }
      if (listing.price !== null) {
        const priceInput = item.querySelector("[class*=priceInputWrapper_] input[class*=money]");
        if (priceInput) { INPUT_SETTER.call(priceInput, listing.price); priceInput.dispatchEvent(new Event("input", { bubbles: true })); }
      }
    });
  }

  function addButton(id, text, onClick) {
    if (document.getElementById(id)) return;
    const btn = Object.assign(document.createElement("button"), { id, textContent: text, onclick: onClick, className: "torn-btn btn-transparent" });
    document.querySelector("div[class^=controls_]")?.appendChild(btn);
  }

  function checkURL() {
    const url = window.location.href;
    clearInterval(window.marketInterval);
    window.marketInterval = setInterval(() => {
      if (!document.querySelector("div[class^=controls_]") || document.getElementById("removeAllBtn")) return;
      if (url.includes("viewListing")) addButton("removeAllBtn", "Remove All", saveListings);
      if (url.includes("addListing")) addButton("relistAllBtn", "Relist Previous", restoreListings);
    }, 500);
  }

  checkURL();
  window.addEventListener("hashchange", checkURL);
})();