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!

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

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