BazNav

An enhanced Torn City bazaar navigator with customization options to make this tool YOURS

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         BazNav
// @namespace    http://tampermonkey.net/
// @version      2.5
// @description  An enhanced Torn City bazaar navigator with customization options to make this tool YOURS
// @author       J4C
// @match        https://www.torn.com/*
// @grant        GM_addStyle
// ==/UserScript==

(() => {
    'use strict';

    const defaultSettings = {
        // UI Colors
        gradientStart: '#000000', gradientEnd: '#ffffff',
        textColor: '#ffffff', fontFamily: 'Arial, sans-serif',
        fontSize: 14, opacity: 1, width: 200,
        borderRadius: 8, borderWidth: 1, borderColor: '#000000',
        shadowBlur: 5, shadowColor: 'rgba(0,0,0,0.2)',

        // Buttons
        buttonBgColor: '#ffffff', buttonTextColor: '#000000',
        buttonBorderColor: '#000000', buttonBorderWidth: 1,
        buttonHoverColor: '#8F4729',

        // Counter
        counterBgColor: '#252525', counterTextColor: '#fff',
        counterBorderColor: '#fff',

        // Settings Panel
        panelBgColor: '#ffffff', panelTextColor: '#000',
        panelBorderColor: '#cccccc',

        // Loading Bar
        loadingBarColor: '#8F4729'
    };

    if (window.bazNavInitialized) {
        console.warn('BazNav is already initialized!');
        return;
    }
    window.bazNavInitialized = true;

    // User IDs
    const userIds = [
        1010587, 3199521, 1281694, 1286142, 1601153, 1821105, 1826175, 1853324, 1927218, 1962806, 2018311,
        2018693, 2029519, 2093595, 2144418, 2145030, 2150517, 2157199, 2163550, 2176411, 2202548,
        2203576, 2214797, 2215721, 2256247, 2263400, 2271357, 2321305, 2327316, 2329817, 2332873,
        2334174, 2349680, 2352900, 2373781, 2418443, 2459465, 2462160, 2466069, 2470308, 2515770,
        2531848, 2541678, 2557282, 2561006, 2570451, 2587064, 2596546, 2599031, 2601828, 2631792,
        2637146, 2649236, 2656557, 2658357, 2659552, 2664822, 2668560, 2675624, 2676295, 2693254,
        2693850, 2700933, 2706250, 2718606, 2733754, 2746056, 2749015, 2759415, 2768219, 2769269,
        2810688, 2812113, 2821007, 2855343, 2858099, 286232, 2865837, 2871891, 2905897, 2930086,
        2954100, 2954103, 2962007, 3025664, 3050251, 3060802, 3108759, 3118416, 3145984, 3146495,
        3152626, 3166857, 3169682, 3170980, 3181495, 3182441, 3185895, 3186599, 3186796, 3187167,
        3187441, 3192720, 3198458, 3200247, 3218273, 3220837, 3237207, 3244939, 3248078,
        3249592, 3253085, 3253836, 3256697, 3259246, 3272175, 3276048, 3284969, 3303003, 3304611,
        3304959, 3306975, 3325064, 3325774, 3327900, 333493, 3338586, 3340959, 3347008, 3348231,
        3351015, 3355475, 3357431, 3369647, 3372209, 3384244, 3385583, 3388276, 3390097, 3392180,
        3394866, 3399085, 3400186, 3405216, 3407522, 3409934, 3424078, 3443006, 3444185, 3445243,
        3447101, 3452004, 3455398, 3455607, 3455822, 3462112, 3465149, 3466754, 3468210, 3469314,
        3474044, 3476656, 3480404, 3484482, 3484674, 3485561, 3488406, 3492821, 3493798, 3499388,
        3504237, 3506751, 3528214, 3532145, 3532830, 3535815, 3546645, 3552837, 3554441, 3555668,
        3561791, 3562619, 3570456, 3571449, 3582325, 3584826, 3588663, 3595133, 3602729, 360330,
        3603393, 3615849, 3617287, 3621114, 3625719, 3626448, 3626655, 3653078, 3657829, 3661330,
        3665796, 3676143, 3738196, 830027, 1145056, 1403609, 1441750, 1496324, 1636350
];

const uniqueUserIds = [...new Set(userIds)];
const originalLinks = uniqueUserIds.map(id => `https://www.torn.com/bazaar.php?userId=${id}`);

const links = new Proxy(originalLinks, {
    get(target, prop) {
        if (prop === 'length') {
            console.log('Links array length accessed:', target.length);
        } else if (prop === 'map') {
            return function(...args) {
                console.log('Links array map called, length:', target.length);
                return Array.prototype.map.apply(target, args);
            };
        }
        return target[prop];
    },
    set() {
        console.error('Attempted to modify frozen links array! Stack:', new Error().stack);
        return false; // Prevent modifications
    }
});

console.log(`Total User IDs before deduplication: ${userIds.length}`);
console.log(`Total Unique User IDs: ${uniqueUserIds.length}`);
console.log(`Total Links: ${links.length}`);
console.log('Links array created at:', new Error().stack);

let index = Math.min(+localStorage.getItem('bazaarLinkIndex') || 0, links.length - 1);

const settings = JSON.parse(localStorage.getItem('bazaarNavSettings')) || defaultSettings;

    // Initialize GM.addStyle if not available
    if (typeof GM === 'undefined') window.GM = {};
    GM.addStyle ||= css => document.head.appendChild(Object.assign(document.createElement('style'), {textContent: css}));

    // Create UI elements
    const container = document.createElement('div');
    container.id = 'bazaarNavFloat';

    const counter = document.createElement('div');
    counter.id = 'bazaarCounter';
    counter.textContent = `${index + 1} / ${links.length}`;
console.log('Initial links length when creating counter:', links.length);
    const cogIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
    cogIcon.setAttribute("id", "cogIcon");
    cogIcon.setAttribute("viewBox", "0 0 24 24");
    cogIcon.setAttribute("aria-label", "Settings");
    cogIcon.setAttribute("role", "button");
    cogIcon.setAttribute("tabindex", "0");
    cogIcon.innerHTML = `<path d="M19.14 12.94c.04-.31.06-.63.06-.94s-.02-.63-.06-.94l2.03-1.58a.5.5 0 00.12-.63l-1.92-3.32a.5.5 0 00-.6-.22l-2.39.96a7.027 7.027 0 00-1.63-.94l-.36-2.54a.5.5 0 00-.5-.42h-3.84a.5.5 0 00-.5.42l-.36 2.54a6.94 6.94 0 00-1.63.94l-2.39-.96a.5.5 0 00-.6.22l-1.92 3.32a.5.5 0 00.12.63l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58a.5.5 0 00-.12.63l1.92 3.32a.5.5 0 00.6.22l2.39-.96c.5.38 1.04.7 1.63.94l.36 2.54a.5.5 0 00.5.42h3.84a.5.5 0 00.5-.42l.36-2.54c.59-.24 1.13-.56 1.63-.94l2.39.96a.5.5 0 00.6-.22l1.92-3.32a.5.5 0 00-.12-.63l-2.03-1.58zM12 15.5a3.5 3.5 0 110-7 3.5 3.5 0 010 7z"/>`;

    const nextBtn = document.createElement('button');
    nextBtn.textContent = 'Next Bazaar';
    const backToFirstBtn = document.createElement('button');
    backToFirstBtn.textContent = 'Back to First';
    const settingsPanel = document.createElement('div');
    settingsPanel.id = 'settingsPanel';

    // Resize handle
    const resizeHandle = document.createElement('div');
    resizeHandle.id = 'resizeHandle';
    resizeHandle.innerHTML = '↔';
    resizeHandle.title = "Drag to resize";

    // Build UI
    counter.appendChild(cogIcon);
    [counter, nextBtn, backToFirstBtn, settingsPanel, resizeHandle].forEach(el => container.appendChild(el));
    document.body.appendChild(container);

    const savedPos = JSON.parse(localStorage.getItem('bazaarNavPosition'));
    if (savedPos?.x !== undefined && savedPos?.y !== undefined) {
        container.style.left = `${savedPos.x}px`;
        container.style.top = `${savedPos.y}px`;
    }

    const updateStyles = () => {
        GM.addStyle(`
            #bazaarNavFloat {
                z-index: 999999; position: fixed; top: 120px; left: 10px; display: flex;
                flex-direction: column; align-items: center; font-weight: bold;
                overflow: hidden; user-select: none; cursor: grab;
                background: linear-gradient(135deg, ${settings.gradientStart}, ${settings.gradientEnd});
                color: ${settings.textColor}; font-family: ${settings.fontFamily};
                font-size: ${settings.fontSize}px; border-radius: ${settings.borderRadius}px;
                border: ${settings.borderWidth}px solid ${settings.borderColor};
                box-shadow: 0 2px ${settings.shadowBlur}px ${settings.shadowColor};
                opacity: ${settings.opacity}; width: ${settings.width}px;
            }
            #bazaarNavFloat button {
                background: ${settings.buttonBgColor}; color: ${settings.buttonTextColor};
                border: ${settings.buttonBorderWidth}px solid ${settings.buttonBorderColor};
                padding: 8px 16px; cursor: pointer; font-weight: bold;
                font-size: ${settings.fontSize}px; width: calc(100% - 16px);
                text-shadow: 0 1px 1px #fff; border-radius: ${settings.borderRadius}px;
                transition: background 0.3s; margin: 4px 8px 0;
            }
            #bazaarNavFloat button:hover { background: ${settings.buttonHoverColor}; }
            #bazaarCounter {
                padding: 10px 14px; background: ${settings.counterBgColor};
                color: ${settings.counterTextColor}; width: calc(100% - 28px);
                text-align: center; border-bottom: 1px solid ${settings.counterBorderColor};
                position: relative; user-select: none; display: flex;
                align-items: center; justify-content: center; gap: 6px;
            }
            #bazaarCounter.loading::after {
                content: ''; position: absolute; bottom: 0; left: 0; height: 3px; width: 100%;
                background: ${settings.loadingBarColor}; animation: loadingBar 3s linear forwards;
                border-radius: 0 0 ${settings.borderRadius}px ${settings.borderRadius}px;
            }
            @keyframes loadingBar { from { width: 0; } to { width: 100%; } }
            #settingsPanel {
                display: none; background: ${settings.panelBgColor};
                color: ${settings.panelTextColor}; width: calc(100% - 16px);
                padding: 8px; box-sizing: border-box; border-top: 1px solid ${settings.panelBorderColor};
                font-size: 12px; max-height: 240px; overflow-y: auto; margin: 0 8px;
            }
            #settingsPanel label { display: block; margin: 8px 0 2px; font-weight: bold; }
            #settingsPanel input, #settingsPanel select {
                width: 100%; padding: 2px 4px; box-sizing: border-box; font-size: 13px;
                border-radius: 4px; border: 1px solid #ccc;
            }
            #settingsPanel .section {
                margin: 10px 0; padding-bottom: 10px;
                border-bottom: 1px dashed #ddd;
            }
            #settingsPanel .section-title {
                font-weight: bold; margin-bottom: 8px; color: #555;
            }
            #cogIcon {
                cursor: pointer; width: 18px; height: 18px; fill: ${settings.textColor};
                user-select: none; flex-shrink: 0;
            }
            #resizeHandle {
                position: absolute; right: 0; bottom: 0; width: 16px; height: 16px;
                background: rgba(0,0,0,0.1); cursor: nwse-resize; display: flex;
                align-items: center; justify-content: center; font-size: 12px;
                border-radius: 4px 0 0 0;
            }
            #resizeHandle:hover { background: rgba(0,0,0,0.2); }
            @media screen and (max-width: 1000px) {
                #bazaarNavFloat { top: 140px; }
            }
        `);
    };

    updateStyles();
    container.style.width = `${settings.width}px`;
container.style.opacity = settings.opacity;

    // Dragging function
    let isDragging = false, dragStartX, dragStartY, elemStartX, elemStartY;
    container.addEventListener('mousedown', e => {
        if (e.target.closest('#settingsPanel, #cogIcon, #resizeHandle')) return;
        isDragging = true;
        [dragStartX, dragStartY] = [e.clientX, e.clientY];
        const rect = container.getBoundingClientRect();
        [elemStartX, elemStartY] = [rect.left, rect.top];
        container.style.cursor = 'grabbing';
        e.preventDefault();
    });

    // Resize function
    let isResizing = false, startWidth, startX;
    resizeHandle.addEventListener('mousedown', e => {
        isResizing = true;
        startWidth = container.offsetWidth;
        startX = e.clientX;
        e.preventDefault();
        e.stopPropagation();
    });

    const handleMove = e => {
        if (isDragging) {
            let newX = Math.max(0, Math.min(window.innerWidth - container.offsetWidth, elemStartX + e.clientX - dragStartX));
            let newY = Math.max(0, Math.min(window.innerHeight - container.offsetHeight, elemStartY + e.clientY - dragStartY));
            container.style.left = `${newX}px`;
            container.style.top = `${newY}px`;
        } else if (isResizing) {
            const newWidth = Math.max(120, Math.min(400, startWidth + (e.clientX - startX)));
            settings.width = newWidth;
            container.style.width = `${newWidth}px`;
            saveSettings();
        }
    };

    const handleUp = () => {
        if (isDragging) {
            isDragging = false;
            container.style.cursor = 'grab';
            const rect = container.getBoundingClientRect();
            localStorage.setItem('bazaarNavPosition', JSON.stringify({x: rect.left, y: rect.top}));
        } else if (isResizing) {
            isResizing = false;
        }
    };

    window.addEventListener('mousemove', handleMove);
    window.addEventListener('mouseup', handleUp);

    // Navigation function
   const openLinkAtIndex = i => {
    console.log('openLinkAtIndex - links length:', links.length, 'index:', i);
    if (i < 0 || i >= links.length) {
        console.error('Invalid index or links array is empty');
        return;
    }
    index = i;
    localStorage.setItem('bazaarLinkIndex', index);
    counter.textContent = `${index + 1} / ${links.length}`;
    counter.appendChild(cogIcon);
    window.location.href = links[index];
};

    nextBtn.addEventListener('click', () => openLinkAtIndex((index + 1) % links.length));
    backToFirstBtn.addEventListener('click', () => openLinkAtIndex(0));

    // Settings management
    const saveSettings = () => {
        localStorage.setItem('bazaarNavSettings', JSON.stringify(settings));
        updateStyles();
    };

    const resetToDefaults = () => {
        Object.assign(settings, defaultSettings);
        saveSettings();
        populateSettingsPanel();
    };

    // Settings panel
    const populateSettingsPanel = () => {
        settingsPanel.innerHTML = `
            <div class="section">
                <div class="section-title">Presets</div>
                <button id="defaultPreset" style="width:100%;margin:4px 0;padding:4px">Default</button>
                <button id="darkPreset" style="width:100%;margin:4px 0;padding:4px">Dark Theme</button>
                <button id="lightPreset" style="width:100%;margin:4px 0;padding:4px">Light Theme</button>
                <button id="resetDefaults" style="width:100%;margin:4px 0;padding:4px">Reset All to Defaults</button>
            </div>

            <div class="section">
                <div class="section-title">Container</div>
                <label for="widthInput">Width:</label>
                <input type="range" id="widthInput" min="120" max="400" step="1" value="${settings.width}">

                <label for="opacityInput">Opacity:</label>
                <input type="range" id="opacityInput" min="0.5" max="1" step="0.05" value="${settings.opacity}">

                <label for="gradientStart">Gradient Start:</label>
                <input type="color" id="gradientStart" value="${settings.gradientStart}">

                <label for="gradientEnd">Gradient End:</label>
                <input type="color" id="gradientEnd" value="${settings.gradientEnd}">

                <label for="borderRadius">Border Radius:</label>
                <input type="number" id="borderRadius" min="0" max="50" value="${settings.borderRadius}">

                <label for="borderWidth">Border Width:</label>
                <input type="number" id="borderWidth" min="0" max="10" value="${settings.borderWidth}">

                <label for="borderColor">Border Color:</label>
                <input type="color" id="borderColor" value="${settings.borderColor}">

                <label for="shadowColor">Shadow Color:</label>
                <input type="color" id="shadowColor" value="${settings.shadowColor}">

                <label for="shadowBlur">Shadow Blur:</label>
                <input type="number" id="shadowBlur" min="0" max="20" value="${settings.shadowBlur}">
            </div>

            <div class="section">
                <div class="section-title">Text</div>
                <label for="textColor">Text Color:</label>
                <input type="color" id="textColor" value="${settings.textColor}">

                <label for="fontFamily">Font Family:</label>
                <select id="fontFamily">
                    <option value="Arial, sans-serif">Arial</option>
                    <option value="'Segoe UI', Tahoma, Geneva, Verdana, sans-serif">Segoe UI</option>
                    <option value="'Courier New', Courier, monospace">Courier New</option>
                    <option value="'Georgia', serif">Georgia</option>
                    <option value="'Comic Sans MS', cursive, sans-serif">Comic Sans MS</option>
                    <option value="'Trebuchet MS', sans-serif">Trebuchet MS</option>
                </select>

                <label for="fontSize">Font Size:</label>
                <input type="number" id="fontSize" min="10" max="20" value="${settings.fontSize}">
            </div>

            <div class="section">
                <div class="section-title">Buttons</div>
                <label for="buttonBgColor">Background:</label>
                <input type="color" id="buttonBgColor" value="${settings.buttonBgColor}">

                <label for="buttonTextColor">Text Color:</label>
                <input type="color" id="buttonTextColor" value="${settings.buttonTextColor}">

                <label for="buttonHoverColor">Hover Color:</label>
                <input type="color" id="buttonHoverColor" value="${settings.buttonHoverColor}">

                <label for="buttonBorderColor">Border Color:</label>
                <input type="color" id="buttonBorderColor" value="${settings.buttonBorderColor}">

                <label for="buttonBorderWidth">Border Width:</label>
                <input type="number" id="buttonBorderWidth" min="0" max="5" value="${settings.buttonBorderWidth}">
            </div>

            <div class="section">
                <div class="section-title">Counter</div>
                <label for="counterBgColor">Background:</label>
                <input type="color" id="counterBgColor" value="${settings.counterBgColor}">

                <label for="counterTextColor">Text Color:</label>
                <input type="color" id="counterTextColor" value="${settings.counterTextColor}">

                <label for="counterBorderColor">Border Color:</label>
                <input type="color" id="counterBorderColor" value="${settings.counterBorderColor}">

                <label for="loadingBarColor">Loading Bar Color:</label>
                <input type="color" id="loadingBarColor" value="${settings.loadingBarColor}">
            </div>

            <div class="section">
                <div class="section-title">Settings Panel</div>
                <label for="panelBgColor">Background:</label>
                <input type="color" id="panelBgColor" value="${settings.panelBgColor}">

                <label for="panelTextColor">Text Color:</label>
                <input type="color" id="panelTextColor" value="${settings.panelTextColor}">

                <label for="panelBorderColor">Border Color:</label>
                <input type="color" id="panelBorderColor" value="${settings.panelBorderColor}">
            </div>
        `;

        settingsPanel.querySelectorAll('input, select').forEach(el => {
    el.addEventListener('input', () => {
        if (el.id === 'widthInput') {
            settings.width = parseInt(el.value);
            container.style.width = `${settings.width}px`;
        } else if (el.id === 'opacityInput') {
            settings.opacity = parseFloat(el.value);
            container.style.opacity = settings.opacity;
        } else if (el.type === 'color') {
            settings[el.id] = el.value;
        } else if (el.type === 'number' || el.type === 'range') {
            settings[el.id] = parseInt(el.value);
        } else {
            settings[el.id] = el.value;
        }
        saveSettings();
    });
});

        // Preset buttons
        settingsPanel.querySelector('#defaultPreset').addEventListener('click', () => {
            Object.assign(settings, defaultSettings);
            saveSettings();
            populateSettingsPanel();
        });

        settingsPanel.querySelector('#darkPreset').addEventListener('click', () => {
            Object.assign(settings, {
                gradientStart: '#2d2d2d', gradientEnd: '#1a1a1a',
                textColor: '#e0e0e0', fontFamily: 'Arial, sans-serif',
                buttonBgColor: '#444444', buttonTextColor: '#000000',
                buttonHoverColor: '#666666', counterBgColor: '#333333',
                counterTextColor: '#ffffff', panelBgColor: '#222222',
                panelTextColor: '#dddddd', borderColor: '#444444',
                panelBorderColor: '#444444', counterBorderColor: '#555555',
                buttonBorderColor: '#666666', loadingBarColor: '#4a90e2'
            });
            saveSettings();
            populateSettingsPanel();
        });

        settingsPanel.querySelector('#lightPreset').addEventListener('click', () => {
            Object.assign(settings, {
                gradientStart: '#f5f5f5', gradientEnd: '#e0e0e0',
                textColor: '#000000', fontFamily: 'Arial, sans-serif',
                buttonBgColor: '#ffffff', buttonTextColor: '#000000',
                buttonHoverColor: '#8F4729', counterBgColor: '#f0f0f0',
                counterTextColor: '#000000', panelBgColor: '#ffffff',
                panelTextColor: '#000000',
                borderColor: '#000000',      // Border color
                borderWidth: 2,              // Border thickness (in pixels)
                panelBorderColor: '#000000',
                counterBorderColor: '#000000',
                buttonBorderColor: '#000000',
                buttonBorderWidth: 2,        // Button border thickness
                loadingBarColor: '#8F4729'
            });
            saveSettings();
            populateSettingsPanel();
        });

        settingsPanel.querySelector('#resetDefaults').addEventListener('click', resetToDefaults);
    };

    populateSettingsPanel();

    // Toggle settings panel
    cogIcon.addEventListener('click', () => settingsPanel.style.display = settingsPanel.style.display === 'block' ? 'none' : 'block');
    cogIcon.addEventListener('keydown', e => (e.key === 'Enter' || e.key === ' ') && (e.preventDefault(), cogIcon.click()));
})();