Attack screen improvements

Improvements to the Attack screen.

2025-07-11 يوللانغان نەشرى. ئەڭ يېڭى نەشرىنى كۆرۈش.

// ==UserScript==
// @name            Attack screen improvements
// @namespace       http://tampermonkey.net/
// @version         1.1.0
// @description     Improvements to the Attack 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';

    const API_KEY = 'MINIMAL_API_KEY'; // <-- Replace with "minimal" API 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;

    // 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.title = state;
                            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, just refresh status
                                    fetchDefenderStatus();
                                }
                            });
                            
                            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
    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) {
                    if (!attackerUsernameElement.nextSibling || !attackerUsernameElement.nextSibling.classList || !attackerUsernameElement.nextSibling.classList.contains('torn-energy-display')) {
                        const energyContainer = document.createElement('div');
                        energyContainer.className = 'torn-energy-display';
                        energyContainer.style.display = 'inline-block';
                        energyContainer.style.marginLeft = '8px';
                        energyContainer.style.verticalAlign = 'middle';
                        energyContainer.title = `Energy: ${currentEnergy}/${maxEnergy}`;

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