Auto Bazaar Pricing

Use this tool to individually or mass update the prices in your bazaar to be the lowest by $1 using data from weav3r.dev

// ==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();
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址