MAX-Button im Item Market (sellerRow) und Bazaar (buy-menu)
// ==UserScript==
// @name Torn – Max Buy Button v5
// @namespace https://www.torn.com/
// @version 5.0
// @description MAX-Button im Item Market (sellerRow) und Bazaar (buy-menu)
// @author Du
// @match https://www.torn.com/bazaar.php*
// @match https://www.torn.com/imarket.php*
// @match https://www.torn.com/page.php?sid=ItemMarket*
// @grant none
// ==/UserScript==
(function () {
'use strict';
document.head.insertAdjacentHTML('beforeend', `<style>
.tmb-max {
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
padding: 0 7px !important;
height: 22px !important;
background: #c0392b !important;
color: #fff !important;
font-size: 10px !important;
font-weight: 800 !important;
font-family: inherit !important;
border: none !important;
border-radius: 3px !important;
cursor: pointer !important;
letter-spacing: 0.5px !important;
margin-left: 4px !important;
box-shadow: 0 1px 3px rgba(0,0,0,0.4) !important;
vertical-align: middle !important;
flex-shrink: 0 !important;
white-space: nowrap !important;
line-height: 1 !important;
position: static !important;
}
.tmb-max:hover { background: #e74c3c !important; }
.tmb-max.ok { background: #27ae60 !important; }
/* Bazaar: buy-menu soll flex sein damit MAX daneben passt */
[data-testid="buy-menu"] {
display: flex !important;
align-items: center !important;
flex-wrap: nowrap !important;
gap: 4px !important;
}
[data-testid="buy-form"] {
display: flex !important;
align-items: center !important;
gap: 4px !important;
}
</style>`);
/* ── React-safe value setter ── */
function setVal(inp, v) {
try {
const d = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
if (d && d.set) d.set.call(inp, String(v));
else inp.value = String(v);
} catch(e) { inp.value = String(v); }
inp.dispatchEvent(new Event('input', { bubbles: true }));
inp.dispatchEvent(new Event('change', { bubbles: true }));
}
function flash(btn) {
btn.classList.add('ok');
btn.textContent = '✓';
setTimeout(() => { btn.classList.remove('ok'); btn.textContent = 'MAX'; }, 700);
}
/* ════════════════════════════════════════
ITEM MARKET
Echte Klassen laut Console-Output:
Zeile: sellerRow___PaRgK
Buy-Link: linkWrap___oVXBN (a-Tag, klickbar)
Input: irgendwo in buyControls / amountInput
════════════════════════════════════════ */
function injectItemMarket(row) {
if (row.dataset.tmbDone) return;
// Der echte Buy-Link (linkWrap) – NUR den nehmen, nicht userInfoBox etc.
const buyLink = row.querySelector('a[class*="linkWrap"]');
if (!buyLink) return;
// Schon injiziert?
if (row.querySelector('.tmb-max')) return;
row.dataset.tmbDone = '1';
// "X available" aus Text der Zeile
const txt = row.innerText || '';
const m = txt.match(/(\d+)\s+available/i);
const maxQty = m ? parseInt(m[1]) : null;
// Input suchen – in buyControls oder amountInput
const input = row.querySelector(
'[class*="amountInput"] input, ' +
'[class*="buyControl"] input, ' +
'.input-money-group input, ' +
'input[type="number"], ' +
'input[type="text"]'
);
const btn = document.createElement('button');
btn.className = 'tmb-max';
btn.textContent = 'MAX';
btn.type = 'button';
btn.title = maxQty ? `Alle ${maxQty} kaufen` : 'Maximum kaufen';
btn.addEventListener('click', e => {
e.preventDefault();
e.stopPropagation();
if (input && maxQty) {
setVal(input, maxQty);
flash(btn);
// Kurz warten damit React den neuen Wert verarbeitet, dann klicken
setTimeout(() => buyLink.click(), 400);
} else if (maxQty === 1 || !input) {
// Nur 1 verfügbar oder kein Input → direkt kaufen
flash(btn);
setTimeout(() => buyLink.click(), 300);
}
});
// Direkt neben den Buy-Link einfügen
buyLink.insertAdjacentElement('afterend', btn);
}
/* ════════════════════════════════════════
BAZAAR
Container: data-testid="buy-menu"
Input: data-testid="number-input"
Submit: button[type="submit"] in buy-form
Stock: "(X in stock)" im item-container
════════════════════════════════════════ */
function injectBazaar(menu) {
if (menu.dataset.tmbDone) return;
if (menu.querySelector('.tmb-max')) return;
menu.dataset.tmbDone = '1';
const input = menu.querySelector('[data-testid="number-input"], input[type="text"], input[type="number"]');
const buyForm = menu.querySelector('[data-testid="buy-form"]') || menu.closest('form');
const buyBtn = menu.querySelector('button[type="submit"]') ||
menu.querySelector('[data-testid="buy-button"]') ||
buyForm?.querySelector('button[type="submit"]');
if (!input && !buyBtn) return;
// Stock aus item-container lesen
const itemEl = menu.closest('[data-testid="item"], [class*="item__"]') || menu.parentElement;
const txt = itemEl?.innerText || '';
const m = txt.match(/\((\d+)\s+in\s+stock\)/i) || txt.match(/(\d+)\s+in\s+stock/i);
const stock = m ? parseInt(m[1]) : parseInt(input?.max || '1');
const btn = document.createElement('button');
btn.className = 'tmb-max';
btn.textContent = 'MAX';
btn.type = 'button';
btn.title = `Alle ${stock} kaufen`;
btn.addEventListener('click', e => {
e.preventDefault();
e.stopPropagation();
if (input) setVal(input, stock);
flash(btn);
setTimeout(() => {
if (buyBtn) buyBtn.click();
else if (buyForm) {
buyForm.requestSubmit ? buyForm.requestSubmit() : buyForm.submit();
}
}, 400);
});
// Neben Buy-Button einfügen (direkt im buy-form oder buy-menu)
if (buyBtn) {
buyBtn.insertAdjacentElement('afterend', btn);
} else if (buyForm) {
buyForm.appendChild(btn);
} else {
menu.appendChild(btn);
}
}
/* ════════════════════════════════════════
SCAN
════════════════════════════════════════ */
function scan() {
// Item Market: NUR sellerRow Zeilen – nicht rowWrapper/userInfoBox
document.querySelectorAll('[class*="sellerRow"]:not([data-tmb-done])').forEach(injectItemMarket);
// Bazaar: buy-menu container
document.querySelectorAll('[data-testid="buy-menu"]:not([data-tmb-done])').forEach(injectBazaar);
}
let t;
new MutationObserver(() => { clearTimeout(t); t = setTimeout(scan, 150); })
.observe(document.body, { childList: true, subtree: true });
setTimeout(scan, 700);
setTimeout(scan, 2000);
})();