Torn Shopping List

Add items directly from the Item Market onto a shopping list, with custom buy prices that highlight on the page, including Weav3r Bazaar listings!

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         Torn Shopping List
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Add items directly from the Item Market onto a shopping list, with custom buy prices that highlight on the page, including Weav3r Bazaar listings!
// @author       HeyItzWerty [3626448]
// @match        https://www.torn.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    let favorites = GM_getValue('torn_im_favorites', {});

    GM_addStyle(`
        :root { --tf-bg: rgba(34, 34, 34, 0.95); --tf-border: #444; --tf-text: #ddd; --tf-accent: #85b200; --tf-star: #ffd700; }
        
        body:not(.tf-market-active) #tf-fab, body:not(.tf-market-active) #tf-menu { display: none !important; }

        #tf-fab { display: none; position: fixed; right: 20px; bottom: 80px; width: 45px; height: 45px; background: var(--tf-bg); border: 2px solid var(--tf-accent); border-radius: 50%; cursor: pointer; align-items: center; justify-content: center; z-index: 999999; box-shadow: 0 4px 6px rgba(0,0,0,0.5); font-size: 20px; transition: transform 0.2s; }
        #tf-fab:hover { transform: scale(1.1); }
        
        #tf-menu { position: fixed; right: 10px; top: 100px; width: 260px; max-height: calc(100vh - 120px); background: var(--tf-bg); border: 1px solid var(--tf-border); border-radius: 8px; z-index: 999998; box-shadow: -2px 5px 15px rgba(0,0,0,0.7); overflow-y: auto; padding: 15px; color: var(--tf-text); font-family: 'Arial', sans-serif; backdrop-filter: blur(5px); }
        
        .tf-header { font-weight: bold; font-size: 14px; margin-bottom: 5px; text-align: center; letter-spacing: 1px;}
        .tf-author { text-align: center; font-size: 11px; margin-bottom: 20px; padding-bottom: 15px; border-bottom: 1px solid var(--tf-border); }
        .tf-author a { color: #aaa; text-decoration: none; transition: color 0.2s; }
        .tf-author a:hover { color: var(--tf-accent); }
        
        .tf-item { display: flex; justify-content: space-between; align-items: center; background: #333; margin-bottom: 8px; padding: 8px; border-radius: 6px; border-left: 4px solid var(--tf-border); transition: background 0.2s, border-color 0.2s; }
        .tf-item.has-price { border-left-color: var(--tf-accent); }
        .tf-item:hover { background: #3a3a3a; }
        .tf-item-name { font-size: 12px; font-weight: bold; flex-grow: 1; text-decoration: none; color: white; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-right: 10px; }
        .tf-item-name:hover { color: var(--tf-accent); }
        .tf-item-price { font-size: 11px; color: var(--tf-accent); font-weight: bold; margin-right: 10px; background: #111; padding: 3px 6px; border-radius: 4px; border: 1px solid #444; cursor: pointer; transition: color 0.2s, background 0.2s;}
        .tf-item-price:hover { background: #222; color: #fff; }
        .tf-item-price.no-price { color: #aaa; border-style: dashed; }
        .tf-btn-del { background: transparent; color: #aaa; border: none; cursor: pointer; font-size: 12px; padding: 0 5px; transition: color 0.2s; }
        .tf-btn-del:hover { color: #d9534f; }
        
        @media (max-width: 800px) { 
            body.tf-market-active #tf-fab { display: flex; }
            #tf-menu { right: 0; bottom: 0; left: 0; top: auto; width: 100%; height: 50vh; max-height: 50vh; border-radius: 15px 15px 0 0; transform: translateY(100%); transition: transform 0.3s ease; }
            #tf-menu.open { transform: translateY(0); }
        }
        
        .tf-tile-star-wrap { position: absolute; top: 4px; right: 4px; z-index: 10; background: rgba(0,0,0,0.5); border-radius: 50%; padding: 3px; display: flex; align-items: center; justify-content: center; }
        .tf-star-btn { background: transparent; border: none; font-size: 14px; cursor: pointer; transition: transform 0.2s, filter 0.2s; padding: 0; line-height: 1; filter: grayscale(100%) opacity(0.4); outline: none;}
        .tf-star-btn:hover { transform: scale(1.2); filter: grayscale(0%) drop-shadow(0 0 5px var(--tf-star)); opacity: 1; }
        .tf-star-btn.is-favorite { filter: grayscale(0%) drop-shadow(0 0 5px var(--tf-star)); opacity: 1; }
        
        .tf-deal-highlight { box-shadow: inset 0 0 15px rgba(133, 178, 0, 0.4) !important; border: 1px solid var(--tf-accent) !important; background-color: rgba(133, 178, 0, 0.15) !important; border-radius: 4px; }
    `);

    function buildUI() {
        if (document.getElementById('tf-fab')) return;

        let fab = document.createElement('div');
        fab.id = 'tf-fab';
        fab.innerHTML = '⭐';
        fab.title = "Open Shopping List";
        fab.onclick = () => document.getElementById('tf-menu').classList.toggle('open');
        document.body.appendChild(fab);

        let menu = document.createElement('div');
        menu.id = 'tf-menu';
        document.body.appendChild(menu);
        renderMenu();
    }

    function renderMenu() {
        let menu = document.getElementById('tf-menu');
        menu.innerHTML = `
            <div class="tf-header">🛒Torn Shopping List🛒</div>
            <div class="tf-author"><a href="https://www.torn.com/profiles.php?XID=3626448" target="_blank">Made by HeyItzWerty [3626448]</a></div>
        `;
        
        let keys = Object.keys(favorites);
        if(keys.length === 0) {
            menu.innerHTML += '<div style="text-align:center; font-size:12px; color:#aaa; padding: 20px 0;">Click the ⭐ on any item image to add a favorite.</div>';
            return;
        }

        keys.forEach(id => {
            let item = favorites[id];
            let row = document.createElement('div');
            let hasPrice = item.targetPrice > 0;
            row.className = 'tf-item' + (hasPrice ? ' has-price' : '');
            
            let priceText = hasPrice ? `$${item.targetPrice.toLocaleString()}` : '+ Set Price';
            let priceClass = hasPrice ? 'tf-item-price' : 'tf-item-price no-price';
            
            row.innerHTML = `
                <a href="/page.php?sid=ItemMarket#/market/view=search&itemID=${id}" class="tf-item-name" title="${item.name}">${item.name}</a>
                <span class="${priceClass}" data-id="${id}" title="Click to edit target buy price">${priceText}</span>
                <button class="tf-btn-del" data-id="${id}" title="Remove">✖</button>
            `;
            menu.appendChild(row);
        });

        menu.querySelectorAll('.tf-item-price').forEach(el => {
            el.addEventListener('click', function() {
                let idToEdit = this.getAttribute('data-id');
                let itemName = favorites[idToEdit].name;
                let currentPrice = favorites[idToEdit].targetPrice > 0 ? favorites[idToEdit].targetPrice : "";
                
                let target = prompt(`Set target BUY price for ${itemName}\n(Numbers only. Leave blank or 0 to clear):`, currentPrice);
                
                if (target !== null) {
                    let parsedPrice = parseInt(target.replace(/,/g, ''));
                    favorites[idToEdit].targetPrice = isNaN(parsedPrice) ? 0 : parsedPrice;
                    GM_setValue('torn_im_favorites', favorites);
                    
                    renderMenu();
                    forceHighlightRefresh();
                }
            });
        });

        menu.querySelectorAll('.tf-btn-del').forEach(btn => {
            btn.addEventListener('click', function() {
                let idToRemove = this.getAttribute('data-id');
                delete favorites[idToRemove];
                GM_setValue('torn_im_favorites', favorites);
                
                renderMenu();
                forceHighlightRefresh();
                
                document.querySelectorAll(`.tf-star-btn[data-item-id="${idToRemove}"]`).forEach(el => {
                    el.classList.remove('is-favorite');
                });
            });
        });
    }

    function forceHighlightRefresh() {
        document.querySelectorAll('.tf-hl-processed').forEach(el => {
            el.classList.remove('tf-hl-processed');
            el.classList.remove('tf-deal-highlight');
        });
        processDOM();
    }

    function processDOM() {
        // Star Injection into Item Tiles
        document.querySelectorAll('.itemTile___cbw7w:not(.tf-processed)').forEach(tile => {
            tile.classList.add('tf-processed');
            
            let infoBtn = tile.querySelector('button[aria-controls^="wai-itemInfo-"]');
            if(!infoBtn) return;
            
            let itemId = infoBtn.getAttribute('aria-controls').replace('wai-itemInfo-', '');
            let nameEl = tile.querySelector('.name___ukdHN');
            if(!nameEl || !itemId) return;
            
            let itemName = nameEl.innerText.trim();
            
            let starWrap = document.createElement('div');
            starWrap.className = 'tf-tile-star-wrap';
            
            let starBtn = document.createElement('button');
            starBtn.className = 'tf-star-btn' + (favorites[itemId] ? ' is-favorite' : '');
            starBtn.innerText = '⭐';
            starBtn.title = "Toggle Favorite";
            starBtn.setAttribute('data-item-id', itemId);
            
            starBtn.onclick = (e) => {
                e.stopPropagation(); e.preventDefault(); 
                
                if (favorites[itemId]) {
                    delete favorites[itemId];
                    starBtn.classList.remove('is-favorite');
                } else {
                    favorites[itemId] = { id: itemId, name: itemName, targetPrice: 0 };
                    starBtn.classList.add('is-favorite');
                }
                
                GM_setValue('torn_im_favorites', favorites);
                renderMenu();
                forceHighlightRefresh();
            };
            
            starWrap.appendChild(starBtn);
            
            let imgWrap = tile.querySelector('.imageWrapper___RqvUg');
            if(imgWrap) {
                imgWrap.style.position = 'relative'; 
                imgWrap.appendChild(starWrap);
            }
        });

        // Highlight Deals - Standard Torn Seller Rows
        document.querySelectorAll('.sellerRow___AI0m6:not(.tf-hl-processed)').forEach(row => {
            row.classList.add('tf-hl-processed');
            let priceEl = row.querySelector('.price___Uwiv2');
            let img = row.querySelector('img[src*="/images/items/"]');
            if(!priceEl || !img) return;
            
            let priceText = priceEl.innerText.replace(/[^0-9]/g, '');
            let price = parseInt(priceText);
            
            let match = img.src.match(/\/items\/(\d+)\//);
            if(!match) return;
            let itemId = match[1];
            
            if(favorites[itemId] && favorites[itemId].targetPrice > 0 && price <= favorites[itemId].targetPrice) {
                row.classList.add('tf-deal-highlight');
            }
        });

        // Highlight Deals - Weav3r Bazaar Cards (Supports both grid and list views)
        document.querySelectorAll('.bazaar-card:not(.tf-hl-processed), .bazaar-listing-card:not(.tf-hl-processed)').forEach(card => {
            card.classList.add('tf-hl-processed');
            let priceMatch = card.innerText.match(/\$([\d,]+)/);
            if(!priceMatch) return;
            let price = parseInt(priceMatch[1].replace(/,/g, ''));
            
            let container = card.closest('.bazaar-info-container');
            if(!container) return;
            let itemId = container.getAttribute('data-itemid');
            
            if(itemId && favorites[itemId] && favorites[itemId].targetPrice > 0 && price <= favorites[itemId].targetPrice) {
                card.classList.add('tf-deal-highlight');
            }
        });
    }

    function checkVisibility() {
        const url = window.location.href;
        const isMarket = url.includes('sid=ItemMarket') || url.includes('imarket.php') || url.includes('bazaar.php');
        
        if (isMarket) {
            document.body.classList.add('tf-market-active');
            processDOM();
        } else {
            document.body.classList.remove('tf-market-active');
        }
    }

    function init() {
        buildUI();
        
        let timeout;
        const observer = new MutationObserver(() => {
            clearTimeout(timeout);
            timeout = setTimeout(() => {
                checkVisibility();
            }, 150); 
        });
        
        observer.observe(document.body, { childList: true, subtree: true });
        checkVisibility();
    }

    if (document.readyState === 'complete') {
        init();
    } else {
        window.addEventListener('load', init);
    }
})();