// ==UserScript== // @name Bazaars in Item Market 2.0, powered by TornPal & IronNerd // @namespace http://tampermonkey.net/ // @version 1.221 // @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 // @downloadURL none // ==/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 = 40; // Increased batch size for more efficient loading // Store all listings here 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; // Update all buttons to the new theme 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; } }); // Update all info containers 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'; // Update headers container.querySelectorAll('.info-header').forEach(header => { header.style.color = darkMode ? '#fff' : '#000'; }); // Update sort controls container.querySelectorAll('.sort-controls').forEach(sortControl => { sortControl.style.backgroundColor = darkMode ? '#333' : '#eee'; }); // Update selects 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'; }); // Update cards 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'; // Maintain proper link colors based on visited status 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'; } } // Update footnotes const footnote = card.querySelector('.listing-footnote'); if (footnote) { footnote.style.color = darkMode ? '#aaa' : '#555'; } // Update source info const sourceInfo = card.querySelector('.listing-source'); if (sourceInfo) { sourceInfo.style.color = darkMode ? '#aaa' : '#555'; } }); // Update listings count container.querySelectorAll('.listings-count').forEach(count => { count.style.color = darkMode ? '#aaa' : '#666'; }); // Update powered by text 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'; }); }); }); // Update modals document.querySelectorAll('#bazaar-modal-container').forEach(modal => { modal.style.backgroundColor = darkMode ? '#2f2f2f' : '#fff'; modal.style.border = darkMode ? '8px solid #444' : '8px solid #000'; }); } // Initialize current dark mode state 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()); // Use the persisted state for button text dollarToggle.textContent = showDollarItems ? "Showing $1 Items" : "Hiding $1 Items"; sortControls.appendChild(dollarToggle); allDollarToggles.push(dollarToggle); dollarToggle.addEventListener('click', () => { showDollarItems = !showDollarItems; // Persist the updated state in localStorage localStorage.setItem("showDollarItems", showDollarItems.toString()); allDollarToggles.forEach(btn => { btn.textContent = showDollarItems ? "Showing $1 Items" : "Hiding $1 Items"; }); if (container.filteredListings) { // Reset visible listings and render again 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); // Create compact footer with count and powered by text const footerContainer = document.createElement('div'); footerContainer.className = 'footer-container'; setStyles(footerContainer, { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: '5px', fontSize: '10px' }); // Create count element const countElement = document.createElement('div'); countElement.className = 'listings-count'; setStyles(countElement, { color: dark ? '#aaa' : '#666' }); countElement.textContent = 'Loading...'; // Create powered by element const poweredBy = document.createElement('div'); poweredBy.className = 'powered-by'; setStyles(poweredBy, { textAlign: 'right' }); poweredBy.innerHTML = ` Powered by TornPal & IronNerd `; footerContainer.appendChild(countElement); footerContainer.appendChild(poweredBy); container.appendChild(footerContainer); sortSelect.addEventListener('change', () => { currentSortKey = sortSelect.value; if (container.filteredListings) { // Reset visible listings and sort again 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) { // Reset visible listings and sort again visibleListings = 0; allListings = sortListings(container.filteredListings); renderCards(container, true); } }); // Scroll event listener for lazy loading scrollWrapper.addEventListener('scroll', () => { const scrollRight = scrollWrapper.scrollWidth - (scrollWrapper.scrollLeft + scrollWrapper.clientWidth); if (scrollRight < 200) { // Increased threshold to load more when approaching the end 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])); } // Update count display const countText = `Showing ${end} of ${allListings.length} listings`; const countElement = container.querySelector('.listings-count'); if (countElement) { countElement.textContent = countText; } // Update visible count visibleListings = end; // No automatic loading - the scroll event will trigger more loads as needed } 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}`; // Store data attributes for theme switching 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 = ` `; 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 = `
Price: $${listing.price.toLocaleString()}
Qty: ${listing.quantity}
`; 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; // Reset visible listings count and sort listings visibleListings = 0; allListings = sortListings(cachedData.listings); renderCards(infoContainer, true); return; } let listings = [], responsesReceived = 0; let apiErrors = false; // Show loading state 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) { // Both API calls have completed setCache(itemId, { listings }); infoContainer.filteredListings = listings; // Clear loading state cardContainer.innerHTML = ''; // Update the count element const countElement = infoContainer.querySelector('.listings-count'); if (listings.length === 0) { // No listings found showNoListingsMessage(cardContainer, apiErrors); if (countElement) { countElement.textContent = apiErrors ? 'API Error - Check back later' : 'No listings available'; } } else { // Reset visible listings count and sort listings visibleListings = 0; allListings = sortListings(listings); renderCards(infoContainer, true); // Update count display 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) { // Show warning icon for API errors iconSvg.innerHTML = ` `; 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 { // Show info icon for no listings iconSvg.innerHTML = ` `; 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 }); // Observe body class changes to detect theme switching 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()); } } })();