Hack your Torn War

Hack your torn (Kidding)

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Advertisement:

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

Advertisement:

// ==UserScript==
// @name         Hack your Torn War
// @namespace    http://tampermonkey.net/
// @version      4
// @description  Hack your torn (Kidding)
// @author       ShAdOwCrEsT [3929345]
// @match        https://www.torn.com/*
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_xmlhttpRequest
// @connect      api.torn.com
// @license      GPU AGPLv3
// ==/UserScript==

(function() {
    'use strict';

    GM_addStyle(`
        .glitter-container {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            z-index: 9999;
            overflow: hidden;
        }

        .glitter-particle {
            position: absolute;
            width: 10px;
            height: 10px;
            opacity: 0.9;
            animation: fall linear infinite, twinkle ease-in-out infinite;
        }

        .glitter-star4 {
            background: linear-gradient(45deg, transparent 40%, var(--sparkle-color) 40%, var(--sparkle-color) 60%, transparent 60%),
                        linear-gradient(-45deg, transparent 40%, var(--sparkle-color) 40%, var(--sparkle-color) 60%, transparent 60%);
        }

        .glitter-star6 {
            clip-path: polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%);
            background: linear-gradient(135deg, var(--sparkle-light) 0%, var(--sparkle-color) 50%, var(--sparkle-bright) 100%);
        }

        .glitter-diamond {
            clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
            background: linear-gradient(135deg, var(--sparkle-light) 0%, var(--sparkle-color) 50%, var(--sparkle-bright) 100%);
        }

        .color-white {
            --sparkle-color: #c0c0c0;
            --sparkle-light: #ffffff;
            --sparkle-bright: #e8e8e8;
            filter: drop-shadow(0 0 3px rgba(255, 255, 255, 0.8));
        }

        .color-yellow {
            --sparkle-color: #ffd700;
            --sparkle-light: #ffff99;
            --sparkle-bright: #ffeb3b;
            filter: drop-shadow(0 0 3px rgba(255, 215, 0, 0.8));
        }

        .color-blue {
            --sparkle-color: #4da6ff;
            --sparkle-light: #b3d9ff;
            --sparkle-bright: #80bfff;
            filter: drop-shadow(0 0 3px rgba(77, 166, 255, 0.8));
        }

        @keyframes fall {
            0% {
                transform: translateY(-20px) rotate(0deg) scale(1);
                opacity: 0;
            }
            10% { opacity: 0.9; }
            90% { opacity: 0.9; }
            100% {
                transform: translateY(100vh) rotate(360deg) scale(1);
                opacity: 0;
            }
        }

        @keyframes twinkle {
            0%, 100% { opacity: 0.9; filter: brightness(1); }
            50% { opacity: 0.5; filter: brightness(1.5); }
        }

        /* Settings button */
        #torn-war-settings-btn {
            position: fixed;
            bottom: 16px;
            right: 16px;
            z-index: 10000;
            width: 36px;
            height: 36px;
            border-radius: 50%;
            background: #2a2a2a;
            border: 2px solid #555;
            color: #ccc;
            font-size: 16px;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            box-shadow: 0 2px 8px rgba(0,0,0,0.5);
            transition: background 0.2s, border-color 0.2s;
        }
        #torn-war-settings-btn:hover {
            background: #3a3a3a;
            border-color: #ffd700;
            color: #ffd700;
        }

        /* Settings modal */
        #torn-war-settings-modal {
            position: fixed;
            bottom: 60px;
            right: 16px;
            z-index: 10001;
            background: #1e1e1e;
            border: 1px solid #444;
            border-radius: 10px;
            padding: 16px;
            width: 240px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.7);
            color: #ccc;
            font-family: sans-serif;
            font-size: 13px;
            display: none;
        }
        #torn-war-settings-modal.open { display: block; }
        #torn-war-settings-modal h3 {
            margin: 0 0 12px;
            color: #ffd700;
            font-size: 14px;
        }
        #torn-war-settings-modal label {
            display: flex;
            align-items: center;
            gap: 8px;
            margin-bottom: 8px;
            cursor: pointer;
        }
        #torn-war-settings-modal button {
            margin-top: 10px;
            width: 100%;
            padding: 6px;
            background: #333;
            border: 1px solid #555;
            border-radius: 6px;
            color: #ccc;
            cursor: pointer;
            font-size: 12px;
        }
        #torn-war-settings-modal button:hover {
            background: #444;
            border-color: #ffd700;
            color: #ffd700;
        }
    `);

    // ─── Settings ────────────────────────────────────────────────────────────

    const SETTINGS_INITIALIZED_KEY = 'settingsInitialized';

    function showFirstTimeSetup() {
        const soundChoice = confirm('Would you like to enable sound alerts?');
        GM_setValue('alertSoundEnabled', soundChoice);

        const glitterChoice = confirm('Would you like to enable glitter animation?');
        GM_setValue('glitterEnabled', glitterChoice);

        GM_setValue('alertTime', 100);
        GM_setValue('minChain', 100);
        GM_setValue(SETTINGS_INITIALIZED_KEY, true);
    }

    if (!GM_getValue(SETTINGS_INITIALIZED_KEY)) {
        showFirstTimeSetup();
    }

    // Live-read settings so the modal changes take effect immediately
    function getSoundEnabled()   { return GM_getValue('alertSoundEnabled', true); }
    function getGlitterEnabled() { return GM_getValue('glitterEnabled', true); }

    // ─── Settings UI ─────────────────────────────────────────────────────────

    function buildSettingsUI() {
        const btn = document.createElement('button');
        btn.id = 'torn-war-settings-btn';
        btn.title = 'Torn War Script Settings';
        btn.textContent = '⚙';
        document.body.appendChild(btn);

        const modal = document.createElement('div');
        modal.id = 'torn-war-settings-modal';
        modal.innerHTML = `
            <h3>⚔ War Script Settings</h3>
            <label>
                <input type="checkbox" id="tws-sound" ${getSoundEnabled() ? 'checked' : ''}>
                Sound alerts
            </label>
            <label>
                <input type="checkbox" id="tws-glitter" ${getGlitterEnabled() ? 'checked' : ''}>
                Glitter animation
            </label>
            <button id="tws-reset-api">🔑 Reset API Key</button>
            <button id="tws-reset-all">🗑 Reset All Settings</button>
        `;
        document.body.appendChild(modal);

        btn.addEventListener('click', () => modal.classList.toggle('open'));
        document.addEventListener('click', (e) => {
            if (!modal.contains(e.target) && e.target !== btn) {
                modal.classList.remove('open');
            }
        });

        modal.querySelector('#tws-sound').addEventListener('change', (e) => {
            GM_setValue('alertSoundEnabled', e.target.checked);
        });
        modal.querySelector('#tws-glitter').addEventListener('change', (e) => {
            GM_setValue('glitterEnabled', e.target.checked);
        });
        modal.querySelector('#tws-reset-api').addEventListener('click', () => {
            GM_setValue(API_STORAGE_KEY, '');
            apiKey = '';
            modal.classList.remove('open');
            requestApiKey();
        });
        modal.querySelector('#tws-reset-all').addEventListener('click', () => {
            if (confirm('Reset all settings? You will be asked to set them up again.')) {
                GM_deleteValue(SETTINGS_INITIALIZED_KEY);
                GM_deleteValue('alertSoundEnabled');
                GM_deleteValue('glitterEnabled');
                GM_deleteValue('alertTime');
                GM_deleteValue('minChain');
                GM_deleteValue(API_STORAGE_KEY);
                location.reload();
            }
        });
    }

    // ─── API Key ─────────────────────────────────────────────────────────────

    const API_STORAGE_KEY = 'tornApiKey';
    const API_URL = 'https://api.torn.com/faction/?selections=chain&key=';
    let apiKey = GM_getValue(API_STORAGE_KEY, '');

    const userAlertTime = GM_getValue('alertTime', 100);

    function requestApiKey() {
        const inputKey = prompt('Enter your Torn API key (only needed once):', '');
        if (inputKey && inputKey.trim()) {
            GM_setValue(API_STORAGE_KEY, inputKey.trim());
            apiKey = inputKey.trim();
        }
    }

    if (!apiKey) requestApiKey();

    // ─── Audio (FIX: lazy init + user-gesture unlock) ────────────────────────

    let audioContext = null;
    let audioUnlocked = false;
    let beepInterval = null;
    let alertTriggered = false;

    // One-time unlock on first user click anywhere on the page
    function unlockAudio() {
        if (audioUnlocked) return;
        getAudioContext(); // creates context
        if (audioContext && audioContext.state === 'suspended') {
            audioContext.resume();
        }
        audioUnlocked = true;
    }
    document.addEventListener('click', unlockAudio, { once: true });

    function getAudioContext() {
        if (!audioContext) {
            audioContext = new (window.AudioContext || window.webkitAudioContext)();
        }
        return audioContext;
    }

    function playBeep() {
        if (!getSoundEnabled()) return;

        const ctx = getAudioContext();
        if (ctx.state === 'suspended') {
            ctx.resume().then(() => _doBeep(ctx));
        } else {
            _doBeep(ctx);
        }
    }

    function _doBeep(ctx) {
        const oscillator = ctx.createOscillator();
        const gainNode = ctx.createGain();

        oscillator.type = 'sine';
        oscillator.frequency.setValueAtTime(800, ctx.currentTime);
        gainNode.gain.setValueAtTime(0.3, ctx.currentTime);
        gainNode.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.3);

        oscillator.connect(gainNode);
        gainNode.connect(ctx.destination);
        oscillator.start(ctx.currentTime);
        oscillator.stop(ctx.currentTime + 0.3);
    }

    function startBeeping() {
        if (beepInterval) return;
        playBeep();
        beepInterval = setInterval(playBeep, 5000);
    }

    function stopBeeping() {
        if (beepInterval) {
            clearInterval(beepInterval);
            beepInterval = null;
        }
    }

    // ─── Glitter ─────────────────────────────────────────────────────────────

    function createGlitterParticle(container) {
        const particle = document.createElement('div');
        particle.className = 'glitter-particle';

        const shapes = ['glitter-star4', 'glitter-star6', 'glitter-diamond'];
        particle.classList.add(shapes[Math.floor(Math.random() * shapes.length)]);

        const colors = ['color-white', 'color-yellow', 'color-blue'];
        particle.classList.add(colors[Math.floor(Math.random() * colors.length)]);

        const duration = 3 + Math.random() * 2;
        const delay    = Math.random() * 2;
        const size     = 6 + Math.random() * 8;
        const twinkle  = 0.5 + Math.random() * 1;

        particle.style.left = (Math.random() * 100) + '%';
        particle.style.width  = size + 'px';
        particle.style.height = size + 'px';
        particle.style.animationDuration = `${duration}s, ${twinkle}s`;
        particle.style.animationDelay = delay + 's';

        container.appendChild(particle);
        setTimeout(() => particle.parentNode && particle.remove(), (duration + delay) * 1000);
    }

    function createGlitterEffect() {
        const container = document.createElement('div');
        container.className = 'glitter-container';
        document.body.appendChild(container);

        for (let i = 0; i < 50; i++) {
            setTimeout(() => {
                if (document.querySelector('.glitter-container')) createGlitterParticle(container);
            }, i * 100);
        }

        const id = setInterval(() => {
            if (!document.querySelector('.glitter-container')) { clearInterval(id); return; }
            createGlitterParticle(container);
        }, 100);
        container.dataset.regenerateInterval = id;
    }

    function removeGlitter() {
        const container = document.querySelector('.glitter-container');
        if (container) {
            clearInterval(parseInt(container.dataset.regenerateInterval));
            container.remove();
        }
        stopBeeping();
    }

    // ─── Chain polling ────────────────────────────────────────────────────────

    function fetchChainData() {
        if (!apiKey) return;

        GM_xmlhttpRequest({
            method: 'GET',
            url: API_URL + apiKey,
            onload(response) {
                try {
                    const data = JSON.parse(response.responseText);
                    if (data.error) {
                        console.error('API Error:', data.error);
                        if (data.error.code === 2) {
                            GM_setValue(API_STORAGE_KEY, '');
                            apiKey = '';
                            requestApiKey();
                        }
                        return;
                    }
                    handleChainData(data.chain);
                } catch (e) {
                    console.error('Failed to parse chain data:', e);
                }
            },
            onerror(e) { console.error('API request failed:', e); }
        });
    }

    function handleChainData(chain) {
        const { current, cooldown, end } = chain;
        const timeRemaining = end - Math.floor(Date.now() / 1000);

        if (current > 0 && timeRemaining > 0 && cooldown === 0 && timeRemaining < userAlertTime) {
            triggerAlert();
        } else {
            alertTriggered = false;
            removeGlitter();
        }
    }

    function triggerAlert() {
        if (alertTriggered) return;
        if (getGlitterEnabled()) createGlitterEffect();
        startBeeping();
        alertTriggered = true;
    }

    // ─── Init ─────────────────────────────────────────────────────────────────

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', buildSettingsUI);
    } else {
        buildSettingsUI();
    }

    setInterval(fetchChainData, 10000);
    fetchChainData();

})();