您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
View items you are searching for in registered bazaars!
当前为
// ==UserScript== // @name Bazaar Item Search powered by IronNerd // @namespace [email protected] // @version 0.1 // @description View items you are searching for in registered bazaars! // @author Nurv [669537] // @match https://www.torn.com/page.php?sid=ItemMarket* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @run-at document-end // @license IronNerd.me // ==/UserScript== (async function() { 'use strict'; await init(); async function init() { addSettingsButton(); const apiKey = await GM_getValue('tornApiKey', ''); if (apiKey) { checkBazaarStatus(apiKey); } detectSearch(); } function addSettingsButton() { const intervalId = setInterval(() => { const linksContainer = document.querySelector('.linksContainer___LiOTN'); if (linksContainer) { clearInterval(intervalId); const button = document.createElement('button'); button.setAttribute('type', 'button'); button.className = 'linkContainer___X16y4 inRow___VfDnd greyLineV___up8VP iconActive___oAum9'; button.style.cursor = 'pointer'; button.style.display = 'flex'; button.style.alignItems = 'center'; button.style.justifyContent = 'center'; button.style.padding = '8px'; const iconWrapper = document.createElement('span'); iconWrapper.className = 'iconWrapper___x3ZLe iconWrapper___COKJD svgIcon___IwbJV'; iconWrapper.style.display = 'flex'; iconWrapper.style.alignItems = 'center'; iconWrapper.style.justifyContent = 'center'; const gearIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); gearIcon.setAttribute('width', '24'); gearIcon.setAttribute('height', '24'); gearIcon.setAttribute('viewBox', '0 0 24 24'); gearIcon.setAttribute('fill', 'currentColor'); gearIcon.innerHTML = ` <path d="M19.14,12.936l1.843-1.061a0.5,0.5,0,0,0,.11-0.63l-1.741-3.012a0.5,0.5,0,0,0-.61-0.22l-2.169,0.875a7.056,7.056,0,0,0-1.607-0.93l-0.33-2.313A0.5,0.5,0,0,0,13.5,4H10.5a0.5,0.5,0,0,0-.49,0.42l-0.33,2.313a7.056,7.056,0,0,0-1.607,0.93l-2.169-0.875a0.5,0.5,0,0,0-.61,0.22L3.56,11.245a0.5,0.5,0,0,0,.11,0.63l1.843,1.061a7.154,7.154,0,0,0,0,1.872L3.67,16.936a0.5,0.5,0,0,0-.11,0.63l1.741,3.012a0.5,0.5,0,0,0,.61,0.22l2.169-0.875a7.056,7.056,0,0,0,1.607,0.93l0.33,2.313a0.5,0.5,0,0,0,.49,0.42h3a0.5,0.5,0,0,0,.49-0.42l0.33-2.313a7.056,7.056,0,0,0,1.607-0.93l2.169,0.875a0.5,0.5,0,0,0,.61-0.22l1.741-3.012a0.5,0.5,0,0,0-.11-0.63Zm-7.14,2.064A3,3,0,1,1,15,12,3,3,0,0,1,12,15Z"/> `; iconWrapper.appendChild(gearIcon); button.appendChild(iconWrapper); button.addEventListener('click', openSettingsModal); linksContainer.appendChild(button); } }, 500); } async function openSettingsModal() { let existingModal = document.getElementById('bazaar-enhancer-modal'); if (existingModal) { document.body.removeChild(existingModal); } const savedApiKey = await GM_getValue('tornApiKey', ''); const modal = document.createElement('div'); modal.id = 'bazaar-enhancer-modal'; modal.setAttribute('role', 'dialog'); modal.setAttribute('aria-labelledby', 'modal-title'); modal.setAttribute('aria-modal', 'true'); modal.style = 'position: fixed; top: 0; left: 0; width: 100%; height: 100%;' + 'background-color: rgba(0,0,0,0.5); z-index: 1001; display: flex; align-items: center; justify-content: center;'; modal.innerHTML = ` <div id="modal-content" style="background-color: #fff; padding: 20px; border-radius: 5px; width: 400px; position: relative;"> <h2 id="modal-title">Bazaar Enhancer Settings</h2> <label for="apiKey">Your Torn API Key:</label><br> <input type="password" id="apiKey" style="width: 100%; border: 1px solid #ccc; border-radius: 5px;" value="${savedApiKey}"><br> <input type="checkbox" id="toggleVisibility"> <label for="toggleVisibility">Show API Key</label><br><br> <button id="saveApiKey">Save API Key</button> <button id="registerBazaar">Register Bazaar</button> <button id="removeBazaar">Remove Bazaar</button><br><br> <div id="statusMessage" style="margin-top: 10px; font-weight: bold;"></div> <button id="closeModal" style="position: absolute; top: 10px; right: 10px;" aria-label="Close Modal">×</button> </div> `; document.body.appendChild(modal); applyThemeToModal(modal); trapFocus(modal); const toggleVisibility = modal.querySelector('#toggleVisibility'); const apiKeyInput = modal.querySelector('#apiKey'); toggleVisibility.addEventListener('change', () => { apiKeyInput.type = toggleVisibility.checked ? 'text' : 'password'; }); modal.querySelector('#saveApiKey').addEventListener('click', async () => { const apiKey = apiKeyInput.value.trim(); const saveButton = modal.querySelector('#saveApiKey'); if (apiKey) { if (!isValidApiKey(apiKey)) { displayStatusMessage('error', 'Invalid API key format. Please check and try again.'); return; } saveButton.disabled = true; const spinner = createLoadingSpinner(); saveButton.parentNode.insertBefore(spinner, saveButton.nextSibling); try { await GM_setValue('tornApiKey', apiKey); displayStatusMessage('success', 'API key saved successfully.'); checkBazaarStatus(apiKey); } catch (error) { displayStatusMessage('error', 'Failed to save API key.'); console.error(error); } finally { saveButton.disabled = false; spinner.remove(); } } else { displayStatusMessage('error', 'Please enter a valid API key.'); } }); modal.querySelector('#registerBazaar').addEventListener('click', async () => { const apiKey = await GM_getValue('tornApiKey', ''); if (apiKey) { registerApiKey(apiKey); } else { displayStatusMessage('error', 'Please save your API key first.'); } }); modal.querySelector('#removeBazaar').addEventListener('click', async () => { const apiKey = await GM_getValue('tornApiKey', ''); if (apiKey) { unregisterApiKey(apiKey); } else { displayStatusMessage('error', 'No API key found to remove.'); } }); modal.querySelector('#closeModal').addEventListener('click', () => { closeSettingsModal(); }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { const modal = document.getElementById('bazaar-enhancer-modal'); if (modal) { closeSettingsModal(); } } }); const observer = new MutationObserver(() => { applyThemeToModal(modal); }); observer.observe(document.body, { attributes: true, attributeFilter: ['class'] }); apiKeyInput.focus(); } function closeSettingsModal() { const modal = document.getElementById('bazaar-enhancer-modal'); if (modal) { document.body.removeChild(modal); } } function applyThemeToModal(modal) { const isDarkMode = modal.querySelector('body')?.classList.contains('darkMode___3kucI') || document.body.classList.contains('darkMode___3kucI'); const modalContent = modal.querySelector('#modal-content'); if (isDarkMode) { modalContent.style.backgroundColor = '#2c2c2c'; modalContent.style.color = '#f0f0f0'; modalContent.querySelectorAll('button').forEach(btn => { btn.style.backgroundColor = '#444'; btn.style.color = '#f0f0f0'; btn.style.border = 'none'; btn.style.padding = '8px 12px'; btn.style.marginRight = '5px'; btn.style.borderRadius = '3px'; }); modalContent.querySelector('h2').style.color = '#f0f0f0'; } else { modalContent.style.backgroundColor = '#fff'; modalContent.style.color = '#000'; modalContent.querySelectorAll('button').forEach(btn => { btn.style.backgroundColor = '#f2f2f2'; btn.style.color = '#000'; btn.style.border = '1px solid #ccc'; btn.style.padding = '8px 12px'; btn.style.marginRight = '5px'; btn.style.borderRadius = '3px'; }); modalContent.querySelector('h2').style.color = '#000'; } const statusDiv = modal.querySelector('#statusMessage'); if (statusDiv) { statusDiv.style.transition = 'background-color 0.3s, color 0.3s'; } } function trapFocus(modal) { const focusableElements = modal.querySelectorAll('a[href], button, textarea, input, select'); const firstFocusable = focusableElements[0]; const lastFocusable = focusableElements[focusableElements.length - 1]; modal.addEventListener('keydown', function(e) { const isTabPressed = (e.key === 'Tab' || e.keyCode === 9); if (!isTabPressed) return; if (e.shiftKey) { if (document.activeElement === firstFocusable) { lastFocusable.focus(); e.preventDefault(); } } else { if (document.activeElement === lastFocusable) { firstFocusable.focus(); e.preventDefault(); } } }); } function createLoadingSpinner() { const spinner = document.createElement('div'); spinner.className = 'loading-spinner'; spinner.style.border = '4px solid #f3f3f3'; spinner.style.borderTop = '4px solid #3498db'; spinner.style.borderRadius = '50%'; spinner.style.width = '24px'; spinner.style.height = '24px'; spinner.style.animation = 'spin 2s linear infinite'; spinner.style.display = 'inline-block'; spinner.style.marginLeft = '10px'; return spinner; } const style = document.createElement('style'); style.type = 'text/css'; style.innerHTML = ` @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } `; document.head.appendChild(style); function isValidApiKey(apiKey) { const apiKeyPattern = /^[A-Za-z0-9]{16}$/; return apiKeyPattern.test(apiKey); } function detectSearch() { window.addEventListener('hashchange', onHashChange, false); window.addEventListener('popstate', onHashChange, false); onHashChange(); } function onHashChange() { const itemName = getItemNameFromUrl(); if (itemName) { fetchBazaarItems(itemName); } } function getItemNameFromUrl() { let itemName = null; const hash = window.location.hash; if (hash && hash.includes('itemName=')) { const hashParams = new URLSearchParams(hash.substring(hash.indexOf('?') + 1)); itemName = hashParams.get('itemName'); console.log('Extracted itemName from hash:', itemName); } if (!itemName) { const searchParams = new URLSearchParams(window.location.search); itemName = searchParams.get('itemName'); console.log('Extracted itemName from search:', itemName); } if (itemName) { return decodeURIComponent(itemName.replace(/\+/g, ' ')); } return null; } function fetchBazaarItems(itemName) { console.log('Fetching bazaar items for:', itemName); GM_xmlhttpRequest({ method: 'GET', url: `https://www.ironnerd.me/search_bazaar_items?item_name=${encodeURIComponent(itemName)}`, onload: function(response) { if (response.status === 200) { try { const data = JSON.parse(response.responseText); displayBazaarItems(data.bazaar_items, itemName); } catch (e) { displayErrorMessage('Error parsing server response.'); console.error('Parse Error:', e); } } else { displayErrorMessage('Error fetching bazaar items.'); console.error('Fetch Bazaar Items Error:', response.responseText); } }, onerror: function(error) { displayErrorMessage('Network error occurred. Please try again later.'); console.error('Network Error:', error); } }); } function displayBazaarItems(items, itemName = '') { console.log('displayBazaarItems - itemName:', itemName); let container = document.getElementById('bazaar-enhancer-container'); if (!container) { container = document.createElement('div'); container.id = 'bazaar-enhancer-container'; container.style.marginTop = '20px'; const delimiter = document.querySelector('.delimiter___zFh2E'); if (delimiter && delimiter.parentNode) { delimiter.parentNode.insertBefore(container, delimiter.nextSibling); } else { document.body.appendChild(container); } } container.innerHTML = ''; const title = document.createElement('h2'); title.textContent = `Bazaar Listings for "${itemName}"`; title.style.textAlign = 'center'; container.appendChild(title); if (items.length === 0) { const noItemsMessage = document.createElement('p'); noItemsMessage.textContent = 'No items found in registered bazaars.'; noItemsMessage.style.textAlign = 'center'; container.appendChild(noItemsMessage); return; } const table = document.createElement('table'); table.style.width = '75%'; table.style.margin = '0 auto'; table.style.borderCollapse = 'collapse'; const headers = ['Image', 'Name', 'Owner', 'Price', 'Quantity', 'Visit Bazaar']; const thead = document.createElement('thead'); const headerRow = document.createElement('tr'); headers.forEach(header => { const th = document.createElement('th'); th.textContent = header; th.style.border = '1px solid #ccc'; th.style.padding = '6px'; th.style.backgroundColor = '#f2f2f2'; th.style.textAlign = 'center'; headerRow.appendChild(th); }); thead.appendChild(headerRow); table.appendChild(thead); const tbody = document.createElement('tbody'); items.forEach(itemData => { const item = itemData.item; const row = document.createElement('tr'); const imgCell = document.createElement('td'); imgCell.style.border = '1px solid #ccc'; imgCell.style.padding = '4px'; imgCell.style.textAlign = 'center'; const img = document.createElement('img'); img.src = `/images/items/${item.ID}/small.png`; img.alt = item.name; img.style.height = '30px'; imgCell.appendChild(img); row.appendChild(imgCell); const nameCell = document.createElement('td'); nameCell.style.border = '1px solid #ccc'; nameCell.style.padding = '4px'; nameCell.style.textAlign = 'center'; nameCell.textContent = item.name; row.appendChild(nameCell); const ownerCell = document.createElement('td'); ownerCell.style.border = '1px solid #ccc'; ownerCell.style.padding = '4px'; ownerCell.style.textAlign = 'center'; const ownerLink = document.createElement('a'); ownerLink.href = `https://www.torn.com/profiles.php?XID=${itemData.user_id}`; ownerLink.textContent = itemData.owner; ownerLink.target = '_blank'; ownerLink.style.color = 'inherit'; ownerCell.appendChild(ownerLink); row.appendChild(ownerCell); const priceCell = document.createElement('td'); priceCell.style.border = '1px solid #ccc'; priceCell.style.padding = '4px'; priceCell.style.textAlign = 'center'; priceCell.textContent = `$${item.price.toLocaleString()}`; row.appendChild(priceCell); const quantityCell = document.createElement('td'); quantityCell.style.border = '1px solid #ccc'; quantityCell.style.padding = '4px'; quantityCell.style.textAlign = 'center'; quantityCell.textContent = item.quantity; row.appendChild(quantityCell); const bazaarCell = document.createElement('td'); bazaarCell.style.border = '1px solid #ccc'; bazaarCell.style.padding = '4px'; bazaarCell.style.textAlign = 'center'; const bazaarLink = document.createElement('a'); bazaarLink.href = `https://www.torn.com/bazaar.php?userID=${itemData.user_id}`; bazaarLink.textContent = 'Visit'; bazaarLink.target = '_blank'; bazaarLink.style.color = '#007bff'; bazaarLink.style.textDecoration = 'none'; bazaarLink.addEventListener('mouseover', () => { bazaarLink.style.textDecoration = 'underline'; }); bazaarLink.addEventListener('mouseout', () => { bazaarLink.style.textDecoration = 'none'; }); bazaarCell.appendChild(bazaarLink); row.appendChild(bazaarCell); tbody.appendChild(row); }); table.appendChild(tbody); container.appendChild(table); adjustStylesForTheme(); } function adjustStylesForTheme() { const isDarkMode = document.body.classList.contains('darkMode___3kucI'); const table = document.querySelector('#bazaar-enhancer-container table'); if (isDarkMode && table) { table.style.backgroundColor = '#1c1c1c'; table.style.color = '#f0f0f0'; table.querySelectorAll('th').forEach(th => { th.style.backgroundColor = '#333'; th.style.color = '#f0f0f0'; }); table.querySelectorAll('tr:nth-child(even)').forEach(tr => { tr.style.backgroundColor = '#2a2a2a'; }); table.querySelectorAll('tr:nth-child(odd)').forEach(tr => { tr.style.backgroundColor = '#1e1e1e'; }); } else if (table) { table.style.backgroundColor = '#fff'; table.style.color = '#000'; table.querySelectorAll('th').forEach(th => { th.style.backgroundColor = '#f2f2f2'; th.style.color = '#000'; }); table.querySelectorAll('tr:nth-child(even)').forEach(tr => { tr.style.backgroundColor = '#f9f9f9'; }); table.querySelectorAll('tr:nth-child(odd)').forEach(tr => { tr.style.backgroundColor = '#fff'; }); } } function registerApiKey(apiKey) { console.log('Registering API Key:', apiKey); const modal = document.getElementById('bazaar-enhancer-modal'); const registerButton = modal.querySelector('#registerBazaar'); registerButton.disabled = true; const spinner = createLoadingSpinner(); registerButton.parentNode.insertBefore(spinner, registerButton.nextSibling); GM_xmlhttpRequest({ method: 'POST', url: 'https://www.ironnerd.me/add_bazaar_api_key', headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({ api_key: apiKey }), onload: function(response) { spinner.remove(); registerButton.disabled = false; console.log('Response Status:', response.status); console.log('Response Text:', response.responseText); try { const data = JSON.parse(response.responseText); if (response.status === 201) { displayStatusMessage('success', 'Bazaar registered successfully.'); } else if (response.status === 200) { if (data.status === 'info') { displayStatusMessage('info', data.message); } else { displayStatusMessage('error', data.message || 'Error registering bazaar.'); } } else if (response.status === 403) { displayStatusMessage('error', data.message || 'Registration forbidden.'); } else { displayStatusMessage('error', 'Error registering bazaar.'); console.error('Register Bazaar Error:', response.responseText); } } catch (e) { displayStatusMessage('error', 'Unexpected response format.'); console.error('Parsing Error:', e); } }, onerror: function(error) { spinner.remove(); registerButton.disabled = false; displayStatusMessage('error', 'Network error occurred. Please try again later.'); console.error('Network Error:', error); } }); } function unregisterApiKey(apiKey) { console.log('Unregistering API Key:', apiKey); const modal = document.getElementById('bazaar-enhancer-modal'); const removeButton = modal.querySelector('#removeBazaar'); removeButton.disabled = true; const spinner = createLoadingSpinner(); removeButton.parentNode.insertBefore(spinner, removeButton.nextSibling); GM_xmlhttpRequest({ method: 'POST', url: 'https://www.ironnerd.me/remove_bazaar_api_key', headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({ api_key: apiKey }), onload: function(response) { spinner.remove(); removeButton.disabled = false; console.log('Response Status:', response.status); console.log('Response Text:', response.responseText); try { const data = JSON.parse(response.responseText); if (response.status === 200) { displayStatusMessage('success', 'Bazaar removed successfully.'); GM_setValue('tornApiKey', ''); modal.querySelector('#apiKey').value = ''; } else { displayStatusMessage('error', data.message || 'Error removing bazaar.'); console.error('Unregister Bazaar Error:', response.responseText); } } catch (e) { displayStatusMessage('error', 'Unexpected response format.'); console.error('Parsing Error:', e); } }, onerror: function(error) { spinner.remove(); removeButton.disabled = false; displayStatusMessage('error', 'Network error occurred. Please try again later.'); console.error('Network Error:', error); } }); } async function checkBazaarStatus(apiKey) { console.log('Checking Bazaar Status for API Key:', apiKey); GM_xmlhttpRequest({ method: 'POST', url: 'https://www.ironnerd.me/check_bazaar_status', headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({ api_key: apiKey }), onload: function(response) { console.log('Check Status Response Status:', response.status); console.log('Check Status Response Text:', response.responseText); try { const data = JSON.parse(response.responseText); if (response.status === 200) { if (data.status === 'info') { displayStatusMessage('info', data.message); } else { displayStatusMessage('error', data.message || 'Error checking bazaar status.'); } } else { displayStatusMessage('error', 'Error checking bazaar status.'); console.error('Check Bazaar Status Error:', response.responseText); } } catch (e) { displayStatusMessage('error', 'Unexpected response format.'); console.error('Parsing Error:', e); } }, onerror: function(error) { displayStatusMessage('error', 'Network error occurred. Please try again later.'); console.error('Network Error:', error); } }); } function displayStatusMessage(status, message) { const modal = document.getElementById('bazaar-enhancer-modal'); const statusDiv = modal.querySelector('#statusMessage'); if (!statusDiv) return; statusDiv.innerHTML = ''; const icon = document.createElement('span'); icon.style.marginRight = '8px'; if (status === 'success') { icon.textContent = '✅'; statusDiv.style.color = '#155724'; statusDiv.style.backgroundColor = '#d4edda'; } else if (status === 'error') { icon.textContent = '❌'; statusDiv.style.color = '#721c24'; statusDiv.style.backgroundColor = '#f8d7da'; } else if (status === 'info') { icon.textContent = 'ℹ️'; statusDiv.style.color = '#004085'; statusDiv.style.backgroundColor = '#cce5ff'; } const messageSpan = document.createElement('span'); messageSpan.textContent = message; statusDiv.appendChild(icon); statusDiv.appendChild(messageSpan); statusDiv.style.padding = '5px'; statusDiv.style.borderRadius = '3px'; statusDiv.style.display = 'flex'; statusDiv.style.alignItems = 'center'; statusDiv.style.transition = 'background-color 0.3s, color 0.3s'; setTimeout(() => { statusDiv.textContent = ''; statusDiv.style.display = 'none'; }, 5000); } function displayErrorMessage(message) { displayStatusMessage('error', message); } })();