您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
View your listings on the Points Market, Bazaar and Item Market even when the page is unavailable!
// ==UserScript== // @name View Listings Anytime // @namespace heartflower.torn // @version 1.0.1 // @description View your listings on the Points Market, Bazaar and Item Market even when the page is unavailable! // @author Heartflower [2626587] // @match https://www.torn.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=torn.com // @grant GM_registerMenuCommand // @grant GM_addStyle // ==/UserScript== (function() { 'use strict'; // Change these variables if you don't want to use parts of the script let bazaar = true; let itemmarket = true; let pointsmarket = true; let listings = true; console.log('[HF] View Listings Running'); let apiKey; let storedAPIKey = localStorage.getItem('hf-full-access-apiKey'); if (storedAPIKey) { apiKey = storedAPIKey; if (typeof GM_registerMenuCommand === 'function') GM_registerMenuCommand('Remove API key', removeAPIKey); } else { setAPIkey(); } // VARIABLES TO USE LATER // let maximumCalls = 40; let apiCallCount = 0; let itemIDs = []; let itemUIDs = []; let userName = 'Unknown'; let userID = ''; let personalListings = []; let timestamp = 0; let hospitalTimestamp = 0; // API SETTINGS // function setAPIkey() { let enterAPIKey = prompt('Enter a full access API key here:'); if (enterAPIKey !== null && enterAPIKey.trim() !== '') { localStorage.setItem('hf-full-access-apiKey', enterAPIKey); alert('API key set succesfully'); apiKey = enterAPIKey; if (typeof GM_registerMenuCommand === 'function') GM_registerMenuCommand('Remove API key', removeAPIKey); } else { alert('No valid API key entered!'); if (typeof GM_registerMenuCommand === 'function') GM_registerMenuCommand('Set API key', setAPIkey); } } function removeAPIKey() { let wantToDelete = confirm('Are you sure you want to remove your API key?'); if (wantToDelete) { localStorage.removeItem('hf-full-access-apiKey'); alert('API key successfully removed.'); } else { alert('API key not removed.'); } } function createAPIlink(type, retries = 30) { let existingLink = document.body.querySelector('.hf-api-link'); if (existingLink) existingLink.remove(); let titleContainer = document.body.querySelector('.content-title'); if (!titleContainer) titleContainer = document.body.querySelector('.info-msg'); if (!titleContainer) { if (retries > 0) { setTimeout(() => createAPIlink(type, retries - 1), 100); } else { console.warn('[HF] Gave up looking for title container after 30 retries.'); } return; } let msg = document.body.querySelector('.info-msg .msg'); // Create the link to remove / add API key let div = document.createElement('div'); if (msg) div = document.createElement('span'); div.className = 'hf-api-link'; div.style.marginLeft = '4px'; div.style.marginBottom = '10px'; div.style.color = 'var(--default-blue-color)'; div.style.cursor = 'pointer'; if (type === 'remove') { div.textContent = 'Remove your full access API key'; div.addEventListener('click', function() { removeAPIKey(); }); } else if (type === 'add') { div.textContent = 'Enter your full access API key'; div.addEventListener('click', function() { setAPIkey(); }); } if (msg) { msg.appendChild(div) } else { titleContainer.parentNode.insertBefore(div, titleContainer.nextSibling); } } // SIDEBAR // function createContainer(type, retries = 30) { let mobile = !document.body.querySelector('.searchFormWrapper___LXcWp'); if (mobile) { let headerMenu = document.body.querySelector('.leftMenu___md3Ch'); createObserver(headerMenu); return; } let existingButton = document.body.querySelector(`.hf-view-listings-container-${type}`); if (existingButton) return; let toggleBlocks = document.body.querySelectorAll('.toggle-block___oKpdF'); if (!toggleBlocks || toggleBlocks.length < 3) { if (retries > 0) { setTimeout(() => createContainer(type, retries - 1), 100); } else { console.warn('[HF] Gave up looking for sidebar after 30 retries.'); } return; } let container = toggleBlocks[2].querySelector('.toggle-content___BJ9Q9'); let classes = container.querySelector('div').classList; let listingsContainer = document.createElement('div'); listingsContainer.classList.add(...classes); listingsContainer.classList.add(`hf-view-listings-container-${type}`); container.appendChild(listingsContainer); let titleDiv = document.createElement('div'); titleDiv.textContent = `My ${type} Listings`; titleDiv.classList.add('area-row___iBD8N'); titleDiv.style.padding = '5px 10px'; titleDiv.style.fontWeight = 'bold'; titleDiv.style.display = 'flex'; titleDiv.style.justifyContent = 'space-between'; listingsContainer.appendChild(titleDiv); if (type === 'Bazaar') { titleDiv.style.background = 'var(--title-msg-blue-gradient)'; } else if (type === 'Market') { titleDiv.style.background = 'var(--title-msg-green-gradient)'; } let arrow = document.createElement('span'); arrow.textContent = '►'; titleDiv.appendChild(arrow); titleDiv.addEventListener('click', function() { let arrowText = arrow.textContent; if (arrowText === '►') { arrow.textContent = '▼'; showListings(type, listingsContainer); } else { arrow.textContent = '►'; let scrollArea = listingsContainer.querySelector('.hf-view-listing-scrollarea'); scrollArea.remove(); } }); } async function showListings(type, listingsContainer) { let scrollArea = document.createElement('div'); scrollArea.classList.add('scrollarea', 'scroll-area___zOH66', 'hf-view-listing-scrollarea') listingsContainer.appendChild(scrollArea); let content = document.createElement('div'); content.classList.add('scrollarea-content'); scrollArea.appendChild(content); let ul = document.createElement('ul'); ul.classList.add('list___NuD9d'); ul.style.listStyle = 'disc'; content.appendChild(ul); if (type === 'Bazaar') { fetchBazaar(ul); } else if (type === 'Market') { fetchMarket(ul); } let scrollbarContainer = document.createElement('div'); scrollbarContainer.classList.add('scrollbar-container', 'vertical'); scrollArea.appendChild(scrollbarContainer); let scrollbar = document.createElement('div'); scrollbar.classList.add('scrollbar'); scrollbarContainer.appendChild(scrollbar); // Scroll container setup let scrollTop = 0; let scrollAreaHeight = scrollArea.clientHeight; let contentHeight = content.scrollHeight; let scrollbarHeight = (scrollAreaHeight / contentHeight) * scrollAreaHeight; // Minimum height for usability scrollbarHeight = Math.max(scrollbarHeight, 140); scrollbar.style.height = `${scrollbarHeight}px`; function updateScroll() { const maxScrollContent = Math.max(0, content.scrollHeight - scrollArea.clientHeight); const maxScrollBar = Math.max(0, scrollArea.clientHeight - scrollbar.clientHeight); // Clamp scrollTop within bounds scrollTop = Math.min(Math.max(0, scrollTop), maxScrollContent); // Move content and scrollbar content.style.transform = `translateY(-${scrollTop}px)`; let scrollbarOffset = scrollTop / maxScrollContent * maxScrollBar || 0; scrollbar.style.transform = `translateY(${scrollbarOffset}px)`; } // Mouse wheel scrolling scrollArea.addEventListener('wheel', (e) => { e.preventDefault(); // Optional: clamp delta speed let delta = Math.max(-60, Math.min(60, e.deltaY)); const maxScrollContent = Math.max(0, content.scrollHeight - scrollArea.clientHeight); scrollTop = Math.min(Math.max(0, scrollTop + delta), maxScrollContent); updateScroll(); }); // Drag to scroll let isDragging = false; let dragStartY = 0; let initialScrollTop = 0; scrollbar.addEventListener('mousedown', (e) => { e.preventDefault(); isDragging = true; dragStartY = e.clientY; initialScrollTop = scrollTop; document.body.style.userSelect = 'none'; // Prevent text selection }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; const deltaY = e.clientY - dragStartY; const maxScrollContent = Math.max(0, content.scrollHeight - scrollArea.clientHeight); const maxScrollBar = Math.max(0, scrollArea.clientHeight - scrollbar.clientHeight); const scrollRatio = maxScrollContent / maxScrollBar; scrollTop = Math.min(Math.max(0, initialScrollTop + deltaY * scrollRatio), maxScrollContent); updateScroll(); }); document.addEventListener('mouseup', () => { if (isDragging) { isDragging = false; document.body.style.userSelect = ''; } }); // Touch to scroll (mobile support) let touchStartY = 0; let touchInitialScrollTop = 0; scrollArea.addEventListener('touchstart', (e) => { if (e.touches.length !== 1) return; touchStartY = e.touches[0].clientY; touchInitialScrollTop = scrollTop; }, { passive: false }); scrollArea.addEventListener('touchmove', (e) => { if (!scrollArea.contains(e.target)) return; if (e.touches.length !== 1) return; e.preventDefault(); // prevent native scrolling const deltaY = touchStartY - e.touches[0].clientY; const maxScrollContent = Math.max(0, content.scrollHeight - scrollArea.clientHeight); scrollTop = Math.min(Math.max(0, touchInitialScrollTop + deltaY), maxScrollContent); updateScroll(); }, { passive: false }); } async function createList(type, ul, data, refreshed, mobile) { if (refreshed) { let lists = ul.querySelectorAll('li'); for (let list of lists) { list.remove(); } } for (let item of data) { let name = 'Unknown'; let qty = 0; let price = 0; if (type === 'Bazaar') { name = item.name; // id = item.id; // img_src = `images/items/${id}/medium.png`; qty = item.quantity; price = item.price; } else if (type === 'Market') { name = item.item.name; qty = item.amount; price = item.price; } let totalPrice = price * qty; let li = document.createElement('li'); li.style.margin = '0px 22px'; li.style.display = 'list-item'; li.style.padding = '0px'; li.style.cursor = 'auto'; li.style.height = 'auto'; li.style.lineHeight = 'normal'; li.style.marginBottom = '4px'; li.textContent = `${name} (${qty})`; li.title = `$${totalPrice.toLocaleString('en-US')}`; ul.appendChild(li); } if (refreshed) { let span = ul.querySelector('.hf-refresh-span'); ul.appendChild(span); return; } let refresh = document.createElement('span'); refresh.classList.add('hf-refresh-span'); refresh.textContent = 'Refresh my listings'; refresh.style.marginTop = '4px'; refresh.style.marginLeft = '8px'; refresh.style.color = 'var(--default-blue-color)'; refresh.style.cursor = 'pointer'; refresh.style.paddingBottom = '8px'; ul.parentNode.appendChild(refresh); refresh.addEventListener('click', function() { if (type === 'Bazaar') { fetchBazaar(ul, true); } else if (type === 'Market') { fetchMarket(ul, true); } }); } function createMobileMenu(ul) { let existingMenu = document.body.querySelector('.hf-view-listings-menu'); if (existingMenu) return; let li = document.createElement('li'); li.classList.add('menu-item-link'); li.classList.add('hf-view-listings-menu'); li.style.paddingLeft = '12px'; li.style.color = 'var(--default-color)'; li.textContent = 'View Listings'; ul.appendChild(li); li.addEventListener('click', function() { window.open('https://www.torn.com/hf-viewlistings', '_self'); }); } function changeMobilePage(retries = 30) { let title = document.body.querySelector('#skip-to-content'); if (!title) { if (retries > 0) { setTimeout(() => changeMobilePage(retries - 1), 100); } else { console.warn('[HF] Gave up looking for title after 30 retries.'); } return; } title.textContent = 'View Listings'; let contentWrapper = document.body.querySelector('.content-wrapper'); let errorWrap = contentWrapper.querySelector('.error-404'); errorWrap.remove(); let div = document.createElement('div'); contentWrapper.appendChild(div); showMobileListings('Market', div); showMobileListings('Bazaar', div) } function showMobileListings(type, div) { let existingContainer = document.body.querySelector(`hf-listings-container-${type}`); if (existingContainer) return; let container = document.createElement('div'); container.classList.add(`hf-listings-container-${type}`); let titleDiv = document.createElement('div'); titleDiv.style.background = 'var(--title-msg-blue-gradient)'; titleDiv.style.padding = '6px'; titleDiv.style.borderRadius = '5px'; titleDiv.style.fontWeight = 'bold'; titleDiv.style.display = 'flex'; titleDiv.style.justifyContent = 'space-between'; titleDiv.style.alignItems = 'center'; container.appendChild(titleDiv); let title = document.createElement('span'); titleDiv.appendChild(title); let refresh = document.createElement('span'); refresh.classList.add('link-icon-svg', 'refresh'); refresh.innerHTML = `<svg style="width: 15px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 14.47"><defs><style>.cls-1{opacity:0.35;}.cls-2{fill:var(--default-color);}.cls-3{fill:var(--default-color);}</style></defs><g id="Слой_2" data-name="Слой 2"><g id="icons"><g class="cls-1"><path class="cls-2" d="M1.68,7.74A5.05,5.05,0,0,1,11.5,6.05H8.42l3.84,4L16,6.05H13.26A6.74,6.74,0,1,0,11,13l-1.06-1.3A5.06,5.06,0,0,1,1.68,7.74"></path></g><path class="cls-3" d="M1.68,6.74A5.05,5.05,0,0,1,11.5,5.05H8.42l3.84,4L16,5.05H13.26A6.74,6.74,0,1,0,11,12l-1.06-1.3A5.06,5.06,0,0,1,1.68,6.74"></path></g></g></svg>` refresh.style.cursor = 'pointer'; titleDiv.appendChild(refresh); refresh.addEventListener('click', function() { if (type === 'Bazaar') { fetchBazaar(ulContainer, true, true); } else if (type === 'Market') { fetchMarket(ulContainer, true, true); } }); if (type === 'Market') { title.textContent = 'Item Market'; } else if (type === 'Bazaar') { title.textContent = 'Bazaar'; container.style.paddingTop = '20px'; } let ulContainer = document.createElement('div'); ulContainer.style.display = 'flex'; ulContainer.style.justifyContent = 'space-between'; if (type === 'Bazaar') { fetchBazaar(ulContainer, null, true); } else if (type === 'Market') { fetchMarket(ulContainer, null, true); } container.appendChild(ulContainer); div.appendChild(container); } function createMobileList(type, container, data, refreshed) { if (refreshed) { let uls = container.querySelectorAll('ul'); for (let ul of uls) { ul.remove(); } } let leftUl = document.createElement('ul'); leftUl.style.listStyle = ('disc'); leftUl.style.padding = '8px 0px 0px 20px'; leftUl.style.flex = '1'; container.appendChild(leftUl); let rightUl = document.createElement('ul'); rightUl.style.listStyle = ('disc'); rightUl.style.padding = '8px 0px 0px 20px'; rightUl.style.flex = '1'; container.appendChild(rightUl); data.forEach((item, index) => { let name = type === 'Bazaar' ? item.name : item.item.name; let qty = type === 'Bazaar' ? item.quantity : item.amount; let price = item.price; let totalPrice = qty * price; let li = document.createElement('li'); li.textContent = `${name} (${qty})`; li.title = `$${totalPrice.toLocaleString('en-US')}`; li.style.marginBottom = '4px'; (index % 2 === 0 ? leftUl : rightUl).appendChild(li); }); } // POINTS MARKET // function pointsMarketPage(retries = 30) { let contentWrapper = document.body.querySelector('.content-wrapper'); if (!contentWrapper) { if (retries > 0) { setTimeout(() => pointsMarketPage(retries - 1), 100); } else { console.warn('[HF] Gave up looking for content wrapper after 30 retries.'); } return; } let brokenWindow = document.body.querySelector('.error-404'); if (brokenWindow) brokenWindow.remove(); let messageContainer = document.body.querySelector('.info-msg'); let message = messageContainer.querySelector('.msg'); let messageContent = 'This area is unavailable'; if (message) messageContent = message.textContent; if (!messageContent.includes('This area is unavailable')) return; let originalTitle = document.querySelector('h4#skip-to-content'); if (originalTitle) originalTitle.parentNode.remove(); // Create the points market title as it's not usually there let titleContainer = document.createElement('div'); let title = document.createElement('p'); title.id = 'hf-pmv-title'; title.textContent = 'Points Market'; // Add a line break after the title let hrElement = document.createElement('hr'); hrElement.className = 'page-head-delimiter m-top10 m-bottom10'; titleContainer.appendChild(title); titleContainer.appendChild(hrElement); contentWrapper.insertBefore(titleContainer, contentWrapper.firstChild); // Change the message if (message) message.innerHTML = `The points market is fetched by the <p style='font-style:italic; display:inline'>View Listings Anytime</p> script with the help of the API!`; if (messageContainer) messageContainer.style.background = 'rgb(85,137,33)'; fetchLogs(); fetchHospitalTime(); createAPIlink('remove'); } // Fetch and display the time remaining in the hospital function changeTimer(data, messageContainer, message) { let status = data.status; hospitalTimestamp = status.until; let timeRemaining = 0; let timerElement = document.createElement('div'); timerElement.id = 'hf-pmv-hospital-timer'; timerElement.style.display = 'inline'; timerElement.style.paddingLeft = '4px'; message.insertBefore(timerElement, message.lastChild); updateTimer(); setInterval(updateTimer, 100); } // Function to calculate remaining time function calculateTimeRemaining() { let currentTime = Math.floor(Date.now() / 1000); // Current time in seconds let timeDifference = hospitalTimestamp - currentTime; if (timeDifference <= 0) return "You're out of the hospital!"; let days = Math.floor(timeDifference / (24 * 60 * 60)); let hours = Math.floor((timeDifference % (24 * 60 * 60)) / (60 * 60)); let minutes = Math.floor((timeDifference % (60 * 60)) / 60); let seconds = timeDifference % 60; let remainingTime = "You will be out of the hospital in "; if (days > 0) remainingTime += `${days} ${days === 1 ? 'day' : 'days'}, `; if (hours > 0) remainingTime += `${hours} ${hours === 1 ? 'hour' : 'hours'}, `; if (minutes > 0) remainingTime += `${minutes} ${minutes === 1 ? 'minute' : 'minutes'} and `; remainingTime += `${seconds} ${seconds === 1 ? 'second' : 'seconds'}`; remainingTime += '.'; return remainingTime; } // Function to update timer display function updateTimer(hospitalTimestamp) { let timerElement = document.getElementById('hf-pmv-hospital-timer'); if (timerElement) { timerElement.textContent = calculateTimeRemaining(hospitalTimestamp); } } // Create the table function createPointsTable(data) { let contentWrapper = document.body.querySelector('.content-wrapper'); if (!contentWrapper) return; let existingContainer = document.getElementById('hf-pmv-table'); if (existingContainer) return; let tableRows = []; // Sort listing IDs by cost for (let listingID in data) { let listing = data[listingID]; let cost = listing.cost; let quantity = listing.quantity; let totalCost = listing.total_cost; let row = { listingID: listingID, cost: cost, quantity: quantity, totalCost: totalCost }; tableRows.push(row); } tableRows.sort((a, b) => a.cost - b.cost); // Create the table container let table = document.createElement('ul'); table.id = 'hf-pmv-table'; // Create the header row let headerRow = document.createElement('li'); headerRow.className = 'hf-pmv-table-header'; createPointsTableCell(headerRow, 'hf-pmv-cost', 'Cost'); createPointsTableCell(headerRow, 'hf-pmv-qty', 'Quantity'); createPointsTableCell(headerRow, 'hf-pmv-total', 'Total'); table.appendChild(headerRow); // Loop through every listingID to fetch the necessary info for (let i = 0; i < tableRows.length; i++) { let row = tableRows[i]; let numericListingID = parseInt(row.listingID); // Check if the listing ID is part of the personal listings fetched from the logs if (personalListings.includes(numericListingID)) { createPointsTableRow(table, 'self', row.listingID, row.cost, row.quantity, row.totalCost); } else { createPointsTableRow(table, 'other', row.listingID, row.cost, row.quantity, row.totalCost); } } contentWrapper.appendChild(table); } // Create a table row with the necessary data function createPointsTableRow(table, info, listingID, cost, quantity, totalCost) { let tableRow = document.createElement('li'); tableRow.id = 'hf-pmv-' + listingID; tableRow.className = 'hf-pmv-table-row'; let costText = cost.toLocaleString('en-US', {style: 'currency', currency: 'USD', maximumFractionDigits: 0, minimumFractionDigits: 0}); createPointsTableCell(tableRow, 'hf-pmv-cost', costText) let quantityText = quantity.toLocaleString('en-US'); createPointsTableCell(tableRow, 'hf-pmv-qty', quantityText) let totalCostText = totalCost.toLocaleString('en-US', {style: 'currency', currency: 'USD', maximumFractionDigits: 0, minimumFractionDigits: 0}); createPointsTableCell(tableRow, 'hf-pmv-total', totalCostText) table.appendChild(tableRow); if (info === 'self') { tableRow.style.background = 'rgba(85,137,33,.5)'; } } // Create a table cell with the necessary data function createPointsTableCell(tableRow, className, textContent) { let span = document.createElement('span'); span.className = className; span.textContent = textContent; tableRow.appendChild(span); } // Add a refresh button function addRefreshButton() { let existingButton = document.getElementById('hf-refresh'); if (existingButton) return; let timer = document.getElementById('hf-refresh-timer'); if (timer) timer.remove(); let title = document.getElementById('hf-pmv-title'); let refreshButton = document.createElement('button'); refreshButton.id = 'hf-refresh'; refreshButton.style.float = 'right'; refreshButton.innerHTML = `<svg class="hf-refresh-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 14.47"><defs><style>.cls-1{opacity:0.35;}.cls-2{fill:#fff;}.cls-3{fill:#777;}</style></defs><g id="Слой_2" data-name="Слой 2"><g id="icons"><g class="cls-1"><path class="cls-2" d="M1.68,7.74A5.05,5.05,0,0,1,11.5,6.05H8.42l3.84,4L16,6.05H13.26A6.74,6.74,0,1,0,11,13l-1.06-1.3A5.06,5.06,0,0,1,1.68,7.74"></path></g><path class="cls-3" d="M1.68,6.74A5.05,5.05,0,0,1,11.5,5.05H8.42l3.84,4L16,5.05H13.26A6.74,6.74,0,1,0,11,12l-1.06-1.3A5.06,5.06,0,0,1,1.68,6.74"></path></g></g></svg>`; title.appendChild(refreshButton); // Add event listener to the button refreshButton.addEventListener('click', function() { location.reload(); }); let cls2 = refreshButton.querySelector('.cls-2'); let cls3 = refreshButton.querySelector('.cls-3'); // Add event listeners for hover refreshButton.addEventListener('mouseenter', function() { refreshButton.style.color = 'var(--default-blue-color)'; cls2.style.fill = 'var(--default-blue-color)'; cls3.style.fill = 'var(--default-blue-color)'; }); refreshButton.addEventListener('mouseleave', function() { refreshButton.style.color = 'var(--default-color)'; cls2.style.fill = 'var(--default-color)'; cls3.style.fill = 'var(--default-color)'; }); } // Add a timer to count down to refresh time function addTimer() { let refreshTimestamp = timestamp + 30; let intervalID = setInterval(() => { let currentTimestamp = Math.floor(Date.now() / 1000); let remainingTime = refreshTimestamp - currentTimestamp; if (remainingTime <= 0) { clearInterval(intervalID); addRefreshButton(); return; } let title = document.getElementById('hf-pmv-title'); let timer = document.getElementById('hf-refresh-timer'); if (!timer) { timer = document.createElement('span'); timer.id = 'hf-refresh-timer'; } timer.textContent = remainingTime; title.appendChild(timer); }, 1000); } // BAZAAR AND MARKET // function bazaarPage() { let contentWrapper = document.querySelector('.content-wrapper'); if (!contentWrapper) return; let messageContent = document.querySelector('.msg.right-round'); let originalText = messageContent.textContent; if (messageContent && (messageContent.textContent.includes('unavailable') || messageContent.textContent.includes('not available'))) { let newText = `${userName}'s bazaar is fetched by the "View Listings Anytime" script" with the help of the API!`; messageContent.textContent = newText; fetchBazaarData(); createAPIlink('remove'); } } function marketPage() { let contentWrapper = document.querySelector('.content-wrapper'); if (!contentWrapper) return; let messageContent = document.querySelector('.msg.right-round'); let originalText = messageContent.textContent; if (messageContent && (messageContent.textContent.includes('unavailable') || messageContent.textContent.includes('not available'))) { let newText = `Your item market listings are fetched by the "View Listings Anytime" script" with the help of the API!`; messageContent.textContent = newText; fetchMarket(null, null, null, true); createAPIlink('remove'); } } function createBazaarTable(data, market) { let contentWrapper = document.querySelector('.content-wrapper'); let tableDiv = document.createElement('div'); if (!market) tableDiv.style.paddingTop = '16px'; if (market) tableDiv.style.marginTop = '-8px'; // Scrollable wrapper let scrollWrapper = document.createElement('div'); scrollWrapper.style.overflowX = 'auto'; scrollWrapper.style.display = 'block'; let table = document.createElement('table'); table.style.margin = '0 auto'; table.style.background = 'var(--default-bg-panel-color)'; table.style.width = '100%'; table.style.minWidth = '800px'; // Optional: force scroll on smaller screens let thead = document.createElement('thead'); let tbody = document.createElement('tbody'); tbody.style.borderRadius = '5px'; // Create table headers let headers = ['Image', 'Name', 'Bonus', 'Stock', 'Price each', 'Price total', 'Lowest price in market']; let headerRow = document.createElement('tr'); headers.forEach(function(header) { let th = document.createElement('th'); th.style.padding = '4px'; th.textContent = header; th.style.background = 'var(--tabs-active-bg-gradient)'; th.style.color = 'var(--default-color)'; th.style.fontWeight = 'bold'; th.style.textAlign = 'center'; th.style.padding = '8px 4px'; th.style.borderBottom = '1px solid grey'; th.style.borderBottomColor = 'var(--default-panel-divider-outer-side-color)'; headerRow.appendChild(th); }); thead.appendChild(headerRow); table.appendChild(thead); // Create table body data.forEach(function(item, index) { let row = document.createElement('tr'); row.style.borderBottom = '1px solid grey'; row.style.borderBottomColor = 'var(--default-panel-divider-outer-side-color)'; if (market) { item.ID = item.item.id; item.UID = item.item.uid; item.name = item.item.name; item.quantity = item.amount; item.price = item.price; } let itemID = item.ID; let itemUID = item.UID; let bonusText = ''; if (itemUID) { itemUIDs.push({ uid: itemUID, index: index }); bonusText = 'Loading...'; } itemIDs.push({ id: itemID, index: index }); createBazaarCell('Image', row, itemID); createBazaarCell(item.name, row); createBazaarCell(bonusText, row); createBazaarCell(item.quantity, row); createBazaarCell(item.price.toLocaleString('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0, minimumFractionDigits: 0 }), row); createBazaarCell((item.quantity * item.price).toLocaleString('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0, minimumFractionDigits: 0 }), row); createBazaarCell('Loading...', row); tbody.appendChild(row); }); table.appendChild(tbody); scrollWrapper.appendChild(table); tableDiv.appendChild(scrollWrapper); contentWrapper.appendChild(tableDiv); fetchMarketDataForItems(); fetchItemDetailsForUIDs(); } function createBazaarCell(text, row, itemID) { let cell = document.createElement('td'); if (text == 'Image') { let img = document.createElement('img'); img = document.createElement('img'); img.src = `/images/items/${itemID}/large.png`; img.srcset = `/images/items/${itemID}/large.png 1x, /images/items/${itemID}/[email protected] 2x, /images/items/${itemID}/[email protected] 3x, /images/items/${itemID}/[email protected] 4x`; img.alt = 'Item Image'; img.style.height = '25px'; cell.appendChild(img); } else { cell.textContent = text; } cell.style.color = 'var(--default-color)'; cell.style.textAlign = 'center'; cell.style.verticalAlign = 'middle'; cell.style.padding = '4px'; row.appendChild(cell); } function fetchMarketDataForItems() { let itemsToFetch = Math.min(maximumCalls, itemIDs.length); for (let i = 0; i < itemsToFetch; i++) { let { id, index } = itemIDs[i]; fetchMarketData(id, index); } if (itemIDs.length > maximumCalls) { setTimeout(function () { fetchMarketDataForItems(); }, 60000); } } function updateLowestPrice(data, index, itemID) { let lowestPrice = data.itemmarket.listings[0].price; // Update table with lowest bazaar price let table = document.querySelector('table'); let cell = table.rows[index + 1].cells[6]; cell.textContent = lowestPrice.toLocaleString('en-US', {style: 'currency', currency: 'USD', maximumFractiondigits: 0, minimumFractionDigits: 0}); // Remove from itemIDs itemIDs = itemIDs.filter(item => item.id !== itemID); } function fetchItemDetailsForUIDs() { let itemsToFetch = Math.min(maximumCalls, itemUIDs.length); for (let i = 0; i < itemsToFetch; i++) { let { uid, index } = itemUIDs[i]; fetchItemDetails(uid, index); } if (itemIDs.length > maximumCalls) { setTimeout(function () { fetchItemDetailsForUIDs(); }, 60000); } } function addBonusInfo(data, index, itemUID) { let rarity = data.itemdetails.rarity; if (rarity == 'None') rarity = ''; let quality = data.itemdetails.quality; let bonuses = data.itemdetails.bonuses; let bonusText = ''; if (bonuses) { if (Object.keys(bonuses).length === 1) { // If there is only one bonus let bonus = bonuses[Object.keys(bonuses)[0]]; bonusText = `<p style="padding:4px">${bonus.value}% ${bonus.bonus}</p>`; } else if (Object.keys(bonuses).length === 2) { // If there are two bonuses Object.keys(bonuses).forEach(key => { let bonus = bonuses[key]; bonusText += `<p style="padding:4px">${bonus.value}% ${bonus.bonus}</p>`; }); } } // Update table with bonus text let table = document.querySelector('table'); let cell = table.rows[index + 1].cells[2]; cell.innerHTML = `<p style="padding:4px">${rarity}<p>${quality} Quality</p>${bonusText}`; // Remove from itemUIDs itemUIDs = itemUIDs.filter(item => item.uid !== itemUID); } // API FETCHERS // async function fetchBazaar(ul, refreshed, mobile) { let apiUrl = `https://api.torn.com/user/?selections=bazaar&key=${apiKey}&comment=ViewListingsAnytime`; fetch(apiUrl) .then(response => response.json()) .then(data => { let bazaar = data.bazaar; if (mobile) { createMobileList('Bazaar', ul, bazaar, refreshed); } else { createList('Bazaar', ul, bazaar, refreshed); } }) .catch(error => console.error('Error fetching data: ' + error)); } async function fetchMarket(ul, refreshed, mobile, page) { let apiUrl = `https://api.torn.com/v2/user/itemmarket?offset=0&key=${apiKey}&comment=ViewListingsAnytime`; fetch(apiUrl) .then(response => response.json()) .then(data => { let itemmarket = data.itemmarket; if (page) { createBazaarTable(itemmarket, true); apiCallCount++; } if (mobile) { createMobileList('Market', ul, itemmarket, refreshed); } else { createList('Market', ul, itemmarket, refreshed); } }) .catch(error => console.error('Error fetching data: ' + error)); } function fetchHospitalTime() { let messageContainer = document.body.querySelector('.info-msg'); let message = messageContainer.querySelector('.msg'); if (!message) return; let apiUrl = `https://api.torn.com/user/?selections=basic&key=${apiKey}&comment=ViewPointsMarketAnytime`; fetch(apiUrl) .then(response => response.json()) .then(data => { changeTimer(data, messageContainer, message) }) .catch(error => console.error('Error fetching data: ' + error)); } function fetchLogs() { let apiUrl = `https://api.torn.com/user/?key=${apiKey}&selections=log&log=5000&comment=ViewListingsAnytime`; fetch(apiUrl) .then(response => response.json()) .then(data => { let logData = data.log; for (let logID in logData) { let log = logData[logID]; let listingID = log.data.listing_id; personalListings.push(listingID); } fetchPointsMarket(); }) .catch(error => console.error('Error fetching data: ' + error)); } function fetchPointsMarket() { let apiUrl = `https://api.torn.com/market/?selections=pointsmarket,timestamp&key=${apiKey}&comment=ViewPointsMarketAnytime`; fetch(apiUrl) .then(response => response.json()) .then(data => { let pointsMarket = data.pointsmarket; timestamp = data.timestamp; addTimer(); createPointsTable(pointsMarket); }) .catch(error => console.error('Error fetching data: ' + error)); } function findUsername() { let url = new URL(window.location.href); userID = url.searchParams.get('userId'); let apiUrl = `https://api.torn.com/user/${userID}?key=${apiKey}&selections=basic&comment=ViewBazaarAnytime`; fetch(apiUrl) .then(response => response.json()) .then(data => { userName = data.name; }) .catch(error => console.error('Error fetching data: ' + error)); } function fetchBazaarData() { let apiUrl = `https://api.torn.com/user/${userID}?key=${apiKey}&selections=bazaar&comment=ViewListingsAnytime`; fetch(apiUrl) .then(response => response.json()) .then(data => { createBazaarTable(data.bazaar); apiCallCount++; }) .catch(error => console.error('Error fetching data: ' + error)); } function fetchOwnMarketData() { let apiUrl = `https://api.torn.com/v2/user/itemmarket?offset=0&key=${apiKey}&comment=ViewListingsAnytime`; fetch(apiUrl) .then(response => response.json()) .then(data => { createBazaarTable(data.itemmarket, true); apiCallCount++; }) .catch(error => console.error('Error fetching data: ' + error)); } function fetchMarketData(itemID, index) { let apiUrl = `https://api.torn.com/v2/market/${itemID}/itemmarket?offset=0&key=${apiKey}&comment=ViewBazaarAnytime` // Make the API call fetch(apiUrl) .then(response => response.json()) .then(data => { if (data.itemmarket && data.itemmarket.listings && data.itemmarket.listings.length > 0) { updateLowestPrice(data, index, itemID) } else { throw new Error('No items found in the bazaar'); } }) .catch(error => console.error('Error fetching data: ' + error)); } function fetchItemDetails(itemUID, index) { let apiUrl = `https://api.torn.com/torn/${itemUID}?selections=itemdetails&key=${apiKey}&comment=ViewBazaarAnytime`; // Make the API call fetch(apiUrl) .then(response => response.json()) .then(data => { if (data.itemdetails) { addBonusInfo(data, index, itemUID); } else { throw new Error('No item details found'); } }) .catch(error => console.error('Error fetching data: ' + error)); } // HELPER FUNCTIONS // function createObserver(element) { let target; target = element; if (!target) { console.error(`[HF] Mutation Observer target not found.`); return; } let observer = new MutationObserver(function(mutationsList, observer) { for (let mutation of mutationsList) { if (mutation.type === 'childList') { mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains('menu-items')) { createMobileMenu(node); } }); } let config = { attributes: true, childList: true, subtree: true, characterData: true }; observer.observe(target, config); } }); let config = { attributes: true, childList: true, subtree: true, characterData: true }; observer.observe(target, config); } function runScript() { if (listings) { createContainer('Bazaar'); createContainer('Market'); } if (window.location.href.includes('hf-viewlistings')) { changeMobilePage(); if (apiKey) { createAPIlink('remove'); } else { createAPIlink('add'); } } else if (pointsmarket && window.location.href.includes('pmarket')) { pointsMarketPage(); } else if (bazaar && window.location.href.includes('bazaar')) { findUsername(); setTimeout(bazaarPage, 500); } else if (itemmarket && window.location.href.includes('ItemMarket')) { marketPage(); } } runScript(); // Attach click event listener to document body document.body.addEventListener('click', handleClick); // Function to run fetchItemQty() on click function handleClick(event) { runScript(); } // STYLESHEET // GM_addStyle(` #hf-pmv-title { padding: 4px; color: var(--content-title-color); font-weight: 700; font-size: 22px; margin: 5px 0 -5px -4px } #hf-pmv-table { text-align: right; width: fit-content; margin: auto; color: var(--default-color); padding-top: 20px; } .hf-pmv-table-header { border-bottom: var(--default-panel-divider-outer-side-color) 1px solid; background: var(--title-black-gradient); border-radius: 5px 5px 0 0; width: fit-content; font-weight: bold; color: var(--tutorial-title-color); } .hf-pmv-table-row { border-bottom: var(--default-panel-divider-outer-side-color) 1px solid; border-top: var(--default-panel-divider-inner-side-color) 1px solid; background: var(--default-bg-panel-color); width: fit-content; } .hf-pmv-qty { border-left = 'var(--default-panel-divider-outer-side-color) 2px solid'; border-right = 'var(--default-panel-divider-outer-side-color) 2px solid'; } .hf-pmv-cost, .hf-pmv-qty { width: 100px; padding: 8px; display: inline-block; } .hf-pmv-total { width: 150px; padding: 8px; display: inline-block; } #hf-refresh { z-index: 9999; top: 188px; right: 475px; color: var(--default-color); cursor: pointer; width: 30px; } #hf-refresh-timer { float: right; font-size: 14px; margin-top: 6px; width: 30px; text-align: center; } .hf-refresh-svg { width: 14px } .hf-refresh-svg { margin-top: 4px; } @media only screen and (max-width: 785px) { .hf-pmv-total { width: 138px; } #hf-pmv-title { font-size: 14px; } .hf-refresh-svg, #hf-refresh-timer { margin-top: 0px; } #hf-refresh { margin-top: -2px; } } @media only screen and (max-width: 386px) { .hf-pmv-total { width: 100px; } .hf-pmv-cost, .hf-pmv-qty { width: 86px; } } `); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址