Torn City Chain Watch Alert

Alert when chain timer drops below user-defined threshold, flash the screen red, and play an alarm sound.

当前为 2024-10-13 提交的版本,查看 最新版本

// ==UserScript==
// @name         Torn City Chain Watch Alert
// @namespace    http://tampermonkey.net/
// @version      1.8
// @description  Alert when chain timer drops below user-defined threshold, flash the screen red, and play an alarm sound.
// Controls organized under "Chain Saver" with toggle functionality.
// @author       Codex234
// @match        https://www.torn.com/*
// ==/UserScript==

(function() {
    'use strict';

    let previousStateBelowThreshold = false;
    let alertedForCurrentThreshold = false;
    let alertThresholdInSeconds = parseInt(localStorage.getItem('alertThreshold')) || 240; // Default to 4 minutes
    let opacity = parseFloat(localStorage.getItem('flashOpacity')) || 0.8; // Default opacity
    let audioSrc = localStorage.getItem('alarmSound') || 'https://www.soundjay.com/transportation/sounds/train-crossing-bell-01.mp3'; // Default sound
    let volumeLevel = localStorage.getItem('volumeLevel') || 'Medium'; // Default volume level
    let audioElement;
    let alarmInterval;

// Function to create the main dropdown container with subcategories
function createDropdownsContainer() {
    const container = document.createElement('div');
    container.id = 'dropdownsContainer';
    container.style.position = 'fixed';
    container.style.top = '10px';
    container.style.right = '10px';
    container.style.zIndex = '10000'; // Ensure it's above other page content
    container.style.backgroundColor = 'rgba(255, 255, 255, 0.95)'; // Slightly more opaque for better readability
    container.style.padding = '8px'; // Reduced padding
    container.style.border = '1px solid #ccc';
    container.style.borderRadius = '8px';
    container.style.boxShadow = '0 0 10px rgba(0,0,0,0.5)';
    container.style.fontFamily = 'Arial, sans-serif';
    container.style.fontSize = '12px'; // Reduced font size
    container.style.width = '180px'; // Adjusted width for minimized view
    container.style.height = '50px'; // Set to fixed height when minimized

    // Main Title with toggle functionality
    const title = document.createElement('h3');
    title.textContent = 'Chain Saver';
    title.style.marginTop = '0';
    title.style.marginBottom = '5px'; // Reduced margin
    title.style.fontSize = '14px'; // Slightly smaller font size
    title.style.textAlign = 'center';
    title.style.cursor = 'pointer'; // Indicate clickable title
    container.appendChild(title);

    // Event listener to toggle the menu
    title.addEventListener('click', () => {
        const contents = container.querySelectorAll('.dropdownContent');
        const isVisible = contents[0].style.display !== 'none'; // Check if contents are currently visible
        contents.forEach(content => {
            content.style.display = isVisible ? 'none' : 'block'; // Toggle display
        });
        container.style.height = isVisible ? '50px' : 'auto'; // Adjust height when minimized
    });

    // Create a wrapper for the dropdown contents
    const dropdownContents = document.createElement('div');
    dropdownContents.className = 'dropdownContent'; // Class for toggling visibility
    dropdownContents.style.display = 'none'; // Start hidden
    container.appendChild(dropdownContents);

        // Alarm Sound Subcategory
        const alarmSoundDiv = document.createElement('div');
        alarmSoundDiv.id = 'alarmSoundDiv';
        alarmSoundDiv.style.marginBottom = '10px';
        dropdownContents.appendChild(alarmSoundDiv);

        const alarmSoundLabel = document.createElement('label');
        alarmSoundLabel.htmlFor = 'soundDropdown';
        alarmSoundLabel.textContent = 'Alarm Sound:';
        alarmSoundLabel.style.display = 'block';
        alarmSoundLabel.style.marginBottom = '5px';
        alarmSoundDiv.appendChild(alarmSoundLabel);

        const soundDropdown = document.createElement('select');
        soundDropdown.id = 'soundDropdown';
        soundDropdown.style.width = '100%';
        alarmSoundDiv.appendChild(soundDropdown);

        // Timer Subcategory
        const timerDiv = document.createElement('div');
        timerDiv.id = 'timerDiv';
        timerDiv.style.marginBottom = '10px';
        dropdownContents.appendChild(timerDiv);

        const timerLabel = document.createElement('label');
        timerLabel.htmlFor = 'chainTimerDropdown';
        timerLabel.textContent = 'Timer:';
        timerLabel.style.display = 'block';
        timerLabel.style.marginBottom = '5px';
        timerDiv.appendChild(timerLabel);

        const timerDropdown = document.createElement('select');
        timerDropdown.id = 'chainTimerDropdown';
        timerDropdown.style.width = '100%';
        timerDiv.appendChild(timerDropdown);

        // Volume Control Subcategory
        const volumeDiv = document.createElement('div');
        volumeDiv.id = 'volumeDiv';
        volumeDiv.style.marginBottom = '10px';
        dropdownContents.appendChild(volumeDiv);

        const volumeLabel = document.createElement('label');
        volumeLabel.htmlFor = 'volumeDropdown';
        volumeLabel.textContent = 'Volume Control:';
        volumeLabel.style.display = 'block';
        volumeLabel.style.marginBottom = '5px';
        volumeDiv.appendChild(volumeLabel);

        const volumeDropdown = document.createElement('select');
        volumeDropdown.id = 'volumeDropdown';
        volumeDropdown.style.width = '100%';
        volumeDiv.appendChild(volumeDropdown);

        // Opacity Control Subcategory
        const opacityDiv = document.createElement('div');
        opacityDiv.id = 'opacityDiv';
        opacityDiv.style.marginBottom = '10px';
        dropdownContents.appendChild(opacityDiv);

        const opacityLabel = document.createElement('label');
        opacityLabel.htmlFor = 'opacityDropdown';
        opacityLabel.textContent = 'Flash Opacity:';
        opacityLabel.style.display = 'block';
        opacityLabel.style.marginBottom = '5px';
        opacityDiv.appendChild(opacityLabel);

        const opacityDropdown = document.createElement('select');
        opacityDropdown.id = 'opacityDropdown';
        opacityDropdown.style.width = '100%';
        opacityDiv.appendChild(opacityDropdown);

    // Append the container to the body
    document.body.appendChild(container);

    // Initialize the dropdowns
    initializeSoundDropdown();
    initializeTimerDropdown();
    initializeVolumeDropdown();
    initializeOpacityDropdown();
}

    // Function to initialize the Alarm Sound dropdown
    function initializeSoundDropdown() {
        const dropdown = document.getElementById('soundDropdown');
        const sounds = [
            { label: 'Train Bell', src: 'https://www.soundjay.com/transportation/sounds/train-crossing-bell-01.mp3' },
            { label: 'Smoke Alarm', src: 'https://www.soundjay.com/mechanical/sounds/smoke-detector-1.mp3' },
            { label: 'Car Alarm', src: 'https://www.soundjay.com/transportation/sounds/car-alarm-1.mp3' },
            // { label: 'Custom...', src: 'custom' } // Uncomment if you implement custom URLs
        ];
        sounds.forEach(sound => {
            const option = document.createElement('option');
            option.value = sound.src;
            option.textContent = sound.label;
            dropdown.appendChild(option);
        });
        dropdown.value = audioSrc;
        dropdown.addEventListener('change', (e) => {
            audioSrc = e.target.value;
            localStorage.setItem('alarmSound', audioSrc);
            if (audioElement) {
                audioElement.src = audioSrc;
                audioElement.load(); // Ensure the new source is loaded
                console.log(`Alarm sound changed to: ${audioSrc}`);
            }
        });
    }

    // Function to initialize the Timer dropdown
    function initializeTimerDropdown() {
        const dropdown = document.getElementById('chainTimerDropdown');
        const timerOptions = [
            { value: -1, text: 'Off' },
            { value: 30, text: formatTime(30) },
            { value: 60, text: formatTime(60) },
            { value: 90, text: formatTime(90) },
            { value: 120, text: formatTime(120) },
            { value: 150, text: formatTime(150) },
            { value: 180, text: formatTime(180) },
            { value: 210, text: formatTime(210) },
            { value: 240, text: formatTime(240) },
            { value: 270, text: formatTime(270) },
            { value: 297, text: formatTime(297) }
        ];
        timerOptions.forEach(optionData => {
            const option = document.createElement('option');
            option.value = optionData.value;
            option.textContent = optionData.text;
            dropdown.appendChild(option);
        });
        dropdown.value = alertThresholdInSeconds;
        dropdown.addEventListener('change', (e) => {
            alertThresholdInSeconds = parseInt(e.target.value);
            localStorage.setItem('alertThreshold', alertThresholdInSeconds);
            alertedForCurrentThreshold = false; // Reset the alert state when threshold changes
            clearInterval(alarmInterval); // Clear any existing alarm interval
            console.log(`Alert threshold set to: ${formatTime(alertThresholdInSeconds)}`);
        });
    }

    // Function to initialize the Volume Control dropdown
    function initializeVolumeDropdown() {
        const dropdown = document.getElementById('volumeDropdown');
        const volumeOptions = [
            { value: 'Silent', text: 'Silent' },
            { value: 'Soft', text: 'Soft' },
            { value: 'Medium', text: 'Medium' },
            { value: 'Loud', text: 'Loud' }
        ];
        volumeOptions.forEach(optionData => {
            const option = document.createElement('option');
            option.value = optionData.value;
            option.textContent = optionData.text;
            dropdown.appendChild(option);
        });
        dropdown.value = volumeLevel;
        dropdown.addEventListener('change', (e) => {
            volumeLevel = e.target.value;
            localStorage.setItem('volumeLevel', volumeLevel);
            setAudioVolume();
            console.log(`Volume level set to: ${volumeLevel}`);
        });

        // Set initial volume
        setAudioVolume();
    }

    // Function to initialize the Opacity Control dropdown
    function initializeOpacityDropdown() {
        const dropdown = document.getElementById('opacityDropdown');
        const opacityOptions = [
            { value: '0.0', text: 'OFF' },
            { value: '0.4', text: '10%' },
            { value: '0.6', text: '20%' },
            { value: '0.8', text: '30%' },
            { value: '1.0', text: '40%' },
            { value: '0.2', text: '50%' },
            { value: '0.4', text: '60%' },
            { value: '0.6', text: '70%' },
            { value: '0.8', text: '80%' },
            { value: '1.0', text: '100%' }
        ];
        opacityOptions.forEach(optionData => {
            const option = document.createElement('option');
            option.value = optionData.value;
            option.textContent = optionData.text;
            dropdown.appendChild(option);
        });
        dropdown.value = opacity.toString();
        dropdown.addEventListener('change', (e) => {
            opacity = parseFloat(e.target.value);
            localStorage.setItem('flashOpacity', opacity);
            console.log(`Flash opacity set to: ${opacity * 100}%`);
        });
    }

    // Function to set the audio volume based on the selected volume level
    function setAudioVolume() {
        const volumeMap = {
            'Silent': 0,
            'Soft': 0.2,
            'Medium': 0.6,
            'Loud': 1.0
        };
        const volume = volumeMap[volumeLevel] !== undefined ? volumeMap[volumeLevel] : 0.6; // Default to Medium if undefined
        if (audioElement) {
            audioElement.volume = volume;
            console.log(`Audio volume set to: ${volume}`);
        }
    }

    // Function to format time from seconds to MM:SS
    function formatTime(seconds) {
        const minutes = Math.floor(seconds / 60);
        const remainingSeconds = seconds % 60;
        return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
    }

    // Function to check the chain timer on the page
    function checkChainTimer() {
        const timerElement = document.querySelector('.bar-timeleft___B9RGV');

        if (timerElement) {
            const timerText = timerElement.textContent.trim();
            const timeParts = timerText.split(':');
            const minutes = parseInt(timeParts[0], 10);
            const seconds = parseInt(timeParts[1], 10);
            const totalTimeInSeconds = (minutes * 60) + seconds;

            if (alertThresholdInSeconds !== -1 && totalTimeInSeconds < alertThresholdInSeconds) {
                if (!alertedForCurrentThreshold) {
                    console.log(`Chain timer is below ${formatTime(alertThresholdInSeconds)}!`);
                    startAlarm();
                    alertedForCurrentThreshold = true;
                }
                flashScreenRed();
                previousStateBelowThreshold = true;
            } else if (totalTimeInSeconds >= alertThresholdInSeconds) {
                stopAlarm();
                previousStateBelowThreshold = false;
                alertedForCurrentThreshold = false; // Reset the alert state when timer goes back above threshold
            }
        }
    }

// Function to flash the screen red
function flashScreenRed() {
    let flashDiv = document.getElementById('flashDiv');

    // Check if the flashDiv exists; if not, create it
    if (!flashDiv) {
        flashDiv = document.createElement('div');
        flashDiv.id = 'flashDiv';
        flashDiv.style.position = 'fixed';
        flashDiv.style.top = '0';
        flashDiv.style.left = '0';
        flashDiv.style.width = '100vw';
        flashDiv.style.height = '100vh';
        flashDiv.style.backgroundColor = 'red'; // Red background
        flashDiv.style.pointerEvents = 'none';  // Prevent interaction blocking
        flashDiv.style.zIndex = '9999';         // High zIndex to be visible
        flashDiv.style.transition = 'opacity 0.5s ease-in-out'; // Smooth transition for fading
        flashDiv.style.opacity = '0'; // Initially transparent
        document.body.appendChild(flashDiv);
    }

    // Set the flash opacity to the user-defined level and fade in
    flashDiv.style.opacity = opacity.toString(); // Fade in to intensity level
    console.log(`Flashing screen with opacity: ${opacity}`);

    // Wait for 0.5 seconds at full opacity before fading out
    setTimeout(() => {
        flashDiv.style.opacity = '0'; // Fade out to transparent
    }, 1000);

    // The flashDiv remains in the DOM to be reused for subsequent flashes
}



    // Function to start the alarm sound
    function startAlarm() {
        if (!audioElement) {
            audioElement = document.createElement('audio');
            audioElement.src = audioSrc;
            audioElement.loop = false;
            audioElement.volume = getVolumeFromLevel(volumeLevel); // Set initial volume
            // audioElement.crossOrigin = "anonymous"; // Removed to fix playback issues
            document.body.appendChild(audioElement);
            console.log(`Audio element created with source: ${audioSrc}`);
        }
        audioElement.play().then(() => {
            console.log('Alarm sound is playing.');
        }).catch(error => {
            console.error('Error playing audio:', error);
        });
        alarmInterval = setInterval(() => {
            if (audioElement.paused) { // Only play if not already playing
                audioElement.currentTime = 0;
                audioElement.play().then(() => {
                    console.log('Alarm sound replayed.');
                }).catch(error => {
                    console.error('Error replaying audio:', error);
                });
            }
        }, 500); // Play alarm sound every 0.5 seconds
    }

    // Function to stop the alarm sound
    function stopAlarm() {
        clearInterval(alarmInterval);
        if (audioElement) {
            audioElement.pause();
            audioElement.currentTime = 0;
            console.log('Alarm sound stopped.');
        }
    }

    // Helper function to get volume level from volume label
    function getVolumeFromLevel(level) {
        const volumeMap = {
            'Silent': 0,
            'Soft': 0.2,
            'Medium': 0.6,
            'Loud': 1.0
        };
        return volumeMap[level] !== undefined ? volumeMap[level] : 0.6; // Default to Medium if undefined
    }

    // Initialize the dropdowns and start the timer check interval
    createDropdownsContainer();
    setInterval(checkChainTimer, 1800);
})();

QingJ © 2025

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