// ==UserScript==
// @name Bazaars in Item Market powered by TornPal and IronNerd
// @namespace http://tampermonkey.net/
// @version 1.301
// @description Displays bazaar listings with sorting controls via TornPal & IronNerd
// @match https://www.torn.com/page.php?sid=ItemMarket*
// @match https://www.torn.com/bazaar.php*
// @grant GM_xmlhttpRequest
// @connect tornpal.com
// @connect www.ironnerd.me
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
dailyCleanup();
let allDollarToggles = [];
const CACHE_DURATION_MS = 60000;
let currentSortKey = "price", currentSortOrder = "asc";
let showDollarItems = localStorage.getItem("showDollarItems") === "true";
let currentDarkMode = false;
const LISTINGS_PER_BATCH = 10;
let allListings = [];
let visibleListings = 0;
function setStyles(el, styles) {
Object.assign(el.style, styles);
}
function isDarkMode() {
return document.body.classList.contains('dark-mode');
}
function getButtonStyle() {
return {
padding: '2px 4px',
border: isDarkMode() ? '1px solid #444' : '1px solid #ccc',
borderRadius: '2px',
backgroundColor: isDarkMode() ? '#1a1a1a' : '#fff',
color: isDarkMode() ? '#fff' : '#000',
cursor: 'pointer'
};
}
function updateThemeForAllElements() {
const darkMode = isDarkMode();
if (currentDarkMode === darkMode) return;
currentDarkMode = darkMode;
document.querySelectorAll('[id^="bazaar-modal-"] button, .sort-controls button, #item-info-container button').forEach(button => {
const style = getButtonStyle();
for (const [key, value] of Object.entries(style)) {
button.style[key] = value;
}
});
document.querySelectorAll('#item-info-container').forEach(container => {
container.style.backgroundColor = darkMode ? '#2f2f2f' : '#f9f9f9';
container.style.color = darkMode ? '#ccc' : '#000';
container.style.border = darkMode ? '1px solid #444' : '1px solid #ccc';
container.querySelectorAll('.info-header').forEach(header => {
header.style.color = darkMode ? '#fff' : '#000';
});
container.querySelectorAll('.sort-controls').forEach(sortControl => {
sortControl.style.backgroundColor = darkMode ? '#333' : '#eee';
});
container.querySelectorAll('select').forEach(select => {
select.style.backgroundColor = darkMode ? '#1a1a1a' : '#fff';
select.style.color = darkMode ? '#fff' : '#000';
select.style.border = darkMode ? '1px solid #444' : '1px solid #ccc';
});
container.querySelectorAll('.listing-card').forEach(card => {
card.style.backgroundColor = darkMode ? '#1a1a1a' : '#fff';
card.style.color = darkMode ? '#fff' : '#000';
card.style.border = darkMode ? '1px solid #444' : '1px solid #ccc';
const playerLink = card.querySelector('a');
if (playerLink) {
const visitedKey = playerLink.getAttribute('data-visited-key');
if (visitedKey) {
let visitedData = null;
try { visitedData = JSON.parse(localStorage.getItem(visitedKey)); } catch(e){}
const listing = JSON.parse(playerLink.getAttribute('data-listing') || '{}');
playerLink.style.color = (visitedData && visitedData.lastClickedUpdated >= listing.updated) ? 'purple' : '#00aaff';
}
}
const footnote = card.querySelector('.listing-footnote');
if (footnote) {
footnote.style.color = darkMode ? '#aaa' : '#555';
}
const sourceInfo = card.querySelector('.listing-source');
if (sourceInfo) {
sourceInfo.style.color = darkMode ? '#aaa' : '#555';
}
});
container.querySelectorAll('.listings-count').forEach(count => {
count.style.color = darkMode ? '#aaa' : '#666';
});
container.querySelectorAll('.powered-by').forEach(poweredBy => {
poweredBy.querySelectorAll('span').forEach(span => {
span.style.color = darkMode ? '#666' : '#999';
});
poweredBy.querySelectorAll('a').forEach(link => {
link.style.color = darkMode ? '#aaa' : '#555';
});
});
});
document.querySelectorAll('#bazaar-modal-container').forEach(modal => {
modal.style.backgroundColor = darkMode ? '#2f2f2f' : '#fff';
modal.style.border = darkMode ? '8px solid #444' : '8px solid #000';
});
}
currentDarkMode = isDarkMode();
function fetchJSON(url, callback) {
GM_xmlhttpRequest({
method: 'GET',
url: url,
onload: function(response) {
try { callback(JSON.parse(response.responseText)); }
catch(e) { callback(null); }
},
onerror: function() { callback(null); }
});
}
const style = document.createElement("style");
style.textContent = `
@keyframes popAndFlash {
0% { transform: scale(1); background-color: rgba(0, 255, 0, 0.6); }
50% { transform: scale(1.05); }
100% { transform: scale(1); background-color: inherit; }
}
.pop-flash { animation: popAndFlash 0.8s ease-in-out forwards; }
.green-outline { border: 3px solid green !important; }
`;
document.head.appendChild(style);
function getCache(itemId) {
try {
const key = "tornBazaarCache_" + itemId;
const cached = localStorage.getItem(key);
if (cached) {
const payload = JSON.parse(cached);
if (Date.now() - payload.timestamp < CACHE_DURATION_MS) return payload.data;
}
} catch(e) {}
return null;
}
function setCache(itemId, data) {
try {
localStorage.setItem("tornBazaarCache_" + itemId, JSON.stringify({ timestamp: Date.now(), data }));
} catch(e) {}
}
function getRelativeTime(ts) {
const diffSec = Math.floor((Date.now() - ts * 1000) / 1000);
if (diffSec < 60) return diffSec + 's ago';
if (diffSec < 3600) return Math.floor(diffSec/60) + 'm ago';
if (diffSec < 86400) return Math.floor(diffSec/3600) + 'h ago';
return Math.floor(diffSec/86400) + 'd ago';
}
function openModal(url) {
const originalOverflow = document.body.style.overflow;
document.body.style.overflow = 'hidden';
const modalOverlay = document.createElement('div');
modalOverlay.id = 'bazaar-modal-overlay';
setStyles(modalOverlay, {
position: 'fixed', top: '0', left: '0', width: '100%', height: '100%',
backgroundColor: 'rgba(0, 0, 0, 0.7)', display: 'flex', justifyContent: 'center',
alignItems: 'center', zIndex: '10000', overflow: 'visible'
});
const modalContainer = document.createElement('div');
modalContainer.id = 'bazaar-modal-container';
setStyles(modalContainer, {
backgroundColor: '#fff', border: '8px solid #000',
boxShadow: '0 0 10px rgba(0,0,0,0.5)', borderRadius: '8px',
position: 'relative', resize: 'both'
});
const savedSize = localStorage.getItem('bazaarModalSize');
if (savedSize) {
try {
const { width, height } = JSON.parse(savedSize);
modalContainer.style.width = (width < 200 ? '80%' : width + 'px');
modalContainer.style.height = (height < 200 ? '80%' : height + 'px');
} catch(e) {
modalContainer.style.width = modalContainer.style.height = '80%';
}
} else {
modalContainer.style.width = modalContainer.style.height = '80%';
}
const closeButton = document.createElement('button');
closeButton.textContent = '×';
setStyles(closeButton, Object.assign({}, getButtonStyle(), {
position: 'absolute', top: '-20px', right: '-20px',
width: '40px', height: '40px', backgroundColor: '#ff0000',
color: '#fff', border: 'none', borderRadius: '50%',
fontSize: '24px', boxShadow: '0 0 5px rgba(0,0,0,0.5)'
}));
closeButton.addEventListener('click', () => {
modalOverlay.remove();
document.body.style.overflow = originalOverflow;
});
modalOverlay.addEventListener('click', e => {
if (e.target === modalOverlay) {
modalOverlay.remove();
document.body.style.overflow = originalOverflow;
}
});
const iframe = document.createElement('iframe');
setStyles(iframe, { width: '100%', height: '100%', border: 'none' });
iframe.src = url;
modalContainer.append(closeButton, iframe);
modalOverlay.appendChild(modalContainer);
document.body.appendChild(modalOverlay);
if (window.ResizeObserver) {
const observer = new ResizeObserver(entries => {
for (let entry of entries) {
const { width, height } = entry.contentRect;
localStorage.setItem('bazaarModalSize', JSON.stringify({
width: Math.round(width),
height: Math.round(height)
}));
}
});
observer.observe(modalContainer);
}
}
function createInfoContainer(itemName, itemId) {
const dark = isDarkMode();
const container = document.createElement('div');
container.id = 'item-info-container';
container.setAttribute('data-itemid', itemId);
setStyles(container, {
backgroundColor: dark ? '#2f2f2f' : '#f9f9f9',
color: dark ? '#ccc' : '#000',
fontSize: '13px',
border: dark ? '1px solid #444' : '1px solid #ccc',
borderRadius: '4px',
margin: '5px 0',
padding: '10px',
display: 'flex',
flexDirection: 'column',
gap: '8px'
});
const header = document.createElement('div');
header.className = 'info-header';
setStyles(header, { fontSize: '16px', fontWeight: 'bold', color: dark ? '#fff' : '#000' });
header.textContent = `Item: ${itemName} (ID: ${itemId})`;
container.appendChild(header);
const sortControls = document.createElement('div');
sortControls.className = 'sort-controls';
setStyles(sortControls, {
display: 'flex',
alignItems: 'center',
gap: '5px',
fontSize: '12px',
padding: '5px',
backgroundColor: dark ? '#333' : '#eee',
borderRadius: '4px'
});
const sortLabel = document.createElement('span');
sortLabel.textContent = "Sort by:";
sortControls.appendChild(sortLabel);
const sortSelect = document.createElement('select');
setStyles(sortSelect, {
padding: '2px',
border: dark ? '1px solid #444' : '1px solid #ccc',
borderRadius: '2px',
backgroundColor: dark ? '#1a1a1a' : '#fff',
color: dark ? '#fff' : '#000'
});
[{ value: "price", text: "Price" },
{ value: "quantity", text: "Quantity" },
{ value: "updated", text: "Last Updated" }]
.forEach(opt => {
const option = document.createElement('option');
option.value = opt.value;
option.textContent = opt.text;
sortSelect.appendChild(option);
});
sortSelect.value = currentSortKey;
sortControls.appendChild(sortSelect);
const orderToggle = document.createElement('button');
setStyles(orderToggle, getButtonStyle());
orderToggle.textContent = currentSortOrder === "asc" ? "Asc" : "Desc";
sortControls.appendChild(orderToggle);
const dollarToggle = document.createElement('button');
setStyles(dollarToggle, getButtonStyle());
dollarToggle.textContent = showDollarItems ? "Showing $1 Items" : "Hiding $1 Items";
sortControls.appendChild(dollarToggle);
allDollarToggles.push(dollarToggle);
dollarToggle.addEventListener('click', () => {
showDollarItems = !showDollarItems;
localStorage.setItem("showDollarItems", showDollarItems.toString());
allDollarToggles.forEach(btn => {
btn.textContent = showDollarItems ? "Showing $1 Items" : "Hiding $1 Items";
});
if (container.filteredListings) {
visibleListings = 0;
allListings = sortListings(container.filteredListings);
renderCards(container, true);
}
});
container.appendChild(sortControls);
const scrollWrapper = document.createElement('div');
setStyles(scrollWrapper, {
overflowX: 'auto',
overflowY: 'hidden',
height: '120px',
whiteSpace: 'nowrap',
paddingBottom: '3px'
});
const cardContainer = document.createElement('div');
cardContainer.className = 'card-container';
setStyles(cardContainer, { display: 'flex', flexWrap: 'nowrap', gap: '10px' });
scrollWrapper.appendChild(cardContainer);
container.appendChild(scrollWrapper);
const footerContainer = document.createElement('div');
footerContainer.className = 'footer-container';
setStyles(footerContainer, {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginTop: '5px',
fontSize: '10px'
});
const countElement = document.createElement('div');
countElement.className = 'listings-count';
setStyles(countElement, {
color: dark ? '#aaa' : '#666'
});
countElement.textContent = 'Loading...';
const poweredBy = document.createElement('div');
poweredBy.className = 'powered-by';
setStyles(poweredBy, { textAlign: 'right' });
poweredBy.innerHTML = `
<span style="color:${dark ? '#666' : '#999'};">Powered by </span>
<a href="https://tornpal.com/login?ref=1853324" target="_blank" style="color:${dark ? '#aaa' : '#555'}; text-decoration:underline;">TornPal</a>
<span style="color:${dark ? '#666' : '#999'};"> & </span>
<a href="https://ironnerd.me/" target="_blank" style="color:${dark ? '#aaa' : '#555'}; text-decoration:underline;">IronNerd</a>
`;
footerContainer.appendChild(countElement);
footerContainer.appendChild(poweredBy);
container.appendChild(footerContainer);
sortSelect.addEventListener('change', () => {
currentSortKey = sortSelect.value;
if (container.filteredListings) {
visibleListings = 0;
allListings = sortListings(container.filteredListings);
renderCards(container, true);
}
});
orderToggle.addEventListener('click', () => {
currentSortOrder = currentSortOrder === "asc" ? "desc" : "asc";
orderToggle.textContent = currentSortOrder === "asc" ? "Asc" : "Desc";
if (container.filteredListings) {
visibleListings = 0;
allListings = sortListings(container.filteredListings);
renderCards(container, true);
}
});
scrollWrapper.addEventListener('scroll', () => {
const scrollRight = scrollWrapper.scrollWidth - (scrollWrapper.scrollLeft + scrollWrapper.clientWidth);
if (scrollRight < 200) {
renderMoreCards(container);
}
});
return container;
}
function sortListings(listings) {
const filtered = showDollarItems ? listings : listings.filter(l => l.price !== 1);
return filtered.slice().sort((a, b) => {
let diff = (currentSortKey === "price") ? a.price - b.price :
(currentSortKey === "quantity") ? a.quantity - b.quantity :
a.updated - b.updated;
return currentSortOrder === "asc" ? diff : -diff;
});
}
function renderMoreCards(container) {
if (visibleListings >= allListings.length) return;
const cardContainer = container.querySelector('.card-container');
const end = Math.min(visibleListings + LISTINGS_PER_BATCH, allListings.length);
for (let i = visibleListings; i < end; i++) {
cardContainer.appendChild(createListingCard(allListings[i]));
}
const countText = `Showing ${end} of ${allListings.length} listings`;
const countElement = container.querySelector('.listings-count');
if (countElement) {
countElement.textContent = countText;
}
visibleListings = end;
}
function renderCards(infoContainer, resetContainer) {
if (resetContainer) {
const cardContainer = infoContainer.querySelector('.card-container');
cardContainer.innerHTML = '';
}
renderMoreCards(infoContainer);
}
function createListingCard(listing) {
const dark = isDarkMode();
const card = document.createElement('div');
card.className = 'listing-card';
setStyles(card, {
position: 'relative',
minWidth: '160px',
maxWidth: '240px',
display: 'inline-block',
verticalAlign: 'top',
backgroundColor: dark ? '#1a1a1a' : '#fff',
color: dark ? '#fff' : '#000',
border: dark ? '1px solid #444' : '1px solid #ccc',
borderRadius: '4px',
padding: '8px',
fontSize: 'clamp(12px, 1vw, 16px)',
boxSizing: 'border-box',
overflow: 'hidden'
});
const linkContainer = document.createElement('div');
setStyles(linkContainer, { display: 'flex', alignItems: 'center', gap: '5px', marginBottom: '6px', flexWrap: 'wrap' });
const visitedKey = `visited_${listing.item_id}_${listing.player_id}`;
let visitedData = null;
try { visitedData = JSON.parse(localStorage.getItem(visitedKey)); } catch(e){}
let linkColor = (visitedData && visitedData.lastClickedUpdated >= listing.updated) ? 'purple' : '#00aaff';
const playerLink = document.createElement('a');
playerLink.href = `https://www.torn.com/bazaar.php?userId=${listing.player_id}&itemId=${listing.item_id}&highlight=1#/`;
playerLink.textContent = `Player: ${listing.player_id}`;
playerLink.setAttribute('data-visited-key', visitedKey);
playerLink.setAttribute('data-listing', JSON.stringify(listing));
setStyles(playerLink, { fontWeight: 'bold', color: linkColor, textDecoration: 'underline' });
playerLink.addEventListener('click', () => {
localStorage.setItem(visitedKey, JSON.stringify({ lastClickedUpdated: listing.updated }));
playerLink.style.color = 'purple';
});
linkContainer.appendChild(playerLink);
const iconSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
iconSvg.setAttribute("viewBox", "0 0 512 512");
iconSvg.setAttribute("width", "16");
iconSvg.setAttribute("height", "16");
iconSvg.style.cursor = "pointer";
iconSvg.style.color = dark ? '#ffa500' : '#cc6600';
iconSvg.title = "Open in modal";
iconSvg.innerHTML = `
<path fill="currentColor" d="M432 64L208 64c-8.8 0-16 7.2-16 16l0 16-64 0 0-16c0-44.2 35.8-80 80-80L432 0c44.2 0 80 35.8 80 80l0 224
c0 44.2-35.8 80-80 80l-16 0 0-64 16 0c8.8 0 16-7.2 16-16l0-224c0-8.8-7.2-16-16-16zM0 192c0-35.3 28.7-64 64-64l256 0c35.3 0
64 28.7 64 64l0 256c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 192zm64 32c0 17.7 14.3 32 32 32l192 0c17.7 0 32-14.3
32-32s-14.3-32-32-32L96 192c-17.7 0-32 14.3-32 32z"/>
`;
iconSvg.addEventListener('click', e => {
e.preventDefault();
localStorage.setItem(visitedKey, JSON.stringify({ lastClickedUpdated: listing.updated }));
playerLink.style.color = 'purple';
openModal(`https://www.torn.com/bazaar.php?userId=${listing.player_id}&itemId=${listing.item_id}&highlight=1#/`);
});
linkContainer.appendChild(iconSvg);
card.appendChild(linkContainer);
const details = document.createElement('div');
details.innerHTML = `<div><strong>Price:</strong> $${listing.price.toLocaleString()}</div>
<div><strong>Qty:</strong> ${listing.quantity}</div>`;
details.style.marginBottom = '6px';
card.appendChild(details);
const footnote = document.createElement('div');
footnote.className = 'listing-footnote';
setStyles(footnote, { fontSize: '11px', color: dark ? '#aaa' : '#555', textAlign: 'right' });
footnote.textContent = `Updated: ${getRelativeTime(listing.updated)}`;
card.appendChild(footnote);
const sourceInfo = document.createElement('div');
sourceInfo.className = 'listing-source';
setStyles(sourceInfo, { fontSize: '10px', color: dark ? '#aaa' : '#555', textAlign: 'right' });
let sourceDisplay = listing.source === "ironnerd" ? "IronNerd" : (listing.source === "bazaar" ? "TornPal" : listing.source);
sourceInfo.textContent = "Source: " + sourceDisplay;
card.appendChild(sourceInfo);
return card;
}
function updateInfoContainer(wrapper, itemId, itemName) {
let infoContainer = document.querySelector(`#item-info-container[data-itemid="${itemId}"]`);
if (!infoContainer) {
infoContainer = createInfoContainer(itemName, itemId);
wrapper.insertBefore(infoContainer, wrapper.firstChild);
} else {
const header = infoContainer.querySelector('.info-header');
if (header) header.textContent = `Item: ${itemName} (ID: ${itemId})`;
const cardContainer = infoContainer.querySelector('.card-container');
if (cardContainer) cardContainer.innerHTML = '';
}
const cachedData = getCache(itemId);
if (cachedData) {
infoContainer.filteredListings = cachedData.listings;
visibleListings = 0;
allListings = sortListings(cachedData.listings);
renderCards(infoContainer, true);
return;
}
let listings = [], responsesReceived = 0;
let apiErrors = false;
const cardContainer = infoContainer.querySelector('.card-container');
const loadingEl = document.createElement('div');
loadingEl.textContent = 'Loading bazaar listings...';
setStyles(loadingEl, {
padding: '10px',
textAlign: 'center',
width: '100%',
color: isDarkMode() ? '#aaa' : '#666'
});
cardContainer.appendChild(loadingEl);
function processResponse(newListings, error) {
if (error) {
apiErrors = true;
}
if (Array.isArray(newListings)) {
newListings.forEach(newItem => {
let normalized = newItem.user_id !== undefined ? {
item_id: newItem.item_id,
player_id: newItem.user_id,
quantity: newItem.quantity,
price: newItem.price,
updated: newItem.last_updated,
source: "ironnerd"
} : newItem;
let duplicate = listings.find(item =>
item.player_id === normalized.player_id &&
item.price === normalized.price &&
item.quantity === normalized.quantity
);
if (duplicate) {
if (duplicate.source !== normalized.source)
duplicate.source = "TornPal & IronNerd";
} else { listings.push(normalized); }
});
}
responsesReceived++;
if (responsesReceived === 2) {
setCache(itemId, { listings });
infoContainer.filteredListings = listings;
cardContainer.innerHTML = '';
const countElement = infoContainer.querySelector('.listings-count');
if (listings.length === 0) {
showNoListingsMessage(cardContainer, apiErrors);
if (countElement) {
countElement.textContent = apiErrors ? 'API Error - Check back later' : 'No listings available';
}
} else {
visibleListings = 0;
allListings = sortListings(listings);
renderCards(infoContainer, true);
const countText = `Showing ${Math.min(LISTINGS_PER_BATCH, allListings.length)} of ${allListings.length} listings`;
if (countElement) {
countElement.textContent = countText;
}
}
}
}
fetchJSON(`https://tornpal.com/api/v1/markets/clist/${itemId}?comment=wBazaarMarket`, data => {
const error = data === null;
processResponse(data && data.listings && Array.isArray(data.listings) ? data.listings.filter(l => l.source === "bazaar") : [], error);
});
fetchJSON(`https://www.ironnerd.me/get_bazaar_items/${itemId}?comment=wBazaarMarket`, data => {
const error = data === null;
processResponse(data && data.bazaar_items && Array.isArray(data.bazaar_items) ? data.bazaar_items : [], error);
});
}
function showNoListingsMessage(container, isApiError) {
const dark = isDarkMode();
const messageContainer = document.createElement('div');
setStyles(messageContainer, {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
padding: '20px',
textAlign: 'center',
width: '100%',
height: '70px'
});
const iconSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
iconSvg.setAttribute("viewBox", "0 0 512 512");
iconSvg.setAttribute("width", "24");
iconSvg.setAttribute("height", "24");
iconSvg.style.marginBottom = "10px";
iconSvg.style.color = dark ? '#aaa' : '#666';
if (isApiError) {
iconSvg.innerHTML = `
<path fill="currentColor" d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24V296c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/>
`;
messageContainer.appendChild(iconSvg);
const errorText = document.createElement('div');
errorText.textContent = "Unable to load bazaar listings. Please try again later.";
errorText.style.color = dark ? '#ff9999' : '#cc0000';
errorText.style.fontWeight = 'bold';
messageContainer.appendChild(errorText);
} else {
iconSvg.innerHTML = `
<path fill="currentColor" d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/>
`;
messageContainer.appendChild(iconSvg);
const noListingsText = document.createElement('div');
noListingsText.textContent = "No bazaar listings available for this item.";
noListingsText.style.color = dark ? '#ccc' : '#666';
messageContainer.appendChild(noListingsText);
}
container.appendChild(messageContainer);
}
function processSellerWrapper(wrapper) {
if (!wrapper || wrapper.id === 'item-info-container') return;
const itemTile = wrapper.previousElementSibling;
if (!itemTile) return;
const nameEl = itemTile.querySelector('.name___ukdHN');
const btn = itemTile.querySelector('button[aria-controls^="wai-itemInfo-"]');
if (nameEl && btn) {
const itemName = nameEl.textContent.trim();
const idParts = btn.getAttribute('aria-controls').split('-');
const itemId = idParts[idParts.length - 1];
updateInfoContainer(wrapper, itemId, itemName);
}
}
function processMobileSellerList() {
if (window.innerWidth >= 784) return;
const sellerList = document.querySelector('ul.sellerList___e4C9_');
if (!sellerList) {
const existing = document.querySelector('#item-info-container');
if (existing) existing.remove();
return;
}
const headerEl = document.querySelector('.itemsHeader___ZTO9r .title___ruNCT');
const itemName = headerEl ? headerEl.textContent.trim() : "Unknown";
const btn = document.querySelector('.itemsHeader___ZTO9r button[aria-controls^="wai-itemInfo-"]');
let itemId = "unknown";
if (btn) {
const parts = btn.getAttribute('aria-controls').split('-');
itemId = parts.length > 2 ? parts[parts.length - 2] : parts[parts.length - 1];
}
if (document.querySelector(`#item-info-container[data-itemid="${itemId}"]`)) return;
const infoContainer = createInfoContainer(itemName, itemId);
sellerList.parentNode.insertBefore(infoContainer, sellerList);
updateInfoContainer(infoContainer, itemId, itemName);
}
function processAllSellerWrappers(root = document.body) {
if (window.innerWidth < 784) return;
root.querySelectorAll('[class*="sellerListWrapper"]').forEach(wrapper => processSellerWrapper(wrapper));
}
processAllSellerWrappers();
processMobileSellerList();
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
if (window.innerWidth < 784 && node.matches('ul.sellerList___e4C9_')) {
processMobileSellerList();
} else {
if (node.matches('[class*="sellerListWrapper"]')) processSellerWrapper(node);
processAllSellerWrappers(node);
}
}
});
mutation.removedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE &&
node.matches('ul.sellerList___e4C9_') &&
window.innerWidth < 784) {
const container = document.querySelector('#item-info-container');
if (container) container.remove();
}
});
});
});
observer.observe(document.body, { childList: true, subtree: true });
const bodyObserver = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
if (mutation.attributeName === 'class') {
updateThemeForAllElements();
}
});
});
bodyObserver.observe(document.body, { attributes: true, attributeFilter: ['class'] });
if (window.location.href.includes("bazaar.php")) {
function scrollToTargetItem() {
const params = new URLSearchParams(window.location.search);
const targetItemId = params.get("itemId"), highlightParam = params.get("highlight");
if (!targetItemId || highlightParam !== "1") return;
function removeHighlightParam() {
params.delete("highlight");
history.replaceState({}, "", window.location.pathname + "?" + params.toString() + window.location.hash);
}
function showToast(message) {
const toast = document.createElement('div');
toast.textContent = message;
setStyles(toast, {
position: 'fixed',
bottom: '20px',
left: '50%',
transform: 'translateX(-50%)',
backgroundColor: 'rgba(0,0,0,0.7)',
color: 'white',
padding: '10px 20px',
borderRadius: '5px',
zIndex: '100000',
fontSize: '14px'
});
document.body.appendChild(toast);
setTimeout(() => { toast.remove(); }, 3000);
}
function findItemCard() {
const img = document.querySelector(`img[src*="/images/items/${targetItemId}/"]`);
return img ? img.closest('.item___GYCYJ') : null;
}
const scrollInterval = setInterval(() => {
const card = findItemCard();
if (card) {
clearInterval(scrollInterval);
removeHighlightParam();
card.classList.add("green-outline", "pop-flash");
card.scrollIntoView({ behavior: "smooth", block: "center" });
setTimeout(() => { card.classList.remove("pop-flash"); }, 800);
} else {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
showToast("Item not found on this page.");
removeHighlightParam();
clearInterval(scrollInterval);
} else {
window.scrollBy({ top: 300, behavior: 'auto' });
}
}
}, 50);
}
function waitForItems() {
const container = document.querySelector('.ReactVirtualized__Grid__innerScrollContainer');
if (container && container.childElementCount > 0) scrollToTargetItem();
else setTimeout(waitForItems, 500);
}
waitForItems();
}
function dailyCleanup() {
const lastCleanup = localStorage.getItem("lastDailyCleanup"),
oneDay = 24 * 60 * 60 * 1000,
now = Date.now();
if (!lastCleanup || (now - parseInt(lastCleanup, 10)) > oneDay) {
const sevenDays = 7 * 24 * 60 * 60 * 1000;
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && (key.startsWith("visited_") || key.startsWith("tornBazaarCache_"))) {
try {
const val = JSON.parse(localStorage.getItem(key));
let ts = null;
if (key.startsWith("visited_") && val && val.lastClickedUpdated) {
ts = val.lastClickedTime || (localStorage.removeItem(key), null);
} else if (key.startsWith("tornBazaarCache_") && val && val.timestamp) {
ts = val.timestamp;
}
if (ts !== null && (now - ts) > sevenDays) localStorage.removeItem(key);
} catch(e) { localStorage.removeItem(key); }
}
}
localStorage.setItem("lastDailyCleanup", now.toString());
}
}
})();