Torn Bazaar Quick Pricer

Auto-fill bazaar items with market-based pricing (PDA optimized)

スクリプトをインストールするには、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         Torn Bazaar Quick Pricer
// @namespace    http://tampermonkey.net/
// @version      2.8.6
// @description  Auto-fill bazaar items with market-based pricing (PDA optimized)
// @author       Zedtrooper [3028329]
// @license      MIT
// @match        https://www.torn.com/bazaar.php*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @connect      api.torn.com
// @run-at       document-end
// @homepage     https://github.com/Musa-dabwe/Torn-Bazaar-Quick-Pricer
// @supportURL   https://github.com/Musa-dabwe/Torn-Bazaar-Quick-Pricer/issues
// ==/UserScript==

(function() {
    'use strict';

    if (typeof GM_getValue === 'undefined') {
        console.error('[BazaarQuickPricer] GM_getValue not available! Please check Tampermonkey settings.');
        return;
    }

    console.log('[BazaarQuickPricer] v2.8.6 Starting (PDA optimized)...');

    // Configuration with Lazy Initialization
    const CONFIG = {
        get defaultDiscount() { return GM_getValue('discountPercent', 0); },
        set defaultDiscount(val) { GM_setValue('discountPercent', val); },
        get apiKey() { return GM_getValue('tornApiKey', ''); },
        set apiKey(val) { GM_setValue('tornApiKey', val); },
        get lastPriceUpdate() { return GM_getValue('lastPriceUpdate', 0); },
        set lastPriceUpdate(val) { GM_setValue('lastPriceUpdate', val); },
        get priceCache() { return GM_getValue('priceCache', {}); },
        set priceCache(val) { GM_setValue('priceCache', val); },
        get disableNpcCheck() { return GM_getValue('disableNpcCheck', false); },
        set disableNpcCheck(val) { GM_setValue('disableNpcCheck', val); },
        cacheTimeout: 5 * 60 * 1000
    };

    const processedItems = new WeakSet();
    const processedManageItems = new WeakSet();
    let mutationDebounceTimer = null;

    // Inject Theme-Consistent CSS (Removed CSS Variable dependencies for Chrome)
    const style = document.createElement('style');
    style.textContent = `
        .qp-btn {
            background: #5F5F5F !important;
            color: white !important;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            box-shadow: 0 2px 4px rgba(0,0,0,0.2);
            transition: filter 0.2s;
            padding: 5px;
            font-size: 13px;
            font-weight: 700;
        }
        .qp-btn:hover {
            filter: brightness(0.8);
        }
        .qp-btn:disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }
        .qp-btn-red {
            background: #E3392C !important;
            color: white !important;
        }
        .qp-btn-top {
            padding: 5px 11px;
            margin-left: 5px;
        }
        .qp-btn-update {
            padding: 5px;
        }
        .qp-btn-settings {
            border-radius: 0 4px 4px 0;
        }
        .qp-btn-fill {
            border-radius: 4px 0 0 4px;
            border-right: 1px solid rgba(0,0,0,0.1);
        }
        .quick-price-btn, .quick-update-price-btn {
            display: flex;
            align-items: center;
            flex-shrink: 0;
            margin-left: auto;
            padding-right: 5px;
            z-index: 10;
        }
    `;
    document.head.appendChild(style);
    const isMobile = window.innerWidth <= 784;
    let buttonsAdded = false;
    let manageButtonsAdded = false;


    // saveConfig is now redundant due to CONFIG setters but kept as no-op for compatibility
    function saveConfig() {
        // Redundant
    }

    // Custom SVGs
    const addButtonSVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M3,7.5v11c0,1.38,1.12,2.5,2.5,2.5h1c.83,0,1.5,.67,1.5,1.5s-.67,1.5-1.5,1.5h-1c-3.03,0-5.5-2.47-5.5-5.5V7.5C0,4.47,2.47,2,5.5,2h.35c.56-1.18,1.76-2,3.15-2h2c1.39,0,2.59,.82,3.15,2h.35c1.96,0,3.78,1.05,4.76,2.75,.42,.72,.17,1.63-.55,2.05-.24,.14-.49,.2-.75,.2-.52,0-1.02-.27-1.3-.75-.45-.77-1.28-1.25-2.17-1.25h-.35c-.56,1.18-1.76,2-3.15,2h-2c-1.39,0-2.59-.82-3.15-2h-.35c-1.38,0-2.5,1.12-2.5,2.5Zm14.5,6.5h-1c-.83,0-1.5,.67-1.5,1.5s.67,1.5,1.5,1.5h1c.83,0,1.5-.67,1.5-1.5s-.67-1.5-1.5-1.5Zm6.5-.5v6c0,2.48-2.02,4.5-4.5,4.5h-5c-2.48,0-4.5-2.02-4.5-4.5v-6c0-2.48,2.02-4.5,4.5-4.5h5c2.48,0,4.5,2.02,4.5,4.5Zm-3,0c0-.83-.67-1.5-1.5-1.5h-5c-.83,0-1.5,.67-1.5,1.5v6c0,.83,.67,1.5,1.5,1.5h5c.83,0,1.5-.67,1.5-1.5v-6Z"/></svg>`;
    const refreshSVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M12,2C6.48,2,2,6.48,2,12s4.48,10,10,10,10-4.48,10-10S17.52,2,12,2Zm0,18c-4.41,0-8-3.59-8-8s3.59-8,8-8,8,3.59,8,8-3.59,8-8,8Zm-1-13h2v6h-2v-6Zm0,8h2v2h-2v-2Z"/><path d="M13,7v6h4l-5,5-5-5h4V7h2Z" transform="translate(0,-1)"/></svg>`;
    const infoSVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M12,2A10,10,0,1,0,22,12,10,10,0,0,0,12,2Zm1,15a1,1,0,0,1-2,0V11a1,1,0,0,1,2,0ZM12,8a1.5,1.5,0,1,1,1.5-1.5A1.5,1.5,0,0,1,12,8Z"/></svg>`;

    function showApiKeyPrompt() {
        const overlay = document.createElement('div');
        overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.8);z-index:99999;display:flex;align-items:center;justify-content:center;padding:20px;box-sizing:border-box;';
        overlay.innerHTML = `
            <div style="background:#2a2a2a;padding:25px;border-radius:8px;max-width:400px;width:100%;color:#fff;">
                <h2 style="margin:0 0 15px 0;color:#fff;font-size:18px;">Quick Pricer Setup</h2>
                <p style="margin:0 0 15px 0;line-height:1.5;font-size:14px;">Enter your <strong>Public API Key</strong>:</p>
                <input type="text" id="apiKeyInput" placeholder="API Key" style="width:100%;padding:10px;margin:10px 0;border:1px solid #555;border-radius:5px;box-sizing:border-box;background:#1a1a1a;color:#fff;font-size:14px;">
                <div style="display:flex;gap:10px;margin-top:15px;">
                    <button id="saveApiKey" style="flex:1;padding:10px;background:#4CAF50;color:white;border:none;border-radius:5px;cursor:pointer;font-size:14px;">Save</button>
                    <button id="cancelApiKey" style="flex:1;padding:10px;background:#f44336;color:white;border:none;border-radius:5px;cursor:pointer;font-size:14px;">Cancel</button>
                </div>
            </div>
        `;
        document.body.appendChild(overlay);

        document.getElementById('saveApiKey').onclick = () => {
            const key = document.getElementById('apiKeyInput').value.trim();
            if (key && key.length === 16) {
                CONFIG.apiKey = key;
                overlay.remove();
                location.reload();
            } else {
                alert('Please enter a valid 16-character API key');
            }
        };
        document.getElementById('cancelApiKey').onclick = () => overlay.remove();
    }

    function showSettingsPanel() {
        const overlay = document.createElement('div');
        overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.8);z-index:99999;display:flex;align-items:center;justify-content:center;padding:20px;box-sizing:border-box;';
        overlay.innerHTML = `
            <div style="background:#2a2a2a;padding:25px;border-radius:8px;max-width:400px;width:100%;color:#fff;">
                <h2 style="margin:0 0 15px 0;font-size:18px;">Quick Pricer Settings</h2>
                
                <div style="margin:15px 0;">
                    <label style="display:block;margin-bottom:5px;font-weight:bold;font-size:14px;">Discount %:</label>
                    <input type="number" id="discountInput" value="${CONFIG.defaultDiscount}" min="-50" max="50" step="0.5" style="width:100%;padding:10px;border:1px solid #555;border-radius:5px;background:#1a1a1a;color:#fff;font-size:14px;">
                    <small style="color:#999;font-size:11px;display:block;margin-top:5px;">Use negative values to price above market (e.g., -5 for +5%)</small>
                </div>

                <div style="margin:15px 0;">
                    <label style="display:block;margin-bottom:5px;font-weight:bold;font-size:14px;">API Key:</label>
                    <input type="text" id="apiKeyUpdateInput" value="${CONFIG.apiKey}" style="width:100%;padding:10px;border:1px solid #555;border-radius:5px;background:#1a1a1a;color:#fff;font-size:14px;">
                </div>

                <div style="margin:15px 0; display:flex; align-items:center; padding: 10px; background: #222; border-radius: 5px;">
                    <input type="checkbox" id="npcOverrideCheck" ${CONFIG.disableNpcCheck ? 'checked' : ''} style="width:18px; height:18px; margin-right:10px; cursor:pointer;">
                    <label for="npcOverrideCheck" style="font-size:13px; color:#fff; cursor:pointer; flex:1;">Disable NPC Safety Limit</label>
                    <div id="npcInfoIcon" style="color:#2196F3; cursor:pointer; display:flex; align-items:center; padding: 5px;">
                        ${infoSVG}
                    </div>
                </div>

                <button id="clearCache" style="width:100%;padding:10px;background:#ff9800;color:white;border:none;border-radius:5px;cursor:pointer;font-size:14px;margin:10px 0;">Clear Cache</button>
                <div style="display:flex;gap:10px;margin-top:15px;">
                    <button id="saveSettings" style="flex:1;padding:10px;background:#4CAF50;color:white;border:none;border-radius:5px;cursor:pointer;font-size:14px;">Save</button>
                    <button id="cancelSettings" style="flex:1;padding:10px;background:#999;color:white;border:none;border-radius:5px;cursor:pointer;font-size:14px;">Cancel</button>
                </div>
                <div style="margin-top:15px;padding-top:15px;border-top:1px solid #555;text-align:center;">
                    <small style="color:#999;font-size:12px;">
                        v2.8.6 | <a href="https://github.com/Musa-dabwe/Torn-Bazaar-Quick-Pricer" target="_blank" style="color:#2196F3;">GitHub</a>
                    </small>
                </div>
            </div>
        `;
        document.body.appendChild(overlay);

        document.getElementById('npcInfoIcon').onclick = (e) => {
            e.preventDefault();
            e.stopPropagation();
            alert("NPC Safety Limit Explanation:\n\nChecked: Items can be priced BELOW their NPC sell value based on your discount.\n\nUnchecked (Default): The script safeguards you by ensuring you never list an item for less than you could sell it to a game shop.");
        };

        document.getElementById('clearCache').onclick = () => {
            CONFIG.priceCache = {};
            CONFIG.lastPriceUpdate = 0;
            alert('Cache cleared!');
        };
        document.getElementById('saveSettings').onclick = () => {
            CONFIG.defaultDiscount = parseFloat(document.getElementById('discountInput').value);
            CONFIG.apiKey = document.getElementById('apiKeyUpdateInput').value.trim();
            CONFIG.disableNpcCheck = document.getElementById('npcOverrideCheck').checked;
            overlay.remove();
            alert('Settings saved!');
        };
        document.getElementById('cancelSettings').onclick = () => overlay.remove();
    }

    const itemIdCache = new Map();
    function getItemIdFromImage(image) {
        const src = image.src;
        if (itemIdCache.has(src)) return itemIdCache.get(src);
        const match = src.match(/\/(\d+)\//);
        if (match) {
            const itemId = parseInt(match[1], 10);
            itemIdCache.set(src, itemId);
            return itemId;
        }
        return null;
    }

    function getQuantity(itemElement) {
        const titleWrap = itemElement.querySelector('div[class*="name___"], div.title-wrap');
        if (!titleWrap) return 1;
        const match = titleWrap.textContent.match(/x(\d+)/i);
        return match ? parseInt(match[1], 10) : 1;
    }

    const requestQueue = [];
    let isProcessingQueue = false;

    function processRequestQueue() {
        if (isProcessingQueue || requestQueue.length === 0) return;
        isProcessingQueue = true;
        const { itemId, callback } = requestQueue.shift();

        GM_xmlhttpRequest({
            method: 'GET',
            url: `https://api.torn.com/torn/${itemId}?selections=items&key=${CONFIG.apiKey}`,
            onload: function(response) {
                try {
                    const data = JSON.parse(response.responseText);
                    if (data.error) {
                        if (data.error.code === 2) {
                            alert('Incorrect API Key!');
                            CONFIG.apiKey = null;
                            saveConfig();
                        }
                        callback({ marketValue: 0, sellPrice: 0 });
                    } else if (data.items?.[itemId]) {
                        const itemData = data.items[itemId];
                        const marketValue = itemData.market_value || 0;
                        const sellPrice = itemData.sell_price || 0;

                        const cache = CONFIG.priceCache;
                        cache[itemId] = {
                            marketValue: marketValue,
                            sellPrice: sellPrice,
                            timestamp: Date.now()
                        };
                        CONFIG.priceCache = cache;
                        CONFIG.lastPriceUpdate = Date.now();

                        callback({ marketValue, sellPrice });
                    } else {
                        callback({ marketValue: 0, sellPrice: 0 });
                    }
                } catch (e) {
                    console.error('[BazaarQuickPricer] Parse error:', e);
                    callback({ marketValue: 0, sellPrice: 0 });
                }
                isProcessingQueue = false;
                setTimeout(processRequestQueue, 300);
            },
            onerror: function() {
                callback({ marketValue: 0, sellPrice: 0 });
                isProcessingQueue = false;
                setTimeout(processRequestQueue, 300);
            }
        });
    }

    function fetchItemData(itemId, callback) {
        const now = Date.now();
        const cached = CONFIG.priceCache[itemId];

        if (cached && cached.timestamp && (now - cached.timestamp < CONFIG.cacheTimeout)) {
            callback({
                marketValue: cached.marketValue,
                sellPrice: cached.sellPrice
            });
            return;
        }

        requestQueue.push({ itemId, callback });
        processRequestQueue();
    }

    function calculateFinalPrice(marketValue, sellPrice, discount) {
        let finalPrice = Math.round(marketValue * (1 - discount / 100));
        
        // Safety Check
        if (!CONFIG.disableNpcCheck && sellPrice > 0 && finalPrice < sellPrice) {
            console.log(`[BazaarQuickPricer] Price ${finalPrice} below NPC sell price ${sellPrice}, adjusting...`);
            finalPrice = sellPrice;
        }

        return finalPrice;
    }

    // Helper to clear inputs
    function clearItemInputs(itemElement) {
        const amountDiv = itemElement.querySelector('div[class*="amount___"], div.amount-main-wrap');
        const priceDiv = itemElement.querySelector('div[class*="price___"], div.price');

        // 1. Clear Price Inputs
        if (priceDiv) {
            const priceInputs = priceDiv.querySelectorAll('input');
            priceInputs.forEach(input => {
                input.value = '';
                input.dispatchEvent(new Event('input', { bubbles: true }));
            });
        }

        // 2. Clear Quantity or Uncheck
        if (amountDiv) {
            const isQuantityCheckbox = amountDiv.querySelector('div.choice-container, [class*="choiceContainer___"]');
            if (isQuantityCheckbox) {
                const checkbox = isQuantityCheckbox.querySelector('input');
                if (checkbox && checkbox.checked) checkbox.click();
            } else {
                const quantityInput = amountDiv.querySelector('input');
                if (quantityInput) {
                    quantityInput.value = '';
                    quantityInput.dispatchEvent(new Event('input', { bubbles: true }));
                }
            }
        }
    }

    function fillItemPrice(itemElement) {
        const image = itemElement.querySelector('img');
        if (!image) return Promise.resolve();

        const itemId = getItemIdFromImage(image);
        if (!itemId) return Promise.resolve();

        const amountDiv = itemElement.querySelector('div[class*="amount___"], div.amount-main-wrap');
        const priceDiv = itemElement.querySelector('div[class*="price___"], div.price');

        if (!priceDiv) return Promise.resolve();
        const priceInputs = priceDiv.querySelectorAll('input');
        if (priceInputs.length === 0) return Promise.resolve();

        return new Promise((resolve) => {
            fetchItemData(itemId, ({ marketValue, sellPrice }) => {
                if (marketValue > 0) {
                    const finalPrice = calculateFinalPrice(marketValue, sellPrice, CONFIG.defaultDiscount);

                    priceInputs.forEach(input => {
                        input.value = finalPrice;
                        input.dispatchEvent(new Event('input', { bubbles: true }));
                    });

                    if (amountDiv) {
                        const isQuantityCheckbox = amountDiv.querySelector('div.choice-container, [class*="choiceContainer___"]');
                        if (isQuantityCheckbox) {
                            const checkbox = isQuantityCheckbox.querySelector('input');
                            if (checkbox && !checkbox.checked) checkbox.click();
                        } else {
                            const quantityInput = amountDiv.querySelector('input');
                            if (quantityInput) {
                                quantityInput.value = getQuantity(itemElement);
                                quantityInput.dispatchEvent(new Event('input', { bubbles: true }));
                                quantityInput.dispatchEvent(new Event('keyup', { bubbles: true }));
                            }
                        }
                    }

                    // UPDATE BUTTON VISUALS FOR UNDO
                    const btn = itemElement.querySelector('.quick-price-btn button');
                    if (btn) {
                        btn.classList.add('qp-btn-red');
                        btn.dataset.mode = 'undo';
                    }
                }
                resolve();
            });
        });
    }

    function getActiveTab() {
        const tabs = document.querySelectorAll('ul.items-tabs li, div[class*="item___UN3Mg"]');
        for (const tab of tabs) {
            if (tab.classList.contains('active') || tab.className.includes('active___')) {
                return tab.getAttribute('data-category') || tab.textContent.trim().toLowerCase() || 'all';
            }
        }
        return 'all';
    }

    function getVisibleItems() {
        const activeTab = getActiveTab();
        const allItemsLists = document.querySelectorAll('ul.items-cont, div[class*="itemsContainner___"], div[class*="rowItems___"]');

        let visibleItems = [];
        for (const list of allItemsLists) {
            const style = window.getComputedStyle(list);
            if (style.display !== 'none') {
                const items = list.querySelectorAll('li.clearfix:not(.disabled), div[class*="item___GYCYJ"], div[class*="item___khvF6"]');
                visibleItems = visibleItems.concat(Array.from(items).filter(item => !item.className.includes('item___UN3Mg')));
            }
        }

        return visibleItems;
    }

    // ===== MANAGE ITEMS PAGE FUNCTIONS =====

    function updateManageItemPrice(priceDiv, itemId) {
        const priceInput = priceDiv.querySelector('input.input-money, input');
        if (!priceInput) return;

        const currentPrice = parseInt(priceInput.value.replace(/,/g, '')) || 0;

        priceInput.disabled = true;
        priceInput.style.opacity = '0.5';
        fetchItemData(itemId, ({ marketValue, sellPrice }) => {
            priceInput.disabled = false;
            priceInput.style.opacity = '1';

            if (marketValue > 0) {
                const newPrice = calculateFinalPrice(marketValue, sellPrice, CONFIG.defaultDiscount);

                // Warn if price change is significant (>20% difference)
                const priceDiff = Math.abs(newPrice - currentPrice);
                const percentDiff = currentPrice > 0 ? (priceDiff / currentPrice) * 100 : 100;

                if (percentDiff > 20 && currentPrice > 0) {
                    const direction = newPrice > currentPrice ? 'increase' : 'decrease';
                    const confirmed = confirm(
                        `Price ${direction} detected!\n\n` +
                        `Current: $${currentPrice.toLocaleString()}\n` +
                        `New: $${newPrice.toLocaleString()}\n` +
                        `Difference: ${percentDiff.toFixed(1)}%\n\n` +
                        `Update to new price?`
                    );
                    if (!confirmed) return;
                }

                // Update the price
                priceInput.value = newPrice;
                priceInput.dispatchEvent(new Event('input', { bubbles: true }));
                priceInput.dispatchEvent(new Event('change', { bubbles: true }));
                // Visual feedback
                const borderColor = (sellPrice > 0 && newPrice === sellPrice) ? '#ff9800' : '#5F5F5F';
                priceInput.style.border = `2px solid ${borderColor}`;
                setTimeout(() => priceInput.style.border = '', 1000);
            } else {
                alert('Could not fetch price for this item');
            }
        });
    }

    function addUpdatePriceButton(manageItem) {
        if (processedManageItems.has(manageItem)) return;
        if (manageItem.className.includes('item___UN3Mg')) return;

        const priceDiv = manageItem.querySelector('div[class*="price"]');
        if (!priceDiv) return;

        // Check if button already exists
        if (priceDiv.querySelector('.quick-update-price-btn')) {
            processedManageItems.add(manageItem);
            return;
        }

        processedManageItems.add(manageItem);
        // Find item image to get item ID
        const image = manageItem.querySelector('img');
        if (!image) return;

        const itemId = getItemIdFromImage(image);
        if (!itemId) return;
        // Create update button
        const btnContainer = document.createElement('div');
        btnContainer.className = 'quick-update-price-btn';

        const btnInput = document.createElement('button');
        btnInput.innerHTML = refreshSVG;
        btnInput.className = 'qp-btn qp-btn-update';
        btnInput.setAttribute('title', 'Update Price');
        btnContainer.appendChild(btnInput);

        // Position relative to priceDiv for flex centering
        priceDiv.style.display = 'flex';
        priceDiv.style.alignItems = 'center';
        priceDiv.appendChild(btnContainer);

        btnInput.addEventListener('click', function(event) {
            event.stopPropagation();
            event.preventDefault();
            updateManageItemPrice(priceDiv, itemId);
        });
    }

    function getManageItems() {
        // Look for manage items list
        const manageItemsList = document.querySelectorAll('div[class*="item___"]');
        return Array.from(manageItemsList).filter(item => !item.className.includes('item___UN3Mg'));
    }

    async function updateAllManagePrices() {
        const items = getManageItems();
        console.log('[BazaarQuickPricer] Updating', items.length, 'manage items...');

        if (items.length === 0) {
            alert('No items found to update!');
            return;
        }

        const updateButton = document.getElementById('quickUpdateAllPricesBtn');
        if (updateButton) {
            updateButton.disabled = true;
            updateButton.style.opacity = '0.5';
            updateButton.textContent = 'Updating...';
        }

        let updated = 0;
        for (const item of items) {
            const priceDiv = item.querySelector('div[class*="price"]');
            const image = item.querySelector('img');

            if (priceDiv && image) {
                const itemId = getItemIdFromImage(image);
                if (itemId) {
                    await new Promise((resolve) => {
                        updateManageItemPrice(priceDiv, itemId);
                        setTimeout(resolve, 350);
                    });
                    updated++;
                }
            }
        }

        if (updateButton) {
            updateButton.disabled = false;
            updateButton.style.opacity = '1';
            updateButton.textContent = 'Update All';
        }

        alert(`Updated ${updated} item prices!`);
        console.log('[BazaarQuickPricer] Update complete!');
    }

    function addManagePageButtons() {
        if (manageButtonsAdded) return;
        let attempts = 0;
        const maxAttempts = 20;

        const tryAddButtons = setInterval(() => {
            attempts++;

            // Find header with broader selector support for current Torn UI
            const headings = Array.from(document.querySelectorAll('div[role="heading"], div[class*="title"], div[class*="panelHeader"], div[class*="titleContainer"]'));
            // Look for "Manage your Bazaar" or "Manage items"
            let manageHeading = headings.find(h => h.textContent.includes('Manage your Bazaar') || h.textContent.includes('Manage items') || h.textContent.includes('Manage Bazaar'));

            if (manageHeading) {
                // Check for the SETTINGS button ID, which exists on both mobile and desktop
                if (document.getElementById('manageSettingsBtn')) {
                    clearInterval(tryAddButtons);
                    manageButtonsAdded = true;
                    return;
                }

                clearInterval(tryAddButtons);
                manageButtonsAdded = true;

                // Container for buttons floating right
                const buttonContainer = document.createElement('div');
                buttonContainer.style.cssText = 'float: right; display: flex; gap: 5px; align-items: center; margin-top: -2px;';

                // 1. Update All Button
                const updateAllBtn = document.createElement('button');
                updateAllBtn.id = 'quickUpdateAllPricesBtn';
                updateAllBtn.textContent = 'Update All';
                updateAllBtn.className = 'qp-btn qp-btn-top';
                updateAllBtn.setAttribute('title', 'Update all item prices to current market value');
                updateAllBtn.addEventListener('click', updateAllManagePrices);

                // 2. Settings Button
                const settingsBtn = document.createElement('button');
                settingsBtn.id = 'manageSettingsBtn';
                settingsBtn.textContent = 'Settings';
                settingsBtn.className = 'qp-btn qp-btn-top';
                settingsBtn.setAttribute('title', 'Open Quick Pricer settings');
                settingsBtn.addEventListener('click', (e) => {
                    e.preventDefault();
                    showSettingsPanel();
                });

                // Only add "Update All" button if NOT on mobile
                if (!isMobile) {
                    buttonContainer.appendChild(updateAllBtn);
                }

                buttonContainer.appendChild(settingsBtn);

                manageHeading.appendChild(buttonContainer);

                console.log('[BazaarQuickPricer] Manage page buttons added');
            } else if (attempts >= maxAttempts) {
                clearInterval(tryAddButtons);
                console.log('[BazaarQuickPricer] Manage page buttons failed to add (header not found)');
            }
        }, 500);
    }

    function processManageItems() {
        const items = getManageItems();
        console.log('[BazaarQuickPricer] Found', items.length, 'manage items');
        if (items.length > 0) {
            items.forEach(item => addUpdatePriceButton(item));
        }
    }

    // ===== ADD ITEMS PAGE FUNCTIONS =====

    function addQuickPriceButton(itemElement) {
        if (processedItems.has(itemElement)) return;

        // Find the description container that usually contains the name
        const descriptionCont = itemElement.querySelector('div[class*="description___"], div.title-wrap');
        if (!descriptionCont) return;

        if (descriptionCont.querySelector('.quick-price-btn')) {
            processedItems.add(itemElement);
            return;
        }

        processedItems.add(itemElement);

        const image = itemElement.querySelector('div.image-wrap img');
        if (!image) return;
        const itemId = getItemIdFromImage(image);
        if (!itemId) return;

        const amountDiv = itemElement.querySelector('div.amount-main-wrap');
        if (!amountDiv) return;

        const priceInputs = amountDiv.querySelectorAll('div.price div input');
        if (priceInputs.length === 0) return;

        const btnContainer = document.createElement('div');
        btnContainer.className = 'quick-price-btn';

        const btnInput = document.createElement('button');
        btnInput.innerHTML = addButtonSVG;
        btnInput.className = 'qp-btn';
        btnInput.setAttribute('title', 'Quick Add / Undo');
        btnInput.dataset.mode = 'add'; // Default mode

        btnContainer.appendChild(btnInput);
        descriptionCont.style.display = 'flex';
        descriptionCont.style.alignItems = 'center';
        descriptionCont.appendChild(btnContainer);

        // Click Handler (Toggle Add/Undo)
        btnInput.addEventListener('click', function(event) {
            event.stopPropagation();
            
            if (btnInput.dataset.mode === 'undo') {
                // UNDO ACTION
                clearItemInputs(itemElement);
                // Reset styling
                btnInput.classList.remove('qp-btn-red');
                btnInput.dataset.mode = 'add';
            } else {
                // ADD ACTION
                btnInput.disabled = true;
                btnInput.style.opacity = '0.5';

                fillItemPrice(itemElement).then(() => {
                    btnInput.disabled = false;
                    btnInput.style.opacity = '1';
                });
            }
        });
    }

    async function fillAllItems() {
        const items = getVisibleItems();
        console.log('[BazaarQuickPricer] Filling', items.length, 'items in current tab simultaneously...');

        if (items.length === 0) {
            alert('No items found to fill!');
            return;
        }

        const fillButton = document.getElementById('quickFillAllBtn');
        if (fillButton) {
            fillButton.disabled = true;
            fillButton.style.opacity = '0.5';
            fillButton.textContent = 'Filling...';
        }

        const promises = items.map(item => fillItemPrice(item));
        await Promise.all(promises);
        if (fillButton) {
            fillButton.disabled = false;
            fillButton.style.opacity = '1';
            fillButton.textContent = 'Quick Fill';
        }

        console.log('[BazaarQuickPricer] Fill complete!');
    }

    function addTopButtons() {
        if (buttonsAdded) return;
        let attempts = 0;
        const maxAttempts = 30;

        const tryAddButtons = setInterval(() => {
            attempts++;
            // Broad search for any header-like element that might contain the text
            const potentialHeaders = Array.from(document.querySelectorAll('div[class*="titleContainer___"], div[class*="panelHeader___"], div.title-black, div[class*="title___"]'));
            const titleSection = potentialHeaders.find(h => h.textContent.includes('Add items to your Bazaar') || h.textContent.includes('Add items'));

            if (titleSection) {
                if (document.getElementById('quickFillAllBtn')) {
                    clearInterval(tryAddButtons);
                    buttonsAdded = true;
                    return;
                }

                clearInterval(tryAddButtons);
                buttonsAdded = true;

                const buttonContainer = document.createElement('div');
                buttonContainer.style.cssText = 'display:inline-flex;margin-left:15px;vertical-align:top;align-items:flex-start;';

                const fillAllBtn = document.createElement('button');
                fillAllBtn.id = 'quickFillAllBtn';
                fillAllBtn.textContent = 'Quick Fill';
                fillAllBtn.className = 'qp-btn qp-btn-fill';
                fillAllBtn.setAttribute('title', 'Fill all items in current tab with market prices');
                fillAllBtn.addEventListener('click', fillAllItems);

                const settingsBtn = document.createElement('button');
                settingsBtn.id = 'quickPricerSettingsBtn';
                settingsBtn.textContent = 'Settings';
                settingsBtn.className = 'qp-btn qp-btn-settings';
                settingsBtn.setAttribute('title', 'Open Quick Pricer settings');
                settingsBtn.addEventListener('click', (e) => {
                    e.preventDefault();
                    showSettingsPanel();
                });
                buttonContainer.appendChild(fillAllBtn);
                buttonContainer.appendChild(settingsBtn);
                titleSection.appendChild(buttonContainer);

                console.log('[BazaarQuickPricer] Buttons added');
            } else if (attempts >= maxAttempts) {
                clearInterval(tryAddButtons);
                console.log('[BazaarQuickPricer] Buttons failed to add');
            }
        }, 500);
    }

    function processAllItems() {
        const items = document.querySelectorAll('ul.items-cont li.clearfix:not(.disabled), div[class*="itemsContainner___"] div[class*="item___"], div[class*="rowItems___"] div[class*="item___"]');
        console.log('[BazaarQuickPricer] Found', items.length, 'items');
        if (items.length > 0) {
            items.forEach(item => {
                if (!item.className.includes('item___UN3Mg')) {
                    addQuickPriceButton(item);
                }
            });
        }
    }

    function setupObserver(bazaarRoot) {
        console.log('[BazaarQuickPricer] Content observer starting');
        const observer = new MutationObserver(() => {
            clearTimeout(mutationDebounceTimer);
            mutationDebounceTimer = setTimeout(() => {
                processAllItems();
                addTopButtons();
                processManageItems();
                addManagePageButtons();
            }, 300);
        });
        observer.observe(bazaarRoot, { childList: true, subtree: true });
    }

    function initScript(bazaarRoot) {
        if (!CONFIG.apiKey || CONFIG.apiKey === 'null') {
            showApiKeyPrompt();
            return;
        }

        processAllItems();
        setupObserver(bazaarRoot);
        addTopButtons();
        processManageItems();
        addManagePageButtons();
    }

    let isScriptInitialized = false;

    function init() {
        console.log('[BazaarQuickPricer] Starting multi-stage initialization...');

        // Stage 1: Wait for DOM
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', checkForBazaar);
        } else {
            checkForBazaar();
        }
    }

    function checkForBazaar() {
        if (isScriptInitialized) return;

        const bazaarRoot = document.getElementById('bazaarRoot') ||
                           document.querySelector('.bazaar-main-wrap');

        if (bazaarRoot) {
            console.log('[BazaarQuickPricer] Bazaar found immediately');
            isScriptInitialized = true;
            initScript(bazaarRoot);
            return;
        }

        // Stage 2: MutationObserver
        const observer = new MutationObserver(() => {
            if (isScriptInitialized) {
                observer.disconnect();
                return;
            }
            const root = document.getElementById('bazaarRoot') ||
                         document.querySelector('.bazaar-main-wrap');
            if (root) {
                console.log('[BazaarQuickPricer] Bazaar detected via observer');
                isScriptInitialized = true;
                observer.disconnect();
                initScript(root);
            }
        });

        if (document.body) {
            observer.observe(document.body, { childList: true, subtree: true });
        } else {
            // Fallback if body doesn't exist yet
            const docObserver = new MutationObserver(() => {
                if (document.body) {
                    docObserver.disconnect();
                    observer.observe(document.body, { childList: true, subtree: true });
                }
            });
            docObserver.observe(document.documentElement, { childList: true });
        }

        // Stage 3: Polling fallback for Chrome
        let attempts = 0;
        const pollInterval = setInterval(() => {
            if (isScriptInitialized) {
                clearInterval(pollInterval);
                return;
            }
            attempts++;
            const root = document.getElementById('bazaarRoot') ||
                         document.querySelector('.bazaar-main-wrap');
            if (root) {
                console.log('[BazaarQuickPricer] Bazaar detected via polling');
                isScriptInitialized = true;
                clearInterval(pollInterval);
                observer.disconnect();
                initScript(root);
            } else if (attempts > 50) { // 5 seconds
                clearInterval(pollInterval);
                console.warn('[BazaarQuickPricer] Failed to find bazaar container after 5s');
            }
        }, 100);
    }

    init();

})();