Ghost Trade Helper - Max Deposit + Quick Buttons

Updated to be torn compliant!

// ==UserScript==
// @name         Ghost Trade Helper - Max Deposit + Quick Buttons
// @namespace    https://www.torn.com/
// @version      4.20
// @description  Updated to be torn compliant!
// @author       swervelord
// @match        https://www.torn.com/trade.php*
// @grant        GM_addStyle
// ==/UserScript==

(function () {
  'use strict';

  const PRESET_BUTTONS = [
    ['-100k', -100_000],
    ['-500k', -500_000],
    ['-1m', -1_000_000],
    ['-5m', -5_000_000],
    ['-10m', -10_000_000],
    ['-100m', -100_000_000],
    ['-1b', -1_000_000_000],
  ];

  GM_addStyle(`
    .gth-btn {
      background: #2b2b2b !important;
      color: #fff !important;
      border: none !important;
      padding: 4px 10px !important;
      margin: 2px !important;
      font-weight: 600 !important;
      border-radius: 4px !important;
      box-shadow: 0 0 4px #00ffff80, 0 0 10px #00ffff40;
      cursor: pointer;
      transition: transform .12s, box-shadow .12s;
    }
    .gth-btn:hover {
      transform: translateY(-1px);
      box-shadow: 0 0 6px #00ffffb3, 0 0 14px #00ffff80;
    }
  `);

  const formatNumber = (n) => n.toLocaleString('en-US');
  const parseNumber = (str) => parseInt(str.replace(/[^0-9]/g, ''), 10) || 0;
  const getTradePageCash = () => {
    const el = document.querySelector('.money-value');
    return el ? parseInt(el.textContent.replace(/[^0-9]/g, ''), 10) : 0;
  };

  const waitForInput = () => new Promise((resolve) => {
    const check = () => {
      const inputs = document.querySelectorAll('.user-id.input-money');
      if (inputs.length === 2) return resolve(inputs);
      requestAnimationFrame(check);
    };
    check();
  });

  const insertButtons = (input, sync) => {
    if (document.querySelector('#ghost-trade-helper')) return;

    const container = document.createElement('div');
    container.id = 'ghost-trade-helper';
    container.style.marginTop = '10px';
    container.style.display = 'flex';
    container.style.flexWrap = 'wrap';

    PRESET_BUTTONS.forEach(([label, amount]) => {
      const btn = document.createElement('button');
      btn.type = 'button';
      btn.className = 'gth-btn';
      btn.textContent = label;
      btn.onclick = () => {
        const current = parseNumber(input.value);
        const newValue = Math.max(0, current + amount);
        input.value = formatNumber(newValue);
        input.dispatchEvent(new Event('input', { bubbles: true }));
        sync();
      };
      container.appendChild(btn);
    });

    const custom = document.createElement('button');
    custom.type = 'button';
    custom.className = 'gth-btn';
    custom.textContent = 'Custom';
    custom.onclick = () => {
      const val = prompt('Enter amount to subtract:');
      if (!val) return;
      const current = parseNumber(input.value);
      const sub = parseNumber(val);
      const newValue = Math.max(0, current - sub);
      input.value = formatNumber(newValue);
      input.dispatchEvent(new Event('input', { bubbles: true }));
      sync();
    };
    container.appendChild(custom);

    const paste = document.createElement('button');
    paste.type = 'button';
    paste.className = 'gth-btn';
    paste.textContent = 'Paste';
    paste.onclick = async () => {
      try {
        const text = await navigator.clipboard.readText();
        const newValue = parseNumber(text);
        input.value = formatNumber(newValue);
        input.dispatchEvent(new Event('input', { bubbles: true }));
        sync();
      } catch {
        alert('Clipboard access denied.');
      }
    };
    container.appendChild(paste);

    input.parentElement.insertAdjacentElement('afterend', container);
  };

  const observeTradeCash = (input) => {
    let lastWallet = getTradePageCash();
    let lastInput = parseNumber(input.value);

    const sync = () => {
      lastWallet = getTradePageCash();
      lastInput = parseNumber(input.value);
    };

    const applyNewValue = () => {
      const newWallet = getTradePageCash();
      if (newWallet === lastWallet) return;
      const delta = lastInput - lastWallet;
      const newValue = Math.max(0, newWallet + delta);
      input.value = formatNumber(newValue);
      input.dispatchEvent(new Event('input', { bubbles: true }));
      lastWallet = newWallet;
      lastInput = newValue;
    };

    const node = document.querySelector('.money-value');
    if (!node) return;

    const observer = new MutationObserver(applyNewValue);
    observer.observe(node, {
      childList: true,
      characterData: true,
      subtree: true,
    });

    setInterval(applyNewValue, 500);
    return sync;
  };

  const waitForLineBreakTarget = () => {
    const check = () => {
      const p = Array.from(document.querySelectorAll('p')).find(p =>
        p.textContent.includes('Enter in the amount of money you want to trade.') &&
        p.textContent.includes('You have $') &&
        !p.innerHTML.includes('<br>') &&
        !p.dataset.lineBreakFixed
      );

      if (p) {
        p.innerHTML = p.innerHTML.replace('. You have', '.<br>You have');
        p.dataset.lineBreakFixed = 'true';
      } else {
        requestAnimationFrame(check);
      }
    };

    requestAnimationFrame(check);
  };

  const startScript = async () => {
    const [input] = await waitForInput();

    const wallet = getTradePageCash();
    const current = parseNumber(input.value);
    input.value = formatNumber(wallet + current);
    input.dispatchEvent(new Event('input', { bubbles: true }));

    const sync = observeTradeCash(input);
    insertButtons(input, sync);
    sync();

    waitForLineBreakTarget();
  };

  const observer = new MutationObserver(() => {
    if (location.pathname === '/trade.php') {
      const input = document.querySelector('.user-id.input-money');
      if (input && !document.querySelector('#ghost-trade-helper')) {
        startScript();
      }
    }
  });

  observer.observe(document.body, { childList: true, subtree: true });
})();
长期地址
遇到问题?请前往 GitHub 提 Issues。