// ==UserScript==
// @name Customizable Bazaar Filler
// @namespace http://tampermonkey.net/
// @version 1.35
// @description On click, auto-fills bazaar item quantities and prices based on your preferences
// @match https://www.torn.com/bazaar.php*
// @require https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// ==/UserScript==
(function() {
'use strict';
const styleBlock = `
/* Existing checkbox styling (visible square) */
.item-toggle {
width: 16px;
height: 16px;
border-radius: 3px;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
outline: none;
}
.item-toggle::after {
content: '\\2713';
position: absolute;
font-size: 12px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: none;
}
.item-toggle:checked::after {
display: block;
}
/* Light mode */
body:not(.dark-mode) .item-toggle {
border: 1px solid #ccc;
background: #fff;
}
body:not(.dark-mode) .item-toggle:checked {
background: #007bff;
}
body:not(.dark-mode) .item-toggle:checked::after {
color: #fff;
}
/* Dark mode */
body.dark-mode .item-toggle {
border: 1px solid #4e535a;
background: #2f3237;
}
body.dark-mode .item-toggle:checked {
background: #4e535a;
}
body.dark-mode .item-toggle:checked::after {
color: #fff;
}
/* Checkbox wrapper to increase clickable area */
.checkbox-wrapper {
position: absolute;
top: 50%;
right: 10px;
width: 26px;
height: 26px;
transform: translateY(-50%);
cursor: pointer;
}
.checkbox-wrapper input.item-toggle {
position: absolute;
top: 5px;
left: 5px;
}
/* Modal overlay */
.settings-modal-overlay {
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
background: rgba(0,0,0,0.5);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
}
/* Modal container */
.settings-modal {
background: #fff;
padding: 20px;
border-radius: 8px;
min-width: 300px;
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
color: #000;
}
.settings-modal h2 {
margin-top: 0;
}
.settings-modal label {
display: block;
margin: 10px 0 5px;
}
.settings-modal input, .settings-modal select {
width: 100%;
padding: 5px;
box-sizing: border-box;
}
.settings-modal button {
margin-top: 15px;
padding: 5px 10px;
}
/* Button group alignment */
.settings-modal div[style*="text-align:right"] {
text-align: right;
}
/* Dark mode modal overrides */
body.dark-mode .settings-modal {
background: #2f3237;
color: #fff;
box-shadow: 0 2px 10px rgba(0,0,0,0.7);
}
body.dark-mode .settings-modal input,
body.dark-mode .settings-modal select {
background: #3c3f41;
color: #fff;
border: 1px solid #555;
}
body.dark-mode .settings-modal button {
background: #555;
color: #fff;
border: none;
}
`;
$('<style>').prop('type', 'text/css').html(styleBlock).appendTo('head');
let apiKey = GM_getValue("tornApiKey", "");
let pricingSource = GM_getValue("pricingSource", "Market Value");
let itemMarketOffset = GM_getValue("itemMarketOffset", -1);
let itemMarketMarginType = GM_getValue("itemMarketMarginType", "absolute");
let itemMarketListing = GM_getValue("itemMarketListing", 1);
let itemMarketClamp = GM_getValue("itemMarketClamp", false);
let marketMarginOffset = GM_getValue("marketMarginOffset", 0);
let marketMarginType = GM_getValue("marketMarginType", "absolute");
const validPages = ["#/add", "#/manage"];
let currentPage = window.location.hash;
let itemMarketCache = {};
function getItemIdByName(itemName) {
const storedItems = JSON.parse(localStorage.getItem("tornItems") || "{}");
for (let [id, info] of Object.entries(storedItems)) {
if (info.name === itemName) return id;
}
return null;
}
function getPriceColor(listedPrice, marketValue) {
if (marketValue <= 0) {
return "#FFFFFF";
}
const ratio = listedPrice / marketValue;
if (ratio < 0) return "#FF0000";
if (ratio > 2) return "#008000";
if (ratio < 1) {
let t = Math.max(0, ratio);
let r = Math.round(255 + (255 - 255) * t);
let g = Math.round(0 + (255 - 0) * t);
let b = Math.round(0 + (255 - 0) * t);
return `rgb(${r},${g},${b})`;
} else {
let t = ratio - 1;
let r = Math.round(255 + (0 - 255) * t);
let g = Math.round(255 + (128 - 255) * t);
let b = Math.round(255 + (0 - 255) * t);
return `rgb(${r},${g},${b})`;
}
}
async function fetchItemMarketData(itemId) {
if (!apiKey) {
console.error("No API key set for Item Market calls.");
alert("No API key set. Please set your Torn API key in Bazaar Filler Settings before continuing.");
return null;
}
const now = Date.now();
if (itemMarketCache[itemId] && (now - itemMarketCache[itemId].time < 30000)) {
return itemMarketCache[itemId].data;
}
const url = `https://api.torn.com/v2/market/${itemId}/itemmarket`;
try {
const res = await fetch(url, {
headers: { 'Authorization': 'ApiKey ' + apiKey }
});
const data = await res.json();
if (data.error) {
console.error("Item Market API error:", data.error);
alert("Item Market API error: " + data.error.error);
return null;
}
itemMarketCache[itemId] = { time: now, data };
return data;
} catch (err) {
console.error("Failed fetching Item Market data:", err);
alert("Failed to fetch Item Market data. Check your API key or try again later.");
return null;
}
}
async function updateAddRow($row, isChecked) {
const $qtyInput = $row.find(".amount input").first();
const $priceInput = $row.find(".price input").first();
const $choiceCheckbox = $row.find("div.amount.choice-container input");
if (!isChecked) {
// If a tickable quantity checkbox exists, trigger a click to untick it.
if ($choiceCheckbox.length && $choiceCheckbox.prop("checked")) {
$choiceCheckbox.click();
}
// Reset quantity field.
if ($qtyInput.data("orig") !== undefined) {
$qtyInput.val($qtyInput.data("orig"));
$qtyInput.removeData("orig");
} else {
$qtyInput.val("");
}
$qtyInput[0].dispatchEvent(new Event("keyup", { bubbles: true }));
// Reset price field.
if ($priceInput.data("orig") !== undefined) {
$priceInput.val($priceInput.data("orig"));
$priceInput.removeData("orig");
$priceInput.css("color", "");
} else {
$priceInput.val("");
}
$priceInput[0].dispatchEvent(new Event("input", { bubbles: true }));
$priceInput[0].dispatchEvent(new Event("keyup", { bubbles: true }));
return;
}
// Save original values if not already saved.
if (!$qtyInput.data("orig")) $qtyInput.data("orig", $qtyInput.val());
if (!$priceInput.data("orig")) $priceInput.data("orig", $priceInput.val());
const itemName = $row.find(".name-wrap span.t-overflow").text().trim();
const itemId = getItemIdByName(itemName);
const storedItems = JSON.parse(localStorage.getItem("tornItems") || "{}");
const matchedItem = Object.values(storedItems).find(i => i.name === itemName);
// Update quantity field: if there's a tickable checkbox, click it; otherwise, set the value.
if ($choiceCheckbox.length) {
$choiceCheckbox.click();
} else {
let qty = $row.find(".item-amount.qty").text().trim();
$qtyInput.val(qty);
$qtyInput[0].dispatchEvent(new Event("keyup", { bubbles: true }));
}
// Update price field based on pricing source.
if (pricingSource === "Market Value" && matchedItem) {
let mv = Number(matchedItem.market_value);
let finalPrice = mv;
if (marketMarginType === "absolute") {
finalPrice += marketMarginOffset;
} else if (marketMarginType === "percentage") {
finalPrice = Math.round(mv * (1 + marketMarginOffset / 100));
}
$priceInput.val(finalPrice.toLocaleString());
$priceInput[0].dispatchEvent(new Event("input", { bubbles: true }));
$priceInput[0].dispatchEvent(new Event("keyup", { bubbles: true }));
$priceInput.css("color", getPriceColor(finalPrice, mv));
}
else if (pricingSource === "Item Market" && itemId) {
const data = await fetchItemMarketData(itemId);
if (!data || !data.itemmarket?.listings?.length) return;
let listings = data.itemmarket.listings;
const $checkbox = $row.find(".checkbox-wrapper input.item-toggle").first();
const listingsText = listings.slice(0, 5)
.map((x, i) => `${i + 1}) $${x.price.toLocaleString()} x${x.amount}`)
.join('\n');
$checkbox.attr("title", listingsText);
setTimeout(() => {
$checkbox.removeAttr("title");
}, 30000);
let baseIndex = Math.min(itemMarketListing - 1, listings.length - 1);
let listingPrice = listings[baseIndex].price;
let finalPrice;
if (itemMarketMarginType === "absolute") {
finalPrice = listingPrice + Number(itemMarketOffset);
} else if (itemMarketMarginType === "percentage") {
finalPrice = Math.round(listingPrice * (1 + Number(itemMarketOffset) / 100));
}
if (itemMarketClamp && matchedItem && matchedItem.market_value) {
finalPrice = Math.max(finalPrice, Number(matchedItem.market_value));
}
if (!$choiceCheckbox.length) {
let qty = $row.find(".item-amount.qty").text().trim();
$qtyInput.val(qty);
$qtyInput[0].dispatchEvent(new Event("keyup", { bubbles: true }));
} else {
$choiceCheckbox.click();
}
$priceInput.val(finalPrice.toLocaleString());
$priceInput[0].dispatchEvent(new Event("input", { bubbles: true }));
$priceInput[0].dispatchEvent(new Event("keyup", { bubbles: true }));
if (matchedItem && matchedItem.market_value) {
let marketVal = Number(matchedItem.market_value);
$priceInput.css("color", getPriceColor(finalPrice, marketVal));
}
}
else if (pricingSource === "Bazaars/TornPal") {
alert("Bazaars/TornPal is not available. Please select another source.");
}
}
async function updateManageRow($row, isChecked) {
const $priceInput = $row.find(".price___DoKP7 .input-money-group.success input.input-money").first();
if (!isChecked) {
if ($priceInput.data("orig") !== undefined) {
$priceInput.val($priceInput.data("orig"));
$priceInput.removeData("orig");
$priceInput.css("color", "");
} else {
$priceInput.val("");
}
$priceInput[0].dispatchEvent(new Event("input", { bubbles: true }));
return;
}
if (!$priceInput.data("orig")) $priceInput.data("orig", $priceInput.val());
const itemName = $row.find(".desc___VJSNQ b").text().trim();
const itemId = getItemIdByName(itemName);
const storedItems = JSON.parse(localStorage.getItem("tornItems") || "{}");
const matchedItem = Object.values(storedItems).find(i => i.name === itemName);
if (pricingSource === "Market Value" && matchedItem) {
let mv = Number(matchedItem.market_value);
let finalPrice = mv;
if (marketMarginType === "absolute") {
finalPrice += marketMarginOffset;
} else if (marketMarginType === "percentage") {
finalPrice = Math.round(mv * (1 + marketMarginOffset / 100));
}
$priceInput.val(finalPrice.toLocaleString());
// Dispatch the input event to trigger listeners.
$priceInput[0].dispatchEvent(new Event("input", { bubbles: true }));
$priceInput.css("color", getPriceColor(finalPrice, mv));
}
else if (pricingSource === "Item Market" && itemId) {
const data = await fetchItemMarketData(itemId);
if (!data || !data.itemmarket?.listings?.length) return;
let listings = data.itemmarket.listings;
const $checkbox = $row.find(".checkbox-wrapper input.item-toggle").first();
const listingsText = listings.slice(0, 5)
.map((x, i) => `${i + 1}) $${x.price.toLocaleString()} x${x.amount}`)
.join('\n');
$checkbox.attr("title", listingsText);
setTimeout(() => {
$checkbox.removeAttr("title");
}, 30000);
let baseIndex = Math.min(itemMarketListing - 1, listings.length - 1);
let listingPrice = listings[baseIndex].price;
let finalPrice;
if (itemMarketMarginType === "absolute") {
finalPrice = listingPrice + Number(itemMarketOffset);
} else if (itemMarketMarginType === "percentage") {
finalPrice = Math.round(listingPrice * (1 + Number(itemMarketOffset) / 100));
}
if (itemMarketClamp && matchedItem && matchedItem.market_value) {
finalPrice = Math.max(finalPrice, Number(matchedItem.market_value));
}
$priceInput.val(finalPrice.toLocaleString());
$priceInput[0].dispatchEvent(new Event("input", { bubbles: true }));
if (matchedItem && matchedItem.market_value) {
let marketVal = Number(matchedItem.market_value);
$priceInput.css("color", getPriceColor(finalPrice, marketVal));
}
}
else if (pricingSource === "Bazaars/TornPal") {
alert("Bazaars/TornPal is not available. Please select another source.");
}
}
async function updateManageRowMobile($row, isChecked) {
const $priceInput = $row.find("[class*=bottomMobileMenu___] [class*=priceMobile___] .input-money-group.success input.input-money").first();
if (!$priceInput.length) {
console.error("Mobile price field not found.");
return;
}
if (!isChecked) {
if ($priceInput.data("orig") !== undefined) {
$priceInput.val($priceInput.data("orig"));
$priceInput.removeData("orig");
$priceInput.css("color", "");
} else {
$priceInput.val("");
}
// Dispatch input event to trigger key listeners.
$priceInput[0].dispatchEvent(new Event("input", { bubbles: true }));
return;
}
if (!$priceInput.data("orig")) $priceInput.data("orig", $priceInput.val());
const itemName = $row.find(".desc___VJSNQ b").text().trim();
const itemId = getItemIdByName(itemName);
const storedItems = JSON.parse(localStorage.getItem("tornItems") || "{}");
const matchedItem = Object.values(storedItems).find(i => i.name === itemName);
if (pricingSource === "Market Value" && matchedItem) {
let mv = Number(matchedItem.market_value);
let finalPrice = mv;
if (marketMarginType === "absolute") {
finalPrice += marketMarginOffset;
} else if (marketMarginType === "percentage") {
finalPrice = Math.round(mv * (1 + marketMarginOffset / 100));
}
$priceInput.val(finalPrice.toLocaleString());
$priceInput[0].dispatchEvent(new Event("input", { bubbles: true }));
$priceInput.css("color", getPriceColor(finalPrice, mv));
}
else if (pricingSource === "Item Market" && itemId) {
const data = await fetchItemMarketData(itemId);
if (!data || !data.itemmarket?.listings?.length) return;
let listings = data.itemmarket.listings;
let baseIndex = Math.min(itemMarketListing - 1, listings.length - 1);
let listingPrice = listings[baseIndex].price;
let finalPrice;
if (itemMarketMarginType === "absolute") {
finalPrice = listingPrice + Number(itemMarketOffset);
} else if (itemMarketMarginType === "percentage") {
finalPrice = Math.round(listingPrice * (1 + Number(itemMarketOffset) / 100));
}
if (itemMarketClamp && matchedItem && matchedItem.market_value) {
finalPrice = Math.max(finalPrice, Number(matchedItem.market_value));
}
$priceInput.val(finalPrice.toLocaleString());
$priceInput[0].dispatchEvent(new Event("input", { bubbles: true }));
if (matchedItem && matchedItem.market_value) {
let marketVal = Number(matchedItem.market_value);
$priceInput.css("color", getPriceColor(finalPrice, marketVal));
}
}
else if (pricingSource === "Bazaars/TornPal") {
alert("Bazaars/TornPal is not available. Please select another source.");
}
}
function openSettingsModal() {
$('.settings-modal-overlay').remove();
const $overlay = $('<div class="settings-modal-overlay"></div>');
const $modal = $(`
<div class="settings-modal" style="width:400px; max-width:90%; font-family:Arial, sans-serif;">
<h2 style="margin-bottom:6px;">Bazaar Filler Settings</h2>
<hr style="border-top:1px solid #ccc; margin:8px 0;">
<div style="margin-bottom:15px;">
<label for="api-key-input" style="font-weight:bold; display:block;">Torn API Key</label>
<input id="api-key-input" type="text" placeholder="Enter API key" style="width:100%; padding:6px; box-sizing:border-box;" value="${apiKey || ''}">
</div>
<hr style="border-top:1px solid #ccc; margin:8px 0;">
<div style="margin-bottom:15px;">
<label for="pricing-source-select" style="font-weight:bold; display:block;">Pricing Source</label>
<select id="pricing-source-select" style="width:100%; padding:6px; box-sizing:border-box;">
<option value="Market Value">Market Value</option>
<option value="Bazaars/TornPal">Bazaars/TornPal</option>
<option value="Item Market">Item Market</option>
</select>
</div>
<div id="market-value-options" style="display:none; margin-bottom:15px;">
<hr style="border-top:1px solid #ccc; margin:8px 0;">
<h3 style="margin:0 0 10px 0; font-size:1em; font-weight:bold;">Market Value Options</h3>
<div style="margin-bottom:10px;">
<label for="market-margin-offset" style="display:block;">Margin (ie: -1 is either $1 less or 1% less depending on margin type)</label>
<input id="market-margin-offset" type="number" style="width:100%; padding:6px; box-sizing:border-box;" value="${marketMarginOffset}">
</div>
<div style="margin-bottom:10px;">
<label for="market-margin-type" style="display:block;">Margin Type</label>
<select id="market-margin-type" style="width:100%; padding:6px; box-sizing:border-box;">
<option value="absolute">Absolute ($)</option>
<option value="percentage">Percentage (%)</option>
</select>
</div>
</div>
<div id="item-market-options" style="display:none; margin-bottom:15px;">
<hr style="border-top:1px solid #ccc; margin:8px 0;">
<h3 style="margin:0 0 10px 0; font-size:1em; font-weight:bold;">Item Market Options</h3>
<div style="margin-bottom:10px;">
<label for="item-market-listing" style="display:block;">Listing Index (1 = lowest, 2 = 2nd lowest, etc)</label>
<input id="item-market-listing" type="number" style="width:100%; padding:6px; box-sizing:border-box;" value="${itemMarketListing}">
</div>
<div style="margin-bottom:10px;">
<label for="item-market-offset" style="display:block;">Margin (ie: -1 is either $1 less or 1% less depending on margin type)</label>
<input id="item-market-offset" type="number" style="width:100%; padding:6px; box-sizing:border-box;" value="${itemMarketOffset}">
</div>
<div style="margin-bottom:10px;">
<label for="item-market-margin-type" style="display:block;">Margin Type</label>
<select id="item-market-margin-type" style="width:100%; padding:6px; box-sizing:border-box;">
<option value="absolute">Absolute ($)</option>
<option value="percentage">Percentage (%)</option>
</select>
</div>
<div style="display:inline-flex; align-items:center; margin-bottom:5px;">
<input id="item-market-clamp" type="checkbox" style="margin-right:5px;" ${itemMarketClamp ? "checked" : ""}>
<label for="item-market-clamp" style="margin:0; cursor:pointer;">Clamp minimum price to Market Value</label>
</div>
</div>
<hr style="border-top:1px solid #ccc; margin:8px 0;">
<div style="text-align:right;">
<button id="settings-save" style="margin-right:8px; padding:6px 10px; cursor:pointer;">Save</button>
<button id="settings-cancel" style="padding:6px 10px; cursor:pointer;">Cancel</button>
</div>
</div>
`);
$overlay.append($modal);
$('body').append($overlay);
$('#pricing-source-select').val(pricingSource);
$('#item-market-margin-type').val(itemMarketMarginType);
$('#market-margin-type').val(marketMarginType);
function toggleFields() {
let src = $('#pricing-source-select').val();
$('#item-market-options').toggle(src === 'Item Market');
$('#market-value-options').toggle(src === 'Market Value');
}
$('#pricing-source-select').change(toggleFields);
toggleFields();
$('#settings-save').click(function() {
apiKey = $('#api-key-input').val().trim();
pricingSource = $('#pricing-source-select').val();
if (pricingSource === "Bazaars/TornPal") {
alert("Bazaars/TornPal is not available. Please select another source.");
return;
}
if (pricingSource === "Market Value") {
marketMarginOffset = Number($('#market-margin-offset').val() || 0);
marketMarginType = $('#market-margin-type').val();
GM_setValue("marketMarginOffset", marketMarginOffset);
GM_setValue("marketMarginType", marketMarginType);
}
if (pricingSource === "Item Market") {
itemMarketListing = Number($('#item-market-listing').val() || 1);
itemMarketOffset = Number($('#item-market-offset').val() || -1);
itemMarketMarginType = $('#item-market-margin-type').val();
itemMarketClamp = $('#item-market-clamp').is(':checked');
GM_setValue("itemMarketListing", itemMarketListing);
GM_setValue("itemMarketOffset", itemMarketOffset);
GM_setValue("itemMarketMarginType", itemMarketMarginType);
GM_setValue("itemMarketClamp", itemMarketClamp);
}
GM_setValue("tornApiKey", apiKey);
GM_setValue("pricingSource", pricingSource);
$overlay.remove();
});
$('#settings-cancel').click(() => $overlay.remove());
}
function addPricingSourceLink() {
if (document.getElementById('pricing-source-button')) return;
let linksContainer = document.querySelector('.linksContainer___LiOTN');
if (!linksContainer) return;
let link = document.createElement('a');
link.id = 'pricing-source-button';
link.href = '#';
link.className = 'linkContainer___X16y4 inRow___VfDnd greyLineV___up8VP iconActive___oAum9';
link.target = '_self';
link.rel = 'noreferrer';
const iconSpan = document.createElement('span');
iconSpan.className = 'iconWrapper___x3ZLe iconWrapper___COKJD svgIcon___IwbJV';
iconSpan.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path d="M8 4.754a3.246 3.246 0 1 1 0 6.492 3.246 3.246 0 0 1 0-6.492zM5.754 8a2.246 2.246 0 1 0 4.492 0 2.246 2.246 0 0 0-4.492 0z"/>
<path d="M9.796 1.343c-.527-1.79-3.065-1.79-3.592 0l-.094.319a.873.873 0 0 1-1.255.52l-.292-.16c-1.64-.892-3.433.902-2.54 2.541l.159.292a.873.873 0 0 1-.52 1.255l-.319.094c-1.79.527-1.79 3.065 0 3.592l.319.094a.873.873 0 0 1 .52 1.255l-.16.292c-.892 1.64.901 3.433 2.54 2.54l.292-.16a.873.873 0 0 1 1.255.52l.094.319c.527 1.79 3.065 1.79 3.592 0l.094-.319a.873.873 0 0 1 1.255-.52l.292.16c1.64.893 3.433-.902 2.54-2.541l-.16-.292a.873.873 0 0 1 .52-1.255l.319-.094c1.79-.527 1.79-3.065 0-3.592l-.319-.094a.873.873 0 0 1-.52-1.255l.16-.292c.893-1.64-.902-3.433-2.54-2.54l-.292.16a.873.873 0 0 1-1.255-.52l-.094-.319zm-2.633.283c.246-.835 1.428-.835 1.674 0l.094.319a1.873 1.873 0 0 0 2.693 1.115l.291-.16c.764-.416 1.6.42 1.184 1.185l-.16.292a1.873 1.873 0 0 0 1.116 2.692l.318.094c.835.246.835 1.428 0 1.674l-.318.094a1.873 1.873 0 0 0-1.116 2.692l.16.292c.416.764-.42 1.6-1.185 1.184l-.291-.16a1.873 1.873 0 0 0-2.693 1.116l-.094.318c-.246.835-1.428.835-1.674 0l-.094-.318a1.873 1.873 0 0 0-2.692-1.116l-.292.16c-.764.416-1.6-.42-1.184-1.185l.16-.292a1.873 1.873 0 0 0-1.116-2.692l-.318-.094c-.835-.246-.835-1.428 0-1.674l.318-.094a1.873 1.873 0 0 0 1.116-2.692l-.16-.292c-.416-.764.42-1.6 1.185-1.184l.292.16a1.873 1.873 0 0 0 2.693-1.116l.094-.318z"/>
</svg>
`;
link.appendChild(iconSpan);
const textSpan = document.createElement('span');
textSpan.className = 'linkTitle____NPyM';
textSpan.textContent = 'Bazaar Filler Settings';
link.appendChild(textSpan);
link.addEventListener('click', function(e) {
e.preventDefault();
openSettingsModal();
});
linksContainer.insertBefore(link, linksContainer.firstChild);
}
function addAddPageCheckboxes() {
$(".items-cont .title-wrap").each(function() {
if ($(this).find(".checkbox-wrapper").length) return;
$(this).css("position", "relative");
const wrapper = $('<div class="checkbox-wrapper"></div>');
const checkbox = $('<input>', {
type: "checkbox",
class: "item-toggle",
click: async function(e) {
e.stopPropagation();
if (!GM_getValue("tornApiKey", "")) {
alert("No Torn API key set. Please click the 'Bazaar Filler Settings' button to enter your API key.");
$(this).prop("checked", false);
openSettingsModal();
return;
}
await updateAddRow($(this).closest("li.clearfix"), this.checked);
}
});
wrapper.append(checkbox);
$(this).append(wrapper);
});
}
function addManagePageCheckboxes() {
$(".item___jLJcf").each(function() {
const $desc = $(this).find(".desc___VJSNQ");
if (!$desc.length || $desc.find(".checkbox-wrapper").length) return;
$desc.css("position", "relative");
const wrapper = $('<div class="checkbox-wrapper"></div>');
const checkbox = $('<input>', {
type: "checkbox",
class: "item-toggle",
click: async function(e) {
e.stopPropagation();
if (!GM_getValue("tornApiKey", "")) {
alert("No Torn API key set. Please click the 'Bazaar Filler Settings' button to enter your API key.");
$(this).prop("checked", false);
openSettingsModal();
return;
}
const $row = $(this).closest(".item___jLJcf");
if (window.innerWidth <= 784) {
const $manageBtn = $row.find('button[aria-label="Manage"]').first();
if ($manageBtn.length) {
if (!$manageBtn.find('span').hasClass('active___OTFsm')) {
$manageBtn.click();
}
setTimeout(async () => {
await updateManageRowMobile($row, this.checked);
}, 200);
return;
}
}
await updateManageRow($row, this.checked);
}
});
wrapper.append(checkbox);
$desc.append(wrapper);
});
}
if (!validPages.includes(currentPage)) return;
const storedItems = localStorage.getItem("tornItems");
const lastUpdated = GM_getValue("lastUpdated", "");
const todayUTC = new Date().toISOString().split('T')[0];
if (apiKey && (!storedItems || lastUpdated !== todayUTC || new Date().getUTCHours() === 0)) {
fetch(`https://api.torn.com/torn/?key=${apiKey}&selections=items`)
.then(r => r.json())
.then(data => {
if (!data.items) {
console.error("Failed to fetch Torn items or no items found. Possibly invalid API key or rate limit.");
return;
}
let filtered = {};
for (let [id, item] of Object.entries(data.items)) {
if (item.tradeable) {
filtered[id] = {
name: item.name,
market_value: item.market_value
};
}
}
localStorage.setItem("tornItems", JSON.stringify(filtered));
GM_setValue("lastUpdated", todayUTC);
})
.catch(err => {
console.error("Error fetching Torn items:", err);
});
}
const domObserver = new MutationObserver(() => {
if (window.location.hash === "#/add") {
addAddPageCheckboxes();
} else if (window.location.hash === "#/manage") {
addManagePageCheckboxes();
}
addPricingSourceLink();
});
domObserver.observe(document.body, { childList: true, subtree: true });
window.addEventListener('hashchange', () => {
currentPage = window.location.hash;
if (currentPage === "#/add") {
addAddPageCheckboxes();
} else if (currentPage === "#/manage") {
addManagePageCheckboxes();
}
addPricingSourceLink();
});
if (currentPage === "#/add") {
addAddPageCheckboxes();
} else if (currentPage === "#/manage") {
addManagePageCheckboxes();
}
addPricingSourceLink();
$(document).on("click", "button.undo___FTgvP", function(e) {
e.preventDefault();
$(".item___jLJcf .checkbox-wrapper input.item-toggle:checked").each(function() {
$(this).prop("checked", false);
const $row = $(this).closest(".item___jLJcf");
updateManageRow($row, false);
});
});
$(document).on("click", ".clear-action", function(e) {
e.preventDefault();
$("li.clearfix .checkbox-wrapper input.item-toggle:checked").each(function() {
$(this).prop("checked", false);
const $row = $(this).closest("li.clearfix");
updateAddRow($row, false);
});
});
})();