您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Displays Energy (Green), Nerve (Red), and Happiness (Yellow) with time-to-full timers. GUI is minimizable/draggable. Updates every 2 minutes.
// ==UserScript== // @name Torn Status Monitor // @namespace TornStatusMonitor // @version 1.9 // @description Displays Energy (Green), Nerve (Red), and Happiness (Yellow) with time-to-full timers. GUI is minimizable/draggable. Updates every 2 minutes. // @author GNSC4 [268863] // @match https://www.torn.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=torn.com // @connect api.torn.com // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @license MIT // ==/UserScript== (function() { 'use strict'; // --- Configuration --- const UPDATE_INTERVAL_MS = 2 * 60 * 1000; // How often to fetch API data (2 minutes) const API_KEY_STORAGE = 'torn_status_api_key_v1'; // Storage key for API key const GUI_MINIMIZED_STORAGE = 'torn_status_gui_minimized_v1'; // Storage key for minimized state const GUI_POSITION_STORAGE = 'torn_status_gui_position_v1'; // Storage key for GUI position const DEFAULT_POSITION = { top: '10px', left: '10px' }; // Default position // --- State Variables --- let apiKey = GM_getValue(API_KEY_STORAGE, null); // Load saved API key let isMinimized = GM_getValue(GUI_MINIMIZED_STORAGE, false); // Load saved minimized state let guiPosition = JSON.parse(GM_getValue(GUI_POSITION_STORAGE, JSON.stringify(DEFAULT_POSITION))); // Load saved position or use default // --- Position Validation --- // Check if the loaded position is valid; reset if not. try { const topPx = parseInt(guiPosition.top, 10); const leftPx = parseInt(guiPosition.left, 10); // Basic check: ensure position is not negative or excessively large (adjust max values if needed) if (isNaN(topPx) || isNaN(leftPx) || topPx < 0 || leftPx < 0 || topPx > (window.innerHeight || 2000) || leftPx > (window.innerWidth || 3000)) { console.warn("Torn Status Monitor: Invalid saved position detected. Resetting to default.", guiPosition); guiPosition = { ...DEFAULT_POSITION }; // Use spread to copy default GM_setValue(GUI_POSITION_STORAGE, JSON.stringify(guiPosition)); // Save the reset position } } catch (e) { console.error("Torn Status Monitor: Error validating position, resetting.", e); guiPosition = { ...DEFAULT_POSITION }; GM_setValue(GUI_POSITION_STORAGE, JSON.stringify(guiPosition)); } let intervals = { update: null, energy: null, nerve: null, happiness: null }; // Holds timer intervals // --- GUI Element References --- let guiContainer, minimizeButton, apiKeyInput, apiKeySaveButton; let energyDisplay, nerveDisplay, happinessDisplay; let energyTimerDisplay, nerveTimerDisplay, happinessTimerDisplay; // --- CSS Styles --- function addStyles() { // Inject CSS styles for the GUI element. Uses !important extensively to override Torn styles. // Use the validated guiPosition here const css = ` #torn-status-gui { position: fixed !important; top: ${guiPosition.top} !important; left: ${guiPosition.left} !important; background-color: rgba(30, 30, 30, 0.9) !important; /* Darker background */ border: 1px solid #999 !important; /* Lighter border */ border-radius: 5px !important; padding: 10px !important; color: #ddd !important; /* Lighter text */ font-family: Arial, sans-serif !important; font-size: 12px !important; z-index: 99999 !important; /* High z-index */ min-width: 190px !important; /* Slightly wider */ box-shadow: 0 2px 15px rgba(0,0,0,0.6) !important; /* Enhanced shadow */ cursor: grab !important; /* Default cursor indicates draggable */ user-select: none !important; /* Prevent text selection */ box-sizing: border-box !important; /* Consistent box model */ } /* Minimized state styles */ #torn-status-gui.minimized { padding: 0 !important; min-width: 0 !important; width: 35px !important; /* Slightly larger minimized size */ height: 35px !important; overflow: hidden !important; cursor: pointer !important; /* Indicate it can be clicked to maximize */ } #torn-status-gui.minimized #torn-status-content, #torn-status-gui.minimized #torn-status-api-setup, #torn-status-gui.minimized #torn-status-header h3 { /* Hide title when minimized */ display: none !important; } #torn-status-gui.minimized #torn-status-header { padding: 0 !important; /* Remove padding in minimized header */ margin: 0 !important; border-bottom: none !important; /* Remove border when minimized */ min-height: 35px !important; /* Ensure header takes full height */ display: flex !important; align-items: center !important; justify-content: center !important; } #torn-status-gui.minimized #torn-status-minimize-btn { position: static !important; /* Reset position */ margin: 0 !important; padding: 5px !important; /* Make button easier to click */ font-size: 16px !important; /* Larger icon */ line-height: 1 !important; } /* Header styles */ #torn-status-header { display: flex !important; justify-content: space-between !important; align-items: center !important; border-bottom: 1px solid #666 !important; margin-bottom: 8px !important; /* Increased spacing */ padding-bottom: 8px !important; cursor: grab !important; /* Cursor for dragging */ } #torn-status-header h3 { margin: 0 !important; font-size: 14px !important; font-weight: bold !important; color: #fff !important; /* White title */ } /* Minimize button styles */ #torn-status-minimize-btn { background: #555 !important; /* Darker button */ border: 1px solid #777 !important; color: #fff !important; cursor: pointer !important; padding: 3px 6px !important; /* Slightly larger padding */ border-radius: 3px !important; font-weight: bold !important; line-height: 1 !important; } #torn-status-minimize-btn:hover { background: #777 !important; /* Lighter hover */ } /* Content area styles */ #torn-status-content div, #torn-status-api-setup div { margin-bottom: 5px !important; /* Consistent spacing */ line-height: 1.4 !important; /* Improve readability */ } #torn-status-content span:first-child { /* Label styling */ display: inline-block !important; min-width: 55px !important; /* Adjusted width for labels */ font-weight: bold !important; color: #aaa !important; /* Grey labels */ } #torn-status-content .value { /* Base style for Current/Max value */ display: inline-block !important; min-width: 65px !important; /* Width for values */ text-align: right !important; margin-right: 5px !important; font-weight: bold !important; /* Make values bold */ } /* --- Color Coding for Values --- */ #torn-status-content .energy-value { color: #99dd99 !important; /* Green for Energy */ } #torn-status-content .nerve-value { color: #ff6666 !important; /* Red for Nerve */ } #torn-status-content .happy-value { color: #ffff99 !important; /* Yellow for Happiness */ } /* --- --- */ #torn-status-content .timer { /* Timer styling */ font-weight: bold !important; color: #99dd99 !important; /* Lighter green for timers */ margin-left: 5px !important; } #torn-status-content .timer.full { color: #ffff99 !important; /* Yellow when full */ } #torn-status-content .error, #torn-status-api-setup .error { color: #ff6666 !important; /* Red for errors */ font-weight: bold !important; margin-top: 5px !important; } /* API Key input section styles */ #torn-status-api-setup { padding-top: 5px !important; } #torn-status-api-setup input[type="text"] { padding: 4px 6px !important; /* More padding */ border: 1px solid #888 !important; border-radius: 3px !important; margin-right: 5px !important; width: calc(100% - 65px) !important; /* Calculate width based on button */ background-color: #fff !important; color: #000 !important; box-sizing: border-box !important; } #torn-status-api-setup button { padding: 4px 10px !important; /* More padding */ border: 1px solid #999 !important; border-radius: 3px !important; background-color: #ccc !important; /* Lighter button */ color: #000 !important; cursor: pointer !important; font-weight: bold !important; box-sizing: border-box !important; } #torn-status-api-setup button:hover { background-color: #ddd !important; } #torn-status-api-setup p { margin-bottom: 8px !important; font-size: 11px !important; color: #ccc !important; } #torn-status-api-setup a { color: #aaa !important; text-decoration: underline !important; } #torn-status-api-setup a:hover { color: #ccc !important; } `; GM_addStyle(css); // Add the styles to the page } // --- GUI Creation --- function createGUI() { console.log("Torn Status Monitor: Attempting to create GUI elements..."); // Create the main container div guiContainer = document.createElement('div'); guiContainer.id = 'torn-status-gui'; if (isMinimized) { guiContainer.classList.add('minimized'); // Apply minimized class if needed } // Create Header const header = document.createElement('div'); header.id = 'torn-status-header'; const title = document.createElement('h3'); title.textContent = 'Status'; // Shorter title minimizeButton = document.createElement('button'); minimizeButton.id = 'torn-status-minimize-btn'; minimizeButton.textContent = isMinimized ? '□' : '−'; // Use symbols for minimize/maximize minimizeButton.title = isMinimized ? 'Maximize' : 'Minimize'; minimizeButton.addEventListener('click', (e) => { e.stopPropagation(); // Prevent drag from starting on button click toggleMinimize(); }); header.appendChild(title); header.appendChild(minimizeButton); guiContainer.appendChild(header); // Add click listener to header for maximizing when minimized header.addEventListener('click', () => { if (isMinimized) { toggleMinimize(); } }); // Create Content Area (switches between API setup and status) const contentArea = document.createElement('div'); contentArea.id = 'torn-status-content-area'; // --- API Setup Section (Initially hidden if key exists) --- const apiSetupDiv = document.createElement('div'); apiSetupDiv.id = 'torn-status-api-setup'; apiSetupDiv.style.display = apiKey ? 'none' : 'block !important'; apiSetupDiv.innerHTML = `<p>Enter Torn API Key (<a href="https://www.torn.com/preferences.php#tab=api" target="_blank" title="Go to API Key settings">Get Key</a>):</p>`; const inputGroup = document.createElement('div'); // Group input and button apiKeyInput = document.createElement('input'); apiKeyInput.type = 'text'; apiKeyInput.placeholder = 'Your API Key'; apiKeyInput.addEventListener('keypress', (e) => { // Allow saving with Enter key if (e.key === 'Enter') { saveApiKey(); } }); apiKeySaveButton = document.createElement('button'); apiKeySaveButton.textContent = 'Save'; apiKeySaveButton.title = 'Save API Key'; apiKeySaveButton.addEventListener('click', saveApiKey); inputGroup.appendChild(apiKeyInput); inputGroup.appendChild(apiKeySaveButton); apiSetupDiv.appendChild(inputGroup); const apiErrorDiv = document.createElement('div'); // For displaying API key errors apiErrorDiv.className = 'error api-error'; // Add class for specific targeting apiSetupDiv.appendChild(apiErrorDiv); contentArea.appendChild(apiSetupDiv); // --- Status Display Section (Initially hidden if no key) --- const statusDiv = document.createElement('div'); statusDiv.id = 'torn-status-content'; statusDiv.style.display = apiKey ? 'block !important' : 'none !important'; // Create display elements for each bar, adding specific classes for color coding energyDisplay = document.createElement('div'); nerveDisplay = document.createElement('div'); happinessDisplay = document.createElement('div'); // Add specific classes like 'energy-value', 'nerve-value', 'happy-value' energyDisplay.innerHTML = '<span>Energy:</span><span class="value energy-value">--/--</span>'; nerveDisplay.innerHTML = '<span>Nerve:</span><span class="value nerve-value">--/--</span>'; happinessDisplay.innerHTML = '<span>Happy:</span><span class="value happy-value">--/--</span>'; // Create timer elements energyTimerDisplay = document.createElement('span'); energyTimerDisplay.className = 'timer'; energyTimerDisplay.textContent = '--:--:--'; energyDisplay.appendChild(energyTimerDisplay); nerveTimerDisplay = document.createElement('span'); nerveTimerDisplay.className = 'timer'; nerveTimerDisplay.textContent = '--:--:--'; nerveDisplay.appendChild(nerveTimerDisplay); happinessTimerDisplay = document.createElement('span'); happinessTimerDisplay.className = 'timer'; happinessTimerDisplay.textContent = '--:--:--'; happinessDisplay.appendChild(happinessTimerDisplay); // Add elements to the status section statusDiv.appendChild(energyDisplay); statusDiv.appendChild(nerveDisplay); statusDiv.appendChild(happinessDisplay); const statusErrorDiv = document.createElement('div'); // For displaying general fetch errors statusErrorDiv.className = 'error status-error'; statusDiv.appendChild(statusErrorDiv); contentArea.appendChild(statusDiv); // Add content area to the main container guiContainer.appendChild(contentArea); // Add the GUI to the page body try { if (!document.body) { throw new Error("Document body not found yet."); } document.body.appendChild(guiContainer); console.log("Torn Status Monitor: GUI appended to body."); } catch (error) { console.error("Torn Status Monitor: Failed to append GUI to document body.", error); return; // Stop if we can't even append the GUI } // Make the GUI draggable using the header as the handle makeDraggable(guiContainer, header); console.log("Torn Status Monitor: GUI creation complete."); } // --- Drag and Drop Functionality --- function makeDraggable(element, handle) { let startX = 0, startY = 0, initialTop = 0, initialLeft = 0; // Use addEventListener for mousedown on the handle handle.addEventListener('mousedown', dragMouseDown); function dragMouseDown(e) { console.log("Torn Status Monitor: dragMouseDown triggered!"); // Ignore clicks on the minimize button itself if (e.target === minimizeButton) return; e = e || window.event; // e.preventDefault(); // Keep this commented out for now // Get the initial mouse cursor position startX = e.clientX; startY = e.clientY; // Get the initial element position (parse from style or use validated guiPosition) initialTop = parseInt(element.style.top || guiPosition.top, 10); initialLeft = parseInt(element.style.left || guiPosition.left, 10); // Fallback if parsing failed if (isNaN(initialTop)) initialTop = parseInt(DEFAULT_POSITION.top, 10); if (isNaN(initialLeft)) initialLeft = parseInt(DEFAULT_POSITION.left, 10); // Set up event listeners for mouse movement and release on the document document.addEventListener('mouseup', closeDragElement); document.addEventListener('mousemove', elementDrag); // Change cursor style to indicate dragging element.style.cursor = 'grabbing !important'; handle.style.cursor = 'grabbing !important'; console.log(`Torn Status Monitor: Drag start - Initial Pos: (${initialLeft}px, ${initialTop}px), Mouse: (${startX}, ${startY})`); } function elementDrag(e) { e = e || window.event; e.preventDefault(); // Prevent text selection during drag // Calculate the distance the mouse has moved const deltaX = e.clientX - startX; const deltaY = e.clientY - startY; // Calculate the element's new position based on initial position + delta let newTop = initialTop + deltaY; let newLeft = initialLeft + deltaX; // --- Boundary checks --- newTop = Math.max(0, newTop); newLeft = Math.max(0, newLeft); newTop = Math.min(newTop, window.innerHeight - 20); newLeft = Math.min(newLeft, window.innerWidth - 20); // --- End Boundary checks --- // Set the element's new position (Re-added !important) // Use setProperty for better compatibility with !important element.style.setProperty('top', newTop + "px", 'important'); element.style.setProperty('left', newLeft + "px", 'important'); } function closeDragElement() { console.log("Torn Status Monitor: closeDragElement triggered!"); // Remove event listeners from the document document.removeEventListener('mouseup', closeDragElement); document.removeEventListener('mousemove', elementDrag); // Restore default cursor styles element.style.cursor = 'grab !important'; handle.style.cursor = 'grab !important'; // Get the final position directly from the style const finalTop = element.style.top; const finalLeft = element.style.left; // Basic check to ensure we save valid pixel values if (finalTop && finalLeft && finalTop.endsWith('px') && finalLeft.endsWith('px')) { guiPosition = { top: finalTop, left: finalLeft }; GM_setValue(GUI_POSITION_STORAGE, JSON.stringify(guiPosition)); console.log("Torn Status Monitor: Drag ended. Position saved:", guiPosition); } else { console.error("Torn Status Monitor: Drag ended but final position style was invalid. Not saving.", { top: finalTop, left: finalLeft }); // Attempt to re-apply last known valid position if save failed element.style.setProperty('top', guiPosition.top, 'important'); // Revert to last saved or default element.style.setProperty('left', guiPosition.left, 'important'); } } } // --- API Interaction --- function fetchData() { // Abort if API key is not set if (!apiKey) { console.warn("Torn Status Monitor: API Key not set. Aborting fetch."); updateDisplay({ error: "API Key needed", target: 'api' }); // Show error in API section switchView(false); // Ensure API input view is shown return; } // Construct the API URL const url = `https://api.torn.com/user/?selections=bars&key=${apiKey}&comment=TornStatusMonitorScript`; // console.log("Torn Status Monitor: Fetching data..."); // Less verbose logging // Use GM_xmlhttpRequest for cross-origin requests GM_xmlhttpRequest({ method: "GET", url: url, timeout: 15000, // 15 second timeout onload: function(response) { try { const data = JSON.parse(response.responseText); console.log("Torn Status Monitor: API Response:", data); // Log the received data // Check for API-level errors in the response if (data.error) { console.error("Torn Status Monitor API Error:", data.error.code, data.error.error); const errorMessage = `API Error ${data.error.code}`; updateDisplay({ error: errorMessage, target: 'api' }); // Show error in API section // If the key is incorrect (code 2), clear it and switch view if (data.error.code === 2) { apiKey = null; GM_setValue(API_KEY_STORAGE, null); // Clear invalid key switchView(false); // Show API input } clearAllTimers(); // Stop timers on error } else { // Process successful data updateDisplay(data); // Update GUI values startTimers(data); // Start/reset countdown timers // Clear any previous error messages clearErrorMessages(); // Ensure the status view is visible if data is successfully fetched if (!isMinimized) { switchView(true); } } } catch (e) { console.error("Torn Status Monitor: Error parsing API response:", e); updateDisplay({ error: "Parse Error", target: 'status' }); // Show error in status section clearAllTimers(); } }, onerror: function(response) { console.error("Torn Status Monitor: GM_xmlhttpRequest error:", response); updateDisplay({ error: "Network Error", target: 'status' }); // Show error in status section clearAllTimers(); }, ontimeout: function() { console.error("Torn Status Monitor: API request timed out."); updateDisplay({ error: "Timeout", target: 'status' }); // Show error in status section clearAllTimers(); } }); } // --- Display Update Logic --- function updateDisplay(data) { // Ensure GUI elements exist before trying to update them if (!guiContainer) return; // Use the specific classes for value spans const energyValSpan = guiContainer.querySelector('.energy-value'); const nerveValSpan = guiContainer.querySelector('.nerve-value'); const happyValSpan = guiContainer.querySelector('.happy-value'); const apiErrorDiv = guiContainer.querySelector('.api-error'); const statusErrorDiv = guiContainer.querySelector('.status-error'); // Clear previous errors first if (apiErrorDiv) apiErrorDiv.textContent = ''; if (statusErrorDiv) statusErrorDiv.textContent = ''; // Handle and display errors if (data.error) { const errorTargetDiv = data.target === 'api' ? apiErrorDiv : statusErrorDiv; if (errorTargetDiv) { errorTargetDiv.textContent = `Error: ${data.error}`; } // Reset value displays on error if (energyValSpan) energyValSpan.textContent = '--/--'; if (nerveValSpan) nerveValSpan.textContent = '--/--'; if (happyValSpan) happyValSpan.textContent = '--/--'; // Reset timer displays on error - handled by clearAllTimers called by fetchData return; // Stop further processing } // Update bar values if data is valid and elements exist if (data.energy && typeof data.energy.current !== 'undefined' && typeof data.energy.maximum !== 'undefined' && energyValSpan) { energyValSpan.textContent = `${data.energy.current}/${data.energy.maximum}`; } else if (energyValSpan) { energyValSpan.textContent = 'N/A'; // Indicate if data is missing } if (data.nerve && typeof data.nerve.current !== 'undefined' && typeof data.nerve.maximum !== 'undefined' && nerveValSpan) { nerveValSpan.textContent = `${data.nerve.current}/${data.nerve.maximum}`; } else if (nerveValSpan) { nerveValSpan.textContent = 'N/A'; } if (data.happy && typeof data.happy.current !== 'undefined' && typeof data.happy.maximum !== 'undefined' && happyValSpan) { happyValSpan.textContent = `${data.happy.current}/${data.happy.maximum}`; } else if (happyValSpan) { happyValSpan.textContent = 'N/A'; } // Timer updates are handled separately by startTimers/updateTimer } // --- Timer Logic --- function startTimers(data) { // Clear any existing timer intervals before starting new ones clearAllTimers(); // Always call updateTimer if the bar data and display element exist. // updateTimer itself will handle displaying "Full" or the countdown. if (data.energy && typeof data.energy.fulltime !== 'undefined' && energyTimerDisplay) { // Pass the remaining seconds directly to updateTimer updateTimer('energy', data.energy.fulltime, energyTimerDisplay); } else if (energyTimerDisplay) { // Handle case where energy data might be missing entirely from API response energyTimerDisplay.textContent = "N/A"; energyTimerDisplay.classList.remove('full'); } if (data.nerve && typeof data.nerve.fulltime !== 'undefined' && nerveTimerDisplay) { updateTimer('nerve', data.nerve.fulltime, nerveTimerDisplay); } else if (nerveTimerDisplay) { nerveTimerDisplay.textContent = "N/A"; nerveTimerDisplay.classList.remove('full'); } if (data.happy && typeof data.happy.fulltime !== 'undefined' && happinessTimerDisplay) { updateTimer('happiness', data.happy.fulltime, happinessTimerDisplay); } else if (happinessTimerDisplay) { happinessTimerDisplay.textContent = "N/A"; happinessTimerDisplay.classList.remove('full'); } } // Recursive function to update a single timer display every second // The 'initialSecondsRemaining' parameter now represents the seconds left *at the time of the API call* function updateTimer(barName, initialSecondsRemaining, displayElement) { if (!displayElement) return; // Exit if the display element is missing // Convert the initial value to a number let secondsRemaining = Number(initialSecondsRemaining); // Check if secondsRemaining is a valid number if (isNaN(secondsRemaining)) { console.error(`Torn Status Monitor: Invalid initialSecondsRemaining for ${barName}`, { initialSecondsRemaining }); displayElement.textContent = "Error"; displayElement.classList.remove('full'); intervals[barName] = null; return; } // --- Timer Tick Function --- function tick() { if (secondsRemaining <= 0) { // Bar is full or past full time displayElement.textContent = "Full"; displayElement.classList.add('full'); intervals[barName] = null; // Clear the interval reference } else { // Bar is still regenerating displayElement.classList.remove('full'); // Calculate hours, minutes, seconds const hours = Math.floor(secondsRemaining / 3600); const minutes = Math.floor((secondsRemaining % 3600) / 60); const seconds = secondsRemaining % 60; // Format as HH:MM:SS displayElement.textContent = `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`; // Decrement the remaining seconds secondsRemaining--; // Schedule the next tick 1 second later using setTimeout intervals[barName] = setTimeout(tick, 1000); } } // --- End Timer Tick Function --- // Start the first tick tick(); } // Function to clear all active timer intervals function clearAllTimers() { clearTimeout(intervals.energy); clearTimeout(intervals.nerve); clearTimeout(intervals.happiness); intervals.energy = null; intervals.nerve = null; intervals.happiness = null; // Also reset timer displays visually if needed if(energyTimerDisplay) { energyTimerDisplay.textContent = '--:--:--'; energyTimerDisplay.classList.remove('full'); } if(nerveTimerDisplay) { nerveTimerDisplay.textContent = '--:--:--'; nerveTimerDisplay.classList.remove('full'); } if(happinessTimerDisplay) { happinessTimerDisplay.textContent = '--:--:--'; happinessTimerDisplay.classList.remove('full'); } } // Function to clear error messages from the GUI function clearErrorMessages() { if (!guiContainer) return; const apiErrorDiv = guiContainer.querySelector('.api-error'); const statusErrorDiv = guiContainer.querySelector('.status-error'); if (apiErrorDiv) apiErrorDiv.textContent = ''; if (statusErrorDiv) statusErrorDiv.textContent = ''; } // --- GUI Interaction Functions --- function toggleMinimize() { isMinimized = !isMinimized; // Toggle the state // Add or remove the 'minimized' class based on the state guiContainer.classList.toggle('minimized', isMinimized); // Change the button text/icon minimizeButton.textContent = isMinimized ? '□' : '−'; minimizeButton.title = isMinimized ? 'Maximize' : 'Minimize'; // Save the new state GM_setValue(GUI_MINIMIZED_STORAGE, isMinimized); // Explicitly manage display of content vs API setup when maximizing if (!isMinimized) { switchView(!!apiKey); // Show status if key exists, else show API setup } } function saveApiKey() { const newKey = apiKeyInput.value.trim(); // Get and trim the entered key if (newKey && /^[a-zA-Z0-9]{16}$/.test(newKey)) { // Basic validation (16 alphanumeric chars) apiKey = newKey; GM_setValue(API_KEY_STORAGE, apiKey); // Save the valid key apiKeyInput.value = ''; // Clear the input field console.log("Torn Status Monitor: API Key saved."); clearErrorMessages(); // Clear any previous key errors switchView(true); // Switch to the status display view fetchData(); // Fetch data immediately with the new key // Ensure the periodic update interval is running (or start it) if (intervals.update) { clearInterval(intervals.update); // Clear existing interval if any } intervals.update = setInterval(fetchData, UPDATE_INTERVAL_MS); // Start new interval } else { // Show an error message if the key is invalid updateDisplay({ error: "Invalid Key format (should be 16 letters/numbers)", target: 'api' }); console.error("Torn Status Monitor: Invalid API Key format entered."); } } // Helper function to switch between API input view and status display view function switchView(showStatus) { if (!guiContainer) return; // Make sure GUI exists const statusDiv = document.getElementById('torn-status-content'); const apiSetupDiv = document.getElementById('torn-status-api-setup'); if (statusDiv && apiSetupDiv) { // Use !important to ensure styles apply statusDiv.style.display = showStatus ? 'block !important' : 'none !important'; apiSetupDiv.style.display = showStatus ? 'none !important' : 'block !important'; } } // --- Initialization --- function init() { try { // Add try...catch around the main init logic console.log("Torn Status Monitor: Initializing script..."); addStyles(); // Add CSS to the page (uses validated guiPosition) createGUI(); // Create the HTML structure for the GUI // If GUI creation failed (e.g., couldn't append), stop here if (!guiContainer || !document.getElementById('torn-status-gui')) { console.error("Torn Status Monitor: GUI container not found after creation attempt. Aborting init."); return; } // Apply the potentially reset position from CSS to the element directly // This ensures the element's style matches the CSS rule if position was reset // Use validated guiPosition which might have been reset guiContainer.style.top = guiPosition.top; // No !important needed here, CSS has it guiContainer.style.left = guiPosition.left; // Perform initial data fetch if API key exists if (apiKey) { // Clear any previously running update interval before starting a new one if (intervals.update) { clearInterval(intervals.update); } fetchData(); // Fetch data on load // Set interval for periodic updates with the new frequency intervals.update = setInterval(fetchData, UPDATE_INTERVAL_MS); } else { // If no key, the GUI will show the API input section by default console.log("Torn Status Monitor: No API Key found. Waiting for user input."); switchView(false); // Explicitly show API input view } console.log("Torn Status Monitor: Initialization complete."); } catch (error) { console.error("Torn Status Monitor: CRITICAL ERROR during initialization:", error); } } // --- Run Initialization --- function runInit() { try { // Add try...catch around the init trigger itself // Check if the script has already run (e.g., to prevent multiple instances in some edge cases) if (document.getElementById('torn-status-gui')) { console.log("Torn Status Monitor: GUI already exists. Skipping initialization."); return; } console.log("Torn Status Monitor: DOM ready or loaded. Running init()."); init(); } catch (error) { console.error("Torn Status Monitor: CRITICAL ERROR setting up initialization:", error); } } // Ensure the script runs after the page DOM is ready if (document.readyState === 'complete' || document.readyState === 'interactive') { // If already loaded, initialize immediately (wrapped in error handler) // Use a small timeout to potentially avoid race conditions with Torn's own scripts setTimeout(runInit, 150); // Slightly increased timeout just in case } else { // Otherwise, wait for the DOMContentLoaded event (generally preferred over 'load') document.addEventListener('DOMContentLoaded', runInit); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址