Bazaar + TE Info Final (Integrated & Fixed)

Bazaar listing ( Weaver Site ) + TE Information PC VERSION

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name          Bazaar + TE Info Final (Integrated & Fixed)
// @namespace     https://weav3r.dev/
// @version       3.5.1
// @description   Bazaar listing ( Weaver Site ) + TE Information PC VERSION
// @author        WTV [3281931]
// @match         https://www.torn.com/*
// @grant         GM_xmlhttpRequest
// @grant         GM_addStyle
// @connect       weav3r.dev
// @connect       tornexchange.com
// @run-at        document-idle
// ==/UserScript==

(function() {
    'use strict';

    // Global State
    window._cachedListings = {};
    window._marketValueCache = {};
    window._currentMarketNetPrice = 0;

    // --- CSS ---
    GM_addStyle(`
        .bazaar-info-container { border: 1px solid #888; margin: 10px 0; padding: 5px; background: #222; color: #fff; border-radius: 4px; }
        .bazaar-info-header { font-weight: bold; margin-bottom: 5px; display: flex; flex-wrap: nowrap; justify-content: space-between; align-items: center; font-size: 14px; }
        .bazaar-title { flex-grow: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
        .best-buyer-line { font-weight: bold; margin-bottom: 5px; color: #FFA500; display: flex; flex-wrap: wrap; justify-content: flex-start; align-items: center; gap: 5px; font-size: 14px; }
        .best-buyer-line .price-display { color: lime; font-weight: bold; white-space: nowrap; font-size: 16px; }
        .best-buyer-line .trader-link { color: #1E90FF; text-decoration: none; font-weight: bold; cursor: pointer; }
        .best-buyer-line .te-listings-link { color: #00BFFF; font-size: 14px; text-decoration: none; white-space: nowrap; margin-left: 5px; font-weight: bold; }
        .bazaar-item-id { color: #aaa; font-size: 13px; font-weight: bold; white-space: nowrap; margin-left: auto; }
        
        /* 4-Box Filter Grid */
        .filter-grid { display: flex; gap: 6px; margin: 8px 0; padding: 5px; border-top: 1px dashed #444; border-bottom: 1px dashed #444; }
        .filter-grid input { flex: 1; background: #111; border: 1px solid #666; color: #00FF00; padding: 5px; border-radius: 3px; text-align: center; font-size: 12px; min-width: 0; }
        .bazaar-reset-all-btn { background: #444; color: white; border: none; padding: 0 10px; cursor: pointer; font-weight: bold; border-radius: 3px; font-size: 16px; }

        .bazaar-market-calc { display: flex; align-items: center; gap: 10px; padding: 8px 0; margin-top: 5px; }
        .bazaar-calc-label { font-weight: bold; color: #ddd; font-size: 14px; white-space: nowrap; }
        .bazaar-net-profit { font-weight: bold; color: limegreen; font-size: 14px; white-space: nowrap; }
        
        .bazaar-card-container { display: flex; overflow-x: auto; padding: 5px; gap: 5px; min-height: 80px; }
        .bazaar-card { border: 1px solid #444; background: #222; color: #eee; padding: 10px; margin: 2px; width: 125px; flex-shrink: 0; display: flex; flex-direction: column; font-size: 15px; gap: 3px; border-radius: 4px; }
        .bazaar-card a { font-weight: bold; text-decoration: none; color: #1E90FF; font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
        
        .diff-text-positive { color: red; font-weight: bold; }
        .diff-text-negative { color: limegreen; font-weight: bold; }
        .diff-text-neutral { color: gold; font-weight: bold; }
        
        .bazaar-loader { border: 4px solid #f3f3f3; border-top: 4px solid #3498db; border-radius: 50%; width: 20px; height: 20px; animation: spin 1s linear infinite; margin: 10px auto; }
        @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
    `);

    // --- UTILITIES ---
    function cleanName(rawName) {
        return rawName.replace(/View Info|Buy Item|Content (collapsed|expanded)/g, '').split('$')[0].trim();
    }

    async function fetchTornExchangeData(itemId) {
        return new Promise(resolve => {
            GM_xmlhttpRequest({
                method: "GET", url: `https://tornexchange.com/api/best_listing?item_id=${itemId}`,
                onload: (r) => {
                    try {
                        const d = JSON.parse(r.responseText);
                        if(d && d.status === 'success') {
                            window._marketValueCache[itemId] = d.data.te_price;
                            resolve(d.data);
                        } else resolve(null);
                    } catch(e) { resolve(null); }
                }
            });
        });
    }

    // --- HIGHLIGHT & SCROLL LOGIC ---
    const checkAndScroll = () => {
        const params = new URLSearchParams(window.location.search);
        const targetId = params.get("highlightItem");
        if (!targetId || !window.location.href.includes("bazaar.php")) return false;

        const targetImg = document.querySelector(`img[src*="/images/items/${targetId}/"]`);
        if (targetImg) {
            const itemBox = targetImg.closest('li') || targetImg.closest('[class*="itemTile"]') || targetImg.parentElement;
            if (itemBox) {
                itemBox.style.setProperty("outline", "4px solid #00FF00", "important");
                itemBox.style.setProperty("outline-offset", "2px", "important");
                itemBox.style.setProperty("background", "rgba(0, 255, 0, 0.1)", "important");
                itemBox.scrollIntoView({ behavior: "smooth", block: "center" });
                return true;
            }
        }
        return false;
    };

    // --- UI GENERATION ---
    function createInfoContainer(itemName, itemId, teData) {
        const container = document.createElement('div');
        container.className = 'bazaar-info-container';
        container.dataset.itemid = itemId;

        const cleaned = cleanName(itemName);
        const marketVal = teData?.te_price ? `$${Math.round(teData.te_price).toLocaleString()}` : 'N/A';
        const encodedName = encodeURIComponent(cleaned);
        const teLink = `https://tornexchange.com/listings?model_name_contains=${encodedName}&order_by=&status=`;

        container.innerHTML = `
            <div class="bazaar-info-header">
                <span class="bazaar-title">${cleaned} (Market Value: ${marketVal})</span>
            </div>
            <div class="best-buyer-line">
                ${teData?.price ? `<span>Best Trader: <span class="price-display">$${Math.round(teData.price).toLocaleString()}</span> by <a class="trader-link" href="https://www.torn.com/profiles.php?XID=${teData.trader_id}" target="_blank">${teData.trader}</a></span>` : ''}
                <a href="${teLink}" target="_blank" class="te-listings-link">(TE Listings)</a>
                <span class="bazaar-item-id">Item #: ${itemId}</span>
            </div>
            <div class="filter-grid">
                <input type="number" class="f-min-p" placeholder="Min $">
                <input type="number" class="f-max-p" placeholder="Max $">
                <input type="number" class="f-min-q" placeholder="Min Qty">
                <input type="number" class="f-max-q" placeholder="Max Qty">
                <button class="bazaar-reset-all-btn">↺</button>
            </div>
            <div class="bazaar-market-calc">
                <span class="bazaar-calc-label">Profit Calc Sell Price:</span>
                <input type="text" placeholder="Enter Price" class="profit-calc-input" style="width:100px; background:#111; border:1px solid #666; color:#fff; padding:4px;">
                <span class="bazaar-calc-label">Net (5%):</span>
                <span class="bazaar-net-profit profit-net-display">$0</span>
            </div>
            <div class="bazaar-card-container"><div class="bazaar-loader"></div></div>
        `;

        setupListeners(container, itemId);
        return container;
    }

    function setupListeners(container, itemId) {
        container.querySelectorAll('.filter-grid input').forEach(input => {
            input.addEventListener('input', () => renderCards(itemId, container));
        });

        const calcInput = container.querySelector(`.profit-calc-input`);
        const netSpan = container.querySelector(`.profit-net-display`);
        calcInput.addEventListener('input', (e) => {
            const val = e.target.value.replace(/[^\d]/g, '');
            e.target.value = val ? Number(val).toLocaleString() : '';
            window._currentMarketNetPrice = Math.floor(parseInt(val || 0) * 0.95);
            netSpan.textContent = `$${window._currentMarketNetPrice.toLocaleString()}`;
            renderCards(itemId, container);
        });

        container.querySelector('.bazaar-reset-all-btn').addEventListener('click', () => {
            container.querySelectorAll('input').forEach(i => i.value = '');
            window._currentMarketNetPrice = 0;
            renderCards(itemId, container);
        });
    }

    function renderCards(itemId, container) {
        const listings = window._cachedListings[itemId] || [];
        const cardBox = container.querySelector('.bazaar-card-container');
        
        const minP = parseInt(container.querySelector('.f-min-p').value || 0);
        const maxP = parseInt(container.querySelector('.f-max-p').value || Infinity);
        const minQ = parseInt(container.querySelector('.f-min-q').value || 0);
        const maxQ = parseInt(container.querySelector('.f-max-q').value || Infinity);
        
        const marketVal = window._marketValueCache[itemId] || 0;
        if (!cardBox) return;
        cardBox.innerHTML = '';

        let filtered = listings.filter(l => 
            l.price >= minP && l.price <= (maxP || Infinity) &&
            l.quantity >= minQ && l.quantity <= (maxQ || Infinity)
        ).sort((a, b) => a.price - b.price);

        filtered.forEach(l => {
            const card = document.createElement('div');
            card.className = 'bazaar-card';
            const diff = marketVal ? ((l.price - marketVal) / marketVal * 100).toFixed(1) : 0;
            const diffClass = diff < -0.5 ? 'diff-text-negative' : (diff > 0.5 ? 'diff-text-positive' : 'diff-text-neutral');

            let marginHTML = '';
            if (window._currentMarketNetPrice > 0) {
                const margin = ((window._currentMarketNetPrice - l.price) / window._currentMarketNetPrice * 100).toFixed(2);
                const marginClass = margin > 0.1 ? 'diff-text-negative' : 'diff-text-positive';
                marginHTML = `<div style="font-size:14px"><b>Margin:</b> <span class="${marginClass}">${margin}%</span></div>`;
            }

            card.innerHTML = `
                <a href="https://www.torn.com/bazaar.php?userId=${l.player_id}&highlightItem=${itemId}#/" target="_blank">${l.player_name}</a>
                <div><b>Price:</b> $${Math.round(l.price).toLocaleString()}</div>
                <div style="display:flex; justify-content:space-between"><span><b>Qty:</b> ${l.quantity}</span><span class="${diffClass}">${diff > 0 ? '+' : ''}${diff}%</span></div>
                ${marginHTML}
            `;
            cardBox.appendChild(card);
        });
    }

    // --- MAIN OBSERVER ---
    const observer = new MutationObserver(() => {
        // Highlight check
        checkAndScroll();

        // Bazaar Data injection
        document.querySelectorAll('[class*="sellerListWrapper"]').forEach(async w => {
            if (w.dataset.bazaarProcessed) return;
            w.dataset.bazaarProcessed = 'true';
            
            const tile = w.closest('[class*="itemTile"]') || w.previousElementSibling;
            const btn = tile?.querySelector('button[aria-controls*="itemInfo"]');
            if (!btn) return;

            const itemId = btn.getAttribute('aria-controls').split('-').pop();
            const itemName = tile.querySelector('div').textContent.trim();
            
            const teData = await fetchTornExchangeData(itemId);
            const container = createInfoContainer(itemName, itemId, teData);
            w.insertBefore(container, w.firstChild);

            GM_xmlhttpRequest({
                method: "GET", url: `https://weav3r.dev/api/marketplace/${itemId}`,
                onload: (r) => {
                    const data = JSON.parse(r.responseText);
                    window._cachedListings[itemId] = data.listings;
                    renderCards(itemId, container);
                }
            });
        });
    });

    observer.observe(document.body, { childList: true, subtree: true });
    setTimeout(checkAndScroll, 1000);

})();