// ==UserScript==
// @name Bazaar Listings on Market *NO PDA* (TE Market Value, No API Key)
// @namespace https://weav3r.dev/
// @version 2.3.4
// @description Bazaar listings on Item Page
// @author WTV [3281931]
// @match https://www.torn.com/*
// @grant GM_xmlhttpRequest
// @license MIT
// ==/UserScript==
(function() {
'use strict';
let currentSortKey = 'price';
let currentSortOrder = 'asc';
let allListings = [];
let filteredListings = [];
window._visitedBazaars = new Set();
function sortListings(listings) {
const key = currentSortKey, order = currentSortOrder;
return [...listings].sort((a,b)=>{
let valA = a[key] || 0, valB = b[key] || 0;
if(key==='player_name'){ valA=valA.toLowerCase(); valB=valB.toLowerCase(); }
return (valA>valB?1:valA<valB?-1:0)*(order==='asc'?1:-1);
});
}
function applyFilters(listings, filters) {
return listings.filter(l=>{
if(filters.minPrice && l.price < parseFloat(filters.minPrice)) return false;
if(filters.maxPrice && l.price > parseFloat(filters.maxPrice)) return false;
if(filters.minQty && l.quantity < parseInt(filters.minQty)) return false;
if(filters.maxQty && l.quantity > parseInt(filters.maxQty)) return false;
return true;
});
}
function renderMessage(container,isError){
const cardContainer = container.querySelector('.bazaar-card-container');
if(!cardContainer) return;
cardContainer.innerHTML = '';
const msg = document.createElement('div');
msg.style.cssText='color:#fff;text-align:center;padding:20px;width:100%;';
msg.innerHTML = isError ? "API Error<br><span style='font-size:12px;color:#ccc;'>Please try again later</span>"
: "No bazaar listings available for this item.";
cardContainer.appendChild(msg);
}
function createInfoContainer(itemName,itemId,marketValue){
const container=document.createElement('div');
container.className='bazaar-info-container';
container.dataset.itemid=itemId;
if(marketValue) container.dataset.marketValue = marketValue;
container.style.cssText='border:1px solid #888;margin:10px 0;padding:5px;background:#222;color:#fff;';
const marketText = marketValue ? ` <span style="color:#FFD700;">(Market Value: ${marketValue})</span>` : '';
container.innerHTML=`
<div class="bazaar-info-header" style="font-weight:bold;margin-bottom:5px;">
Bazaar Listings for ${itemName}${marketText}
</div>
<div class="bazaar-controls" style="margin-bottom:5px; display:flex; gap:5px; flex-wrap:wrap;"></div>
<div class="bazaar-card-container" style="display:flex;overflow-x:auto;padding:5px;gap:5px;"></div>
`;
const cardContainer = container.querySelector('.bazaar-card-container');
if(cardContainer){
cardContainer.addEventListener("wheel", e=>{
if(e.deltaY!==0){ e.preventDefault(); cardContainer.scrollLeft += e.deltaY; }
});
}
return container;
}
function createFilters(container){
const controls=container.querySelector('.bazaar-controls');
controls.innerHTML='';
const sortSelect=document.createElement('select');
['price','quantity','player_name'].forEach(opt=>{
const o=document.createElement('option'); o.value=opt; o.textContent=opt.charAt(0).toUpperCase()+opt.slice(1);
if(opt===currentSortKey) o.selected=true;
sortSelect.appendChild(o);
});
sortSelect.addEventListener('change', ()=>{ currentSortKey=sortSelect.value; renderCards(container); });
const orderBtn=document.createElement('button');
orderBtn.textContent=currentSortOrder==='asc'?'Asc':'Desc';
orderBtn.addEventListener('click', ()=>{
currentSortOrder=currentSortOrder==='asc'?'desc':'asc';
orderBtn.textContent=currentSortOrder==='asc'?'Asc':'Desc';
renderCards(container);
});
const minPrice=document.createElement('input'); minPrice.type='number'; minPrice.placeholder='Min Price'; minPrice.style.width='80px';
const maxPrice=document.createElement('input'); maxPrice.type='number'; maxPrice.placeholder='Max Price'; maxPrice.style.width='80px';
const minQty=document.createElement('input'); minQty.type='number'; minQty.placeholder='Min Qty'; minQty.style.width='80px';
const maxQty=document.createElement('input'); maxQty.type='number'; maxQty.placeholder='Max Qty'; maxQty.style.width='80px';
const applyBtn=document.createElement('button'); applyBtn.textContent="Apply";
applyBtn.addEventListener('click', ()=>renderCards(container));
[minPrice,maxPrice,minQty,maxQty].forEach(inp=>{
inp.addEventListener('keydown', e=>{ if(e.key==='Enter'){ renderCards(container); } });
});
controls.appendChild(sortSelect);
controls.appendChild(orderBtn);
[minPrice,maxPrice,minQty,maxQty].forEach(inp=>controls.appendChild(inp));
controls.appendChild(applyBtn);
}
function renderCards(container){
const cardContainer=container.querySelector('.bazaar-card-container');
if(!cardContainer || !allListings) return;
const controls=container.querySelector('.bazaar-controls');
const filters={
minPrice: controls.querySelector('input[placeholder="Min Price"]').value,
maxPrice: controls.querySelector('input[placeholder="Max Price"]').value,
minQty: controls.querySelector('input[placeholder="Min Qty"]').value,
maxQty: controls.querySelector('input[placeholder="Max Qty"]').value
};
filteredListings=applyFilters(allListings,filters);
filteredListings=sortListings(filteredListings);
cardContainer.innerHTML='';
if(filteredListings.length===0){ renderMessage(container,false); return; }
const marketValueStr = container.dataset.marketValue;
const marketNum = marketValueStr ? parseInt(marketValueStr.replace(/\D/g,'')) : null;
filteredListings.forEach(listing=>{
const card=document.createElement('div');
const isVisited=window._visitedBazaars.has(listing.player_id);
card.style.cssText=`
border:1px solid #444;
background:#222;
color:#eee;
padding:10px;
margin:2px;
min-width:160px;
cursor:pointer;
display:flex;
flex-direction:column;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-size:15px;
transition:transform 0.2s;
position: relative;
`;
if(!isVisited){
card.addEventListener('mouseenter', ()=>card.style.transform='scale(1.03)');
card.addEventListener('mouseleave', ()=>card.style.transform='scale(1)');
}
const formattedPrice = `$${Math.round(listing.price).toLocaleString()}`;
let diffText = '';
if(marketNum){
const percent = ((listing.price - marketNum)/marketNum*100).toFixed(1);
const color = percent < 0 ? 'limegreen' : 'red';
const sign = percent > 0 ? '+' : '';
diffText = `<span style="position:absolute; right:10px; top:50%; transform:translateY(-50%); font-weight:bold; color:${color};">${sign}${percent}%</span>`;
}
card.innerHTML=`
<div style="font-weight:bold; color:${isVisited?'#800080':'#1E90FF'}">${listing.player_name || 'Unknown'}</div>
<div>Qty: ${listing.quantity}</div>
<div>Price: ${formattedPrice}</div>
${diffText}
`;
card.addEventListener('click', ()=>{
if(listing.player_id){
window._visitedBazaars.add(listing.player_id);
const nameDiv = card.querySelector('div:first-child');
if(nameDiv) nameDiv.style.color='#800080';
window.open(`https://www.torn.com/bazaar.php?userId=${listing.player_id}&highlightItem=${listing.item_id}#/`,'_blank');
}
});
cardContainer.appendChild(card);
});
}
function updateInfoContainer(wrapper,itemId,itemName){
let infoContainer=document.querySelector(`.bazaar-info-container[data-itemid="${itemId}"]`);
if(!infoContainer){
// Fetch market value from TornExchange public API
GM_xmlhttpRequest({
method: "GET",
url: `https://tornexchange.com/api/te_price?item_id=${itemId}`,
onload: function(response){
let marketValue = '';
try{
const data = JSON.parse(response.responseText);
if(data && data.status === 'success' && data.data && data.data.te_price){
const rounded = Math.round(data.data.te_price);
marketValue = `$${rounded.toLocaleString()}`;
}
} catch(e){}
// create the info container with marketValue
infoContainer = createInfoContainer(itemName, itemId, marketValue);
wrapper.insertBefore(infoContainer, wrapper.firstChild);
createFilters(infoContainer);
fetchBazaarListings(itemId, infoContainer);
},
onerror:function(){
infoContainer = createInfoContainer(itemName, itemId,'');
wrapper.insertBefore(infoContainer, wrapper.firstChild);
createFilters(infoContainer);
fetchBazaarListings(itemId, infoContainer);
}
});
} else {
renderCards(infoContainer);
}
}
function fetchBazaarListings(itemId, infoContainer){
GM_xmlhttpRequest({
method:"GET",
url:`https://weav3r.dev/api/marketplace/${itemId}`,
onload:function(response){
try{
const data = JSON.parse(response.responseText);
if(!data || !data.listings){ renderMessage(infoContainer,true); return; }
allListings = data.listings.map(l=>({
player_name:l.player_name,
player_id:l.player_id,
quantity:l.quantity,
price:l.price,
item_id:l.item_id
}));
renderCards(infoContainer);
} catch(e){ renderMessage(infoContainer,true); }
},
onerror:function(){ renderMessage(infoContainer,true); }
});
}
function processSellerWrapper(wrapper){
if(!wrapper || wrapper.dataset.bazaarProcessed) return;
const itemTile = wrapper.closest('[class*="itemTile"]') || wrapper.previousElementSibling;
if(!itemTile) return;
let nameEl = itemTile.querySelector('div[class*="name"]') || itemTile.querySelector('div');
const btn = itemTile.querySelector('button[aria-controls*="itemInfo"]');
if(!nameEl || !btn) return;
const itemName = nameEl.textContent.trim();
const idParts = btn.getAttribute('aria-controls').split('-');
const itemId = idParts[idParts.length-1];
wrapper.dataset.bazaarProcessed='true';
updateInfoContainer(wrapper,itemId,itemName);
}
function processAllSellerWrappers(root=document.body){
const sellerWrappers=root.querySelectorAll('[class*="sellerListWrapper"]');
sellerWrappers.forEach(wrapper=>processSellerWrapper(wrapper));
}
const observer=new MutationObserver(()=>{ processAllSellerWrappers(); });
observer.observe(document.body,{childList:true,subtree:true});
processAllSellerWrappers();
})();
// --- Bazaar Page Green Highlight ---
(function(){
const params=new URLSearchParams(window.location.search);
const itemIdToHighlight=params.get('highlightItem');
if(!itemIdToHighlight) return;
const observer=new MutationObserver(()=>{
const imgs=document.querySelectorAll('img');
imgs.forEach(img=>{
if(img.src.includes(`/images/items/${itemIdToHighlight}/`)){
img.closest('div')?.style.setProperty('outline','3px solid green','important');
img.scrollIntoView({behavior:'smooth', block:'center'});
}
});
});
observer.observe(document.body,{childList:true,subtree:true});
})();