Hack your Torn War

Hack your torn (Kidding)

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

Advertisement:

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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();

})();