您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Improvements to the Attacking screen.
// ==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(); } })();