Item Market Auto Price

Automatically set the price of items relative to the current market with settings menu

// ==UserScript==
// @name         Item Market Auto Price
// @namespace    dev.kwack.torn.imarket-auto-price
// @version      1.1.3
// @description  Automatically set the price of items relative to the current market with settings menu
// @author       Kwack (original), Mr_Awaken (modified)
// @match        https://www.torn.com/page.php?sid=ItemMarket
// @connect      api.torn.com
// @grant        GM_addStyle
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// ==/UserScript==

// @ts-check

const inputSelector = `
  div[class*=itemRowWrapper] div[class*=priceInputWrapper]
  > div.input-money-group > input.input-money:not([type=hidden]):not(.kw--price-set),
  div[class*=itemRowWrapper] div[class*=amountInputWrapper][class*=hideMaxButton]
  > div.input-money-group > input.input-money:not([type=hidden]):not(.kw--price-set)
`;

const diff = 5;

let mainCalled = false;

// Function to show API key modal
function showApiKeyModal() {
  console.log("Showing API key modal");
  const modalHTML = `
    <div id="kw-modal" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%;
         background: rgba(0, 0, 0, 0.8); z-index: 9999; display: flex; justify-content: center; align-items: center;">
      <div style="background: #1c1c1c; padding: 30px; border-radius: 8px; width: 400px; max-width: 90%;
          color: #fff; text-align: center; font-family: Arial, sans-serif;">
        <h2 style="margin-bottom: 10px;">🔐 Enter Your Torn API Key</h2>
        <p style="font-size: 14px; color: #ccc; margin-bottom: 20px;">
          We'll store it securely in your browser and only use it for fetching item market prices.
        </p>
        <input id="kw-api-input" type="text" value="${localStorage.getItem("tornAutoPriceAPIKey") || ''}" placeholder="Your API Key..." style="
          width: 100%; padding: 10px; border-radius: 4px; border: none; font-size: 16px;
          margin-bottom: 20px;" />
        <button id="kw-save-api" style="
          background: #00ccff; border: none; color: #000; padding: 10px 20px;
          font-weight: bold; border-radius: 5px; cursor: pointer;">
          Save & Continue
        </button>
      </div>
    </div>
  `;

  document.body.insertAdjacentHTML("beforeend", modalHTML);
  document.getElementById("kw-save-api").addEventListener("click", () => {
    const input = /** @type {HTMLInputElement} */ (document.getElementById("kw-api-input"));
    const value = input.value.trim();
    if (value.length >= 16) {
      localStorage.setItem("tornAutoPriceAPIKey", value);
      document.getElementById("kw-modal").remove();
      console.log("API key saved:", value);
      main();
    } else {
      input.style.border = "2px solid red";
    }
  });
}

// Function to get API key from localStorage or show modal
function getApiKey() {
  const key = localStorage.getItem("tornAutoPriceAPIKey");
  if (!key) {
    showApiKeyModal();
  } else {
    console.log("API key retrieved:", key);
    main();
  }
}

// Add settings menu item
function addMarketAutoPriceSettingsMenuItem() {
  const menu = document.querySelector('.settings-menu');
  if (!menu || document.querySelector('.market-auto-price-settings-button')) return;
  const li = document.createElement('li');
  li.className = 'link market-auto-price-settings-button';
  const a = document.createElement('a');
  a.href = '#';
  const iconDiv = document.createElement('div');
  iconDiv.className = 'icon-wrapper';
  const svgIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  svgIcon.setAttribute('class', 'default');
  svgIcon.setAttribute('fill', '#fff');
  svgIcon.setAttribute('stroke', 'transparent');
  svgIcon.setAttribute('stroke-width', '0');
  svgIcon.setAttribute('width', '16');
  svgIcon.setAttribute('height', '16');
  svgIcon.setAttribute('viewBox', '0 0 512 512');
  const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  path.setAttribute('d', 'M487.4 315.7l-42.6-24.6c4.3-23.2 4.3-47 0-70.2l42.6-24.6c4.9-2.8 7.1-8.6 5.5-14-11.1-35.6-30-67.8-54.7-94.6-3.8-4.1-10-5.1-14.8-2.3L380.8 110c-17.9-15.4-38.5-27.3-60.8-35.1V25.8c0-5.6-3.9-10.5-9.4-11.7-36.7-8.2-74.3-7.8-109.2 0-5.5 1.2-9.4 6.1-9.4 11.7V75c-22.2 7.9-42.8 19.8-60.8 35.1L88.7 85.5c-4.9-2.8-11-1.9-14.8 2.3-24.7 26.7-43.6 58.9-54.7 94.6-1.7 5.4.6 11.2 5.5 14L67.3 221c-4.3 23.2-4.3 47 0 70.2l-42.6 24.6c-4.9 2.8-7.1 8.6-5.5 14 11.1 35.6 30 67.8 54.7 94.6 3.8 4.1 10 5.1 14.8 2.3l42.6-24.6c17.9 15.4 38.5 27.3 60.8 35.1v49.2c0 5.6 3.9 10.5 9.4 11.7 36.7 8.2 74.3 7.8 109.2 0 5.5-1.2 9.4-6.1 9.4-11.7v-49.2c22.2-7.9 42.8-19.8 60.8-35.1l42.6 24.6c4.9 2.8 11 1.9 14.8-2.3 24.7-26.7 43.6-58.9 54.7-94.6 1.5-5.5-.7-11.3-5.6-14.1zM256 336c-44.1 0-80-35.9-80-80s35.9-80 80-80 80 35.9 80 80-35.9 80-80 80z');
  svgIcon.appendChild(path);
  iconDiv.appendChild(svgIcon);
  const span = document.createElement('span');
  span.textContent = 'Item Market Auto Price Settings';
  a.appendChild(iconDiv);
  a.appendChild(span);
  li.appendChild(a);
  a.addEventListener('click', e => {
    e.preventDefault();
    document.body.click();
    showApiKeyModal();
  });
  const logoutButton = menu.querySelector('li.logout');
  if (logoutButton) {
    menu.insertBefore(li, logoutButton);
  } else {
    menu.appendChild(li);
  }
}

