Final PC Version: Shows Bazaar listing on Item Market with TE Data, offering sort/filter controls and a permanent player-link visited state. Optimized with Promises and better structure.
目前為
// ==UserScript==
// @name Bazaar + TE Info
// @namespace https://weav3r.dev/
// @version 2.0
// @description Final PC Version: Shows Bazaar listing on Item Market with TE Data, offering sort/filter controls and a permanent player-link visited state. Optimized with Promises and better structure.
// @author WTV [3281931]
// @match https://www.torn.com/*
// @grant GM_xmlhttpRequest
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// Global State Variables
window._visitedBazaars = new Set();
window._cachedListings = {};
window._activeSort = {
type: 'price',
dir: 'asc'
};
// --- UTILITY FUNCTIONS ---
/**
* Converts GM_xmlhttpRequest into a standard Promise for cleaner async handling.
* @param {string} url - The API endpoint URL.
* @returns {Promise<Object|null>} - JSON data or null on failure.
*/
function fetchApi(url) {
return new Promise(resolve => {
GM_xmlhttpRequest({
method: "GET",
url: url,
onload: function(response) {
try {
const data = JSON.parse(response.responseText);
if (data && data.status === 'success') {
resolve(data);
} else {
resolve(null);
}
} catch (e) {
resolve(null);
}
},
onerror: function() {
resolve(null);
}
});
});
}
// --- API DATA FETCHING (Optimized with async/await) ---
async function fetchProfileData(userId) {
const url = `https://tornexchange.com/api/profile?user_id=${userId}`;
const data = await fetchApi(url);
if (data && data.data) {
return {
votes: data.data.votes,
torn_id: data.data.torn_id // The Torn XID required for the link
};
}
return { votes: null, torn_id: null };
}
async function fetchTornExchangeData(itemId) {
let marketValue = '';
let bestBuyer = null;
let upvoteCount = null;
let traderId = null;
// Execute te_price and best_listing concurrently
const [tePriceData, bestListingData] = await Promise.all([
fetchApi(`https://tornexchange.com/api/te_price?item_id=${itemId}`),
fetchApi(`https://tornexchange.com/api/best_listing?item_id=${itemId}`)
]);
// 1. Process TE Price
if (tePriceData && tePriceData.data && tePriceData.data.te_price) {
const rounded = Math.round(tePriceData.data.te_price);
marketValue = `$${rounded.toLocaleString()}`;
}
// 2. Process Best Listing
if (bestListingData && bestListingData.data && bestListingData.data.price) {
const buyerTeId = bestListingData.data.trader_id;
bestBuyer = {
price: bestListingData.data.price,
trader: bestListingData.data.trader || null,
player_id: buyerTeId || null
};
// 3. Fetch Profile Data (Conditional and Awaited)
if (buyerTeId) {
const profile = await fetchProfileData(buyerTeId);
upvoteCount = profile.votes;
traderId = profile.torn_id;
}
}
return { marketValue, bestBuyer, upvoteCount, traderId };
}
// --- RENDERING FUNCTIONS ---
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;'>Could not fetch bazaar data. (Weaver API)</span>"
: "No bazaar listings available for this item.";
cardContainer.appendChild(msg);
const countSpan = container.querySelector('.bazaar-count-info');
if(countSpan) countSpan.textContent = '';
}
function createBestBuyerHTML(bestBuyer, upvoteCount, traderId, encodedItemName) {
const listingsLink = `https://tornexchange.com/listings?model_name_contains=${encodedItemName}&order_by=&status=`;
const teListingsLinkHTML = `<a href="${listingsLink}" target="_blank" style="color:#00BFFF; font-size: 14px; text-decoration: none; white-space:nowrap; margin-left: 5px; font-weight: bold;">(TE Listings)</a>`;
let bestBuyerInfoHTML = '';
if (bestBuyer && bestBuyer.price && bestBuyer.trader) {
const formattedPrice = `$${Math.round(bestBuyer.price).toLocaleString()}`;
const traderName = bestBuyer.trader;
let upvoteText = upvoteCount ? ` (⭐ ${upvoteCount} Upvotes)` : '';
if (traderId) {
// The critical link construction using the resolved Torn XID
const profileLink = `https://www.torn.com/profiles.php?XID=${traderId}`;
const traderLinkHTML = `
<a href="${profileLink}" target="_blank" style="color:#1E90FF; text-decoration:none; font-weight:bold; cursor:pointer;"
onmouseover="this.style.textDecoration='underline';"
onmouseout="this.style.textDecoration='none';">
${traderName}
</a>
`;
const priceDisplayHTML = `<span style="color:lime; font-weight:bold; white-space:nowrap; font-size: 16px;">${formattedPrice}</span>`;
bestBuyerInfoHTML = `
<span style="white-space:nowrap;">Best Trader: ${priceDisplayHTML}</span>
<span style="white-space:nowrap;">by ${traderLinkHTML}${upvoteText}${teListingsLinkHTML}</span>
`;
} else {
// Fallback if traderId is null
const priceDisplayHTML = `<span style="color:lime; font-weight:bold; font-size: 16px;">${formattedPrice}</span>`;
bestBuyerInfoHTML = `
<span style="white-space:nowrap;">Best Trader: ${priceDisplayHTML}</span>
<span style="white-space:nowrap;">by <span style="color:#1E90FF;">${traderName}</span>${upvoteText}${teListingsLinkHTML}</span>
`;
}
} else if (bestBuyer && bestBuyer.price) {
const formattedPrice = `$${Math.round(bestBuyer.price).toLocaleString()}`;
const priceDisplayHTML = `<span style="color:lime; font-weight:bold; font-size: 16px;">${formattedPrice}</span>`;
bestBuyerInfoHTML = `
<span style="white-space:nowrap;">Best Trader: ${priceDisplayHTML}</span>
${teListingsLinkHTML}
`;
} else {
bestBuyerInfoHTML = `${teListingsLinkHTML}`;
}
return bestBuyerInfoHTML;
}
function createInfoContainer(itemName, itemId, marketValue, bestBuyer, upvoteCount, traderId) {
const container = document.createElement('div');
container.className = 'bazaar-info-container';
container.dataset.itemid = itemId;
if (marketValue) container.dataset.marketValue = marketValue;
if (traderId) container.dataset.bestBuyerId = traderId;
container.style.cssText = 'border:1px solid #888;margin:10px 0;padding:5px;background:#222;color:#fff;';
const marketText = marketValue ? ` <span style="color:#FFD700; flex-shrink: 0; font-size: 14px;">(Market Value: ${marketValue})</span>` : '';
const encodedItemName = encodeURIComponent(itemName);
const bestBuyerInfoHTML = createBestBuyerHTML(bestBuyer, upvoteCount, traderId, encodedItemName);
const itemIdHTML = `
<span style="color:#aaa; font-size:13px; font-weight: bold; white-space: nowrap; margin-left: auto;">
Item #: ${itemId}
</span>
`;
const bestBuyerHTML = `
<div class="best-buyer-line" style="font-weight:bold; margin-bottom: 5px; color:#FFA500; display: flex; flex-wrap: wrap; justify-content: flex-start; align-items: center; gap: 5px; font-size: 14px;">
${bestBuyerInfoHTML}
${itemIdHTML}
</div>
`;
// --- Filter and Sort Controls ---
const filterControlsHTML = `
<div class="bazaar-control-row" style="display:flex; flex-direction: column; gap:8px; margin: 5px 0 8px 0;">
<div class="bazaar-control-line" style="display: flex; flex-wrap: wrap; gap: 8px; align-items: center;">
<div class="bazaar-price-controls" style="display: flex; gap: 4px; align-items: center; flex-shrink: 0; white-space: nowrap;">
<button class="bazaar-filter-toggle-btn" data-filter-type="price" style="background:#007bff; color:white; border:none; padding:4px 8px; cursor:pointer; font-weight:bold; height: 28px; width: 65px; font-size: 12px;">
Price
</button>
<div style="display: flex; flex-direction: column; height: 28px; justify-content: center; align-items: center; padding-right: 4px;">
<span class="bazaar-sort-btn" data-sort-by="price" data-sort-dir="asc" style="color:#00BFFF; font-weight:bold; cursor:pointer; font-size: 14px; line-height: 1; margin: 0; padding: 0;">
🔼
</span>
<span class="bazaar-sort-btn" data-sort-by="price" data-sort-dir="desc" style="color:#555; font-weight:bold; cursor:pointer; font-size: 14px; line-height: 1; margin: 0; padding: 0;">
🔽
</span>
</div>
</div>
<div class="bazaar-quantity-controls" style="display: flex; gap: 4px; align-items: center; flex-shrink: 0; white-space: nowrap;">
<button class="bazaar-filter-toggle-btn" data-filter-type="quantity" style="background:#555; color:white; border:none; padding:4px 8px; cursor:pointer; font-weight:bold; height: 28px; width: 65px; font-size: 12px;">
Qty
</button>
<div style="display: flex; flex-direction: column; height: 28px; justify-content: center; align-items: center; padding-right: 4px;">
<span class="bazaar-sort-btn" data-sort-by="quantity" data-sort-dir="asc" style="color:#555; font-weight:bold; cursor:pointer; font-size: 14px; line-height: 1; margin: 0; padding: 0;">
🔼
</span>
<span class="bazaar-sort-btn" data-sort-by="quantity" data-sort-dir="desc" style="color:#555; font-weight:bold; cursor:pointer; font-size: 14px; line-height: 1; margin: 0; padding: 0;">
🔽
</span>
</div>
</div>
<div class="bazaar-filter-inputs-group" style="display: flex; gap: 8px; align-items: center; flex-wrap: wrap;">
<div class="bazaar-filter-group-price" style="display: flex; align-items:center; gap:4px; flex-shrink: 0;">
<input type="number" placeholder="Min Price" class="bazaar-filter-input" data-filter-type="minPrice" style="width: 70px; padding: 4px; background: #333; border: 1px solid #444; color: white; height: 28px; box-sizing: border-box; font-size: 12px;">
<input type="number" placeholder="Max Price" class="bazaar-filter-input" data-filter-type="maxPrice" style="width: 70px; padding: 4px; background: #333; border: 1px solid #444; color: white; height: 28px; box-sizing: border-box; font-size: 12px;">
</div>
<div class="bazaar-filter-group-quantity" style="display: none; align-items:center; gap:4px; flex-shrink: 0;">
<input type="number" placeholder="Min Qty" class="bazaar-filter-input" data-filter-type="minQty" style="width: 60px; padding: 4px; background: #333; border: 1px solid #444; color: white; height: 28px; box-sizing: border-box; font-size: 12px;">
<input type="number" placeholder="Max Qty" class="bazaar-filter-input" data-filter-type="maxQty" style="width: 60px; padding: 4px; background: #333; border: 1px solid #444; color: white; height: 28px; box-sizing: border-box; font-size: 12px;">
</div>
<button class="bazaar-apply-btn" style="background:#28a745; color:white; border:none; padding:4px 8px; cursor:pointer; font-weight:bold; height: 28px; flex-shrink: 0; font-size: 12px;">
Apply
</button>
<button class="bazaar-reset-all-btn" style="background:#444; color:white; border:none; padding:4px 8px; cursor:pointer; font-weight:bold; height: 28px; flex-shrink: 0; font-size: 14px; margin-left: 5px;" title="Reset Filters and Visited Links">
↺
</button>
</div>
</div>
</div>
`;
// --- END Filter and Sort Controls ---
container.innerHTML = `
<div class="bazaar-info-header" style="font-weight:bold;margin-bottom:5px; display: flex; flex-wrap: nowrap; justify-content: space-between; align-items: center; font-size: 14px;">
<span class="bazaar-title" style="flex-grow: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">Bazaar Listings for ${itemName}${marketText}</span>
<span class="bazaar-count-info" style="font-size: 13px; color: #aaa; font-weight: normal; flex-shrink: 0; margin-left: 10px;"></span>
</div>
${bestBuyerHTML}
${filterControlsHTML}
<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; }
});
}
addFilterListeners(container, itemId);
return container;
}
// --- Core Logic ---
function sortAndFilterListings(itemId, container) {
let listings = window._cachedListings[itemId];
if (!listings) return;
const sortType = window._activeSort.type;
const sortDir = window._activeSort.dir;
const minPrice = parseFloat(container.querySelector('[data-filter-type="minPrice"]').value) || null;
const maxPrice = parseFloat(container.querySelector('[data-filter-type="maxPrice"]').value) || null;
const minQty = parseInt(container.querySelector('[data-filter-type="minQty"]').value) || null;
const maxQty = parseInt(container.querySelector('[data-filter-type="maxQty"]').value) || null;
const isFiltered = minPrice !== null || maxPrice !== null || minQty !== null || maxQty !== null;
let filteredListings = listings.slice().filter(listing => {
const price = parseFloat(listing.price.toString().replace(/,/g, ''));
const qty = parseInt(listing.quantity);
if (minPrice !== null && price < minPrice) return false;
if (maxPrice !== null && price > maxPrice) return false;
if (minQty !== null && qty < minQty) return false;
if (maxQty !== null && qty > maxQty) return false;
return true;
});
filteredListings.sort((a, b) => {
let primaryValA, primaryValB;
if (sortType === 'price') {
primaryValA = parseFloat(a.price.toString().replace(/,/g, ''));
primaryValB = parseFloat(b.price.toString().replace(/,/g, ''));
} else {
primaryValA = parseInt(a.quantity);
primaryValB = parseInt(b.quantity);
}
let comparison = 0;
if (sortDir === 'asc') { comparison = primaryValA - primaryValB; }
else if (sortDir === 'desc') { comparison = primaryValB - primaryValA; }
if (comparison === 0) {
const priceA = parseFloat(a.price.toString().replace(/,/g, ''));
const priceB = parseFloat(b.price.toString().replace(/,/g, ''));
return priceA - priceB;
}
return comparison;
});
const marketValueStr = container.dataset.marketValue;
const marketNum = marketValueStr ? parseInt(marketValueStr.replace(/\D/g,'')) : null;
renderCards(container, filteredListings, marketNum, isFiltered);
}
function resetAllState(container, itemId) {
window._visitedBazaars.clear();
container.querySelector('[data-filter-type="minPrice"]').value = '';
container.querySelector('[data-filter-type="maxPrice"]').value = '';
container.querySelector('[data-filter-type="minQty"]').value = '';
container.querySelector('[data-filter-type="maxQty"]').value = '';
sortAndFilterListings(itemId, container);
}
function addFilterListeners(container, itemId) {
const priceFilterGroup = container.querySelector('.bazaar-filter-group-price');
const quantityFilterGroup = container.querySelector('.bazaar-filter-group-quantity');
const sortBtns = container.querySelectorAll('.bazaar-sort-btn');
const filterToggleBtns = container.querySelectorAll('.bazaar-filter-toggle-btn');
const applyBtn = container.querySelector('.bazaar-apply-btn');
const resetAllBtn = container.querySelector('.bazaar-reset-all-btn');
const defaultColor = '#555';
const activeColor = '#00BFFF';
const reverseColor = '#dc3545';
const updateSortVisuals = () => {
filterToggleBtns.forEach(btn => {
const filterType = btn.dataset.filterType;
if (filterType === window._activeSort.type) {
btn.style.background = activeColor;
} else {
btn.style.background = defaultColor;
}
});
sortBtns.forEach(btn => {
const type = btn.dataset.sortBy;
const dir = btn.dataset.sortDir;
let color = defaultColor;
if (type === window._activeSort.type && dir === window._activeSort.dir) {
color = dir === 'asc' ? activeColor : reverseColor;
} else if (type === window._activeSort.type) {
color = '#777';
}
btn.style.color = color;
});
};
sortBtns.forEach(btn => {
btn.addEventListener('click', () => {
window._activeSort.type = btn.dataset.sortBy;
window._activeSort.dir = btn.dataset.sortDir;
updateSortVisuals();
sortAndFilterListings(itemId, container);
});
});
filterToggleBtns.forEach(btn => {
btn.addEventListener('click', () => {
const filterType = btn.dataset.filterType;
const targetGroup = filterType === 'price' ? priceFilterGroup : quantityFilterGroup;
const wasActive = btn.classList.contains('active-filter');
priceFilterGroup.style.display = 'none';
quantityFilterGroup.style.display = 'none';
applyBtn.style.display = 'none';
filterToggleBtns.forEach(b => b.classList.remove('active-filter'));
if (!wasActive) {
targetGroup.style.display = 'flex';
applyBtn.style.display = 'block';
btn.classList.add('active-filter');
}
updateSortVisuals();
sortAndFilterListings(itemId, container);
});
});
applyBtn.addEventListener('click', () => {
sortAndFilterListings(itemId, container);
});
if (resetAllBtn) {
resetAllBtn.addEventListener('click', () => {
resetAllState(container, itemId);
});
}
updateSortVisuals();
const initialPriceToggle = container.querySelector('.bazaar-filter-toggle-btn[data-filter-type="price"]');
if (initialPriceToggle) {
initialPriceToggle.style.background = activeColor;
initialPriceToggle.classList.add('active-filter');
priceFilterGroup.style.display = 'flex';
applyBtn.style.display = 'block';
}
container.querySelectorAll('.bazaar-filter-input').forEach(input => {
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
input.blur();
sortAndFilterListings(itemId, container);
}
});
input.addEventListener('blur', () => sortAndFilterListings(itemId, container));
});
}
function renderCards(container, listings, marketNum, isFiltered){
const cardContainer=container.querySelector('.bazaar-card-container');
if(!cardContainer || !listings) return;
cardContainer.innerHTML='';
if(listings.length===0){
const msg = document.createElement('div');
msg.style.cssText='color:#fff;text-align:center;padding:20px;width:100%;';
msg.innerHTML = "No bazaar listings match the current filters.";
cardContainer.appendChild(msg);
return;
}
const countSpan = container.querySelector('.bazaar-count-info');
if (countSpan) {
const totalListings = window._cachedListings[container.dataset.itemid].length;
if (isFiltered && listings.length < totalListings) {
countSpan.innerHTML = `(Displaying ${listings.length} Listings - Filtered)`;
countSpan.style.color = '#FFA500';
} else if (totalListings > 100) {
countSpan.innerHTML = `(Displaying 100+ Listings)`;
countSpan.style.color = 'orange';
} else if (totalListings > 0) {
countSpan.innerHTML = `(Displaying ${totalListings} Listings)`;
countSpan.style.color = '#aaa';
} else {
countSpan.textContent = '';
}
}
const bestBuyerId = container.dataset.bestBuyerId;
listings.forEach(listing=>{
const card=document.createElement('div');
card.className = 'bazaar-card';
card.dataset.playerId = listing.player_id;
const isVisited=window._visitedBazaars.has(listing.player_id);
const isBestBuyer = bestBuyerId && listing.player_id == bestBuyerId;
if (isBestBuyer) card.classList.add('is-best-buyer');
const bazaarLink = `https://www.torn.com/bazaar.php?userId=${listing.player_id}&highlightItem=${listing.item_id}#/`;
const linkColor = isVisited ? '#800080' : '#1E90FF';
let cardStyle = `
border:1px solid #444;
background:#222;
color:#eee;
padding:10px;
margin:2px;
width: 125px;
flex-shrink: 0;
cursor:pointer;
display:flex;
flex-direction:column;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-size:15px;
transition:transform 0.2s, border 0.2s, background 0.2s;
position: relative;
gap: 3px;
`;
if (isBestBuyer) {
cardStyle += `border: 2px solid #28a745 !important; background: #333 !important;`;
}
card.style.cssText = cardStyle;
if(!isVisited){
card.addEventListener('mouseenter', ()=>card.style.transform='scale(1.03)');
card.addEventListener('mouseleave', ()=>card.style.transform='scale(1)');
}
const priceNum = parseFloat(listing.price.toString().replace(/,/g, ''));
const formattedPrice = `$${Math.round(priceNum).toLocaleString()}`;
let diffTextHTML = '';
if(marketNum){
const percent = ((priceNum - marketNum)/marketNum*100).toFixed(1);
let color = 'gold';
if (percent < -0.5) { color = 'limegreen'; }
else if (percent > 0.5) { color = 'red'; }
const sign = percent > 0 ? '+' : '';
diffTextHTML = `<span style="font-weight:bold; color:${color};">${sign}${percent}%</span>`;
}
card.innerHTML=`
<a href="${bazaarLink}" target="_blank" data-linkclump="true"
style="font-weight:bold; color:${linkColor}; text-decoration:none; cursor:pointer; font-size: 15px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
onmouseover="this.style.textDecoration='underline';"
onmouseout="this.style.textDecoration='none';">
${listing.player_name || 'Unknown'}
</a>
<div style="font-size: 14px; white-space: nowrap;"><b>Price:</b> ${formattedPrice}</div>
<div style="font-size: 14px; display: flex; justify-content: space-between; align-items: baseline; line-height: 1; margin-bottom: 0;">
<span style="white-space: nowrap;"><b>Qty:</b> ${listing.quantity}</span>
<span style="white: nowrap;">${diffTextHTML}</span>
</div>
`;
card.addEventListener('click', (e)=>{
const link = e.currentTarget.querySelector('a:first-child');
if(listing.player_id && link){
window._visitedBazaars.add(listing.player_id);
link.style.color='#800080';
}
});
cardContainer.appendChild(card);
});
}
// --- MAIN EXECUTION FLOW ---
async function updateInfoContainer(wrapper,itemId,itemName){
let infoContainer=document.querySelector(`.bazaar-info-container[data-itemid="${itemId}"]`);
if(!infoContainer){
// Await all TE data, including the Best Trader link
const { marketValue, bestBuyer, upvoteCount, traderId } = await fetchTornExchangeData(itemId);
infoContainer = createInfoContainer(itemName, itemId, marketValue, bestBuyer, upvoteCount, traderId);
wrapper.insertBefore(infoContainer, wrapper.firstChild);
// Fetch local listings only after the info container is rendered
fetchBazaarListings(itemId, infoContainer);
} else {
if (window._cachedListings[itemId]) {
sortAndFilterListings(itemId, 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);
const listingsReceived = data.listings ? data.listings.length : 0;
const countSpan = infoContainer.querySelector('.bazaar-count-info');
if (countSpan) {
if (listingsReceived > 100) {
countSpan.innerHTML = `(Displaying 100+ Listings)`;
countSpan.style.color = 'orange';
} else if (listingsReceived > 0) {
countSpan.innerHTML = `(Displaying ${listingsReceived} Listings)`;
countSpan.style.color = '#aaa';
} else {
countSpan.textContent = '';
}
}
if(!data || !data.listings || listingsReceived === 0){
renderMessage(infoContainer,false);
return;
}
const 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
}));
window._cachedListings[itemId] = allListings;
sortAndFilterListings(itemId, infoContainer);
} catch(e){
console.error(`%c[BazaarScript Error] Failed to process Weaver API response for item ${itemId}:`, 'color: red; font-weight: bold;', e);
renderMessage(infoContainer,true);
}
},
onerror:function(error){
console.error(`%c[BazaarScript Error] GM_xmlhttpRequest failed for item ${itemId}:`, 'color: red; font-weight: bold;', error);
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 (Unchanged) ---
(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'});
}
const itemDetailsContainer = document.querySelector('[aria-labelledby*="itemInfo"]');
if (itemDetailsContainer) {
const itemImg = itemDetailsContainer.querySelector(`img[src*="/images/items/${itemIdToHighlight}/"]`);
if (itemImg) {
itemImg.closest('div')?.style.setProperty('outline','3px solid green','important');
}
}
});
});
observer.observe(document.body,{childList:true,subtree:true});
})();