Bazaar + TE Info PC Version

Adds Bazaar listings, TornExchange data, sorting/filtering to Item Market/Bazaar detail views. Optimized for PC/Desktop view.

当前为 2025-11-03 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Bazaar + TE Info PC Version
// @namespace    https://weav3r.dev/
// @version      2.2
// @description  Adds Bazaar listings, TornExchange data, sorting/filtering to Item Market/Bazaar detail views. Optimized for PC/Desktop view.
// @author       WTV [3281931]
// @match        https://www.torn.com/*
// @match        https://www.torn.com/itemmarket.php*
// @match        https://www.torn.com/bazaar.php*
// @grant        GM_xmlhttpRequest
// @connect      weav3r.dev
// @connect      tornexchange.com
// @connect      www.torn.com
// @run-at       document-end 
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';
    
    console.log("[Bazaar + TE Info PC Version V 1.6 RUNNING].");

    // Global state
    window._visitedBazaars = window._visitedBazaars || new Set();
    window._cachedListings = window._cachedListings || {}; 
    window._marketValueCache = window._marketValueCache || {}; 
    window._activeSort = window._activeSort || { type: 'price', dir: 'asc' }; 
    
    let isMobileView = false;
    let currentDarkMode = document.body.classList.contains("dark-mode");
    let lastUrl = window.location.href; 
    
    // ----------------------------------------------------------------------
    // --- UTILITY & STYLES ---
    // ----------------------------------------------------------------------
    
    function checkMobileView() {
        // PC version will rely mostly on PC selectors, but this flag is used internally for minor adjustments
        isMobileView = window.innerWidth <= 784; 
        return isMobileView;
    }
    checkMobileView();
    window.addEventListener("resize", checkMobileView);
    
    function extractItemId() {
        let itemId = null;
        
        if (isMobileView) {
            const btn = document.querySelector('[class*="itemsHeader"] button[aria-controls^="wai-itemInfo-"]');
            if (btn) {
                const controls = btn.getAttribute("aria-controls");
                const parts = controls.split("-");
                itemId = parts.length > 2 ? parts[parts.length - 2] : parts[parts.length - 1];
            }
            
            if (!document.querySelector('ul[class*="sellerList"]')) {
                return null;
            }
            
        } else { // PC View
            // Use a combination of PC and potential page content wrapper selectors
            const itemDetails = document.querySelector('[class*="item-details"], [class*="item-info-desc"], .content-title, .title-market');
            
            if (!itemDetails) {
                 return null;
            }
            
            const itemImg = itemDetails.querySelector('img[src*="/images/items/"]');
            if (itemImg) {
                 const src = itemImg.getAttribute('src');
                 const imgMatch = src.match(/\/images\/items\/(\d+)\//i);
                 if (imgMatch && imgMatch[1]) { itemId = imgMatch[1]; }
            }
            
            if (!itemId) {
                const currentURL = new URL(window.location.href);
                const urlMatch = currentURL.search.match(/ID=(\d+)/i) || window.location.hash.match(/ID=(\d+)/i);
                if (urlMatch && urlMatch[1]) { itemId = urlMatch[1]; }
            }
        }
        
        return (itemId && itemId !== "unknown") ? itemId : null;
    }
    
    function updateStyles() {
        let styleEl = document.getElementById("bazaar-enhanced-styles");
        if (!styleEl) {
            styleEl = document.createElement("style");
            styleEl.id = "bazaar-enhanced-styles";
            document.head.appendChild(styleEl);
        }
        styleEl.textContent = `
            .bazaar-info-container { border: 1px solid #888; margin: 5px 0 10px 0; padding: 5px; background: #222; color: #fff; border-radius: 4px; max-width: 100%; box-sizing: border-box; }
            .dark-mode .bazaar-info-container { background: #222; border-color: #444; }
            .bazaar-control-line { display: flex; flex-wrap: nowrap; gap: 4px; align-items: center; overflow-x: auto; padding-right: 5px; } 
            .bazaar-filter-toggle-btn { background: #555; color: white; border: none; padding: 4px 5px; cursor: pointer; font-weight: bold; height: 28px; width: 50px; flex-shrink: 0; font-size: 12px; border-radius: 4px; }
            .bazaar-filter-toggle-btn.active-filter { background: #007bff; }
            .bazaar-sort-btn { cursor:pointer; font-size: 14px; line-height: 1; margin: 0; padding: 0; }
            .bazaar-filter-input { padding: 4px; background: #333; border: 1px solid #444; color: white; height: 28px; box-sizing: border-box; font-size: 12px; border-radius: 4px; }
            .bazaar-filter-group-price input { width: 48px; } 
            .bazaar-filter-group-quantity input { width: 45px; } 
            .bazaar-apply-btn { background: #28a745; color: white; border: none; padding: 4px 8px; cursor: pointer; font-weight: bold; height: 28px; flex-shrink: 0; font-size: 12px; border-radius: 4px; }
            .bazaar-card-container { display:flex; overflow-x:auto; padding:5px; gap:5px; }
            .bazaar-card { border:1px solid #444; background:#222; color:#eee; padding: 2px 3px; margin:2px; width: 95px; flex-shrink: 0; cursor:pointer; display:flex; flex-direction:column; font-family: inherit; font-size:14px; transition:transform 0.2s; position: relative; gap: 0; border-radius: 4px; } 
            .bazaar-card a { overflow: hidden !important; white-space: nowrap !important; text-overflow: ellipsis; line-height: 1.1; font-size: 14px !important; padding: 0; margin: 0 0 3px 0; display: block; }
            .bazaar-card div:nth-child(2) { font-size: 14px !important; line-height: 1.0; padding: 0; margin: 3px 0 3px 0; font-weight: bold; }
            .bazaar-card div:nth-child(3) { font-size: 12px !important; line-height: 1.0; padding: 0; margin: 3px 0 0 0; display: flex; justify-content: space-between; align-items: baseline; }
            .bazaar-title { font-size: 16px !important; font-weight: bold !important; flex-grow: 1; overflow: visible; white-space: normal;}
            .bazaar-info-header { font-weight:bold;margin-bottom:5px; display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; font-size: 14px; overflow: visible; }
            .bazaar-te-id-line { display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; }
            .bazaar-te-id-line .item-id-display { color:#aaa; font-size:13px; font-weight: bold; white-space: nowrap; }
            .bazaar-filter-inputs-group { display: flex; gap: 2px; align-items: center; flex-wrap: nowrap; flex-grow: 1; justify-content: flex-end; flex-shrink: 0; }
            .bazaar-filter-group-price, .bazaar-filter-group-quantity { display: flex; align-items:center; gap:2px; flex-shrink: 0; }
            .best-buyer-line { font-weight:bold; margin-bottom: 5px; color:#FFA500; display: flex; flex-wrap: nowrap; justify-content: flex-start; align-items: center; gap: 5px; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
            .best-buyer-line > span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
            .best-buyer-line > span:first-child, .best-buyer-line .price-display-span { flex-shrink: 0; }
            .best-buyer-line a { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: inline-block; }
        `;
    }
    updateStyles();

    const darkModeObserver = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
            if (mutation.attributeName === "class") {
                const newDarkMode = document.body.classList.contains("dark-mode");
                if (newDarkMode !== currentDarkMode) {
                    currentDarkMode = newDarkMode;
                    updateStyles();
                }
            }
        });
    });
    // Start observing body if it exists, otherwise wait for the body to be added (critical for document-start)
    if (document.body) {
         darkModeObserver.observe(document.body, { attributes: true });
    } else {
         new MutationObserver((mutations, obs) => {
              if (document.body) {
                  darkModeObserver.observe(document.body, { attributes: true });
                  obs.disconnect();
              }
         }).observe(document.documentElement, { childList: true });
    }

    function renderNoListingsMessage(container, isError){
        const cardContainer = container.querySelector('.bazaar-card-container');
        if(!cardContainer) return;
        cardContainer.innerHTML = '';
        const msg = document.createElement('div');
        msg.style.cssText='color:#fff;text-align:center;padding:20px;width:100%;';
        msg.innerHTML = isError ? "API Error<br><span style='font-size:12px;color:#ccc;'>Could not fetch bazaar data.</span>"
                                 : "No bazaar listings available for this item.";
        cardContainer.appendChild(msg);
    }
    
    function fetchUpvoteCountAndId(userName, callback) {
        const url = `https://tornexchange.com/prices/${userName}/`;
        GM_xmlhttpRequest({
            method: "GET", url: url,
            onload: function(response) {
                let upvoteCount = null;
                let traderId = null;
                try {
                    const doc = new DOMParser().parseFromString(response.responseText, "text/html");
                    const upvoteElement = doc.querySelector('#vote-score');
                    if (upvoteElement) { upvoteCount = upvoteElement.textContent.trim(); }
                    const profileLink = doc.querySelector('a[href*="profiles.php?XID="]');
                    if (profileLink) {
                        const href = profileLink.getAttribute('href');
                        const match = href.match(/XID=(\d+)/);
                        if (match && match[1]) { traderId = match[1]; }
                    }
                } catch (e) { /* silent fail, as per V 1.0 */ }
                callback(upvoteCount, traderId);
            },
            onerror: function() { callback(null, null); }
        });
    }

    function createInfoContainer(itemName, itemId, marketValue, bestBuyer, upvoteCount, traderId) {
        const container = document.createElement('div');
        container.className = 'bazaar-info-container';
        container.dataset.itemid = itemId;
        if (marketValue) container.dataset.marketValue = marketValue; 
        if (traderId) container.dataset.bestBuyerId = traderId;
        
        const marketText = marketValue ? ` <span style="color:#FFD700; flex-shrink: 0; font-size: 14px;">(MV: ${marketValue})</span>` : '';
        const encodedItemName = encodeURIComponent(itemName);
        const teListingsLink = `https://tornexchange.com/listings?model_name_contains=${encodedItemName}&order_by=&status=`;
        const teListingHTML = `<a href="${teListingsLink}" target="_blank" style="color:#00BFFF; font-size: 13px; text-decoration: none; white-space:nowrap; font-weight: bold;">TE Listings</a>`;
        const itemIdHTML = `<span class="item-id-display">Item #: ${itemId}</span>`;
        
        const teIdLineHTML = `<div class="bazaar-te-id-line" style="margin-bottom: 5px;">${teListingHTML}${itemIdHTML}</div>`;
        
        let bestBuyerInfoHTML = '';
        if (bestBuyer && bestBuyer.price) {
            const formattedPrice = `$${Math.round(bestBuyer.price).toLocaleString()}`;
            const priceDisplayHTML = `<span class="price-display-span" style="color:lime; font-weight:bold; white-space:nowrap; font-size: 16px;">${formattedPrice}</span>`;
            if (bestBuyer.trader && traderId) {
                const traderLinkHTML = `<a href="https://www.torn.com/profiles.php?XID=${traderId}" target="_blank" style="color:#1E90FF; text-decoration:none; font-weight:bold; cursor:pointer;" onmouseover="this.style.textDecoration='underline';" onmouseout="this.style.textDecoration='none';">${bestBuyer.trader}</a>`;
                const upvoteText = upvoteCount ? ` (⭐ ${upvoteCount})` : ''; 
                bestBuyerInfoHTML = `<span style="flex-shrink: 0;">Best Trader: ${priceDisplayHTML}</span><span style="flex-grow: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis;">by ${traderLinkHTML}${upvoteText}</span>`;
            } else {
                 bestBuyerInfoHTML = `<span style="flex-shrink: 0;">Best Trader: ${formattedPrice}</span>`;
                }
        } else {
             bestBuyerInfoHTML = `<span style="flex-shrink: 0;">No Best Trader Data</span>`;
        }
        const bestBuyerHTML = `<div class="best-buyer-line">${bestBuyerInfoHTML}</div>`;
        
        const filterControlsHTML = `
            <div class="bazaar-control-row" style="display:flex; flex-direction: column; gap:8px; margin: 5px 0 8px 0;">
                <div class="bazaar-control-line">
                    
                    <div class="bazaar-price-controls" style="display: flex; gap: 4px; align-items: center; flex-shrink: 0; white-space: nowrap;">
                        <button class="bazaar-filter-toggle-btn active-filter" data-filter-type="price" style="background:#007bff;">Price</button>
                        <div style="display: flex; flex-direction: column; height: 28px; justify-content: center; align-items: center; padding-right: 4px;">
                            <span class="bazaar-sort-btn" data-sort-by="price" data-sort-dir="asc" style="color:#00BFFF;">🔼</span>
                            <span class="bazaar-sort-btn" data-sort-by="price" data-sort-dir="desc" style="color:#555;">🔽</span>
                        </div>
                    </div>
                    
                    <div class="bazaar-quantity-controls" style="display: flex; gap: 4px; align-items: center; flex-shrink: 0; white-space: nowrap;">
                        <button class="bazaar-filter-toggle-btn" data-filter-type="quantity">Qty</button>
                        <div style="display: flex; flex-direction: column; height: 28px; justify-content: center; align-items: center; padding-right: 4px;">
                            <span class="bazaar-sort-btn" data-sort-by="quantity" data-sort-dir="asc" style="color:#555;">🔼</span>
                            <span class="bazaar-sort-btn" data-sort-by="quantity" data-sort-dir="desc" style="color:#555;">🔽</span>
                        </div>
                    </div>
                    
                    <div class="bazaar-filter-inputs-group">
                        <div class="bazaar-filter-group-price">
                            <input type="number" placeholder="Min" class="bazaar-filter-input" data-filter-type="minPrice">
                            <input type="number" placeholder="Max" class="bazaar-filter-input" data-filter-type="maxPrice">
                        </div>
                        <div class="bazaar-filter-group-quantity" style="display: none;">
                            <input type="number" placeholder="Min" class="bazaar-filter-input" data-filter-type="minQty">
                            <input type="number" placeholder="Max" class="bazaar-filter-input" data-filter-type="maxQty">
                        </div>
                        <button class="bazaar-apply-btn" style="display: block;">Apply</button>
                    </div>
                </div>
            </div>
        `;

        container.innerHTML = `
            <div class="bazaar-info-header">
                <span class="bazaar-title">${itemName}${marketText}</span>
            </div>
            ${bestBuyerHTML}
            ${teIdLineHTML}
            ${filterControlsHTML}
            <div class="bazaar-card-container"></div>
        `;

        const cardContainer = container.querySelector('.bazaar-card-container');
        if (cardContainer) {
            cardContainer.addEventListener("wheel", e => {
                if (e.deltaY !== 0) { e.preventDefault(); cardContainer.scrollLeft += e.deltaY; }
            });
        }

        addFilterListeners(container, itemId);
        return container;
    }

    function sortAndFilterListings(itemId, container) {
        let listings = window._cachedListings[itemId];
        if (!listings) {
            renderNoListingsMessage(container, !window._cachedListings[itemId]);
            return; 
        }
        
        const sortType = window._activeSort.type;
        const sortDir = window._activeSort.dir;
        
        const minPrice = parseFloat(container.querySelector('[data-filter-type="minPrice"]').value) || null;
        const maxPrice = parseFloat(container.querySelector('[data-filter-type="maxPrice"]').value) || null;
        const minQty = parseInt(container.querySelector('[data-filter-type="minQty"]').value) || null;
        const maxQty = parseInt(container.querySelector('[data-filter-type="maxQty"]').value) || null;
        
        let filteredListings = listings.slice().filter(listing => {
            const price = parseFloat(listing.price.toString().replace(/,/g, ''));
            const qty = parseInt(listing.quantity);
            if (minPrice !== null && price < minPrice) return false;
            if (maxPrice !== null && price > maxPrice) return false;
            if (minQty !== null && qty < minQty) return false;
            if (maxQty !== null && qty > maxQty) return false;
            return true;
        });

        filteredListings.sort((a, b) => {
            let primaryValA, primaryValB;
            if (sortType === 'price') {
                primaryValA = parseFloat(a.price.toString().replace(/,/g, ''));
                primaryValB = parseFloat(b.price.toString().replace(/,/g, ''));
            } else { // 'quantity'
                primaryValA = parseInt(a.quantity);
                primaryValB = parseInt(b.quantity);
            }
            let comparison = 0;
            if (sortDir === 'asc') { comparison = primaryValA - primaryValB; } else if (sortDir === 'desc') { comparison = primaryValB - primaryValA; }
            
            if (comparison === 0) {
                 const priceA = parseFloat(a.price.toString().replace(/,/g, ''));
                 const priceB = parseFloat(b.price.toString().replace(/,/g, ''));
                 return priceA - priceB;
            }
            return comparison;
        });
        
        const marketNum = window._marketValueCache?.[itemId] || null;
        renderCards(container, filteredListings, marketNum);
    }
    
    function addFilterListeners(container, itemId) {
        const priceFilterGroup = container.querySelector('.bazaar-filter-group-price');
        const quantityFilterGroup = container.querySelector('.bazaar-filter-group-quantity');
        const sortBtns = container.querySelectorAll('.bazaar-sort-btn'); 
        const filterToggleBtns = container.querySelectorAll('.bazaar-filter-toggle-btn');
        const applyBtn = container.querySelector('.bazaar-apply-btn');
        const defaultColor = '#555';
        const activeColor = '#00BFFF'; 
        const reverseColor = '#dc3545'; 

        const updateSortVisuals = () => {
             filterToggleBtns.forEach(btn => {
                 const filterType = btn.dataset.filterType;
                 if (filterType === window._activeSort.type) { 
                      btn.style.background = '#007bff';
                      btn.classList.add('active-filter');
                 } else {
                      btn.style.background = defaultColor;
                      btn.classList.remove('active-filter');
                 }
             });
             sortBtns.forEach(btn => {
                 const type = btn.dataset.sortBy;
                 const dir = btn.dataset.sortDir;
                 let color = defaultColor;
                 if (type === window._activeSort.type && dir === window._activeSort.dir) {
                     color = dir === 'asc' ? activeColor : reverseColor;
                 } else if (type === window._activeSort.type) {
                     color = '#777'; 
                 }
                 btn.style.color = color;
             });
        };

        sortBtns.forEach(btn => {
            btn.addEventListener('click', (e) => {
                window._activeSort.type = btn.dataset.sortBy;
                window._activeSort.dir = btn.dataset.sortDir;
                updateSortVisuals();
                sortAndFilterListings(itemId, container);
            });
        });

        filterToggleBtns.forEach(btn => {
            btn.addEventListener('click', (e) => {
                const filterType = btn.dataset.filterType;
                priceFilterGroup.style.display = (filterType === 'price') ? 'flex' : 'none';
                quantityFilterGroup.style.display = (filterType === 'quantity') ? 'flex' : 'none';
                applyBtn.style.display = 'block';
                updateSortVisuals();
                sortAndFilterListings(itemId, container);
            });
        });

        applyBtn.addEventListener('click', () => { sortAndFilterListings(itemId, container); });

        container.querySelectorAll('.bazaar-filter-input').forEach(input => {
            input.addEventListener('keydown', (e) => {
                if (e.key === 'Enter') { e.preventDefault(); input.blur(); sortAndFilterListings(itemId, container); }
            });
            input.addEventListener('blur', () => sortAndFilterListings(itemId, container));
        });
        updateSortVisuals();
    }

    function renderCards(container, listings, marketNum){
        const cardContainer=container.querySelector('.bazaar-card-container');
        if(!cardContainer || !listings) return;
        cardContainer.innerHTML='';
        
        if(listings.length===0){
            const msg = document.createElement('div');
            msg.style.cssText='color:#fff;text-align:center;padding:20px;width:100%;';
            msg.innerHTML = "No bazaar listings match the current filters.";
            cardContainer.appendChild(msg);
            return;
        }

        const bestBuyerId = container.dataset.bestBuyerId;
        listings.forEach(listing=>{
            const card=document.createElement('div');
            card.className = "bazaar-card";
            const isVisited=window._visitedBazaars.has(listing.player_id);
            const isBestBuyer = bestBuyerId && listing.player_id == bestBuyerId;
            const bazaarLink = `https://www.torn.com/bazaar.php?userId=${listing.player_id}&highlightItem=${listing.item_id}#/`;
            
            if (isBestBuyer) { card.style.cssText += `border: 2px solid #28a745 !important; background: #333 !important;`; }

            const priceNum = parseFloat(listing.price.toString().replace(/,/g, ''));
            const formattedPrice = `$${Math.round(priceNum).toLocaleString()}`;
            
            let diffTextHTML = '';
            if(marketNum){
                const percent = ((priceNum - marketNum)/marketNum*100).toFixed(1);
                let color = 'gold'; 
                if (percent < -0.5) { color = 'limegreen'; } else if (percent > 0.5) { color = 'red'; } 
                const sign = percent > 0 ? '+' : '';
                diffTextHTML = `<span style="font-weight:bold; color:${color}; font-size: 12px; margin-left: auto; white-space: nowrap; flex-shrink: 0;">${sign}${percent}%</span>`;
            }

            card.innerHTML=`
                <a href="${bazaarLink}" target="_blank"
                    style="font-weight:bold; color:${isVisited?'#800080':'#1E90FF'}; text-decoration:none; cursor:pointer; 
                           overflow: hidden; white-space: nowrap; text-overflow: ellipsis; 
                           line-height: 1.1; padding: 0; margin: 0 0 3px 0; display: block;">
                    ${listing.player_name || 'Unknown'}
                </a>
                <div style="font-weight: bold; color: #eee; white-space: nowrap; line-height: 1.0; padding: 0; margin: 3px 0 3px 0;">${formattedPrice}</div>
                <div style="display: flex; justify-content: space-between; align-items: baseline; line-height: 1.0; padding: 0; margin: 3px 0 0 0;">
                    <span style="white-space: nowrap;">Qty: ${listing.quantity}</span>
                    ${diffTextHTML}
                </div>
            `;
            
            card.addEventListener('click', (e)=>{
                if(e.target.tagName === 'A') return;
                if(listing.player_id){
                    window._visitedBazaars.add(listing.player_id);
                    const nameLink = card.querySelector('a:first-child');
                    if(nameLink) nameLink.style.color='#800080';
                }
                window.open(bazaarLink, '_blank');
            });
            cardContainer.appendChild(card);
        });
    }

    // ----------------------------------------------------------------------
    // --- CORE FETCH LOGIC ---
    // ----------------------------------------------------------------------
    
    function fetchTornExchangeData(itemId, callback) {
        let marketValue = null; 
        let bestBuyer = null;
        let requestsCompleted = 0;
        
        const checkCompletion = () => {
             requestsCompleted++;
             if (requestsCompleted === 2) {
                 if (bestBuyer && bestBuyer.trader) {
                     fetchUpvoteCountAndId(bestBuyer.trader, (count, id) => {
                         callback(marketValue, bestBuyer, count, id);
                     });
                 } else {
                     callback(marketValue, bestBuyer, null, null);
                 }
             }
        };
        
        GM_xmlhttpRequest({
            method: "GET", url: `https://tornexchange.com/api/te_price?item_id=${itemId}`,
            onload: function(response) {
                try {
                    const data = JSON.parse(response.responseText);
                    if (data && data.status === 'success' && data.data && data.data.te_price) { 
                        window._marketValueCache[itemId] = data.data.te_price; 
                        marketValue = `$${Math.round(data.data.te_price).toLocaleString()}`; 
                    }
                } catch (e) { /* silent fail, as per V 1.0 */ }
                checkCompletion();
            },
            onerror: function() { checkCompletion(); }
        });
        
        GM_xmlhttpRequest({
            method: "GET", url: `https://tornexchange.com/api/best_listing?item_id=${itemId}`,
            onload: function(response) {
                try {
                    const data = JSON.parse(response.responseText);
                    if (data && data.status === 'success' && data.data) {
                        if (data.data.price) { bestBuyer = { price: data.data.price, trader: data.data.trader || null }; }
                    }
                } catch (e) { /* silent fail, as per V 1.0 */ }
                checkCompletion();
            },
            onerror: function() { checkCompletion(); }
        });
    }

    function fetchBazaarListings(itemId, infoContainer) {
        GM_xmlhttpRequest({
            method:"GET",
            url:`https://weav3r.dev/api/marketplace/${itemId}`,
            onload:function(response){
                try{
                    const data = JSON.parse(response.responseText); 
                    const listingsReceived = data.listings ? data.listings.length : 0;
                    
                    if(!data || !data.listings || listingsReceived === 0){
                        renderNoListingsMessage(infoContainer, false);
                        return;
                    }
                    
                    const allListings = data.listings.map(l=>({
                        player_name:l.player_name, player_id:l.player_id, quantity:l.quantity, price:l.price, item_id:l.item_id
                    }));

                    window._cachedListings[itemId] = allListings;
                    sortAndFilterListings(itemId, infoContainer); 

                } catch(e){
                    console.error(`[Bazaar + TE Info PC Version Error] Failed to process Weaver API response for item ${itemId}:`, e);
                    renderNoListingsMessage(infoContainer, true);
                }
            },
            onerror:function(error){
                console.error(`[Bazaar + TE Info PC Version Error] GM_xmlhttpRequest failed for item ${itemId}:`, error);
                renderNoListingsMessage(infoContainer, true);
            }
        });
    }

    function updateInfoContainer(wrapper, itemId, itemName) {
        const infoContainer = createInfoContainer(itemName, itemId, null, null, null, null);
        wrapper.insertBefore(infoContainer, wrapper.firstChild);

        fetchTornExchangeData(itemId, (marketValue, bestBuyer, upvoteCount, traderId) => {
            
            let bestBuyerInfoHTML = '';
            if (bestBuyer && bestBuyer.price) {
                const formattedPrice = `$${Math.round(bestBuyer.price).toLocaleString()}`;
                const priceDisplayHTML = `<span class="price-display-span" style="color:lime; font-weight:bold; white-space:nowrap; font-size: 16px;">${formattedPrice}</span>`;
                if (bestBuyer.trader && traderId) {
                    const traderLinkHTML = `<a href="https://www.torn.com/profiles.php?XID=${traderId}" target="_blank" style="color:#1E90FF; text-decoration:none; font-weight:bold; cursor:pointer;" onmouseover="this.style.textDecoration='underline';" onmouseout="this.style.textDecoration='none';">${bestBuyer.trader}</a>`;
                    const upvoteText = upvoteCount ? ` (⭐ ${upvoteCount})` : ''; 
                    bestBuyerInfoHTML = `<span style="flex-shrink: 0;">Best Trader: ${priceDisplayHTML}</span><span style="flex-grow: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis;">by ${traderLinkHTML}${upvoteText}</span>`;
                } else {
                     bestBuyerInfoHTML = `<span style="flex-shrink: 0;">Best Trader: ${formattedPrice}</span>`;
                }
            } else {
                 bestBuyerInfoHTML = `<span style="flex-shrink: 0;">No Best Trader Data</span>`;
            }

            const newBestBuyerHTML = `<div class="best-buyer-line">${bestBuyerInfoHTML}</div>`;
            const oldBestBuyer = infoContainer.querySelector('.best-buyer-line');
            if (oldBestBuyer && oldBestBuyer.parentNode) {
                 const tempDiv = document.createElement('div');
                 tempDiv.innerHTML = newBestBuyerHTML;
                 oldBestBuyer.parentNode.replaceChild(tempDiv.firstChild, oldBestBuyer);
            }
            
            const marketText = marketValue ? ` (MV: ${marketValue})` : '';
            const itemNameEl = infoContainer.querySelector('.bazaar-title');
            if (itemNameEl) {
                itemNameEl.innerHTML = `${itemName}<span style="color:#FFD700; flex-shrink: 0; font-size: 14px;">${marketText}</span>`;
            }
            
            if (window._cachedListings[itemId]) {
                sortAndFilterListings(itemId, infoContainer);
            }
        });

        fetchBazaarListings(itemId, infoContainer);
    }

    // ----------------------------------------------------------------------
    // --- MAIN EXECUTION & MONITORS ---
    // ----------------------------------------------------------------------
    
    function processBazaarOrMarketItem(){
        const itemId = extractItemId();
        if (!itemId) return;

        let wrapper = null;
        let itemName = '';
        
        if (isMobileView) {
            const sellerListWrapper = document.querySelector('ul.sellerList__e4C9, ul[class*="sellerList"]');
            if (!sellerListWrapper) return;
            wrapper = sellerListWrapper.parentNode;
            
            const headerEl = document.querySelector('.itemsHeader__ZTO9r .title__ruNCT, [class*="itemsHeader"] [class*="title"]');
            itemName = headerEl ? headerEl.textContent.trim() : "Unknown";
        } else {
            // Updated Selector Strategy: The main item details box is usually inside a 'content' div.
            const itemDetails = document.querySelector('[class*="item-details"], [class*="item-info-desc"]');
            if (!itemDetails) return;
            
            // Try to find the immediate parent that is part of the main content area (PC view structure)
            wrapper = itemDetails.closest('.content-wrapper > .content') || itemDetails.parentElement;

            const nameEl = itemDetails.querySelector('h3.item-name, [class*="item-name"], .title');
            if (nameEl) { itemName = nameEl.textContent.trim(); }
        }

        if (!wrapper || !itemName) return;

        const existingContainerForCurrentItem = document.querySelector(`.bazaar-info-container[data-itemid="${itemId}"]`);
        if (existingContainerForCurrentItem) return;

        // Clean up old container if present and belonging to a different item
        const existingContainer = document.querySelector(`.bazaar-info-container`);
        if (existingContainer && existingContainer.dataset.itemid !== itemId) {
             console.log(`[Bazaar + TE Info PC Version] Removing old container for item ${existingContainer.dataset.itemid}.`);
             existingContainer.remove();
        }

        if (!document.querySelector('.bazaar-info-container')) {
             updateInfoContainer(wrapper, itemId, itemName);
        }
    }

    // URL Monitor
    setInterval(() => {
        if (window.location.href !== lastUrl) {
            document.querySelectorAll('.bazaar-info-container').forEach(el => el.remove());
            lastUrl = window.location.href;
            // Reset filters/caches on page change
            window._cachedListings = {}; 
            window._marketValueCache = {}; 
            window._activeSort = { type: 'price', dir: 'asc' }; 
            
            // Force an immediate check after URL change
            processBazaarOrMarketItem(); 
        }
    }, 500); 

    // Mutation Observer
    const observer = new MutationObserver(()=>{ 
        processBazaarOrMarketItem(); 
    });
    
    // Start observing a highly stable root element (document.body) for dynamic content loading
    if (document.body) {
         observer.observe(document.body, {childList:true,subtree:true});
    } else {
         // Fallback to wait for the body to ensure the observer is attached properly
         new MutationObserver((mutations, obs) => {
              if (document.body) {
                  observer.observe(document.body, { childList: true, subtree: true });
                  obs.disconnect();
              }
         }).observe(document.documentElement, { childList: true });
    }

    // NEW: Initial Delay for Robustness
    // This gives Torn's page-rendering scripts time to populate the main content area.
    let attempts = 0;
    const initialCheck = setInterval(() => {
        if (document.querySelector('[class*="item-details"], [class*="item-info-desc"]')) {
             clearInterval(initialCheck);
             processBazaarOrMarketItem();
        }
        attempts++;
        if (attempts > 20) { // Stop after 10 seconds (20 attempts * 500ms)
             clearInterval(initialCheck);
             console.log("[Bazaar + TE Info PC Version] Initial check failed to find Item Details after 10 seconds.");
        }
    }, 500);
    
})();

// --- Bazaar Page Green Highlight ---
;(() => {
    const params = new URLSearchParams(window.location.search);
    const itemIdToHighlight = params.get("highlightItem");
    if (!itemIdToHighlight) return;
    
    const observer = new MutationObserver(() => {
        const imgs = document.querySelectorAll("img");
        imgs.forEach((img) => {
            if (img.src.includes(`images/items/${itemIdToHighlight}/`)) {
                img.closest("div")?.style.setProperty("outline", "3px solid green", "important");
                img.scrollIntoView({ behavior: "smooth", block: "center" });
            }
        });
    });
    observer.observe(document.body || document.documentElement, { childList: true, subtree: true });
})();