Bazaar + TE Info Final (Integrated & Fixed)

Bazaar listing ( Weaver Site ) + TE Information PC VERSION

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

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

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

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

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

// ==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);

})();