Attack screen improvements

Improvements to the Attacking screen.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name            Attack screen improvements
// @namespace       http://tampermonkey.net/
// @version         1.2.5
// @description     Improvements to the Attacking screen.
// @author          Cypher-[2641265]
// @license         MIT
// @match           https://www.torn.com/loader.php?sid=attack&user2ID=*
// @icon            https://www.google.com/s2/favicons?sz=64&domain=torn.com
// @grant           none
// ==/UserScript==
 
// Todo: 
//  -add refresh when leave/mug/hosp window comes up. 
//
// Implemented:
//  -On/offline status icon for defender
//  -Energy display
//  -current status for target (hosp/okay etc with timer)
 
 
(function() {
    'use strict';
 
    // API Key management
    function getAPIKey() {
        return localStorage.getItem('torn_minimal_key');
    }
 
    function setAPIKey(key) {
        localStorage.setItem('torn_minimal_key', key);
    }
 
    const SVGs = {
        Online: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="-1.5 -1.2 14 14"><circle cx="6" cy="6" r="6" fill="#43d854" stroke="#fff" stroke-width="0"/></svg>`,
        Idle: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="-1.5 -1.2 14 14"><circle cx="6" cy="6" r="6" fill="#f7c325" stroke="#fff" stroke-width="0"/><rect x="5" y="3" width="4" height="4" fill="#f2f2f2"/></svg>`,
        Offline: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="-1.5 -1.2 14 14"><circle cx="6" cy="6" r="6" fill="#b3b3b3" stroke="#fff" stroke-width="0"/><rect x="3" y="5" width="6" height="2" fill="#f2f2f2"/></svg>`
    };
 
    const urlParams = new URLSearchParams(window.location.search);
    const userID = urlParams.get('user2ID');
    if (!userID) return;
 
    // Check if API key is available
    let API_KEY = getAPIKey();
    if (!API_KEY) {
        showAPIKeySetup();
        return;
    }
 
    // API Key setup interface
    function showAPIKeySetup() {
        // Create popup immediately without any DOM elements
        const overlay = document.createElement('div');
        overlay.style.position = 'fixed';
        overlay.style.top = '0';
        overlay.style.left = '0';
        overlay.style.width = '100%';
        overlay.style.height = '100%';
        overlay.style.backgroundColor = 'rgba(0,0,0,0.5)';
        overlay.style.zIndex = '10000';
        overlay.style.display = 'flex';
        overlay.style.alignItems = 'center';
        overlay.style.justifyContent = 'center';

        const dialog = document.createElement('div');
        dialog.style.backgroundColor = '#2a2a2a';
        dialog.style.padding = '20px';
        dialog.style.borderRadius = '8px';
        dialog.style.border = '1px solid #444';
        dialog.style.minWidth = '300px';
        dialog.style.textAlign = 'center';

        const input = document.createElement('input');
        input.type = 'text';
        input.placeholder = 'Minimal API';
        input.style.width = '100%';
        input.style.padding = '8px';
        input.style.marginBottom = '15px';
        input.style.backgroundColor = '#1a1a1a';
        input.style.color = 'white';
        input.style.border = '1px solid #444';
        input.style.borderRadius = '4px';

        const buttonContainer = document.createElement('div');
        buttonContainer.style.display = 'flex';
        buttonContainer.style.gap = '10px';
        buttonContainer.style.justifyContent = 'center';

        const getApiButton = document.createElement('button');
        getApiButton.textContent = 'Get API';
        getApiButton.style.padding = '8px 16px';
        getApiButton.style.backgroundColor = '#007bff';
        getApiButton.style.color = 'white';
        getApiButton.style.border = 'none';
        getApiButton.style.borderRadius = '4px';
        getApiButton.style.cursor = 'pointer';

        const okButton = document.createElement('button');
        okButton.textContent = 'OK';
        okButton.style.padding = '8px 16px';
        okButton.style.backgroundColor = '#0ea01fff';
        okButton.style.color = 'white';
        okButton.style.border = 'none';
        okButton.style.borderRadius = '4px';
        okButton.style.cursor = 'pointer';

        const cancelButton = document.createElement('button');
        cancelButton.textContent = 'Cancel';
        cancelButton.style.padding = '8px 16px';
        cancelButton.style.backgroundColor = '#6c757d';
        cancelButton.style.color = 'white';
        cancelButton.style.border = 'none';
        cancelButton.style.borderRadius = '4px';
        cancelButton.style.cursor = 'pointer';

        getApiButton.addEventListener('click', () => {
            window.open('https://www.torn.com/preferences.php#tab=api', '_blank');
        });

        okButton.addEventListener('click', () => {
            const apiKey = input.value.trim();
            if (apiKey) {
                setAPIKey(apiKey);
                API_KEY = apiKey;
                overlay.remove();
                initializeScript();
            }
        });

        cancelButton.addEventListener('click', () => {
            overlay.remove();
        });

        // Close on overlay click
        overlay.addEventListener('click', (e) => {
            if (e.target === overlay) {
                overlay.remove();
            }
        });

        // Close on escape key
        document.addEventListener('keydown', function escapeHandler(e) {
            if (e.key === 'Escape') {
                overlay.remove();
                document.removeEventListener('keydown', escapeHandler);
            }
        });

        // Enter key submits
        input.addEventListener('keydown', (e) => {
            if (e.key === 'Enter') {
                okButton.click();
            }
        });

        buttonContainer.appendChild(getApiButton);
        buttonContainer.appendChild(okButton);
        buttonContainer.appendChild(cancelButton);
        dialog.appendChild(input);
        dialog.appendChild(buttonContainer);
        overlay.appendChild(dialog);
        document.body.appendChild(overlay);
        
        // Focus the input
        input.focus();
    }    // Initialize script features
    function initializeScript() {
        // Initial fetches
        fetchDefenderStatus();
        fetchAttackerEnergy();
    }
 
    // Unified refresh function for all elements
    function refreshAllData() {
        fetchDefenderStatus();
        fetchAttackerEnergy();
    }
 
    // Fetch defender status
    function fetchDefenderStatus() {
        fetch(`https://api.torn.com/user/${userID}?selections=profile&key=${API_KEY}&comment=attackpageimprovements`)
            .then(res => res.json())
            .then(data => {
                if (!data) return;
                
                // Handle online/offline status icon
                if (data.last_action && data.last_action.status) {
                    const state = data.last_action.status;
                    const svg = SVGs[state] || SVGs.Offline;
 
                    function insertIcon() {
                        const usernameElement = document.querySelector('div[class*="rose"] .user-name');
                        if (usernameElement) {
                            // Remove existing icon if present
                            const existingIcon = usernameElement.parentNode.querySelector('.torn-status-icon');
                            if (existingIcon) {
                                existingIcon.remove();
                            }
                            
                            const iconSpan = document.createElement('span');
                            iconSpan.className = 'torn-status-icon';
                            iconSpan.innerHTML = svg;
                            iconSpan.style.verticalAlign = "middle";
                            iconSpan.style.marginRight = "4px";
                            iconSpan.style.cursor = "pointer";
                            iconSpan.title = state + " - Click to refresh";
                            
                            // Add click handler to refresh defender status
                            iconSpan.addEventListener('click', () => {
                                refreshAllData();
                            });
                            
                            usernameElement.parentNode.insertBefore(iconSpan, usernameElement);
                        } else {
                            setTimeout(insertIcon, 200);
                        }
                    }
                    insertIcon();
                }
 
                // Handle health status display
                if (data.status && data.status.state) {
                    const statusState = data.status.state;
                    const statusColor = data.status.color || 'gray';
                    const statusUntil = data.status.until;
 
                    function insertHealthStatus() {
                        const usernameElement = document.querySelector('div[class*="rose"] .user-name');
                        if (usernameElement) {
                            // Remove existing health status if present
                            const existingHealthStatus = usernameElement.parentNode.querySelector('.torn-health-status');
                            if (existingHealthStatus) {
                                existingHealthStatus.remove();
                            }
                            
                            const healthContainer = document.createElement('span');
                            healthContainer.className = 'torn-health-status';
                            healthContainer.style.marginLeft = '8px';
                            healthContainer.style.fontSize = '0.85em';
                            healthContainer.style.fontWeight = 'bold';
                            healthContainer.style.cursor = 'pointer';
                            
                            // Color mapping for different states
                            const colorMap = {
                                'red': '#dc3545',
                                'orange': '#fd7e14', 
                                'yellow': '#ffc107',
                                'green': '#28a745',
                                'blue': '#007bff',
                                'gray': '#6c757d'
                            };
                            
                            healthContainer.style.color = colorMap[statusColor] || '#6c757d';
                            
                            // Add click handler for refresh
                            healthContainer.addEventListener('click', () => {
                                // Check if the display shows "Click to refresh"
                                if (healthContainer.textContent.includes("Click to refresh")) {
                                    // Timer expired, refresh whole page
                                    location.reload();
                                } else {
                                    // Timer still active, refresh all data
                                    refreshAllData();
                                }
                            });
                            
                            function updateCountdown() {
                                let displayText = statusState;
                                
                                // Add countdown timer if available
                                if (statusUntil) {
                                    const currentTime = Math.floor(Date.now() / 1000);
                                    const timeRemaining = statusUntil - currentTime;
                                    
                                    if (timeRemaining > 0) {
                                        const hours = Math.floor(timeRemaining / 3600);
                                        const minutes = Math.floor((timeRemaining % 3600) / 60);
                                        const seconds = timeRemaining % 60;
                                        
                                        if (hours > 0) {
                                            displayText += ` (${hours}h ${minutes}m)`;
                                        } else if (minutes > 0) {
                                            displayText += ` (${minutes}m ${seconds}s)`;
                                        } else {
                                            displayText += ` (${seconds}s)`;
                                        }
                                    } else {
                                        // Timer expired, show click to refresh message
                                        displayText = statusState + " - Click to refresh";
                                        healthContainer.style.textDecoration = 'underline';
                                    }
                                }
                                
                                healthContainer.textContent = displayText;
                            }
                            
                            // Initial update
                            updateCountdown();
                            
                            // Update countdown every second if there's a timer
                            if (statusUntil) {
                                setInterval(updateCountdown, 1000);
                            }
                            
                            usernameElement.parentNode.insertBefore(healthContainer, usernameElement.nextSibling);
                        } else {
                            setTimeout(insertHealthStatus, 200);
                        }
                    }
                    insertHealthStatus();
                }
            });
    }
 
    // Initial fetch
    fetchDefenderStatus();
 
    // Fetch attacker energy
    function fetchAttackerEnergy() {
        fetch(`https://api.torn.com/user/?selections=bars&key=${API_KEY}&comment=attackerEnergy&comment=attackpageimprovements`)
            .then(res => res.json())
            .then(data => {
                if (!data || !data.energy) return;
                const currentEnergy = data.energy.current;
                const maxEnergy = data.energy.maximum;
 
                function insertEnergyDisplay() {
                    const attackerUsernameElement = document.querySelector('div[class*="green"] .user-name');
                    if (attackerUsernameElement) {
                        // Remove existing energy display if present
                        const existingEnergyDisplay = attackerUsernameElement.parentNode.querySelector('.torn-energy-display');
                        if (existingEnergyDisplay) {
                            existingEnergyDisplay.remove();
                        }
                        
                        const energyContainer = document.createElement('div');
                        energyContainer.className = 'torn-energy-display';
                        energyContainer.style.display = 'inline-block';
                        energyContainer.style.marginLeft = '8px';
                        energyContainer.style.verticalAlign = 'middle';
                        energyContainer.style.cursor = 'pointer';
                        energyContainer.title = `Energy: ${currentEnergy}/${maxEnergy} - Click to refresh, Long press to change API key`;

                        // Long press functionality for API key change
                        let longPressTimer;
                        let isLongPress = false;

                        energyContainer.addEventListener('mousedown', () => {
                            isLongPress = false;
                            longPressTimer = setTimeout(() => {
                                isLongPress = true;
                                showAPIKeySetup();
                            }, 1000); // 1 second long press
                        });

                        energyContainer.addEventListener('mouseup', () => {
                            clearTimeout(longPressTimer);
                        });

                        energyContainer.addEventListener('mouseleave', () => {
                            clearTimeout(longPressTimer);
                        });

                        // Touch events for mobile
                        energyContainer.addEventListener('touchstart', (e) => {
                            e.preventDefault();
                            isLongPress = false;
                            longPressTimer = setTimeout(() => {
                                isLongPress = true;
                                showAPIKeySetup();
                            }, 1000);
                        });

                        energyContainer.addEventListener('touchend', (e) => {
                            e.preventDefault();
                            clearTimeout(longPressTimer);
                            // If it wasn't a long press, treat as regular click
                            if (!isLongPress) {
                                refreshAllData();
                            }
                        });

                        energyContainer.addEventListener('touchcancel', () => {
                            clearTimeout(longPressTimer);
                        });

                        // Regular click handler (for mouse)
                        energyContainer.addEventListener('click', (e) => {
                            // Only refresh if it wasn't a long press
                            if (!isLongPress) {
                                refreshAllData();
                            }
                        });                        // Create progress bar container
                        const progressContainer = document.createElement('div');
                        progressContainer.style.position = 'relative';
                        progressContainer.style.width = '80px';
                        progressContainer.style.height = '12px';
                        progressContainer.style.backgroundColor = '#2a2a2a';
                        progressContainer.style.borderRadius = '8px';
                        progressContainer.style.overflow = 'hidden';
                        progressContainer.style.border = '1px solid #444';
 
                        // Create progress bar fill
                        const progressBar = document.createElement('div');
                        const percentage = (currentEnergy / maxEnergy) * 100;
                        progressBar.style.width = `${percentage}%`;
                        progressBar.style.height = '100%';
                        progressBar.style.backgroundColor = '#0ea01fff';
                        progressBar.style.borderRadius = '8px';
                        progressBar.style.transition = 'width 0.3s ease';
 
                        // Create text display (overlaid on bar)
                        const textDisplay = document.createElement('span');
                        textDisplay.textContent = `${currentEnergy}/${maxEnergy}`;
                        textDisplay.style.position = 'absolute';
                        textDisplay.style.top = '50%';
                        textDisplay.style.left = '50%';
                        textDisplay.style.transform = 'translate(-50%, -50%)';
                        textDisplay.style.fontSize = '10px';
                        textDisplay.style.color = '#fff';
                        textDisplay.style.fontWeight = 'bold';
                        textDisplay.style.textShadow = '1px 1px 2px rgba(0,0,0,0.8)';
                        textDisplay.style.zIndex = '10';
 
                        progressContainer.appendChild(progressBar);
                        progressContainer.appendChild(textDisplay);
                        energyContainer.appendChild(progressContainer);
 
                        attackerUsernameElement.parentNode.insertBefore(energyContainer, attackerUsernameElement.nextSibling);
                    } else {
                        setTimeout(insertEnergyDisplay, 200);
                    }
                }
                insertEnergyDisplay();
            });
    }
 
    // Initialize script if API key is available
    if (API_KEY) {
        initializeScript();
    }
})();