// ==UserScript==
// @name Bazaars in Item Market Powered by TornW3B
// @namespace http://tampermonkey.net/
// @version 2.41
// @description Displays bazaar listings with sorting controls
// @author Weav3r
// @match https://www.torn.com/*
// @grant GM.xmlHttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_listValues
// @grant GM.setValue
// @grant GM.getValue
// @grant GM.deleteValue
// @grant GM.listValues
// @connect weav3r.dev
// @run-at document-end
// @downloadURL https://update.greasyfork.icu/scripts/527616/Bazaars%20in%20Item%20Market%20Powered%20by%20TornW3B.user.js
// @updateURL https://update.greasyfork.icu/scripts/527616/Bazaars%20in%20Item%20Market%20Powered%20by%20TornW3B.meta.js
// ==/UserScript==
(function () {
'use strict';
if (typeof GM_setValue === 'undefined' && typeof GM !== 'undefined') {
const GM_getValue = function(key, defaultValue) {
let value;
try {
value = localStorage.getItem('GMcompat_' + key);
if (value !== null) {
return JSON.parse(value);
}
GM.getValue(key, defaultValue).then(val => {
if (val !== undefined) {
localStorage.setItem('GMcompat_' + key, JSON.stringify(val));
}
});
return defaultValue;
} catch (e) {
console.error('Error in GM_getValue compatibility:', e);
return defaultValue;
}
};
const GM_setValue = function(key, value) {
try {
// Store in both localStorage and GM.setValue
localStorage.setItem('GMcompat_' + key, JSON.stringify(value));
GM.setValue(key, value);
} catch (e) {
console.error('Error in GM_setValue compatibility:', e);
}
};
const GM_deleteValue = function(key) {
try {
localStorage.removeItem('GMcompat_' + key);
GM.deleteValue(key);
} catch (e) {
console.error('Error in GM_deleteValue compatibility:', e);
}
};
const GM_listValues = function() {
// This is an approximation - we can only list keys with our prefix
const keys = [];
try {
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.startsWith('GMcompat_')) {
keys.push(key.substring(9)); // Remove the prefix
}
}
} catch (e) {
console.error('Error in GM_listValues compatibility:', e);
}
return keys;
};
window.GM_getValue = GM_getValue;
window.GM_setValue = GM_setValue;
window.GM_deleteValue = GM_deleteValue;
window.GM_listValues = GM_listValues;
}
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;
const scriptSettings = {
defaultSort: "price",
defaultOrder: "asc",
apiKey: "",
listingFee: parseFloat(GM_getValue("bazaarListingFee") || "0"),
defaultDisplayMode: "percentage",
linkBehavior: GM_getValue("bazaarLinkBehavior") || "new_tab"
};
const updateStyles = () => {
let styleEl = document.getElementById('bazaar-enhanced-styles');
if (!styleEl) {
styleEl = document.createElement('style');
styleEl.id = 'bazaar-enhanced-styles';
document.head.appendChild(styleEl);
}
styleEl.textContent = `
.bazaar-profit-tooltip {
position: fixed;
background: ${currentDarkMode ? '#333' : '#fff'};
color: ${currentDarkMode ? '#fff' : '#333'};
border: 1px solid ${currentDarkMode ? '#555' : '#ddd'};
padding: 10px 14px;
border-radius: 5px;
box-shadow: 0 3px 10px rgba(0,0,0,0.3);
z-index: 99999;
min-width: 200px;
max-width: 280px;
width: auto;
pointer-events: none;
transition: opacity 0.2s ease;
font-size: 13px;
line-height: 1.4;
}
@media (max-width: 768px) {
.bazaar-profit-tooltip {
font-size: 12px;
max-width: 260px;
padding: 8px 12px;
}
}
`;
};
updateStyles();
const darkModeObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.attributeName === 'class') {
const newDarkMode = document.body.classList.contains('dark-mode');
if (newDarkMode !== currentDarkMode) {
currentDarkMode = newDarkMode;
updateStyles();
}
}
});
});
darkModeObserver.observe(document.body, { attributes: true });
function checkMobileView() {
isMobileView = window.innerWidth < 784;
return isMobileView;
}
checkMobileView();
window.addEventListener('resize', function() {
checkMobileView();
processMobileSellerList();
});
function loadSettings() {
try {
const saved = GM_getValue("bazaarsSettings");
if (saved) {
const parsedSettings = JSON.parse(saved);
Object.assign(scriptSettings, parsedSettings);
if (parsedSettings.defaultSort) {
currentSortKey = parsedSettings.defaultSort;
}
if (parsedSettings.defaultOrder) {
currentSortOrder = parsedSettings.defaultOrder;
}
if (parsedSettings.defaultDisplayMode) {
displayMode = parsedSettings.defaultDisplayMode;
}
}
} catch (e) {
console.error("Oops, settings failed to load:", e);
}
}
function saveSettings() {
try {
GM_setValue("bazaarsSettings", JSON.stringify(scriptSettings));
GM_setValue("bazaarApiKey", scriptSettings.apiKey || "");
GM_setValue("bazaarDefaultSort", scriptSettings.defaultSort || "price");
GM_setValue("bazaarDefaultOrder", scriptSettings.defaultOrder || "asc");
GM_setValue("bazaarListingFee", scriptSettings.listingFee || 0);
GM_setValue("bazaarDefaultDisplayMode", scriptSettings.defaultDisplayMode || "percentage");
GM_setValue("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: fixed;
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: 99999;
min-width: 200px;
max-width: 280px;
width: auto;
pointer-events: none;
transition: opacity 0.2s ease;
}
.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-min-qty {
background-color: #fff;
color: #000;
font-size: 12px;
}
.dark-mode .bazaar-min-qty {
border: 1px solid #444 !important;
background-color: #1a1a1a;
color: #fff;
}
.bazaar-min-qty:focus {
outline: none;
border-color: #0078d7 !important;
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;
/* Added transition for position, opacity and scale */
transition: left 0.5s ease, opacity 0.5s ease, transform 0.5s ease;
}
.dark-mode .bazaar-listing-card {
background-color: #1a1a1a;
color: #fff;
border: 1px solid #444;
}
/* Fade-in/out classes for animations */
.fade-in {
opacity: 0;
transform: translateY(-50%) scale(0.8);
}
.fade-out {
opacity: 0;
transform: translateY(-50%) scale(0.8);
}
.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: 200px;
}
.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: 200px;
}
.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;
function makeRequest(options) {
if (typeof GM_xmlhttpRequest !== 'undefined') {
return GM_xmlhttpRequest(options);
} else if (typeof GM !== 'undefined' && typeof GM.xmlHttpRequest !== 'undefined') {
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(GM_getValue("tornItems") || "{}");
} catch (e) {
cachedItemsData = {};
console.error("Stored items got funky:", e);
}
}
return cachedItemsData;
}
function getCache(itemId) {
try {
const key = "tornBazaarCache_" + itemId,
cached = GM_getValue(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 {
GM_setValue("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: `