Attack screen improvements

Improvements to the Attacking screen.

目前為 2025-07-19 提交的版本,檢視 最新版本

// ==UserScript==
// @name            Attack screen improvements
// @namespace       http://tampermonkey.net/
// @version         1.2.0
// @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() {
        function insertAPISetup() {
            const attackerUsernameElement = document.querySelector('div[class*="green"] .user-name');
            if (attackerUsernameElement) {
                // Remove existing setup if present
                const existingSetup = attackerUsernameElement.parentNode.querySelector('.torn-api-setup');
                if (existingSetup) {
                    existingSetup.remove();
                }
                
                const setupContainer = document.createElement('div');
                setupContainer.className = 'torn-api-setup';
                setupContainer.style.display = 'inline-block';
                setupContainer.style.marginLeft = '8px';
                setupContainer.style.verticalAlign = 'middle';
                setupContainer.style.position = 'relative';

                const setupButton = document.createElement('button');
                setupButton.textContent = 'Input Minimal API';
                setupButton.style.fontSize = '9px';
                setupButton.style.padding = '1px 3px';
                setupButton.style.backgroundColor = '#0ea01fff';
                setupButton.style.color = 'white';
                setupButton.style.border = 'none';
                setupButton.style.borderRadius = '2px';
                setupButton.style.cursor = 'pointer';
                setupButton.style.fontWeight = 'bold';
                setupButton.style.height = '14px';
                setupButton.style.lineHeight = '1';
                setupButton.style.position = 'absolute';
                setupButton.style.top = '-8px';
                setupButton.style.left = '0px';
                setupButton.style.whiteSpace = 'nowrap';
                setupButton.style.zIndex = '1000';

                setupButton.addEventListener('click', () => {
                    // Create a more compact custom dialog
                    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();
                            setupContainer.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();
                });

                setupContainer.appendChild(setupButton);
                attackerUsernameElement.parentNode.insertBefore(setupContainer, attackerUsernameElement.nextSibling);
            } else {
                setTimeout(insertAPISetup, 200);
            }
        }
        insertAPISetup();
    }

    // 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`;

                        // Add click handler to refresh energy
                        energyContainer.addEventListener('click', () => {
                            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();
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址