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!

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

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