Hack your Torn War

Hack your torn (Kidding)

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Advertisement:

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

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

})();