Add items directly from the Item Market onto a shopping list, with custom buy prices that highlight on the page, including Weav3r Bazaar listings!
// ==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);
}
})();