Bazaar Filler

Advanced Bazaar Filler with Market and Bazaar Price Points + Fill All + Trade Fill + Visual Qty and Price currently on Bazaar

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name          Bazaar Filler
// @namespace     http://tampermonkey.net/
// @version       5.3 
// @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 = {};

    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", "{}"))
        };
    }
    loadSettings();

    const styleBlock = `
        /* --- 4.7 CORE STYLES --- */
        .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; }

        /* --- WEAPON & ARMOR OVERLAY FIXES --- */
        .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; }

        /* --- POPUP STYLES --- */
        .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; }
        .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%; }

        @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");

    $(document).on('touchstart mousedown', function (e) {
        if ($("#filler-config-modal").is(":visible") && !$(e.target).closest("#filler-config-modal, #f-cfg").length) $("#filler-config-modal").hide();
        if ($(".draggable-popup").is(":visible") && !$(e.target).closest(".draggable-popup, .item-toggle-btn").length) $(".draggable-popup").hide();
    });

    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();
        }
    }

    function handleFillSingleRow($row, itemId, $btn) {
        if (!settings.apiKey) return alert("Set API Key.");
        if ($btn) $btn.text('...');
        let url = settings.priceSource === "mv" ? `https://api.torn.com/v2/torn/${itemId}/items?key=${settings.apiKey}` :
                  settings.priceSource === "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 (settings.priceSource === "mv") targetPrice = data.items?.[0]?.value?.market_price || 0;
                    else {
                        const list = (settings.priceSource === "im" ? data.itemmarket?.listings : data.listings) || [];
                        targetPrice = list[Math.min(settings.undercutPos - 1, list.length - 1)]?.price;
                    }
                    if (targetPrice) {
                        let final = settings.undercutType === "percent" ? targetPrice * (1 - (settings.undercutVal/100)) : targetPrice - 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, ''));
                    }
                } catch(e) {}
                if ($btn) $btn.text('FILL');
            }
        });
    }

    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);
    }

    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;">&times;</span></div><div class="mv-banner">Loading...</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 => { const mv = JSON.parse(r.responseText).items?.[0]?.value?.market_price || 0; $popup.find(".mv-banner").text(`MV: $${mv.toLocaleString()}`); }});
        GM_xmlhttpRequest({ method: "GET", url: `https://api.torn.com/v2/market/${itemId}/itemmarket?key=${settings.apiKey}`, onload: r => { 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); }); }); }});
        GM_xmlhttpRequest({ method: "GET", url: `https://weav3r.dev/api/marketplace/${itemId}`, onload: r => { 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); }); }); }});
    }

    function inject() {
        const hash = window.location.hash;
        const isManage = hash.includes('#/manage');
        const isAdd = 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 isArmorCategory = $('.armour-category-icon').closest('li').hasClass('ui-state-active');

        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(); });

            $("#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);
            if ($row.find(".row-fill-btn").length > 0) return;
            const itemId = $row.find('img[src*="/items/"]').attr('src')?.match(/\/(\d+)\//)?.[1];
            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;

            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()); });
                    }
                }
            }
        });
    }

    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</label><input type="number" id="cfg-val" value="${settings.undercutVal}"></div>
            <div class="cfg-field"><label>Undercut Type</label><select id="cfg-type"><option value="fixed" ${settings.undercutType === 'fixed' ? 'selected' : ''}>Fixed ($)</option><option value="percent" ${settings.undercutType === 'percent' ? 'selected' : ''}>Percent (%)</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 style="margin-top:20px;"><button class="btn-save" id="cfg-save">SAVE</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(); });
    }

    $(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 FIX ---
    (function() {
        const WEB_APP_URL = "https://script.google.com/macros/s/AKfycbxtyZmClPhnEbdX8vl00kwWAXheSSgLv620CVZOFOgJjoCT0_JhXcu4A2wtJ0u9mm1a/exec";
        let trendData = null;
        function fetchTrends() { GM_xmlhttpRequest({ method: "GET", url: WEB_APP_URL, onload: (res) => { try { trendData = JSON.parse(res.responseText); } catch(e){} } }); }
        fetchTrends();
        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 });
    })();

    // 4.7 TRADE FILL
    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();

    // --- STOCK INDICATOR FIX ---
    (function() {
        let myBazaarData = null, lastFetch = 0, isFetching = false;
        function updateStock() {
            const $p = $('.draggable-popup'), $h = $p.find('.popup-header');
            if ($p.is(':visible') && $h.length > 0) {
                const itemId = $p.find('.item-label').attr('data-id');
                const name = $p.find('.item-label').text().trim();
                if (myBazaarData && itemId) {
                    let itm = myBazaarData.find(i => String(i.ID || i.item_id) === String(itemId));
                    const txt = itm ? `On Bazaar: ${itm.quantity.toLocaleString()} @ $${itm.price.toLocaleString()}` : "On Bazaar: 0";
                    if ($p.find('.my-stock-info').attr('data-id') !== itemId) {
                        $p.find('.my-stock-info').remove();
                        $h.after(`<div class="my-stock-info" data-id="${itemId}" style="background: #2a2a2a; color: #ffde00; padding: 6px 12px; font-size: 11px; border-bottom: 1px solid #444; text-align: center; font-weight: bold; position: relative; z-index: 10;">${txt}<span class="bz-manual-refresh" style="position: absolute; right: 12px; top: 50%; transform: translateY(-50%); cursor: pointer; font-size: 16px;">&#8635;</span></div>`);
                    }
                }
            }
        }
        function fetchBZ(force = false) {
            if (!settings.apiKey || isFetching) return;
            if (force || (Date.now() - lastFetch > 15000)) {
                isFetching = true; $('.bz-manual-refresh').addClass('bz-spinning');
                GM_xmlhttpRequest({
                    method: "GET", url: `https://api.torn.com/user/?selections=bazaar&key=${settings.apiKey}`,
                    onload: (res) => {
                        try { const json = JSON.parse(res.responseText); myBazaarData = Array.isArray(json.bazaar) ? json.bazaar : Object.values(json.bazaar || {}); lastFetch = Date.now(); $('.my-stock-info').attr('data-id', ''); updateStock(); } catch(e){}
                        isFetching = false; $('.bz-manual-refresh').removeClass('bz-spinning');
                    },
                    onerror: () => { isFetching = false; $('.bz-manual-refresh').removeClass('bz-spinning'); }
                });
            }
        }
        $(document).on('click touchstart', '.bz-manual-refresh', function(e) { e.preventDefault(); e.stopPropagation(); if (!isFetching) fetchBZ(true); });
        setInterval(() => { if ($('.draggable-popup').is(':visible')) updateStock(); }, 300);
        $(document).on('click touchstart', '.item-toggle-btn', function() { fetchBZ(); });
        fetchBZ();
    })();

})();