Bezas Bazaar

Manually set a price and highlight both item market and bazaar sellers accordingly in Torn.com.

当前为 2025-04-09 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Bezas Bazaar
// @namespace    http://tampermonkey.net/
// @version      3.0.2
// @description  Manually set a price and highlight both item market and bazaar sellers accordingly in Torn.com.
// @author       JeffBezas[3408347]
// @match        https://www.torn.com/page.php?sid=ItemMarket*
// @match        https://www.torn.com/bazaar.php?*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// @license MIT
// ==/UserScript==

(function () {
    'use strict';

    GM_addStyle(`
        .manual-highlight-good { background-color: #004d00 !important; color: white; }
        .manual-highlight-warning { background-color: #ffa500 !important; color: black; }
        .manual-highlight-bad { background-color: #8b0000 !important; color: white; }
        .manual-highlight-missing { background-color: #d3d3d3 !important; color: #000; }

        #manual-price-modal, #manual-price-manager {
            position: fixed;
            background: #1e1e1e;
            color: white;
            padding: 20px;
            border: 2px solid #888;
            border-radius: 10px;
            z-index: 9999;
        }

        #manual-price-modal {
            top: 30%;
            left: 50%;
            transform: translate(-50%, -30%);
            display: none;
        }

        #manual-price-modal input {
            width: 100px;
            padding: 5px;
            font-size: 14px;
        }

        #manual-price-modal button,
        #manual-price-manager button {
            margin-left: 10px;
            padding: 5px 10px;
            background: #444;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
        }
    `);

    const storage = typeof GM_getValue === 'function' ? {
        get: GM_getValue,
        set: GM_setValue
    } : {
        get: key => JSON.parse(localStorage.getItem(key)),
        set: (key, value) => localStorage.setItem(key, JSON.stringify(value))
    };

    const modal = document.createElement("div");
    modal.id = "manual-price-modal";
    modal.innerHTML = `
        <label>Manual price: $<input type="number" id="manual-price-input" /></label>
        <button id="manual-price-save">Save</button>
        <button id="manual-price-cancel">Cancel</button>
    `;
    document.body.appendChild(modal);

    const priceInput = document.getElementById("manual-price-input");
    const saveBtn = document.getElementById("manual-price-save");
    const cancelBtn = document.getElementById("manual-price-cancel");

    let currentItemId = null;

    const manager = document.createElement("div");
    manager.id = "manual-price-manager";
    manager.style.bottom = "10px";
    manager.style.right = "10px";
    manager.style.maxHeight = "300px";
    manager.style.overflowY = "auto";
    document.body.appendChild(manager);

    const refreshButton = document.createElement("button");
    refreshButton.textContent = "📋 View Manual Prices";
    refreshButton.style = "position: fixed; bottom: 10px; left: 10px; z-index: 10000;";
    refreshButton.onclick = () => {
        manager.innerHTML = "<b>📋 Saved Prices:</b><br/><br/>";
        let found = false;
        for (let key in localStorage) {
            if (key.startsWith("manual_price_")) {
                found = true;
                const itemId = key.split("_").pop();
                const val = storage.get(key);
                if (val) {
                    manager.innerHTML += `Item ID ${itemId}: $${Number(val).toLocaleString()}
                        <button data-del="${key}" style="margin-left: 10px;">❌ Delete</button><br/>`;
                }
            }
        }
        if (!found) manager.innerHTML += "No saved prices.";
    };
    document.body.appendChild(refreshButton);

    manager.addEventListener("click", (e) => {
        if (e.target.dataset.del) {
            storage.set(e.target.dataset.del, null);
            e.target.parentElement.remove();
        }
    });

    function getPageType() {
    const url = window.location.href;
    if (url.includes("page.php?sid=ItemMarket")) return "itemmarket";
    if (url.includes("bazaar.php")) return "bazaar";
    return null;
}



    function makeDraggable(element) {
    let isDragging = false;
    let offsetX = 0;
    let offsetY = 0;

    element.addEventListener("mousedown", function (e) {
        // Prevent drag if interacting with input, textarea, or button
        if (["INPUT", "TEXTAREA", "BUTTON"].includes(e.target.tagName)) return;

        isDragging = true;
        offsetX = e.clientX - element.offsetLeft;
        offsetY = e.clientY - element.offsetTop;
        e.preventDefault();
    });

    document.addEventListener("mouseup", () => isDragging = false);
    document.addEventListener("mousemove", function (e) {
        if (isDragging) {
            element.style.left = e.clientX - offsetX + 'px';
            element.style.top = e.clientY - offsetY + 'px';
            element.style.right = 'auto';
            element.style.bottom = 'auto';
        }
    });
}


    makeDraggable(modal);
    makeDraggable(manager);

    saveBtn.onclick = () => {
        const value = parseInt(priceInput.value);
        if (value > 0 && currentItemId) {
            storage.set(`manual_price_${currentItemId}`, value);
            modal.style.display = "none";

            const observer = new MutationObserver(() => {
                const sellerRows = document.querySelectorAll('li[class*="rowWrapper___"]');
                if (sellerRows.length > 0) {
                    observer.disconnect();
                    highlightSellerRows(currentItemId, value);
                }
            });

            observer.observe(document.body, { childList: true, subtree: true });
            waitForBazaarPrices(currentItemId, value);
        }
    };

    cancelBtn.onclick = () => modal.style.display = "none";

    function highlightSellerRows(itemId, manualPrice) {
        const rows = document.querySelectorAll('li[class*="rowWrapper___"]');
        rows.forEach(row => {
            const priceEl = row.querySelector('div[class*="price___"]');
            if (!priceEl) return;
            const match = priceEl.textContent.match(/\$([\d,]+)/);
            if (!match) return;
            const listed = parseInt(match[1].replace(/,/g, ''));
            const diffPercent = ((listed - manualPrice) / manualPrice) * 100;
            row.classList.remove('manual-highlight-good', 'manual-highlight-warning', 'manual-highlight-bad');
            if (listed <= manualPrice) {
                row.classList.add('manual-highlight-good');
            } else if (diffPercent <= 5) {
                row.classList.add('manual-highlight-warning');
            } else {
                row.classList.add('manual-highlight-bad');
            }
            addPercentIndicator(priceEl, diffPercent);
        });
    }

    function addPercentIndicator(el, diffPercent) {
        if (el.querySelector('.manual-price-diff')) return;
        const span = document.createElement('span');
        span.className = 'manual-price-diff';
        span.style.marginLeft = '6px';
        span.style.fontSize = '12px';
        span.style.fontWeight = 'bold';
        span.style.color = 'black';
        span.style.backgroundColor = 'rgba(255,255,255,0.6)';
        span.style.padding = '1px 4px';
        span.style.borderRadius = '4px';
        const sign = diffPercent > 0 ? '+' : '';
        span.textContent = `(${sign}${diffPercent.toFixed(1)}%)`;
        el.appendChild(span);
    }

    function highlightBazaarPrices(priceLinks, manualPrice) {
        priceLinks.forEach(link => {
            if (link.dataset.manualChecked === "true") return;
            const match = link.textContent.match(/\$([\d,]+)/);
            if (!match) return;
            const listed = parseInt(match[1].replace(/,/g, ''));
            const diff = ((listed - manualPrice) / manualPrice) * 100;
            link.dataset.manualChecked = "true";
            link.style.padding = "2px 4px";
            link.style.borderRadius = "4px";
            link.style.fontWeight = "bold";
            if (listed <= manualPrice) {
                link.style.backgroundColor = "#004d00";
                link.style.color = "#fff";
            } else if (diff <= 5) {
                link.style.backgroundColor = "#ffa500";
                link.style.color = "#000";
            } else {
                link.style.backgroundColor = "#8b0000";
                link.style.color = "#fff";
            }
            addPercentIndicator(link, diff);
        });
    }

    function waitForBazaarPrices(itemId, manualPrice) {
        const listingsView = document.querySelector('#fullListingsView');
        if (!listingsView) return;
        const observer = new MutationObserver(() => {
            const noItem = listingsView.textContent.includes("No item selected");
            const priceLinks = listingsView.querySelectorAll('a[href*="bazaar.php?userID="]:not([data-checked])');
            if (!noItem && priceLinks.length > 0) {
                observer.disconnect();
                highlightBazaarPrices(priceLinks, manualPrice);
            }
        });
        observer.observe(listingsView, { childList: true, subtree: true, characterData: true });
    }

    function extractItemIdFromButton(button) {
        const container = button.closest('.itemTile___cbw7w');
        const img = container?.querySelector('img.torn-item');
        const match = img?.src?.match(/\/items\/(\d+)\//);
        return match ? match[1] : null;
    }

    function setupBuyButtonListeners() {
        document.querySelectorAll('.actionButton___pb_Da').forEach(btn => {
            if (btn.dataset.boundManual === "true") return;
            btn.dataset.boundManual = "true";
            btn.addEventListener('click', () => {
                const itemId = extractItemIdFromButton(btn);
                if (!itemId) return;
                currentItemId = itemId;
                const saved = storage.get(`manual_price_${itemId}`);
                priceInput.value = saved || "";
                modal.style.display = "block";
                if (saved) {
                    const observer = new MutationObserver(() => {
                        const sellerRows = document.querySelectorAll('li[class*="rowWrapper___"]');
                        if (sellerRows.length > 0) {
                            observer.disconnect();
                            highlightSellerRows(itemId, parseInt(saved));
                        }
                    });
                    observer.observe(document.body, { childList: true, subtree: true });
                    waitForBazaarPrices(itemId, parseInt(saved));
                }
            });
        });
    }

    function highlightItemTilesFromSavedPrices() {
        document.querySelectorAll('.itemTile___cbw7w').forEach(tile => {
            if (tile.dataset.checkedManual === "true") return;
            tile.dataset.checkedManual = "true";
            const img = tile.querySelector('img.torn-item');
            const priceSpan = tile.querySelector('.priceAndTotal___eEVS7 span');
            if (!img || !priceSpan) return;
            const match = img.src.match(/\/items\/(\d+)\//);
            if (!match) return;
            const itemId = match[1];
            const priceText = priceSpan.textContent.trim();
            const priceMatch = priceText.match(/\$([\d,]+)/);
            if (!priceMatch) return;
            const listedPrice = parseInt(priceMatch[1].replace(/,/g, ''));
            const savedPrice = storage.get(`manual_price_${itemId}`);
            tile.classList.remove('manual-highlight-good', 'manual-highlight-warning', 'manual-highlight-bad', 'manual-highlight-missing');
            if (savedPrice) {
                const diffPercent = ((listedPrice - savedPrice) / savedPrice) * 100;
                if (listedPrice <= savedPrice) {
                    tile.classList.add('manual-highlight-good');
                } else if (diffPercent <= 5) {
                    tile.classList.add('manual-highlight-warning');
                } else {
                    tile.classList.add('manual-highlight-bad');
                }
                addPercentIndicator(priceSpan, diffPercent);
            } else {
                tile.classList.add('manual-highlight-missing');
            }
        });
    }

    setInterval(setupBuyButtonListeners, 1000);
    setInterval(highlightItemTilesFromSavedPrices, 1000);
    const pageType = getPageType();
    console.log(`[ManualPrice] Detected page: ${pageType}`);
    alert(`ManualPrice] Detected page: ${pageType}`)

if (pageType === "itemmarket") {
    // Run Item Market-specific logic
} else if (pageType === "bazaar") {
    // Setup Bazaar-specific logic (next step)
}


    window.addEventListener('keydown', (e) => {
        if (e.key === "Escape") modal.style.display = "none";
    });
})();