// ==UserScript==
// @name Auto Bazaar Pricing
// @namespace https://www.torn.com/
// @version 1.0
// @description Use this tool to individually or mass update the prices in your bazaar to be the lowest by $1 using data from weav3r.dev
// @author swervelord [3637232]
// @match https://www.torn.com/bazaar.php*
// @grant GM_xmlhttpRequest
// @connect weav3r.dev
// @connect api.torn.com
// ==/UserScript==
(function () {
'use strict';
const waitFor = (sel, cb) => {
const el = document.querySelector(sel);
if (el) return cb(el);
const mo = new MutationObserver(() => {
const f = document.querySelector(sel);
if (f) { mo.disconnect(); cb(f); }
});
mo.observe(document.body, { childList: true, subtree: true });
};
const getJSON = (url) => new Promise((res, rej) => {
GM_xmlhttpRequest({
method: 'GET', url,
onload: r => (r.status === 200 ? res(JSON.parse(r.responseText))
: rej(new Error(`HTTP ${r.status}`))),
onerror: () => rej(new Error('Network error'))
});
});
const addStyles = () => {
if (document.querySelector('#undercut-style')) return;
const s = document.createElement('style');
s.id = 'undercut-style';
s.textContent = `
.undercut-label {
display:inline-flex;
align-items:center;
cursor:pointer;
margin-left:8px;
position:relative;
z-index:10;
}
.undercut-checkbox {
appearance:none;
background:#1f1f1f;
border:1px solid #444;
width:16px;
height:16px;
border-radius:3px;
position:relative;
}
.undercut-checkbox:checked::before {
content:'';
position:absolute;
top:2px;
left:5px;
width:4px;
height:8px;
border:solid #666;
border-width:0 2px 2px 0;
transform:rotate(45deg);
}
.undercut-container {
display:flex;
justify-content:center;
margin-top:10px;
margin-bottom:4px;
}
#undercut-all-btn {
background:#2b2b2b;
color:#ffffff;
border:1px solid #00bcd4;
padding:6px 14px;
border-radius:6px;
font-weight:bold;
cursor:pointer;
font-size:14px;
box-shadow:0 0 6px rgba(0,188,212,0.3);
transition:all 0.2s ease-in-out;
}
#undercut-all-btn:hover {
background:#1c1c1c;
box-shadow:0 0 12px rgba(0,188,212,0.6);
transform:scale(1.02);
}
.undercut-flash {
animation: flash-cyan 1.2s ease-out;
}
@keyframes flash-cyan {
0% { background-color: rgba(0,188,212,0.3); }
50% { background-color: rgba(0,188,212,0.1); }
100% { background-color: transparent; }
}
`;
document.head.appendChild(s);
};
const idCache = new Map();
const ensureItemCache = async (key) => {
if (idCache.size) return;
const data = await getJSON(`https://api.torn.com/torn/?selections=items&key=${key}`);
Object.entries(data.items).forEach(([id, obj]) =>
idCache.set(obj.name.toLowerCase(), Number(id)));
};
const fetchWeav3r = (id) => getJSON(`https://weav3r.dev/api/marketplace/${id}`);
const undercutItem = async (itemEl, key, visual = false) => {
const name = itemEl.getAttribute('aria-label');
try {
await ensureItemCache(key);
const id = idCache.get(name.toLowerCase()); if (!id) throw 'ID not found';
const { listings = [] } = await fetchWeav3r(id); if (!listings.length) throw 'no listings';
const newP = listings[0].price - 1;
const box = itemEl.querySelector('.price___DoKP7 input.input-money');
if (!box) throw 'price box missing';
box.value = newP;
box.dispatchEvent(new Event('input', { bubbles: true }));
box.dispatchEvent(new Event('change', { bubbles: true }));
if (visual) {
itemEl.classList.add('undercut-flash');
setTimeout(() => itemEl.classList.remove('undercut-flash'), 1200);
}
} catch (e) {
console.error(`[Bazaar] ${name}: ${e}`);
}
};
const buildSettingsBtn = () => {
const bar = document.querySelector('.linksContainer___LiOTN');
if (!bar || document.querySelector('#undercut-settings-btn')) return;
const a = document.createElement('a');
a.id = 'undercut-settings-btn';
a.href = '#';
a.className = 'linkContainer___X16y4 inRow___VfDnd greyLineV___up8VP iconActive___oAum9';
a.innerHTML = `<span class="iconWrapper___x3ZLe svgIcon___IwbJV">
<svg xmlns="http://www.w3.org/2000/svg" stroke="transparent" width="16" height="16" viewBox="0 1 16 17">
<path d="M0,15.26c3.67.31,2.36-3.59,5.75-3.62l1.47,1.22c.37,4.43-5,5.42-7.22,2.4M11.25,9.57c1.14-1.79,4.68-7.8,4.68-7.8a.52.52,0,0,0-.78-.65s-5.26,4.58-6.81,6C7.12,8.29,7.11,8.81,6.71,10.7l1.35,1.12c1.78-.73,2.29-.83,3.19-2.25"></path></svg>
</span><span class="linkTitle____NPyM">Settings</span>`;
a.onclick = e => {
e.preventDefault();
const k = prompt('Enter your Torn API key (requires "items" access):', localStorage.getItem('torn_api_key') || '');
if (k) {
localStorage.setItem('torn_api_key', k.trim());
alert('API key saved');
}
};
bar.prepend(a);
};
const buildUpdateAllBtn = () => {
if (document.querySelector('#undercut-all-btn')) return;
const headerRow = document.querySelector('ul.cellsHeader___hZgkv');
if (!headerRow || !headerRow.parentNode) return;
const container = document.createElement('div');
container.className = 'undercut-container';
const btn = document.createElement('div');
btn.id = 'undercut-all-btn';
btn.textContent = 'Update ALL Prices';
btn.onclick = async () => {
const key = localStorage.getItem('torn_api_key');
if (!key) return alert('Set your API key first (Settings).');
const items = [...document.querySelectorAll('.item___jLJcf')];
for (const it of items) await undercutItem(it, key, true);
};
container.appendChild(btn);
headerRow.parentNode.insertBefore(container, headerRow);
};
const addCheckboxes = () => {
document.querySelectorAll('.item___jLJcf').forEach(item => {
if (item.querySelector('.undercut-checkbox')) return;
const label = document.createElement('label');
label.className = 'undercut-label';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'undercut-checkbox';
['pointerdown','pointerup','click','mousedown','mouseup']
.forEach(ev => checkbox.addEventListener(ev, e => {
e.stopPropagation();
e.stopImmediatePropagation();
}, true));
checkbox.onchange = async () => {
if (!checkbox.checked) return;
const key = localStorage.getItem('torn_api_key');
if (!key) {
alert('Set your Torn API key first using the settings tab.');
return;
}
await undercutItem(item, key);
};
label.appendChild(checkbox);
item.querySelector('.desc___VJSNQ span')?.appendChild(label);
});
};
const promptForApiKeyIfMissing = () => {
const existing = localStorage.getItem('torn_api_key');
if (!existing) {
const key = prompt('This script requires your Torn API key with "items" access.\nPlease enter it now:');
if (key) localStorage.setItem('torn_api_key', key.trim());
}
};
const init = () => {
addStyles();
promptForApiKeyIfMissing();
waitFor('.linksContainer___LiOTN', buildSettingsBtn);
waitFor('ul.cellsHeader___hZgkv', () => {
buildUpdateAllBtn();
});
waitFor('.item___jLJcf', () => {
addCheckboxes();
new MutationObserver(() => {
addCheckboxes();
buildUpdateAllBtn();
}).observe(document.body, { childList: true, subtree: true });
});
};
init();
})();