YouTube Shorts Auto Closer

Automatically closes YouTube Shorts pages after daily limit

// ==UserScript==
// @name         YouTube Shorts Auto Closer
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Automatically closes YouTube Shorts pages after daily limit
// @author       se7
// @icon         https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @match        https://www.youtube.com/*
// @grant        GM_getValue
// @grant        GM_setValue
// @run-at       document-end
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const DAILY_LIMIT_MINUTES = 40;
    const WARNING_BEFORE_CLOSE_SECONDS = 10;
    let timeTrackingInterval = null;
    let countdownInterval = null;
    let isPageVisible = true;
    let isScriptActive = true;
    let uiElements = {
        timer: null,
        warning: null
    };

    // Storage keys
    const STORAGE_KEYS = {
        dailyTime: 'shortsTimeToday',
        lastDate: 'shortsLastDate'
    };

    // Get today's date in YYYY-MM-DD format
    const getTodayDate = () => new Date().toISOString().split('T')[0];

    // Reset daily time if it's a new day
    const checkAndResetDaily = () => {
        const lastDate = GM_getValue(STORAGE_KEYS.lastDate, '');
        const today = getTodayDate();
        
        if (lastDate !== today) {
            GM_setValue(STORAGE_KEYS.dailyTime, 0);
            GM_setValue(STORAGE_KEYS.lastDate, today);
        }
    };

    // Get remaining daily time in seconds
    const getRemainingDailyTime = () => {
        checkAndResetDaily();
        const timeSpentToday = GM_getValue(STORAGE_KEYS.dailyTime, 0);
        return Math.max(0, DAILY_LIMIT_MINUTES * 60 - timeSpentToday);
    };

    // Main function to run every second
    const tick = () => {
        if (!isPageVisible || !isScriptActive) return;

        const remainingTime = getRemainingDailyTime();

        if (remainingTime <= 0) {
            cleanup();
            window.close();
            return;
        }

        const newTimeSpent = GM_getValue(STORAGE_KEYS.dailyTime, 0) + 1;
        GM_setValue(STORAGE_KEYS.dailyTime, newTimeSpent);

        updateTimerDisplay(remainingTime - 1);

        if (remainingTime <= WARNING_BEFORE_CLOSE_SECONDS && !uiElements.warning) {
            showWarning(remainingTime - 1);
        }
    };

    // Create timer display
    const createTimerDisplay = () => {
        // Remove existing timer if any
        const existingTimer = document.getElementById('shorts-closer-timer');
        if (existingTimer) {
            existingTimer.remove();
        }

        const timer = document.createElement('div');
        timer.id = 'shorts-closer-timer';
        timer.style.cssText = `
            position: fixed;
            bottom: 80px;
            right: 20px;
            background: rgba(0, 0, 0, 0.8);
            color: white;
            padding: 10px 15px;
            border-radius: 20px;
            z-index: 9999;
            font-family: 'YouTube Sans', Arial, sans-serif;
            font-size: 14px;
            display: flex;
            align-items: center;
            gap: 8px;
            backdrop-filter: blur(5px);
            border: 1px solid rgba(255, 255, 255, 0.1);
            transition: opacity 0.3s;
        `;

        // Create SVG element
        const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
        svg.setAttribute("width", "20");
        svg.setAttribute("height", "20");
        svg.setAttribute("viewBox", "0 0 24 24");
        svg.setAttribute("fill", "currentColor");

        const path1 = document.createElementNS("http://www.w3.org/2000/svg", "path");
        path1.setAttribute("d", "M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z");
        
        const path2 = document.createElementNS("http://www.w3.org/2000/svg", "path");
        path2.setAttribute("d", "M12.5 7H11v6l5.25 3.15.75-1.23-4.5-2.67z");

        svg.appendChild(path1);
        svg.appendChild(path2);

        const span = document.createElement('span');
        span.textContent = 'Daily time left: ';
        
        const strong = document.createElement('strong');
        strong.id = 'shorts-closer-time';
        strong.textContent = '00:00';
        
        span.appendChild(strong);
        timer.appendChild(svg);
        timer.appendChild(span);

        document.body.appendChild(timer);
        return timer;
    };

    // Create warning notification element
    const createWarning = () => {
        // Remove existing warning if any
        const existingWarning = document.getElementById('shorts-closer-warning');
        if (existingWarning) {
            existingWarning.remove();
        }

        const warning = document.createElement('div');
        warning.id = 'shorts-closer-warning';
        warning.style.cssText = `
            position: fixed;
            bottom: 140px;
            right: 20px;
            background: rgba(255, 0, 0, 0.9);
            color: white;
            padding: 15px 20px;
            border-radius: 8px;
            z-index: 9999;
            font-family: 'YouTube Sans', Arial, sans-serif;
            font-size: 14px;
            animation: fadeIn 0.3s ease;
            display: flex;
            align-items: center;
            gap: 10px;
            backdrop-filter: blur(5px);
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
        `;

        const warningIcon = document.createElement('span');
        warningIcon.textContent = '⚠️';

        const contentDiv = document.createElement('div');

        const titleDiv = document.createElement('div');
        titleDiv.style.fontWeight = 'bold';
        titleDiv.style.marginBottom = '4px';
        titleDiv.textContent = 'Daily limit almost reached!';

        const countdownDiv = document.createElement('div');
        const countdownText = document.createElement('span');
        countdownText.textContent = 'Closing in: ';
        const countdownSpan = document.createElement('span');
        countdownSpan.id = 'shorts-closer-countdown';
        countdownSpan.textContent = WARNING_BEFORE_CLOSE_SECONDS.toString();
        const secondsText = document.createElement('span');
        secondsText.textContent = ' seconds';

        countdownDiv.appendChild(countdownText);
        countdownDiv.appendChild(countdownSpan);
        countdownDiv.appendChild(secondsText);

        contentDiv.appendChild(titleDiv);
        contentDiv.appendChild(countdownDiv);

        warning.appendChild(warningIcon);
        warning.appendChild(contentDiv);

        document.body.appendChild(warning);
        return warning;
    };

    // Update timer display
    const updateTimerDisplay = (remainingTime) => {
        if (!uiElements.timer) return;
        
        const minutes = Math.floor(remainingTime / 60);
        const seconds = remainingTime % 60;
        const timeString = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
        
        const strongElement = uiElements.timer.querySelector('#shorts-closer-time');
        if (strongElement) {
            strongElement.textContent = timeString;
            
            if (minutes < 5) {
                strongElement.style.color = '#ff4444';
            } else if (minutes < 10) {
                strongElement.style.color = '#ffd700';
            } else {
                strongElement.style.color = '#fff';
            }
        }
    };

    // Function to show warning with countdown
    const showWarning = (initialCountdown) => {
        if (uiElements.warning) return;

        uiElements.warning = createWarning();
        
        let countdown = initialCountdown;
        const countdownElement = uiElements.warning.querySelector('#shorts-closer-countdown');
        if (countdownElement) {
            countdownElement.textContent = countdown.toString();
        }

        countdownInterval = setInterval(() => {
            if (!isPageVisible || !isScriptActive) return;

            countdown--;
            if (countdownElement) {
                countdownElement.textContent = Math.max(0, countdown).toString();
            }
            if (countdown <= 0) {
                clearInterval(countdownInterval);
                countdownInterval = null;
            }
        }, 1000);
    };
    
    const stopTimersAndRemoveUI = () => {
        if (timeTrackingInterval) {
            clearInterval(timeTrackingInterval);
            timeTrackingInterval = null;
        }
        if (countdownInterval) {
            clearInterval(countdownInterval);
            countdownInterval = null;
        }

        if (uiElements.timer && uiElements.timer.parentNode) {
            uiElements.timer.remove();
            uiElements.timer = null;
        }
        if (uiElements.warning && uiElements.warning.parentNode) {
            uiElements.warning.remove();
            uiElements.warning = null;
        }
    };

    const startTimers = () => {
        // Prevent multiple intervals
        if (timeTrackingInterval) return;

        // Initial check
        const remainingTime = getRemainingDailyTime();
        if (remainingTime <= 0) {
            cleanup();
            window.close();
            return;
        }

        // Create UI
        if (!uiElements.timer) {
            uiElements.timer = createTimerDisplay();
        }
        updateTimerDisplay(remainingTime);

        // Start the main tick interval
        timeTrackingInterval = setInterval(tick, 1000);
    };

    // Comprehensive cleanup function
    const cleanup = () => {
        isScriptActive = false;
        stopTimersAndRemoveUI();

        // Remove event listeners
        document.removeEventListener('visibilitychange', handleVisibilityChange);
        document.removeEventListener('yt-navigate-finish', handlePageNavigation);
    };

    // Handle visibility changes
    const handleVisibilityChange = () => {
        if (!isScriptActive) return;
        isPageVisible = document.visibilityState === 'visible';

        if (uiElements.timer) {
            uiElements.timer.style.opacity = isPageVisible ? '1' : '0.5';
        }
    };
    
    const handlePageNavigation = () => {
        if (!isScriptActive) return;

        const isOnShortsPage = window.location.pathname.startsWith('/shorts/');

        if (isOnShortsPage) {
            startTimers();
        } else {
            stopTimersAndRemoveUI();
        }
    };

    // Set up event listeners
    document.addEventListener('visibilitychange', handleVisibilityChange);
    document.addEventListener('yt-navigate-finish', handlePageNavigation);

    // Cleanup on page unload
    window.addEventListener('beforeunload', cleanup);
    window.addEventListener('unload', cleanup);

    // Initial check when the script runs
    handlePageNavigation();

})();