Advanced Bazaar Filler with Market and Bazaar Price Points + Fill All + Trade Fill + Visual Qty and Price currently on Bazaar
// ==UserScript==
// @name Bazaar Filler
// @namespace http://tampermonkey.net/
// @version 6.9
// @author WTV1
// @description Advanced Bazaar Filler with Market and Bazaar Price Points + Fill All + Trade Fill + Visual Qty and Price currently on Bazaar
// @match https://www.torn.com/bazaar.php*
// @match https://www.torn.com/trade.php*
// @require https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_xmlhttpRequest
// @connect api.torn.com
// @connect weav3r.dev
// @connect script.google.com
// ==/UserScript==
(function () {
"use strict";
const isPDA = window.innerWidth <= 700;
let settings = {};
let trendData = null;
let myBazaarData = null;
let lastFetch = 0;
let isFetching = false;
function loadSettings() {
settings = {
apiKey: GM_getValue("tornApiKey", ""),
undercutVal: parseFloat(GM_getValue("undercutVal", 1)),
undercutType: GM_getValue("undercutType", "fixed"),
undercutPos: parseInt(GM_getValue("undercutPos", 1)),
priceSource: GM_getValue("priceSource", "bz"),
skippedItems: JSON.parse(GM_getValue("skippedItems", "{}")),
categoryRules: JSON.parse(GM_getValue("categoryRules", "{}"))
};
}
loadSettings();
// --- SYNC ENGINE ---
function performExport() {
const bundle = {
k: GM_getValue("tornApiKey", ""),
v: GM_getValue("undercutVal", 1),
t: GM_getValue("undercutType", "fixed"),
p: GM_getValue("undercutPos", 1),
s: GM_getValue("priceSource", "bz"),
rules: GM_getValue("categoryRules", "{}"),
skip: GM_getValue("skippedItems", "{}")
};
navigator.clipboard.writeText(JSON.stringify(bundle)).then(() => alert("Settings Copied to Clipboard!"));
}
function performImport() {
const raw = prompt("Paste your settings JSON string here:");
if (!raw) return;
try {
const d = JSON.parse(raw);
GM_setValue("tornApiKey", d.k || "");
GM_setValue("undercutVal", d.v || 1);
GM_setValue("undercutType", d.t || "fixed");
GM_setValue("undercutPos", d.p || 1);
GM_setValue("priceSource", d.s || "bz");
GM_setValue("categoryRules", typeof d.rules === 'string' ? d.rules : JSON.stringify(d.rules));
GM_setValue("skippedItems", typeof d.skip === 'string' ? d.skip : JSON.stringify(d.skip));
alert("Import Successful! Reloading...");
location.reload();
} catch(e) { alert("Error: Invalid settings format."); }
}
const WEB_APP_URL = "https://script.google.com/macros/s/AKfycbxtyZmClPhnEbdX8vl00kwWAXheSSgLv620CVZOFOgJjoCT0_JhXcu4A2wtJ0u9mm1a/exec";
function fetchTrendColors() {
GM_xmlhttpRequest({
method: "GET",
url: WEB_APP_URL,
onload: (res) => {
try {
trendData = JSON.parse(res.responseText);
inject();
} catch(e){}
}
});
}
fetchTrendColors();
const styleBlock = `
.filler-header-links { float: right; margin-right: 10px; height: 34px; display: flex; align-items: center; }
.fill-all-btn { cursor: pointer; margin-left: 15px; font-size: 12px; color: #7cfc00; text-transform: uppercase; font-weight: bold; }
.fill-qty-btn { cursor: pointer; margin-left: 15px; font-size: 12px; color: #00aaff; text-transform: uppercase; font-weight: bold; }
.filler-nav-link { cursor: pointer; margin-left: 15px; font-size: 12px; color: #ccc; text-transform: uppercase; font-weight: bold; }
@media screen and (max-width: 600px) {
.title-black { height: auto !important; min-height: 34px; padding-bottom: 5px !important; }
.filler-header-links { float: none !important; width: 100%; justify-content: space-around; margin-top: 5px; border-top: 1px solid #333; padding-top: 5px; }
.fill-all-btn, .fill-qty-btn, .filler-nav-link { margin-left: 0 !important; }
}
.filler-relative { position: relative !important; }
.fill-wrapper-left { position: absolute !important; right: 155px !important; top: 50% !important; transform: translateY(-50%) !important; z-index: 10; }
.pda-skip-container { position: absolute !important; left: 5px !important; top: 50% !important; transform: translateY(-50%) !important; z-index: 11; }
.add-wrapper { position: absolute !important; right: 8px !important; top: 50% !important; transform: translateY(-50%) !important; z-index: 10; }
.pc-filler-container { display: inline-flex; gap: 8px; align-items: center; margin-left: auto; padding-right: 5px; }
.row-fill-btn { padding: 0 8px; height: 24px; cursor: pointer; border: 1px solid #444; border-radius: 3px; background: #222; display: flex; align-items: center; justify-content: center; color: #7cfc00; font-size: 10px; font-weight: bold; text-transform: uppercase; }
.item-toggle-btn { width: 24px; height: 24px; cursor: pointer; border: 1px solid #444; border-radius: 3px; background: rgba(0,0,0,0.8); display: flex; align-items: center; justify-content: center; color: #ffde00; font-size: 14px; font-weight: bold; }
.filler-skip-checkbox { cursor: pointer; width: 20px; height: 20px; accent-color: #ff3b3b; margin: 0; opacity: 0.8; }
.row-fill-btn.trend-cold { background: #0077bb !important; border-color: #00aaff !important; color: #ffffff !important; }
.row-fill-btn.trend-hot { background: #cc7000 !important; border-color: #ff8c00 !important; color: #ffffff !important; }
.item-toggle-btn.alert-red { color: #ff3b3b !important; border-color: #ff3b3b !important; }
.title-wrap { position: relative !important; }
.stat-row-filler-overlay { position: absolute !important; right: 45px; top: 50%; transform: translateY(-50%); z-index: 5; display: flex; gap: 5px; align-items: center; }
.name-wrap.bold { display: flex !important; align-items: center; gap: 10px; width: 100%; }
.armor-inline-btns { display: flex; gap: 5px; align-items: center; }
.draggable-popup { position: fixed !important; z-index: 999999998; background: #1a1a1a; color: #fff; border: 1px solid #444; border-radius: 5px; width: 220px; display: none; box-shadow: 0 8px 30px rgba(0,0,0,0.9); overflow: hidden; font-family: Arial, sans-serif; touch-action: none; }
.popup-header { background: #222; padding: 12px; cursor: move; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #333; font-weight: bold; color: #ffde00; font-size: 12px; user-select: none; }
.mv-banner { background: #111; padding: 6px; text-align: center; font-weight: bold; border-bottom: 1px solid #333; font-size: 11px; color: #fff; }
.market-table { width: 100%; border-collapse: collapse; table-layout: fixed; }
.market-table th { background: #222; font-size: 10px; color: #888; text-transform: uppercase; padding: 6px; border-bottom: 1px solid #333; }
.market-table td { padding: 12px 2px; text-align: center; border-bottom: 1px solid #222; cursor: pointer; font-size: 11px; font-weight: bold; color: #7cfc00; border-right: 1px solid #222; overflow: hidden; white-space: nowrap; }
.qty-label { color: #aaa; font-size: 9px; font-weight: normal; margin-right: 4px; }
.fill-max-btn { width: 100%; background: #222; color: #7cfc00; border: none; border-top: 1px solid #444; padding: 14px; cursor: pointer; font-weight: bold; font-size: 12px; text-transform: uppercase; }
.filler-force-hide { display: none !important; }
#filler-config-modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #1a1a1a; color: #fff; padding: 20px; border-radius: 8px; z-index: 2147483647; display: none; width: 280px; border: 1px solid #333; box-shadow: 0 0 30px #000; max-height: 90vh; overflow-y: auto; }
.cfg-field { margin-bottom: 12px; }
.cfg-field label { display: block; font-size: 10px; font-weight: bold; color: #aaa; text-transform: uppercase; margin-bottom: 4px; }
.cfg-field input, .cfg-field select { background: #fff; color: #000; border: none; padding: 10px; border-radius: 2px; width: 100%; box-sizing: border-box; font-size: 14px; }
.btn-save { width: 48%; background: #7cfc00; color: #000; border: none; padding: 12px; cursor: pointer; font-weight: bold; border-radius: 4px; }
.btn-close { width: 48%; background: #333; color: #fff; border: none; padding: 12px; cursor: pointer; border-radius: 4px; margin-left: 4%; }
.sync-container { border-top: 1px solid #444; margin-top: 15px; padding-top: 10px; }
.sync-btns { display: flex; gap: 8px; margin-top: 8px; }
.sync-btn { flex: 1; background: #333; color: #fff; border: 1px solid #555; padding: 8px; cursor: pointer; font-size: 10px; font-weight: bold; border-radius: 3px; text-transform: uppercase; }
@keyframes bzSpin { from { transform: translateY(-50%) rotate(0deg); } to { transform: translateY(-50%) rotate(360deg); } }
.bz-spinning { animation: bzSpin 0.8s linear infinite !important; opacity: 0.5 !important; pointer-events: none !important; }
`;
$("<style>").html(styleBlock).appendTo("head");
// --- UTILITIES (Fixed Category Mapping) ---
function getActiveTornCategory() {
const $activeLi = $('li.ui-tabs-active.ui-state-active');
if (!$activeLi.length) return null;
let title = $activeLi.find('a.ui-tabs-anchor').attr('title');
if (!title) {
const reactId = $activeLi.attr('data-reactid');
if (reactId) {
const parts = reactId.split('$');
title = parts[parts.length - 1];
}
}
title = (title || "").trim();
// Optimized Mapping for Category Overrides
const mapping = {
"Plushie": "Plushies", "Flower": "Flowers", "Medical": "Medical Items", "Drug": "Drugs",
"Temporary": "Temporary", "Energy Drink": "Energy Drinks", "Booster": "Boosters",
"Candy": "Candy", "Alcohol": "Alcohol", "Melee": "Melee", "Primary": "Primary",
"Secondary": "Secondary", "Armour": "Armour", "Defensive": "Armour",
"Enhancer": "Enhancer", "Clothing": "Clothing", "Electronic": "Electronic",
"Jewelry": "Jewelry", "Car": "Car", "Supply Pack": "Supply Pack",
"Special": "Special", "Artifact": "Artifacts", "Book": "Books", "Material": "Materials"
};
for (let key in mapping) { if (title.includes(key)) return mapping[key]; }
return title;
}
function updateTornInput($input, value) {
if ($input.length) {
const el = $input[0];
const val = Math.max(0, Math.floor(value));
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
setter.call(el, val);
el.dispatchEvent(new Event('input', { bubbles: true }));
el.dispatchEvent(new Event('change', { bubbles: true }));
$(el).blur();
}
}
// --- CORE LOGIC ---
function handleFillSingleRow($row, itemId, $btn) {
const cat = getActiveTornCategory();
const rule = settings.categoryRules ? settings.categoryRules[cat] : null;
const val = rule ? parseFloat(rule.val) : settings.undercutVal;
const type = rule ? rule.type : settings.undercutType;
const pos = rule ? parseInt(rule.pos) : settings.undercutPos;
const source = rule ? rule.source : settings.priceSource;
if (type === "absolute") {
updateTornInput($row.find('input[placeholder*="Price"], .input-money, [aria-label="Price"]'), val);
updateTornInput($row.find('input[placeholder*="Amount"], input[name="amount"], [aria-label="Amount"]'), $row.find('[class*="amount"], .amount, .count').first().text().replace(/[^0-9]/g, ''));
if ($btn) $btn.text('FILL');
return;
}
if (!settings.apiKey) return alert("Set API Key.");
if ($btn) $btn.text('...');
let url = source === "mv" ? `https://api.torn.com/v2/torn/${itemId}/items?key=${settings.apiKey}` :
source === "im" ? `https://api.torn.com/v2/market/${itemId}/itemmarket?key=${settings.apiKey}` :
`https://weav3r.dev/api/marketplace/${itemId}`;
GM_xmlhttpRequest({
method: "GET", url: url,
onload: r => {
try {
const data = JSON.parse(r.responseText);
let targetPrice = 0;
if (source === "mv") targetPrice = data.items?.[0]?.value?.market_price || 0;
else {
const list = (source === "im" ? data.itemmarket?.listings : data.listings) || [];
targetPrice = list[Math.min(pos - 1, list.length - 1)]?.price;
}
if (targetPrice) {
let final = type === "percent" ? targetPrice * (1 - (val/100)) : targetPrice - val;
updateTornInput($row.find('input[placeholder*="Price"], .input-money, [aria-label="Price"]'), final);
updateTornInput($row.find('input[placeholder*="Amount"], input[name="amount"], [aria-label="Amount"]'), $row.find('[class*="amount"], .amount, .count').first().text().replace(/[^0-9]/g, ''));
GM_xmlhttpRequest({
method: "GET",
url: `https://weav3r.dev/api/marketplace/${itemId}`,
onload: res => {
try {
const bzData = JSON.parse(res.responseText);
const bzFloor = bzData.listings?.[0]?.price || 0;
if (bzFloor > final) $row.find('.item-toggle-btn').addClass('alert-red');
else $row.find('.item-toggle-btn').removeClass('alert-red');
} catch(e){}
}
});
}
} catch(e) {}
if ($btn) $btn.text('FILL');
}
});
}
function showDualTable(e, $row, itemId, itemName) {
let $popup = $('.draggable-popup');
if (!$popup.length) {
$popup = $(`<div class="draggable-popup">
<div class="popup-header"><span class="item-label"></span><span class="close-x" style="cursor:pointer; padding: 5px;">×</span></div>
<div class="mv-banner">Loading...</div>
<div id="test-line" style="text-align:center; color:#ffde00; font-size:11px; padding: 10px; border-bottom: 1px solid #333; font-weight: bold; background: #222;">Loading Stock...</div>
<div class="popup-body"></div>
<button class="fill-max-btn">Fill Max Quantity</button>
</div>`).appendTo('body');
$popup.find('.close-x').on('touchstart click', (ev) => { ev.preventDefault(); $popup.hide(); });
makeDraggable($popup[0]);
}
const rect = e.currentTarget.getBoundingClientRect();
$popup.show().css({ top: Math.max(10, rect.top - 100), left: Math.max(10, rect.left - 230) });
$popup.find('.item-label').text(itemName.substring(0, 22)).attr('data-id', itemId);
$popup.find('.popup-body').html(`<table class="market-table"><tr><th>Market</th><th>Bazaar</th></tr>` + Array(5).fill('<tr><td class="im-row">--</td><td class="bz-row">--</td></tr>').join('') + `</table>`);
$popup.find('.fill-max-btn').off().on('touchstart click', function(ev) {
ev.preventDefault();
const maxVal = $row.find('[class*="amount"], .amount, .count').first().text().replace(/[^0-9]/g, '');
updateTornInput($row.find('input[placeholder*="Amount"], input[name="amount"], [aria-label="Amount"]'), maxVal);
$popup.hide();
});
const handlePriceClick = (price) => {
let final = settings.undercutType === "percent" ? price * (1 - (settings.undercutVal/100)) : price - settings.undercutVal;
updateTornInput($row.find('input[placeholder*="Price"], .input-money, [aria-label="Price"]'), final);
updateTornInput($row.find('input[placeholder*="Amount"], input[name="amount"], [aria-label="Amount"]'), $row.find('[class*="amount"], .amount, .count').first().text().replace(/[^0-9]/g, ''));
$popup.hide();
};
GM_xmlhttpRequest({ method: "GET", url: `https://api.torn.com/v2/torn/${itemId}/items?key=${settings.apiKey}`, onload: r => { try { const mv = JSON.parse(r.responseText).items?.[0]?.value?.market_price || 0; $popup.find(".mv-banner").text(`MV: $${mv.toLocaleString()}`); } catch(e){} }});
GM_xmlhttpRequest({ method: "GET", url: `https://api.torn.com/v2/market/${itemId}/itemmarket?key=${settings.apiKey}`, onload: r => { try { const list = JSON.parse(r.responseText).itemmarket?.listings || []; list.slice(0, 5).forEach((item, i) => { $popup.find(`.im-row`).eq(i).html(`<span class="qty-label">${item.amount.toLocaleString()} x</span>$${item.price.toLocaleString()}`).off().on('touchstart click', (ev) => { ev.preventDefault(); handlePriceClick(item.price); }); }); } catch(e){} }});
GM_xmlhttpRequest({ method: "GET", url: `https://weav3r.dev/api/marketplace/${itemId}`, onload: r => { try { const list = JSON.parse(r.responseText).listings || []; list.slice(0, 5).forEach((item, i) => { $popup.find(`.bz-row`).eq(i).html(`<span class="qty-label">${item.quantity.toLocaleString()} x</span>$${item.price.toLocaleString()}`).off().on('touchstart click', (ev) => { ev.preventDefault(); handlePriceClick(item.price); }); }); } catch(e){} }});
}
function inject() {
const isManage = window.location.hash.includes('#/manage');
const isAdd = window.location.hash.includes('#/add');
if ($('input.torn-btn[value="CONFIRM"]').length > 0) {
$(".pc-filler-container, .stat-row-filler-overlay, .fill-wrapper-left, .add-wrapper, .filler-header-links, .pda-skip-container, .armor-inline-btns").addClass("filler-force-hide"); return;
} else {
$(".pc-filler-container, .stat-row-filler-overlay, .fill-wrapper-left, .add-wrapper, .filler-header-links, .pda-skip-container, .armor-inline-btns").removeClass("filler-force-hide");
}
if (isPDA && !isAdd) return;
const header = isManage ? $(".panelHeader___PHqEv:contains('Manage your Bazaar')") : $(".title-black:contains('Add items to your Bazaar')");
if (header.length && $(".fill-all-btn").length === 0) {
header.append(`<div class="filler-header-links"><a class="fill-qty-btn" id="f-qty">Fill Qty</a><a class="fill-all-btn" id="f-all">Fill All</a><a class="filler-nav-link" id="f-cfg">Settings</a></div>`);
$("#f-cfg").on('touchstart click', (e) => { e.preventDefault(); $("#filler-config-modal").show(); renderCatList(); });
$("#f-all, #f-qty").on('touchstart click', function(e) {
e.preventDefault();
const act = $(this).attr('id');
$("li:visible, [class*='listItem___'], [class*='row___']").each(function() {
const $r = $(this);
if ($r.find('input').length > 0 && !$r.find('.filler-skip-checkbox').is(':checked')) {
if (act === 'f-all') {
const id = $r.find('img[src*="/items/"]').attr('src')?.match(/\/(\d+)\//)?.[1];
if (id) handleFillSingleRow($r, id, null);
} else {
const max = $r.find('[class*="amount"], .amount, .count').first().text().replace(/[^0-9]/g, '');
updateTornInput($r.find('input[placeholder*="Amount"], input[name="amount"], [aria-label="Amount"]'), max);
}
}
});
});
}
$("li, [class*='listItem___'], [class*='row___']").each(function () {
const $row = $(this);
const itemId = $row.find('img[src*="/items/"]').attr('src')?.match(/\/(\d+)\//)?.[1];
if (trendData && itemId && trendData[itemId]) {
const h = trendData[itemId].history, latest = h[h.length-1], hi = Math.max(...h), lo = Math.min(...h);
const $b = $row.find('.row-fill-btn');
if ($b.length) {
$b.removeClass('trend-cold trend-hot');
if (latest <= lo) $b.addClass('trend-cold');
else if (latest >= hi) $b.addClass('trend-hot');
}
}
if ($row.find(".row-fill-btn").length > 0) return;
if (!itemId || $row.find('input').length === 0) return;
const isSkipped = settings.skippedItems[itemId] ? "checked" : "";
const cbHTML = `<input type="checkbox" class="filler-skip-checkbox" data-id="${itemId}" ${isSkipped}>`;
const isWeapon = $row.find('i[class*="damage"], i[class*="accuracy"]').length > 0;
const isArmorCategory = $('.armour-category-icon').closest('li').hasClass('ui-state-active');
if (isPDA && isAdd) {
$row.addClass("filler-relative");
$(`<div class="pda-skip-container">${cbHTML}</div>`).appendTo($row);
const $fW = $('<div class="fill-wrapper-left"><div class="row-fill-btn">FILL</div></div>').appendTo($row);
$fW.find('.row-fill-btn').on('touchstart click', (e) => { e.preventDefault(); handleFillSingleRow($row, itemId, $fW.find('.row-fill-btn')); });
$('<div class="add-wrapper"><div class="item-toggle-btn">$</div></div>').appendTo($row).on('touchstart click', (e) => { e.preventDefault(); e.stopPropagation(); showDualTable(e, $row, itemId, $row.find('[class*="name"]').first().text().trim()); });
} else if (!isPDA) {
if (isArmorCategory) {
const $nameTarget = $row.find('div.name-wrap.bold');
if ($nameTarget.length) {
const $cont = $('<div class="armor-inline-btns"></div>').appendTo($nameTarget);
$cont.append(cbHTML);
const $f = $('<div class="row-fill-btn">FILL</div>').appendTo($cont).on('click', (e) => { e.stopPropagation(); handleFillSingleRow($row, itemId, $f); });
$('<div class="item-toggle-btn">$</div>').appendTo($cont).on('click', (e) => { e.stopPropagation(); showDualTable(e, $row, itemId, $nameTarget.text().trim()); });
}
} else if (isWeapon) {
const $target = $row.find('.title-wrap');
if ($target.length) {
const $cont = $(`<div class="stat-row-filler-overlay">${cbHTML}</div>`).appendTo($target);
const $f = $('<div class="row-fill-btn">FILL</div>').appendTo($cont).on('click', (e) => { e.stopPropagation(); handleFillSingleRow($row, itemId, $f); });
$('<div class="item-toggle-btn">$</div>').appendTo($cont).on('click', (e) => { e.stopPropagation(); showDualTable(e, $row, itemId, $row.find('[class*="name"]').first().text().trim()); });
}
} else {
let $target = isAdd ? $row.find('.info-wrap') : $row.find('[class*="bonuses___pTH_L"]');
if ($target.length) {
$target.css('display', 'flex');
const $cont = $(`<div class="pc-filler-container">${cbHTML}</div>`).appendTo($target);
const $f = $('<div class="row-fill-btn">FILL</div>').appendTo($cont).on('click', (e) => { e.stopPropagation(); handleFillSingleRow($row, itemId, $f); });
$('<div class="item-toggle-btn">$</div>').appendTo($cont).on('click', (e) => { e.stopPropagation(); showDualTable(e, $row, itemId, $row.find('[class*="name"]').first().text().trim()); });
}
}
}
});
}
// --- CONFIG MODAL & EVENT LISTENERS ---
if ($("#filler-config-modal").length === 0) {
$(`<div id="filler-config-modal">
<span style="color:#ffde00;font-weight:bold;font-size:14px;display:block;margin-bottom:15px;">Settings</span>
<div class="cfg-field"><label>API Key</label><input type="text" id="cfg-api" value="${settings.apiKey}"></div>
<div class="cfg-field"><label>Undercut Value / Fixed Price</label><input type="number" id="cfg-val" value="${settings.undercutVal}"></div>
<div class="cfg-field"><label>Mode</label>
<select id="cfg-type">
<option value="fixed" ${settings.undercutType === 'fixed' ? 'selected' : ''}>Undercut ($)</option>
<option value="percent" ${settings.undercutType === 'percent' ? 'selected' : ''}>Undercut (%)</option>
<option value="absolute" ${settings.undercutType === 'absolute' ? 'selected' : ''}>Fixed Price (Force Value)</option>
</select>
</div>
<div class="cfg-field"><label>Undercut Position (1-5)</label><input type="number" id="cfg-pos" min="1" max="5" value="${settings.undercutPos}"></div>
<div class="cfg-field"><label>Auto-Fill Source</label>
<select id="cfg-source"><option value="bz" ${settings.priceSource === 'bz' ? 'selected' : ''}>Bazaar</option><option value="im" ${settings.priceSource === 'im' ? 'selected' : ''}>Item Market</option><option value="mv" ${settings.priceSource === 'mv' ? 'selected' : ''}>Market Value (MV)</option></select>
</div>
<div id="cfg-cat-section" style="border-top: 1px solid #444; margin-top: 15px; padding-top: 10px;">
<div id="toggle-cat-editor" style="color:#00aaff; font-weight:bold; font-size:11px; cursor:pointer; display:flex; justify-content:space-between; padding: 5px 0;">
<span>CATEGORY OVERRIDES</span> <span id="cat-status-icon">▼</span>
</div>
<div id="cat-editor-body" style="margin-top:10px; display:none;">
<select id="cfg-cat-select" style="width:100%; margin-bottom:8px; height:35px; color:#000; background:#fff;">
<option value="Plushies">Plushies</option><option value="Flowers">Flowers</option><option value="Medical Items">Medical Items</option>
<option value="Drugs">Drugs</option><option value="Energy Drinks">Energy Drinks</option><option value="Boosters">Boosters</option>
<option value="Candy">Candy</option><option value="Alcohol">Alcohol</option><option value="Melee">Melee</option>
<option value="Primary">Primary</option><option value="Secondary">Secondary</option><option value="Armour">Armour</option>
<option value="Enhancer">Enhancer</option><option value="Clothing">Clothing</option><option value="Electronic">Electronic</option>
<option value="Jewelry">Jewelry</option><option value="Car">Car</option><option value="Supply Pack">Supply Pack</option>
<option value="Special">Special</option><option value="Artifacts">Artifacts</option><option value="Books">Books</option>
<option value="Materials">Materials</option>
</select>
<div style="display:flex; gap:5px; margin-bottom:8px;">
<input type="number" id="cfg-cat-val" placeholder="Amount / Value" style="width:40%; height:35px; color:#000; background:#fff;">
<select id="cfg-cat-type" style="width:35%; height:35px; color:#000; background:#fff;">
<option value="fixed">$ Under</option><option value="percent">% Under</option><option value="absolute">Fixed</option>
</select>
<input type="number" id="cfg-cat-pos" placeholder="Position" value="" style="width:25%; height:35px; color:#000; background:#fff;">
</div>
<select id="cfg-cat-source" style="width:100%; height:35px; margin-bottom:10px; color:#000; background:#fff;">
<option value="im">Item Market</option><option value="bz">Bazaar</option><option value="mv">Market Value</option>
</select>
<button id="cfg-add-cat" style="width:100%; background:#00aaff; color:#fff; padding:10px; border:none; font-weight:bold; border-radius:4px;">ADD RULE</button>
<div id="cfg-cat-list" style="font-size:12px; color:#ccc; max-height:100px; overflow-y:auto; margin-top:10px;"></div>
</div>
</div>
<div class="sync-container">
<label style="font-size:10px; font-weight:bold; color:#aaa; text-transform:uppercase;">Device Sync</label>
<div class="sync-btns">
<button class="sync-btn" id="btn-export">Export</button>
<button class="sync-btn" id="btn-import">Import</button>
</div>
</div>
<div style="margin-top:20px;"><button class="btn-save" id="cfg-save">SAVE ALL</button><button class="btn-close" id="cfg-close">CLOSE</button></div>
</div>`).appendTo("body");
$("#cfg-save").on('touchstart click', (e) => { e.preventDefault(); GM_setValue("tornApiKey", $("#cfg-api").val()); GM_setValue("undercutVal", $("#cfg-val").val()); GM_setValue("undercutType", $("#cfg-type").val()); GM_setValue("undercutPos", $("#cfg-pos").val()); GM_setValue("priceSource", $("#cfg-source").val()); loadSettings(); $("#filler-config-modal").hide(); });
$("#cfg-close").on('touchstart click', (e) => { e.preventDefault(); $("#filler-config-modal").hide(); });
$('#toggle-cat-editor').on('click', () => { $('#cat-editor-body').slideToggle(150); $('#cat-status-icon').text($('#cat-status-icon').text() === '▼' ? '▲' : '▼'); });
$("#btn-export").on('click', performExport);
$("#btn-import").on('click', performImport);
}
$(document).on('click', '#cfg-add-cat', function(e) {
e.preventDefault();
const val = $('#cfg-cat-val').val(); if(!val) return;
const cat = $('#cfg-cat-select').val();
settings.categoryRules[cat] = { val, type: $('#cfg-cat-type').val(), pos: $('#cfg-cat-pos').val() || 1, source: $('#cfg-cat-source').val() };
GM_setValue("categoryRules", JSON.stringify(settings.categoryRules));
renderCatList();
$('#cfg-cat-val').val('');
$('#cfg-cat-pos').val('');
});
function renderCatList() {
let html = "";
const types = { 'fixed': '$ Under', 'percent': '% Under', 'absolute': 'Fixed' };
Object.keys(settings.categoryRules || {}).forEach(cat => {
let r = settings.categoryRules[cat];
let typeLabel = types[r.type] || r.type;
html += `<div style="display:flex; justify-content:space-between; border-bottom:1px solid #222; padding:4px 0;">
<span>[${r.source.toUpperCase()}] <b>${cat}</b>: ${r.val} ${typeLabel} (P${r.pos})</span>
<span class="del-cat" data-cat="${cat}" style="color:#ff3b3b; cursor:pointer;">[X]</span>
</div>`;
});
$('#cfg-cat-list').html(html || "No overrides");
}
$(document).on('click', '.del-cat', function() { delete settings.categoryRules[$(this).data('cat')]; GM_setValue("categoryRules", JSON.stringify(settings.categoryRules)); renderCatList(); });
$(document).on('change', '.filler-skip-checkbox', function() { settings.skippedItems[$(this).data('id')] = $(this).is(':checked'); GM_setValue("skippedItems", JSON.stringify(settings.skippedItems)); });
const obs = new MutationObserver(inject);
obs.observe(document.body, { childList: true, subtree: true });
inject();
// Trends Logic
(function() {
const trendObserver = new MutationObserver(() => {
const $popup = $('.draggable-popup');
const $mvLine = $popup.find('.mv-banner');
const itemId = $popup.find('.item-label').attr('data-id');
if ($popup.is(':visible') && $mvLine.length > 0 && $mvLine.find('.pro-trend').length === 0 && trendData && itemId) {
if (trendData[itemId]) {
const h = trendData[itemId].history, latest = h[h.length - 1], prev = h[h.length - 2] || latest, hi = Math.max(...h), lo = Math.min(...h);
const change = (((latest - prev) / prev) * 100).toFixed(2), col = change > 0 ? "#7cfc00" : (change < 0 ? "#ff3b3b" : "#aaa");
let stat = latest >= hi ? "🔥" : (latest <= lo ? "🧊" : "");
const pts = h.map((p, i) => `${(i / (h.length - 1 || 1)) * 40},${10 - ((p - lo) / (hi - lo || 1)) * 10}`).join(' ');
$mvLine.append(`<span class="pro-trend" style="margin-left:10px; display:inline-flex; align-items:center; border-left: 1px solid #444; padding-left: 10px;"><span style="color:${col}; font-weight:bold; font-size:12px; margin-right:5px;">${stat} ${change}%</span><svg width="40" height="12"><polyline fill="none" stroke="${col}" stroke-width="1.5" points="${pts}"/></svg></span>`);
}
}
});
trendObserver.observe(document.body, { childList: true, subtree: true });
})();
// Stock Logic - Optimized to prevent flashing
(function() {
function updateStock() {
const $p = $('.draggable-popup'), $h = $p.find('.popup-header');
if ($p.is(':visible') && $h.length > 0 && !isFetching) {
const itemId = $p.find('.item-label').attr('data-id');
if (myBazaarData && itemId) {
let itm = myBazaarData.find(i => String(i.ID || i.item_id) === String(itemId));
if (itm) {
const testTxt = `Qty: ${itm.quantity.toLocaleString()} @ $${itm.price.toLocaleString()}`;
if ($p.find('#test-line').text() !== testTxt) {
$p.find('#test-line').text(testTxt).css('opacity', '1');
}
} else {
$p.find('#test-line').text("Qty: 0 @ $0").css('opacity', '1');
}
}
}
}
function fetchBZ(force = false) {
if (!settings.apiKey || isFetching) return;
if (force || (Date.now() - lastFetch > 15000)) {
isFetching = true;
$('.draggable-popup #test-line').css('opacity', '0.5');
GM_xmlhttpRequest({
method: "GET",
url: `https://api.torn.com/user/?selections=bazaar&key=${settings.apiKey}`,
onload: (res) => {
try {
const json = JSON.parse(res.responseText);
if (json.bazaar) {
myBazaarData = Array.isArray(json.bazaar) ? json.bazaar : Object.values(json.bazaar);
lastFetch = Date.now();
updateStock();
}
} catch(e){ console.error("Bazaar Sync Error", e); }
isFetching = false;
$('.draggable-popup #test-line').css('opacity', '1');
},
onerror: () => {
isFetching = false;
$('.draggable-popup #test-line').css('opacity', '1');
}
});
}
}
setInterval(() => { if ($('.draggable-popup').is(':visible')) updateStock(); }, 500);
$(document).on('click touchstart', '.item-toggle-btn', function() { fetchBZ(); });
fetchBZ();
})();
// Trade Fill Logic
function injectTradeFill() {
if (!window.location.href.includes('trade.php')) return;
const topFooter = $(".items-footer.clearfix").first();
if (topFooter.length && !$("#trade-fill-qty-single").length) {
const $btn = $('<a id="trade-fill-qty-single" style="cursor: pointer; margin-left: 15px; font-size: 12px; color: #00aaff; text-transform: uppercase; font-weight: bold; line-height: 24px;">Fill Qty</a>');
topFooter.append($btn);
$btn.on('click', function(e) {
e.preventDefault();
$("li.clearfix.no-mods").each(function() {
const qtyMatch = $(this).find(".name-wrap").text().match(/x(\d+)/);
updateTornInput($(this).find("input[type='text']"), qtyMatch ? qtyMatch[1] : "1");
});
});
}
}
const tradeObs = new MutationObserver(injectTradeFill);
tradeObs.observe(document.body, { childList: true, subtree: true });
injectTradeFill();
// --- POPUP CONTROLS (DRAG & AUTO-CLOSE) ---
function makeDraggable(el) {
let p1 = 0, p2 = 0, p3 = 0, p4 = 0;
const header = el.querySelector('.popup-header') || el;
const dragStart = (e) => {
const touch = e.type === 'touchstart' ? e.touches[0] : e;
p3 = touch.clientX; p4 = touch.clientY;
if (e.type === 'mousedown') {
document.addEventListener('mousemove', dragging); document.addEventListener('mouseup', dragEnd);
} else {
document.addEventListener('touchmove', dragging, { passive: false }); document.addEventListener('touchend', dragEnd);
}
};
const dragging = (e) => {
if (e.type === 'touchmove') e.preventDefault();
const touch = e.type === 'touchmove' ? e.touches[0] : e;
p1 = p3 - touch.clientX; p2 = p4 - touch.clientY;
p3 = touch.clientX; p4 = touch.clientY;
el.style.top = (el.offsetTop - p2) + "px"; el.style.left = (el.offsetLeft - p1) + "px";
};
const dragEnd = () => {
document.removeEventListener('mousemove', dragging); document.removeEventListener('mouseup', dragEnd);
document.removeEventListener('touchmove', dragging); document.removeEventListener('touchend', dragEnd);
};
header.addEventListener('mousedown', dragStart); header.addEventListener('touchstart', dragStart);
}
$(document).on('mousedown touchstart', function (e) {
const $popup = $('.draggable-popup');
if ($popup.is(':visible')) {
if (!$popup.is(e.target) && $popup.has(e.target).length === 0 && !$(e.target).closest('.item-toggle-btn').length) {
$popup.hide();
}
}
});
})();