const menuObserver = new MutationObserver(mutations => {
  mutations.forEach(mutation => {
    if (mutation.addedNodes.length > 0) {
      for (const node of mutation.addedNodes) {
        if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains('settings-menu')) {
          addMarketAutoPriceSettingsMenuItem();
          break;
        }
      }
    }
  });
});
menuObserver.observe(document.body, { childList: true, subtree: true });

addMarketAutoPriceSettingsMenuItem();

// Wait for the specific elements to be present
function waitForElements(selector, callback) {
  const observer = new MutationObserver(() => {
    if (document.querySelector(selector)) {
      observer.disconnect();
      callback();
    }
  });
  observer.observe(document.body, { childList: true, subtree: true });
}

// Main function to set up event listeners
function main() {
  if (mainCalled) return;
  mainCalled = true;
  console.log("Running main");
  $(document).on("click", inputSelector, (e) => {
    const input = e.target;
    if (input.getAttribute("placeholder") === "Qty") {
      addQuantity(input).catch((e) => handleError(e, input));
    } else {
      addPrice(input).catch((e) => handleError(e, input));
    }
  });
}

// Function to get the lowest price from the API
function getLowestPrice(itemId, apiKey) {
  const baseURL = "https://api.torn.com/v2/market";
  const searchParams = new URLSearchParams({
    selections: "itemmarket",
    key: apiKey,
    id: itemId,
    offset: "0",
  });
  const url = new URL(`?${searchParams.toString()}`, baseURL);
  return fetch(url)
    .then((res) => res.json())
    .then((data) => {
      if ("error" in data) throw new Error(data.error.error);
      const price = data?.itemmarket?.listings?.[0]?.price;
      if (typeof price === "number" && price >= 1) return price;
      throw new Error(`Invalid price: ${price}`);
    });
}

// Function to update the input field
function updateInput(input, value) {
  input.value = `${value}`;
  input.dispatchEvent(new Event("input", { bubbles: true }));
}

// Function to set the price
async function addPrice(input) {
  if (!(input instanceof HTMLInputElement))
    throw new Error("Input is not an HTMLInputElement");
  const apiKey = localStorage.getItem("tornAutoPriceAPIKey");
  if (!apiKey) {
    throw new Error("API key not set. Please set it in the settings.");
  }
  const row = input.closest("div[class*=itemRowWrapper]");
  const image = row?.querySelector("img");
  if (!image) throw new Error("Could not find image element");
  if (image.parentElement?.matches("[class*='glow-']"))
    throw new Warning("Skipping a glowing RW item");
  const itemId = image.src?.match(/\/images\/items\/([\d]+)\//)?.[1];
  if (!itemId) throw new Error("Could not find item ID");
  const currentLowestPrice = await getLowestPrice(itemId, apiKey);
  const priceToSet = Math.max(1, currentLowestPrice - diff);
  updateInput(input, priceToSet);
  input.classList.add("kw--price-set");
}

// Function to set the quantity to max
async function addQuantity(input) {
  if (!(input instanceof HTMLInputElement))
    throw new Error("Input is not an HTMLInputElement");
  updateInput(input, "max");
  input.classList.add("kw--price-set");
}

// Error handling
function handleError(e, input) {
  if (e instanceof Warning) {
    console.warn(e);
    input.style.outline = "2px solid yellow";
  } else {
    console.error(e);
    input.style.outline = "2px solid red";
  }
}

// Custom Warning class
class Warning extends Error {}

// Start the script by waiting for the elements
waitForElements(inputSelector, getApiKey);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址