Hack your Torn War

Hack your torn (Kidding)

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

Advertisement:

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

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

})();