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!

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

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

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

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