// ==UserScript==
// @name Bazaars in Item Market powered by TornPal and IronNerd
// @namespace http://tampermonkey.net/
// @version 2.13
// @description Displays bazaar listings with sorting controls via TornPal & IronNerd
// @author Weav3r
// @match https://www.torn.com/*
// @grant GM_xmlhttpRequest
// @connect tornpal.com
// @connect www.ironnerd.me
// @run-at document-end
// @downloadURL none
// ==/UserScript==
// ==UserScript==
// @name Bazaars in Item Market powered by TornPal and IronNerd
// @namespace http://tampermonkey.net/
// @version 2.11
// @description Displays bazaar listings with sorting controls via TornPal & IronNerd
// @author Weav3r
// @match https://www.torn.com/*
// @grant GM_xmlhttpRequest
// @grant GM.xmlHttpRequest
// @connect tornpal.com
// @connect www.ironnerd.me
// @updateURL https://greasyfork.org/en/scripts/527616/code/Bazaars%20in%20Item%20Market%20powered%20by%20TornPal%20and%20IronNerd.user.js
// @downloadURL https://greasyfork.org/en/scripts/527616/code/Bazaars%20in%20Item%20Market%20powered%20by%20TornPal%20and%20IronNerd.user.js
// @run-at document-end
// ==/UserScript==
(function () {
'use strict';
const CACHE_DURATION_MS = 60000,
CARD_WIDTH = 180;
let currentSortKey = "price",
currentSortOrder = "asc",
allListings = [],
currentDarkMode = document.body.classList.contains('dark-mode'),
currentItemName = "",
displayMode = "percentage",
isMobileView = false;
let scriptSettings = {
defaultSort: "price",
defaultOrder: "asc",
apiKey: "",
listingFee: parseFloat(localStorage.getItem("bazaarListingFee") || "0"),
defaultDisplayMode: "percentage",
linkBehavior: localStorage.getItem("bazaarLinkBehavior") || "new_tab"
};
function checkMobileView() {
isMobileView = window.innerWidth < 784 ||
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
return isMobileView;
}
checkMobileView();
window.addEventListener('resize', function() {
checkMobileView();
processMobileSellerList();
});
function loadSettings() {
try {
const saved = localStorage.getItem("bazaarsSettings");
if (saved) {
scriptSettings = { ...scriptSettings, ...JSON.parse(saved) };
currentSortKey = scriptSettings.defaultSort;
currentSortOrder = scriptSettings.defaultOrder;
displayMode = scriptSettings.defaultDisplayMode || "percentage";
}
} catch (e) {
console.error("Oops, settings failed to load:", e);
}
}
function saveSettings() {
try {
localStorage.setItem("bazaarsSettings", JSON.stringify(scriptSettings));
localStorage.setItem("bazaarApiKey", scriptSettings.apiKey || "");
localStorage.setItem("bazaarDefaultSort", scriptSettings.defaultSort || "price");
localStorage.setItem("bazaarDefaultOrder", scriptSettings.defaultOrder || "asc");
localStorage.setItem("bazaarListingFee", scriptSettings.listingFee || 0);
localStorage.setItem("bazaarDefaultDisplayMode", scriptSettings.defaultDisplayMode || "percentage");
localStorage.setItem("bazaarLinkBehavior", scriptSettings.linkBehavior || "new_tab");
} catch (e) {
console.error("Settings save hiccup:", e);
}
}
loadSettings();
const style = document.createElement("style");
style.textContent = `
.bazaar-button {
padding: 3px 6px;
border: 1px solid #ccc;
border-radius: 4px;
background-color: #fff;
color: #000;
cursor: pointer;
font-size: 12px;
margin-left: 4px;
}
.dark-mode .bazaar-button {
border: 1px solid #444;
background-color: #1a1a1a;
color: #fff;
}
.bazaar-modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 99999;
}
.bazaar-info-container {
font-size: 13px;
border-radius: 4px;
margin: 5px 0;
padding: 10px;
display: flex;
flex-direction: column;
gap: 8px;
background-color: #f9f9f9;
color: #000;
border: 1px solid #ccc;
box-sizing: border-box;
width: 100%;
overflow: hidden;
}
.dark-mode .bazaar-info-container {
background-color: #2f2f2f;
color: #ccc;
border: 1px solid #444;
}
.bazaar-info-header {
font-size: 16px;
font-weight: bold;
color: #000;
}
.dark-mode .bazaar-info-header {
color: #fff;
}
.bazaar-sort-controls {
display: flex;
align-items: center;
gap: 5px;
font-size: 12px;
padding: 5px;
background-color: #eee;
border-radius: 4px;
border: 1px solid #ccc;
}
.dark-mode .bazaar-sort-controls {
background-color: #333;
border: 1px solid #444;
}
.bazaar-sort-select {
padding: 3px 24px 3px 8px;
border: 1px solid #ccc;
border-radius: 4px;
background: #fff url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAiIGhlaWdodD0iNiIgdmlld0JveD0iMCAwIDEwIDYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTAgMGw1IDYgNS02eiIgZmlsbD0iIzY2NiIvPjwvc3ZnPg==") no-repeat right 8px center;
background-size: 10px 6px;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
cursor: pointer;
}
.bazaar-profit-tooltip {
position: absolute;
background: #fff;
color: #333;
border: 1px solid #ddd;
padding: 8px 12px;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
z-index: 10000;
max-width: 280px;
pointer-events: none;
}
.dark-mode .bazaar-profit-tooltip {
background: #333;
color: #fff;
border: 1px solid #555;
}
.dark-mode .bazaar-sort-select {
border: 1px solid #444;
background-color: #1a1a1a;
color: #fff;
background-image: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAiIGhlaWdodD0iNiIgdmlld0JveD0iMCAwIDEwIDYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTAgMGw1IDYgNS02eiIgZmlsbD0iI2NjYyIvPjwvc3ZnPg==");
}
.bazaar-sort-select:focus {
outline: none;
border-color: #0078d7;
box-shadow: 0 0 0 1px #0078d7;
}
.bazaar-scroll-container {
position: relative;
display: flex;
align-items: stretch;
width: 100%;
box-sizing: border-box;
}
.bazaar-scroll-wrapper {
flex: 1;
overflow-x: auto;
overflow-y: hidden;
height: 100px;
white-space: nowrap;
padding-bottom: 3px;
border-radius: 4px;
border: 1px solid #ccc;
margin: 0 auto;
max-width: calc(100% - 30px);
position: relative;
}
.dark-mode .bazaar-scroll-wrapper {
border: 1px solid #444;
}
.bazaar-scroll-arrow {
display: flex;
align-items: center;
justify-content: center;
width: 12px;
flex-shrink: 0;
flex-grow: 0;
cursor: pointer;
background-color: transparent;
border: none;
opacity: 0.5;
transition: opacity 0.2s ease;
margin: 0 1px;
z-index: 2;
position: relative;
}
.bazaar-scroll-arrow:hover {
opacity: 0.9;
background-color: transparent;
}
.dark-mode .bazaar-scroll-arrow {
background-color: transparent;
border: none;
}
.bazaar-scroll-arrow svg {
width: 18px !important;
height: 18px !important;
color: #888;
}
.dark-mode .bazaar-scroll-arrow svg {
color: #777;
}
.bazaar-scroll-arrow.left {
padding-left: 10px;
margin-left: -10px;
}
.bazaar-scroll-arrow.right {
padding-right: 10px;
margin-right: -10px;
}
.bazaar-scroll-wrapper::-webkit-scrollbar {
height: 8px;
}
.bazaar-scroll-wrapper::-webkit-scrollbar-track {
background: #f1f1f1;
}
.bazaar-scroll-wrapper::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
}
.bazaar-scroll-wrapper::-webkit-scrollbar-thumb:hover {
background: #555;
}
.dark-mode .bazaar-scroll-wrapper::-webkit-scrollbar-track {
background: #333;
}
.dark-mode .bazaar-scroll-wrapper::-webkit-scrollbar-thumb {
background: #555;
}
.dark-mode .bazaar-scroll-wrapper::-webkit-scrollbar-thumb:hover {
background: #777;
}
.bazaar-card-container {
position: relative;
height: 100%;
display: flex;
align-items: center;
}
.bazaar-listing-card {
position: absolute;
min-width: 140px;
max-width: 200px;
display: flex;
flex-direction: column;
justify-content: space-between;
border-radius: 4px;
padding: 8px;
font-size: clamp(12px, 1vw, 14px);
box-sizing: border-box;
overflow: hidden;
background-color: #fff;
color: #000;
border: 1px solid #ccc;
top: 50%;
transform: translateY(-50%);
word-break: break-word;
height: auto;
}
.dark-mode .bazaar-listing-card {
background-color: #1a1a1a;
color: #fff;
border: 1px solid #444;
}
.bazaar-listing-footnote {
font-size: 11px;
text-align: right;
color: #555;
}
.dark-mode .bazaar-listing-footnote {
color: #aaa;
}
.bazaar-listing-source {
font-size: 10px;
text-align: right;
color: #555;
}
.dark-mode .bazaar-listing-source {
color: #aaa;
}
.bazaar-footer-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 5px;
font-size: 10px;
}
.bazaar-powered-by span {
color: #999;
}
.dark-mode .bazaar-powered-by span {
color: #666;
}
.bazaar-powered-by a {
text-decoration: underline;
color: #555;
}
.dark-mode .bazaar-powered-by a {
color: #aaa;
}
@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;
}
.bazaar-settings-modal {
background-color: #fff;
border-radius: 8px;
padding: 24px;
width: 500px;
max-width: 95vw;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
position: relative;
z-index: 100000;
font-family: 'Arial', sans-serif;
}
.dark-mode .bazaar-settings-modal {
background-color: #2a2a2a;
color: #e0e0e0;
border: 1px solid #444;
}
.bazaar-settings-title {
font-size: 20px;
font-weight: bold;
margin-bottom: 20px;
color: #333;
}
.dark-mode .bazaar-settings-title {
color: #fff;
}
.bazaar-tabs {
display: flex;
border-bottom: 1px solid #ddd;
margin-bottom: 20px;
padding-bottom: 0;
flex-wrap: wrap;
}
.dark-mode .bazaar-tabs {
border-bottom: 1px solid #444;
}
.bazaar-tab {
padding: 10px 16px;
cursor: pointer;
margin-right: 5px;
margin-bottom: 5px;
border: 1px solid transparent;
border-bottom: none;
border-radius: 4px 4px 0 0;
font-weight: normal;
background-color: #f5f5f5;
color: #555;
position: relative;
bottom: -1px;
}
.dark-mode .bazaar-tab {
background-color: #333;
color: #ccc;
}
.bazaar-tab.active {
background-color: #fff;
color: #333;
border-color: #ddd;
font-weight: bold;
padding-bottom: 11px;
}
.dark-mode .bazaar-tab.active {
background-color: #2a2a2a;
color: #fff;
border-color: #444;
}
.bazaar-tab-content {
display: none;
}
.bazaar-tab-content.active {
display: block;
}
.bazaar-settings-group {
margin-bottom: 20px;
}
.bazaar-settings-item {
margin-bottom: 18px;
}
.bazaar-settings-item label {
display: block;
margin-bottom: 8px;
font-weight: bold;
font-size: 14px;
}
.bazaar-settings-item input[type="text"],
.bazaar-settings-item select,
.bazaar-number-input {
width: 100%;
padding: 8px 12px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
background-color: #fff;
color: #333;
max-width: 250px;
}
.dark-mode .bazaar-settings-item input[type="text"],
.dark-mode .bazaar-settings-item select,
.dark-mode .bazaar-number-input {
border: 1px solid #444;
background-color: #222;
color: #e0e0e0;
}
.bazaar-settings-item select {
max-width: 250px;
}
.bazaar-number-input {
-moz-appearance: textfield;
appearance: textfield;
width: 60px !important;
}
.bazaar-number-input::-webkit-outer-spin-button,
.bazaar-number-input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
.bazaar-api-note {
font-size: 12px;
margin-top: 6px;
color: #666;
line-height: 1.4;
}
.dark-mode .bazaar-api-note {
color: #aaa;
}
.bazaar-script-item {
margin-bottom: 16px;
padding-bottom: 16px;
border-bottom: 1px solid #eee;
}
.dark-mode .bazaar-script-item {
border-bottom: 1px solid #333;
}
.bazaar-script-item:last-child {
border-bottom: none;
}
.bazaar-script-name {
font-weight: bold;
font-size: 16px;
margin-bottom: 5px;
}
.bazaar-script-desc {
margin-bottom: 8px;
line-height: 1.4;
color: #555;
}
.dark-mode .bazaar-script-desc {
color: #bbb;
}
.bazaar-script-link {
display: inline-block;
margin-top: 5px;
color: #2196F3;
text-decoration: none;
}
.bazaar-script-link:hover {
text-decoration: underline;
}
.bazaar-changelog {
margin-bottom: 20px;
}
.bazaar-changelog-version {
font-weight: bold;
margin-bottom: 8px;
font-size: 15px;
}
.bazaar-changelog-date {
font-style: italic;
color: #666;
font-size: 13px;
margin-bottom: 5px;
}
.dark-mode .bazaar-changelog-date {
color: #aaa;
}
.bazaar-changelog-list {
margin-left: 20px;
margin-bottom: 15px;
}
.bazaar-changelog-item {
margin-bottom: 5px;
line-height: 1.4;
}
.bazaar-credits {
margin-top: 20px;
padding-top: 15px;
border-top: 1px solid #eee;
}
.dark-mode .bazaar-credits {
border-top: 1px solid #444;
}
.bazaar-credits h3 {
font-size: 16px;
margin-bottom: 10px;
}
.bazaar-credits p {
line-height: 1.4;
margin-bottom: 8px;
}
.bazaar-provider {
font-weight: bold;
}
.bazaar-settings-buttons {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 30px;
}
.bazaar-settings-save,
.bazaar-settings-cancel {
padding: 8px 16px;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
font-weight: bold;
}
.bazaar-settings-save {
background-color: #4CAF50;
color: white;
}
.bazaar-settings-save:hover {
background-color: #45a049;
}
.bazaar-settings-cancel {
background-color: #f5f5f5;
color: #333;
border: 1px solid #ddd;
}
.dark-mode .bazaar-settings-cancel {
background-color: #333;
color: #e0e0e0;
border: 1px solid #444;
}
.bazaar-settings-cancel:hover {
background-color: #e9e9e9;
}
.dark-mode .bazaar-settings-cancel:hover {
background-color: #444 !important;
border-color: #555 !important;
}
.bazaar-settings-footer {
margin-top: 20px;
font-size: 12px;
color: #777;
text-align: center;
padding-top: 15px;
border-top: 1px solid #eee;
}
.dark-mode .bazaar-settings-footer {
color: #999;
border-top: 1px solid #444;
}
.bazaar-settings-footer a {
color: #2196F3;
text-decoration: none;
}
.bazaar-settings-footer a:hover {
text-decoration: underline;
}
@media (max-width: 600px) {
.bazaar-settings-modal {
padding: 16px;
width: 100%;
max-width: 100%;
border-radius: 0;
max-height: 100vh;
}
.bazaar-settings-title {
font-size: 18px;
margin-bottom: 16px;
}
.bazaar-tab {
padding: 8px 12px;
font-size: 14px;
}
.bazaar-settings-item label {
font-size: 13px;
}
.bazaar-settings-item input[type="text"],
.bazaar-settings-item select,
.bazaar-number-input {
padding: 6px 10px;
font-size: 13px;
max-width: 100%;
}
.bazaar-settings-item {
margin-bottom: 14px;
}
.bazaar-settings-save,
.bazaar-settings-cancel {
padding: 6px 12px;
font-size: 13px;
}
.bazaar-api-note {
font-size: 11px;
}
.bazaar-settings-buttons {
margin-top: 20px;
}
.bazaar-settings-footer {
font-size: 11px;
}
}
`;
document.head.appendChild(style);
function fetchJSON(url, callback) {
let retryCount = 0;
const MAX_RETRIES = 2;
const TIMEOUT_MS = 10000;
const RETRY_DELAY_MS = 2000;
// Helper function to handle different XMLHttpRequest implementations
function makeRequest(options) {
// Try GM_xmlhttpRequest first, then GM.xmlHttpRequest as fallback
if (typeof GM_xmlhttpRequest !== 'undefined') {
return GM_xmlhttpRequest(options);
} else if (typeof GM !== 'undefined' && typeof GM.xmlHttpRequest !== 'undefined') {
// GM.xmlHttpRequest returns a promise in some managers, but we handle callbacks directly
return GM.xmlHttpRequest(options);
} else {
console.error('Neither GM_xmlhttpRequest nor GM.xmlHttpRequest are available');
options.onerror && options.onerror(new Error('XMLHttpRequest API not available'));
return null;
}
}
function attemptFetch() {
let timeoutId = setTimeout(() => {
console.warn(`Request to ${url} timed out, ${retryCount < MAX_RETRIES ? 'retrying...' : 'giving up.'}`);
if (retryCount < MAX_RETRIES) {
retryCount++;
setTimeout(attemptFetch, RETRY_DELAY_MS);
} else {
callback(null);
}
}, TIMEOUT_MS);
makeRequest({
method: 'GET',
url,
timeout: TIMEOUT_MS,
onload: res => {
clearTimeout(timeoutId);
try {
if (res.status >= 200 && res.status < 300) {
callback(JSON.parse(res.responseText));
} else {
console.warn(`Request to ${url} failed with status ${res.status}`);
if (retryCount < MAX_RETRIES) {
retryCount++;
setTimeout(attemptFetch, RETRY_DELAY_MS);
} else {
callback(null);
}
}
} catch (e) {
console.error(`Error parsing response from ${url}:`, e);
callback(null);
}
},
onerror: (error) => {
clearTimeout(timeoutId);
console.warn(`Request to ${url} failed:`, error);
if (retryCount < MAX_RETRIES) {
retryCount++;
setTimeout(attemptFetch, RETRY_DELAY_MS);
} else {
callback(null);
}
},
ontimeout: () => {
clearTimeout(timeoutId);
console.warn(`Request to ${url} timed out natively`);
if (retryCount < MAX_RETRIES) {
retryCount++;
setTimeout(attemptFetch, RETRY_DELAY_MS);
} else {
callback(null);
}
}
});
}
attemptFetch();
}
let cachedItemsData = null;
function getStoredItems() {
if (cachedItemsData === null) {
try {
cachedItemsData = JSON.parse(localStorage.getItem("tornItems") || "{}");
} catch (e) {
cachedItemsData = {};
console.error("Stored items got funky:", e);
}
}
return cachedItemsData;
}
function getCache(itemId) {
try {
const key = "tornBazaarCache_" + itemId,
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';
}
const svgTemplates = {
rightArrow: ``,
leftArrow: ``,
warningIcon: `