Bazaar profit filter that highlights items over your custom threshold.
// ==UserScript==
// @name Snipe Scout
// @namespace http://tampermonkey.net/
// @version 2.35
// @description Bazaar profit filter that highlights items over your custom threshold.
// @author Pint-Shot-Riot
// @match https://www.torn.com/page.php?sid=ItemMarket*
// @match https://www.torn.com/bazaar.php*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addStyle
// @grant GM.xmlHttpRequest
// @license MIT
// ==/UserScript==
(function() {
'use strict';
let torn_market_values = {};
let minProfitHighlight = parseInt(GM_getValue("MinProfitHighlight", 100000));
let apiKey = GM_getValue("Torn_API_Key", "");
let lastUrl = location.href;
GM_addStyle(`
#snipe-pill {
position: fixed; z-index: 999999;
background: rgba(0, 0, 0, 0.9); border: 2px solid #378c37;
padding: 8px 16px; border-radius: 50px; color: #4fa34f;
font-weight: bold; cursor: move; display: flex; align-items: center;
box-shadow: 0 4px 15px rgba(0,0,0,0.5); font-family: sans-serif;
touch-action: none; user-select: none; font-size: 11px; letter-spacing: 1px;
}
#snipe-menu {
display: none; position: fixed; background: #111; border: 1px solid #333;
padding: 15px; border-radius: 12px; z-index: 999998; width: 240px;
box-shadow: 0 10px 30px rgba(0,0,0,0.8); font-family: sans-serif;
}
.snipe-hit {
outline: 4px solid #378c37 !important;
outline-offset: -4px !important;
background-color: rgba(55, 140, 55, 0.15) !important;
box-shadow: inset 0 0 15px rgba(0, 0, 0, 0.5) !important;
}
.snipe-label { color: #888; font-size: 10px; margin-top: 10px; display: block; text-transform: uppercase; }
.snipe-input {
width: 100%; background: #000; color: #4fa34f; border: 1px solid #333;
padding: 8px; border-radius: 4px; margin-top: 4px; box-sizing: border-box; font-size: 12px;
}
.snipe-btn {
position: relative; width: 100%; padding: 12px; margin-top: 12px;
background: #222; color: #eee; border: 1px solid #444;
border-radius: 6px; cursor: pointer; overflow: hidden; font-weight: bold;
}
#progress-fill {
position: absolute; left: 0; top: 0; height: 100%;
background: rgba(79, 163, 79, 0.3); width: 0%;
transition: width 0.3s ease; pointer-events: none;
}
`);
try {
torn_market_values = JSON.parse(GM_getValue("Torn_Market_Values", "{}"));
} catch (e) { torn_market_values = {}; }
function createUI() {
if (document.getElementById('snipe-pill')) return;
const pill = document.createElement('div');
pill.id = 'snipe-pill';
pill.innerHTML = `SNIPE`;
document.body.appendChild(pill);
const menu = document.createElement('div');
menu.id = 'snipe-menu';
menu.innerHTML = `
<div style="color:#4fa34f; font-size:13px; margin-bottom:5px; text-align:center; font-weight:bold; letter-spacing:0.5px;">SNIPE SCOUT</div>
<label class="snipe-label">Torn API Key</label>
<input type="text" id="snipe-key-input" class="snipe-input" value="${apiKey}" placeholder="Enter Key...">
<label class="snipe-label">Min Profit Target</label>
<input type="number" id="snipe-profit-input" class="snipe-input" value="${minProfitHighlight}">
<button id="sync-btn" class="snipe-btn">
<div id="progress-fill"></div>
<span id="sync-text">SAVE & SYNC</span>
</button>
`;
document.body.appendChild(menu);
let isDragging = false, xOffset = GM_getValue("pillX", 10), yOffset = GM_getValue("pillY", 150), initialX, initialY;
const updatePosition = (x, y) => {
pill.style.left = x + "px";
pill.style.top = y + "px";
menu.style.left = x + "px";
menu.style.top = (y + 40) + "px";
};
updatePosition(xOffset, yOffset);
pill.onpointerdown = (e) => {
isDragging = true;
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
pill.setPointerCapture(e.pointerId);
};
document.onpointermove = (e) => {
if (isDragging) {
xOffset = e.clientX - initialX;
yOffset = e.clientY - initialY;
updatePosition(xOffset, yOffset);
}
};
pill.onpointerup = () => {
isDragging = false;
GM_setValue("pillX", xOffset);
GM_setValue("pillY", yOffset);
};
pill.onclick = (e) => {
if (Math.abs(e.clientX - (initialX + xOffset)) > 5) return;
const isVisible = menu.style.display === "block";
menu.style.display = isVisible ? "none" : "block";
if (!isVisible) updatePosition(xOffset, yOffset);
};
document.getElementById('sync-btn').onclick = () => {
const btn = document.getElementById('sync-btn'), fill = document.getElementById('progress-fill'), text = document.getElementById('sync-text');
apiKey = document.getElementById('snipe-key-input').value.trim();
minProfitHighlight = parseInt(document.getElementById('snipe-profit-input').value) || 0;
GM_setValue("Torn_API_Key", apiKey); GM_setValue("MinProfitHighlight", minProfitHighlight);
if (!apiKey) return;
btn.disabled = true; text.innerText = "SYNCING..."; fill.style.width = "20%";
GM.xmlHttpRequest({
method: "GET",
url: `https://api.torn.com/torn/?key=${apiKey}&selections=items`,
onload: (res) => {
const data = JSON.parse(res.responseText);
if (data.items) {
torn_market_values = Object.fromEntries(Object.entries(data.items).map(([id, item]) => [id, item.market_value || 0]));
GM_setValue("Torn_Market_Values", JSON.stringify(torn_market_values));
fill.style.width = "100%"; text.innerText = "SYNC OK!";
setTimeout(() => { text.innerText = "SAVE & SYNC"; fill.style.width = "0%"; btn.disabled = false; }, 2000);
processElements();
}
}
});
};
}
function processElements() {
const items = document.querySelectorAll('li, [class*="listItem"], [class*="row___"], [class*="sellerRow"]');
items.forEach(container => {
const img = container.querySelector('img[src*="/items/"], img[src*="/images/items/"]');
if (!img) return;
const idMatch = img.src.match(/\/(\d+)\//) || img.src.match(/\/(\d+)\.png/);
const itemId = idMatch ? idMatch[1] : null;
if (!itemId || !torn_market_values[itemId]) return;
const textContent = container.innerText || "";
const priceMatch = textContent.match(/\$[\d,]+/);
if (!priceMatch) return;
const currentPrice = parseInt(priceMatch[0].replace(/[^0-9]/g, ''));
const marketValue = torn_market_values[itemId];
if (currentPrice > 0 && (marketValue - currentPrice) >= minProfitHighlight) {
container.classList.add('snipe-hit');
} else {
container.classList.remove('snipe-hit');
}
});
}
setInterval(() => {
if (location.href !== lastUrl) {
lastUrl = location.href;
processElements();
}
}, 500);
const observer = new MutationObserver(() => processElements());
observer.observe(document.body, { childList: true, subtree: true });
createUI();
processElements();
})();