您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
View items you are searching for in bazaars!
// ==UserScript== // @name Bazaar Item Search powered by IronNerd // @namespace [email protected] // @version 0.6 // @description View items you are searching for in 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 Copyright IronNerd.me // @connect ironnerd.me // ==/UserScript== (function () { "use strict"; const BACKEND_URL = "https://www.ironnerd.me"; let ongoingRequests = new Set(); let allBazaarItems = []; let currentItemData = null; let lastUrl = location.href; let sortCriteria = []; const API = { fetchBazaarItems: function (itemID) { if (!itemID) return; const fullListingsView = document.getElementById("fullListingsView"); const topCheapestView = document.getElementById("topCheapestView"); if (ongoingRequests.has(`bazaar_items_${itemID}`)) return; ongoingRequests.add(`bazaar_items_${itemID}`); fullListingsView.innerHTML = `<p>Loading full listings...</p><div class="loading-spinner"></div>`; topCheapestView.innerHTML = `<p>Loading top 3 cheapest items...</p><div class="loading-spinner"></div>`; GM_xmlhttpRequest({ method: "GET", url: `${BACKEND_URL}/get_bazaar_items/${itemID}`, headers: { Accept: "application/json" }, onload: function (response) { ongoingRequests.delete(`bazaar_items_${itemID}`); if (response.status === 200) { try { const data = JSON.parse(response.responseText); if (data.bazaar_items) { allBazaarItems = data.bazaar_items; App.renderListings(); } else { fullListingsView.innerHTML = `<p>No items found.</p>`; topCheapestView.innerHTML = `<p>No items found.</p>`; } } catch (e) { fullListingsView.innerHTML = `<p>Error parsing server response.</p>`; topCheapestView.innerHTML = `<p>Error parsing server response.</p>`; console.error("Error parsing bazaar items response:", e); } } else { fullListingsView.innerHTML = `<p>Error: ${response.status} - ${response.statusText}</p>`; topCheapestView.innerHTML = `<p>Error: ${response.status} - ${response.statusText}</p>`; } }, onerror: function (error) { ongoingRequests.delete(`bazaar_items_${itemID}`); fullListingsView.innerHTML = `<p>Network error occurred. Please try again later.</p>`; topCheapestView.innerHTML = `<p>Network error occurred. Please try again later.</p>`; console.error("Network error (bazaar items):", error); }, }); }, }; const Util = { createCellWithLink: function (url, text) { const td = document.createElement("td"); const a = document.createElement("a"); a.href = url; a.innerText = text; a.target = "_blank"; a.style.color = "#007bff"; a.style.textDecoration = "none"; a.addEventListener("mouseover", () => { a.style.textDecoration = "underline"; }); a.addEventListener("mouseout", () => { a.style.textDecoration = "none"; }); td.appendChild(a); return td; }, createCell: function (content) { const td = document.createElement("td"); td.innerText = content; return td; }, createCellWithImage: function (src, alt) { const td = document.createElement("td"); const img = document.createElement("img"); img.src = src; img.alt = alt; img.style.height = "30px"; img.setAttribute("loading", "lazy"); td.appendChild(img); return td; }, formatTimestamp: function (unixTime) { if (unixTime.toString().length === 10) { unixTime *= 1000; } const date = new Date(unixTime); const now = new Date(); const diff = Math.floor((now - date) / 1000); if (diff < 60) return diff + "s ago"; const minutes = Math.floor(diff / 60); if (minutes < 60) return minutes + "m ago"; const hours = Math.floor(minutes / 60); if (hours < 24) return hours + "h ago"; const days = Math.floor(hours / 24); return days + "d ago"; }, }; const UI = { injectAdditionalStyles: function () { const style = document.createElement("style"); style.type = "text/css"; style.innerHTML = ` :root { --primary-bg: #ffffff; --primary-color: #000000; --border-color: #ddd; --table-header-bg: #f2f2f2; --nav-bg: #f2f2f2; --nav-text: #000; --button-padding: 5px 10px; --font-family: Arial, sans-serif; } .dark-mode { --primary-bg: rgba(0,0,0,0.6); --primary-color: #f0f0f0; --border-color: rgba(255,255,255,0.1); --table-header-bg: #333; --nav-bg: #444; --nav-text: #fff; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } #bazaar-enhancer-container { background-color: var(--primary-bg); color: var(--primary-color); border: 1px solid var(--border-color); box-shadow: 0 4px 8px rgba(0,0,0,0.1); border-radius: 8px; padding: 10px; margin: 10px 0; transition: background-color 0.3s, color 0.3s; font-family: var(--font-family); } #showBazaarModal table.bazaar-table, #bazaar-enhancer-container table.top-cheapest-table { width: 100%; border-collapse: collapse; margin-top: 10px; table-layout: auto; } #showBazaarModal table.bazaar-table th, #showBazaarModal table.bazaar-table td, #bazaar-enhancer-container table.top-cheapest-table th, #bazaar-enhancer-container table.top-cheapest-table td { text-align: center; padding: 8px; border: 1px solid var(--border-color); color: var(--primary-color); } #showBazaarModal table.bazaar-table th, #bazaar-enhancer-container table.top-cheapest-table th { background-color: var(--table-header-bg); cursor: pointer; font-size: 14px; transition: background-color 0.3s; } table.bazaar-table tr:hover, #bazaar-enhancer-container table.top-cheapest-table tr:hover { background-color: var(--nav-bg); } #showBazaarModal .loading-spinner, #bazaar-enhancer-container .loading-spinner { border: 4px solid #f3f3f3; border-top: 4px solid #3498db; border-radius: 50%; width: 24px; height: 24px; animation: spin 2s linear infinite; display: inline-block; margin-left: 10px; } #bazaar-enhancer-container a.visited-link, #bazaar-enhancer-container table a.visited-link, #showBazaarModal a.visited-link, #showBazaarModal table a.visited-link { color: purple !important; } #bazaar-nav { display: flex; justify-content: center; gap: 10px; margin-bottom: 15px; } #bazaar-nav button { margin: 0 5px; padding: var(--button-padding); cursor: pointer; background-color: var(--nav-bg); color: var(--nav-text); border: 1px solid var(--border-color); border-radius: 4px; font-size: 14px; transition: background-color 0.3s, transform 0.2s; } #bazaar-nav button:hover { background-color: var(--table-header-bg); transform: scale(1.02); } #topCheapestView, #fullListingsView, #filtersView { width: 100%; box-sizing: border-box; padding: 10px; background-color: var(--primary-bg); border: 1px solid var(--border-color); border-radius: 4px; margin-bottom: 15px; } #fullListingsView { height: 500px; overflow-y: auto; } .filter-form { display: grid; grid-template-columns: 1fr; gap: 10px; padding: 15px; border: 1px solid var(--border-color); border-radius: 4px; background-color: var(--primary-bg); margin-bottom: 15px; } .filter-group { display: flex; flex-direction: column; } .filter-group label { margin-top: 5px; margin-bottom: 5px; font-weight: bold; } .filter-group input { padding: 5px; border: 1px solid var(--border-color); border-radius: 4px; background-color: #cdcaca; } .filter-form .filter-buttons { display: flex; justify-content: center; gap: 10px; margin-top: 10px; } .filter-form .filter-buttons button { padding: var(--button-padding); cursor: pointer; background-color: var(--nav-bg); color: var(--nav-text); border: 1px solid var(--border-color); border-radius: 4px; font-size: 14px; transition: background-color 0.3s, transform 0.2s; } .filter-form .filter-buttons button:hover { background-color: var(--table-header-bg); transform: scale(1.02); } #rating-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); display: flex; justify-content: center; align-items: center; z-index: 1000; } #rating-modal { background-color: var(--primary-bg); padding: 20px; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.3); min-width: 300px; font-family: var(--font-family); } .rating-label { display: block; margin-bottom: 5px; font-weight: bold; } .rating-input { width: 100%; padding: 5px; border: 1px solid var(--border-color); border-radius: 4px; background-color: var(--primary-bg); color: var(--primary-color); } .rating-btn { padding: var(--button-padding); cursor: pointer; background-color: var(--nav-bg); color: var(--nav-text); border: 1px solid var(--border-color); border-radius: 4px; font-size: 14px; transition: background-color 0.3s, transform 0.2s; } .rating-btn:hover { background-color: var(--table-header-bg); transform: scale(1.02); } @media only screen and (max-width: 600px) { #bazaar-enhancer-container table.top-cheapest-table th, #bazaar-enhancer-container table.top-cheapest-table td { padding: 4px !important; font-size: 12px !important; } #bazaar-enhancer-container table.top-cheapest-table th { font-size: 12px !important; } } `; document.head.appendChild(style); }, ensureContainer: function () { return new Promise((resolve) => { if (document.querySelector(".captcha-container")) { return; } let container = document.getElementById("bazaar-enhancer-container"); if (container) { return resolve(container); } container = document.createElement("div"); container.id = "bazaar-enhancer-container"; container.style.overflow = "hidden"; let target = document.querySelector(".delimiter___zFh2E"); if (target && target.parentNode) { target.parentNode.insertBefore(container, target.nextSibling); return resolve(container); } const observer = new MutationObserver((mutations, obs) => { if (document.querySelector(".captcha-container")) { obs.disconnect(); return; } target = document.querySelector(".delimiter___zFh2E"); if (target && target.parentNode) { target.parentNode.insertBefore(container, target.nextSibling); obs.disconnect(); return resolve(container); } }); observer.observe(document.body, { childList: true, subtree: true }); }); }, displayFiltersView: function () { const filtersDiv = document.createElement("div"); filtersDiv.id = "filtersView"; filtersDiv.className = "filter-form"; filtersDiv.innerHTML = ` <div class="filter-group"> <label for="filter-min-price">Min Price:</label> <input type="number" id="filter-min-price" placeholder="e.g., 1000"> </div> <div class="filter-group"> <label for="filter-max-price">Max Price:</label> <input type="number" id="filter-max-price" placeholder="e.g., 10000"> </div> <div class="filter-group"> <label for="filter-min-quantity">Min Qty:</label> <input type="number" id="filter-min-quantity" placeholder="e.g., 1"> </div> <div class="filter-group"> <label for="filter-max-quantity">Max Qty:</label> <input type="number" id="filter-max-quantity" placeholder="e.g., 100"> </div> <div class="filter-group"> <label for="filter-min-rating">Min Seller Rating:</label> <input type="number" id="filter-min-rating" placeholder="1-5" min="1" max="5" step="0.1"> </div> <div class="filter-buttons"> <button id="apply-filters-btn">Apply Filters</button> <button id="clear-filters-btn">Clear All Filters</button> </div> `; filtersDiv .querySelector("#apply-filters-btn") .addEventListener("click", () => { App.filterCriteria.minPrice = parseFloat(document.getElementById("filter-min-price").value) || null; App.filterCriteria.maxPrice = parseFloat(document.getElementById("filter-max-price").value) || null; App.filterCriteria.minQuantity = parseFloat(document.getElementById("filter-min-quantity").value) || null; App.filterCriteria.maxQuantity = parseFloat(document.getElementById("filter-max-quantity").value) || null; App.filterCriteria.minRating = parseFloat(document.getElementById("filter-min-rating").value) || null; GM_setValue("bazaar_filters", App.filterCriteria); UI.updateFiltersButtonStyle(); App.renderListings(); }); filtersDiv .querySelector("#clear-filters-btn") .addEventListener("click", () => { App.filterCriteria = { minPrice: null, maxPrice: null, minQuantity: null, maxQuantity: null, minRating: null, }; GM_setValue("bazaar_filters", App.filterCriteria); document.getElementById("filter-min-price").value = ""; document.getElementById("filter-max-price").value = ""; document.getElementById("filter-min-quantity").value = ""; document.getElementById("filter-max-quantity").value = ""; document.getElementById("filter-min-rating").value = ""; UI.updateFiltersButtonStyle(); App.renderListings(); }); return filtersDiv; }, initTabbedInterface: function (container) { container.innerHTML = ""; const nav = document.createElement("div"); nav.id = "bazaar-nav"; nav.style.display = "flex"; nav.style.justifyContent = "center"; nav.style.marginBottom = "10px"; const btnTopCheapest = document.createElement("button"); btnTopCheapest.innerText = "Top 3"; btnTopCheapest.addEventListener("click", () => { UI.setActiveTab(0); }); const btnFullListings = document.createElement("button"); btnFullListings.innerText = "All Bazaars"; btnFullListings.addEventListener("click", () => { UI.setActiveTab(1); }); const btnFilters = document.createElement("button"); btnFilters.innerText = "Filters"; btnFilters.id = "filters-btn"; btnFilters.addEventListener("click", () => { UI.setActiveTab(2); }); UI.updateFiltersButtonStyle(btnFilters); nav.appendChild(btnTopCheapest); nav.appendChild(btnFullListings); nav.appendChild(btnFilters); container.appendChild(nav); const topCheapestView = document.createElement("div"); topCheapestView.id = "topCheapestView"; topCheapestView.style.padding = "10px"; topCheapestView.innerHTML = "<p>Loading top cheapest listings...</p>"; const fullListingsView = document.createElement("div"); fullListingsView.id = "fullListingsView"; fullListingsView.style.padding = "10px"; fullListingsView.style.maxHeight = "350px"; fullListingsView.style.height = "auto"; fullListingsView.style.overflowY = "auto"; fullListingsView.innerHTML = "<p>Loading full listings...</p>"; const filtersView = UI.displayFiltersView(); container.appendChild(topCheapestView); container.appendChild(fullListingsView); container.appendChild(filtersView); UI.setActiveTab(0); }, setActiveTab: function (tabIndex) { const topCheapestView = document.getElementById("topCheapestView"); const fullListingsView = document.getElementById("fullListingsView"); const filtersView = document.getElementById("filtersView"); if (tabIndex === 0) { topCheapestView.style.display = "block"; fullListingsView.style.display = "none"; filtersView.style.display = "none"; } else if (tabIndex === 1) { topCheapestView.style.display = "none"; fullListingsView.style.display = "block"; filtersView.style.display = "none"; } else if (tabIndex === 2) { topCheapestView.style.display = "none"; fullListingsView.style.display = "none"; filtersView.style.display = "block"; document.getElementById("filter-min-price").value = App.filterCriteria.minPrice || ""; document.getElementById("filter-max-price").value = App.filterCriteria.maxPrice || ""; document.getElementById("filter-min-quantity").value = App.filterCriteria.minQuantity || ""; document.getElementById("filter-max-quantity").value = App.filterCriteria.maxQuantity || ""; document.getElementById("filter-min-rating").value = App.filterCriteria.minRating || ""; } }, updateFiltersButtonStyle: function (buttonEl) { const btn = buttonEl || document.getElementById("filters-btn"); const active = App.filterCriteria.minPrice !== null || App.filterCriteria.maxPrice !== null || App.filterCriteria.minQuantity !== null || App.filterCriteria.maxQuantity !== null || App.filterCriteria.minRating !== null; if (active) { btn.style.backgroundColor = "lightgreen"; } else { btn.style.backgroundColor = "var(--nav-bg)"; } }, createSortableHeader: function (text, key) { const th = document.createElement("th"); th.innerText = text; th.style.border = "1px solid var(--border-color)"; th.style.padding = "8px"; th.style.backgroundColor = "var(--table-header-bg)"; th.style.textAlign = "center"; th.style.fontSize = "14px"; th.style.cursor = "pointer"; th.addEventListener("click", (e) => { if (e.shiftKey) { let existing = sortCriteria.find((crit) => crit.key === key); if (existing) { existing.order = existing.order === "asc" ? "desc" : "asc"; } else { sortCriteria.push({ key: key, order: "asc" }); } } else { let existing = sortCriteria.find((crit) => crit.key === key); if (existing) { existing.order = existing.order === "asc" ? "desc" : "asc"; sortCriteria = [existing]; } else { sortCriteria = [{ key: key, order: "asc" }]; } } App.renderListings(); }); return th; }, displayFullListings: function (items) { const fullListingsView = document.getElementById("fullListingsView"); fullListingsView.innerHTML = ""; if (items.length === 0) { fullListingsView.innerHTML = `<p>No items found.</p>`; return; } const title = document.createElement("h3"); title.innerText = `Full Listings`; title.style.textAlign = "center"; title.style.marginTop = "2px"; title.style.marginBottom = "10px"; fullListingsView.appendChild(title); const tableContainer = document.createElement("div"); tableContainer.style.overflowX = "auto"; tableContainer.style.width = "100%"; const table = document.createElement("table"); table.className = "top-cheapest-table"; table.style.width = "100%"; table.style.borderCollapse = "collapse"; const thead = document.createElement("thead"); const headerRow = document.createElement("tr"); headerRow.appendChild(UI.createSortableHeader("Price ($) ↑↓", "price")); headerRow.appendChild(UI.createSortableHeader("Quantity ↑↓", "quantity")); headerRow.appendChild( UI.createSortableHeader("Updated ↑↓", "last_updated") ); headerRow.appendChild(Util.createCell("Seller")); headerRow.appendChild(Util.createCell("Rating")); thead.appendChild(headerRow); table.appendChild(thead); const tbody = document.createElement("tbody"); items.forEach((item) => { const tr = document.createElement("tr"); tr.appendChild( Util.createCellWithLink( `https://www.torn.com/bazaar.php?userID=${item.user_id}`, `$${item.price.toLocaleString()}` ) ); tr.appendChild(Util.createCell(item.quantity)); tr.appendChild( Util.createCell(Util.formatTimestamp(item.last_updated)) ); const sellerText = item.player_name ? item.player_name : item.user_id; tr.appendChild( Util.createCellWithLink( `https://www.torn.com/profiles.php?XID=${item.user_id}`, sellerText ) ); const ratingTd = document.createElement("td"); if (item.seller_rating && item.seller_rating.avg_rating !== null) { ratingTd.innerText = `${item.seller_rating.avg_rating}/5 (${item.seller_rating.rating_count})`; } else { ratingTd.innerText = "Not Rated"; } ratingTd.style.cursor = "pointer"; ratingTd.title = "Click to rate seller"; ratingTd.addEventListener("click", () => { UI.openRatingModal(item.user_id, sellerText); }); tr.appendChild(ratingTd); tbody.appendChild(tr); }); table.appendChild(tbody); tableContainer.appendChild(table); fullListingsView.appendChild(tableContainer); UI.adjustUnifiedTableTheme(); }, displayTopCheapestItems: function (items, itemName) { const topCheapestView = document.getElementById("topCheapestView"); topCheapestView.innerHTML = ""; if (!items || items.length === 0) { topCheapestView.innerHTML = `<p>No items found.</p>`; return; } const title = document.createElement("h3"); title.innerText = `Top 3 Cheapest ${itemName} Bazaar Items`; title.style.textAlign = "center"; title.style.marginTop = "2px"; title.style.marginBottom = "10px"; topCheapestView.appendChild(title); const tableContainer = document.createElement("div"); tableContainer.style.overflowX = "auto"; tableContainer.style.width = "100%"; const table = document.createElement("table"); table.className = "top-cheapest-table"; table.style.width = "100%"; table.style.borderCollapse = "collapse"; const thead = document.createElement("thead"); const headerRow = document.createElement("tr"); headerRow.appendChild(UI.createSortableHeader("Price ($) ↑↓", "price")); headerRow.appendChild(UI.createSortableHeader("Quantity ↑↓", "quantity")); headerRow.appendChild( UI.createSortableHeader("Updated ↑↓", "last_updated") ); headerRow.appendChild(Util.createCell("Seller")); headerRow.appendChild(Util.createCell("Rating")); thead.appendChild(headerRow); table.appendChild(thead); const tbody = document.createElement("tbody"); items.slice(0, 3).forEach((item) => { const tr = document.createElement("tr"); tr.appendChild( Util.createCellWithLink( `https://www.torn.com/bazaar.php?userID=${item.user_id}`, `$${item.price.toLocaleString()}` ) ); const quantityTd = document.createElement("td"); quantityTd.innerText = item.quantity; quantityTd.style.border = "1px solid var(--border-color)"; quantityTd.style.padding = "6px"; quantityTd.style.textAlign = "center"; quantityTd.style.fontSize = "14px"; tr.appendChild(quantityTd); const updatedTd = document.createElement("td"); updatedTd.innerText = Util.formatTimestamp(item.last_updated); updatedTd.style.border = "1px solid var(--border-color)"; updatedTd.style.padding = "6px"; updatedTd.style.textAlign = "center"; updatedTd.style.fontSize = "14px"; tr.appendChild(updatedTd); const sellerText = item.player_name ? item.player_name : item.user_id; tr.appendChild( Util.createCellWithLink( `https://www.torn.com/profiles.php?XID=${item.user_id}`, sellerText ) ); const ratingTd = document.createElement("td"); if (item.seller_rating && item.seller_rating.avg_rating !== null) { ratingTd.innerText = `${item.seller_rating.avg_rating}/5 (${item.seller_rating.rating_count})`; } else { ratingTd.innerText = "Not Rated"; } ratingTd.style.cursor = "pointer"; ratingTd.title = "Click to rate seller"; ratingTd.addEventListener("click", () => { UI.openRatingModal(item.user_id, sellerText); }); tr.appendChild(ratingTd); tbody.appendChild(tr); }); table.appendChild(tbody); tableContainer.appendChild(table); topCheapestView.appendChild(tableContainer); UI.adjustUnifiedTableTheme(); }, openRatingModal: function (seller_id, seller_name) { const overlay = document.createElement("div"); overlay.id = "rating-overlay"; const modal = document.createElement("div"); modal.id = "rating-modal"; modal.addEventListener("click", function (e) { e.stopPropagation(); }); const title = document.createElement("h3"); title.innerText = `Rate Seller: ${seller_name}`; title.style.marginTop = "0"; modal.appendChild(title); const starsDiv = document.createElement("div"); starsDiv.style.marginBottom = "10px"; starsDiv.style.fontSize = "24px"; starsDiv.style.cursor = "pointer"; let selectedRating = 0; for (let i = 1; i <= 5; i++) { const star = document.createElement("span"); star.innerText = "☆"; star.dataset.value = i; star.addEventListener("click", function () { selectedRating = parseInt(this.dataset.value); const allStars = starsDiv.querySelectorAll("span"); allStars.forEach((s) => { s.innerText = parseInt(s.dataset.value) <= selectedRating ? "★" : "☆"; }); }); starsDiv.appendChild(star); } modal.appendChild(starsDiv); const apiKeyDiv = document.createElement("div"); apiKeyDiv.style.marginBottom = "10px"; const apiKeyLabel = document.createElement("label"); apiKeyLabel.innerText = "Your API Key: "; apiKeyLabel.className = "rating-label"; const apiKeyInput = document.createElement("input"); apiKeyInput.type = "text"; apiKeyInput.className = "rating-input"; apiKeyInput.placeholder = "Enter your Torn API key"; apiKeyInput.value = GM_getValue("buyer_api_key", ""); apiKeyDiv.appendChild(apiKeyLabel); apiKeyDiv.appendChild(apiKeyInput); modal.appendChild(apiKeyDiv); const messageDiv = document.createElement("div"); messageDiv.style.marginBottom = "10px"; messageDiv.style.textAlign = "center"; modal.appendChild(messageDiv); const btnContainer = document.createElement("div"); btnContainer.style.textAlign = "right"; btnContainer.style.marginTop = "15px"; const submitBtn = document.createElement("button"); submitBtn.innerText = "Submit"; submitBtn.className = "rating-btn"; submitBtn.style.marginRight = "10px"; submitBtn.addEventListener("click", function () { if (selectedRating === 0) { messageDiv.style.color = "red"; messageDiv.innerText = "Please select a rating."; return; } const apiKey = apiKeyInput.value.trim(); if (!apiKey) { messageDiv.style.color = "red"; messageDiv.innerText = "Please enter your API key."; return; } GM_setValue("buyer_api_key", apiKey); const payload = { seller_id: seller_id, api_key: apiKey, rating: selectedRating, }; GM_xmlhttpRequest({ method: "POST", url: `${BACKEND_URL}/v1/api/bazaar/rate_seller`, headers: { "Content-Type": "application/json" }, data: JSON.stringify(payload), onload: function (response) { if (response.status === 200) { messageDiv.style.color = "green"; messageDiv.innerText = "Rating submitted successfully!"; App.renderListings(); } else { messageDiv.style.color = "red"; messageDiv.innerText = "Error: " + response.responseText; } setTimeout(() => { if (document.body.contains(overlay)) { document.body.removeChild(overlay); } }, 1500); }, onerror: function (error) { messageDiv.style.color = "red"; messageDiv.innerText = "Network error. Please try again later."; setTimeout(() => { if (document.body.contains(overlay)) { document.body.removeChild(overlay); } }, 1500); }, }); }); btnContainer.appendChild(submitBtn); const cancelBtn = document.createElement("button"); cancelBtn.innerText = "Cancel"; cancelBtn.className = "rating-btn"; cancelBtn.addEventListener("click", function () { if (document.body.contains(overlay)) { document.body.removeChild(overlay); } }); btnContainer.appendChild(cancelBtn); modal.appendChild(btnContainer); overlay.appendChild(modal); document.body.appendChild(overlay); overlay.addEventListener("click", function (e) { if (e.target === overlay && document.body.contains(overlay)) { document.body.removeChild(overlay); } }); document.addEventListener("keydown", function escHandler(e) { if (e.key === "Escape") { if (document.body.contains(overlay)) { document.body.removeChild(overlay); } document.removeEventListener("keydown", escHandler); } }); }, adjustUnifiedTableTheme: function () { const isDarkMode = document.body.classList.contains("dark-mode"); const tables = document.querySelectorAll(".top-cheapest-table"); tables.forEach((table) => { if (isDarkMode) { table.style.backgroundColor = "#1c1c1c"; table.style.color = "#f0f0f0"; table.querySelectorAll("th").forEach((th) => { th.style.backgroundColor = "#444"; th.style.color = "#ffffff"; }); table.querySelectorAll("tr:nth-child(even)").forEach((tr) => { tr.style.backgroundColor = "#2a2a2a"; }); table.querySelectorAll("tr:nth-child(odd)").forEach((tr) => { tr.style.backgroundColor = "#1e1e1e"; }); table.querySelectorAll("td a").forEach((a) => { a.style.color = "#4ea8de"; }); } else { table.style.backgroundColor = "#fff"; table.style.color = "#000"; table.querySelectorAll("th").forEach((th) => { th.style.backgroundColor = "var(--table-header-bg)"; 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"; }); table.querySelectorAll("td a").forEach((a) => { a.style.color = "#007bff"; }); } }); }, adjustBazaarEnhancerContainerTheme: function () { const container = document.getElementById("bazaar-enhancer-container"); const isDarkMode = document.body.classList.contains("dark-mode"); if (container) { if (isDarkMode) { container.style.backgroundColor = "rgba(0,0,0,0.6)"; container.style.color = "#f0f0f0"; container.style.border = "1px solid rgba(255,255,255,0.1)"; container.style.boxShadow = "0 4px 8px rgba(0,0,0,0.4)"; } else { container.style.backgroundColor = "#ffffff"; container.style.color = "#000000"; container.style.border = "1px solid #ddd"; container.style.boxShadow = "0 4px 8px rgba(0,0,0,0.1)"; } } }, observeDarkMode: function () { const observer = new MutationObserver(() => { UI.adjustUnifiedTableTheme(); UI.adjustBazaarEnhancerContainerTheme(); }); observer.observe(document.body, { attributes: true, attributeFilter: ["class"], }); }, }; const App = { filterCriteria: GM_getValue("bazaar_filters", { minPrice: null, maxPrice: null, minQuantity: null, maxQuantity: null, minRating: null, }), getItemInfoFromURL: function () { const url = new URL(window.location.href); let itemID = null; let itemName = ""; if (url.hash) { let hash = url.hash.startsWith("#/") ? url.hash.substring(2) : url.hash.substring(1); let params = new URLSearchParams(hash); itemID = params.get("itemID"); itemName = decodeURIComponent(params.get("itemName") ?? ""); } if (!itemID) { let params = url.searchParams; itemID = params.get("itemID"); itemName = decodeURIComponent(params.get("itemName") ?? ""); } return { itemID: itemID ? parseInt(itemID, 10) : null, itemName: itemName, }; }, clearListingsData: function () { const topCheapestView = document.getElementById("topCheapestView"); const fullListingsView = document.getElementById("fullListingsView"); if (topCheapestView) { topCheapestView.innerHTML = `<p>No item selected.</p>`; } if (fullListingsView) { fullListingsView.innerHTML = `<p>No item selected.</p>`; } }, applyFilters: function (items) { return items.filter((item) => { if ( App.filterCriteria.minPrice !== null && item.price < App.filterCriteria.minPrice ) return false; if ( App.filterCriteria.maxPrice !== null && item.price > App.filterCriteria.maxPrice ) return false; if ( App.filterCriteria.minQuantity !== null && item.quantity < App.filterCriteria.minQuantity ) return false; if ( App.filterCriteria.maxQuantity !== null && item.quantity > App.filterCriteria.maxQuantity ) return false; if (App.filterCriteria.minRating !== null) { if ( !item.seller_rating || item.seller_rating.avg_rating === null || item.seller_rating.avg_rating < App.filterCriteria.minRating ) { return false; } } return true; }); }, multiSort: function (a, b) { for (let crit of sortCriteria) { const key = crit.key; const order = crit.order; if (a[key] < b[key]) return order === "asc" ? -1 : 1; if (a[key] > b[key]) return order === "asc" ? 1 : -1; } return 0; }, renderListings: function () { if (currentItemData && allBazaarItems.length > 0) { let filteredItems = App.applyFilters(allBazaarItems); let sortedItems = filteredItems.sort(App.multiSort); UI.displayFullListings(sortedItems); UI.displayTopCheapestItems( sortedItems.slice(0, 3), currentItemData.itemName ); } }, init: function () { UI.injectAdditionalStyles(); UI.ensureContainer().then((container) => { UI.initTabbedInterface(container); UI.observeDarkMode(); const info = App.getItemInfoFromURL(); if (info.itemID) { currentItemData = info; API.fetchBazaarItems(info.itemID); } else { App.clearListingsData(); } UI.adjustBazaarEnhancerContainerTheme(); }); }, checkForItems: function (wrapper) { if (!wrapper || wrapper.id === "bazaar-enhancer-container") return; let itemTile = wrapper.previousElementSibling; if (itemTile && itemTile.id === "bazaar-enhancer-container") { itemTile = itemTile.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]; currentItemData = { itemID: parseInt(itemId, 10), itemName: itemName, }; API.fetchBazaarItems(currentItemData.itemID); } }, checkForItemsMobile: function () { if (window.innerWidth >= 784) return; const sellerList = document.querySelector("ul.sellerList___e4C9_"); 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 = null; if (btn) { const parts = btn.getAttribute("aria-controls").split("-"); itemId = parts.length > 2 ? parts[parts.length - 2] : parts[parts.length - 1]; } if (!itemId) return; currentItemData = { itemID: parseInt(itemId, 10), itemName: itemName, }; API.fetchBazaarItems(currentItemData.itemID); }, observeMutations: function () { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.nodeType !== Node.ELEMENT_NODE) return; if ( window.innerWidth < 784 && node.classList.contains("sellerList___e4C9_") ) { App.checkForItemsMobile(); } else if ( window.innerWidth >= 784 && node.className.includes("sellerListWrapper") ) { App.checkForItems(node); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); }, }; App.observeMutations(); setInterval(() => { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(() => { let info = App.getItemInfoFromURL(); if (info.itemID) { currentItemData = info; API.fetchBazaarItems(info.itemID); } else { if (window.innerWidth < 784) { App.checkForItemsMobile(); } else { const wrapper = document.querySelector( '[class*="sellerListWrapper"]' ); if (wrapper) App.checkForItems(wrapper); else { App.clearListingsData(); currentItemData = null; } } } }, 100); } }, 500); App.init(); function varFallback(variable, fallback) { try { return ( getComputedStyle(document.documentElement) .getPropertyValue(variable) .trim() || fallback ); } catch (e) { return fallback; } } })();