YouTube - Remove Annoyances

Remove annoying elements on YouTube pages

// ==UserScript==
// @name         YouTube - Remove Annoyances
// @namespace    https://github.com/x3ric
// @version      1.76
// @description  Remove annoying elements on YouTube pages
// @author       x3ric
// @license      MIT
// @match        https://youtube.com/*
// @match        https://www.youtube.com/*
// @match        https://music.youtube.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_notification
// @grant        unsafeWindow
// ==/UserScript==

(function () {
    'use strict';

    // Retrieve or initialize configuration
    const config = GM_getValue('ytCleanerConfig', {
        removeAds: false,
        skipContinueWatching: true,
        bypassAgeRestriction: true,
    });

    const saveConfig = () => {
        GM_setValue('ytCleanerConfig', config);
    };

    // Toggle feature in config
    const toggleFeature = (feature) => {
        config[feature] = !config[feature];
        saveConfig();
        GM_notification({
            text: `${feature.replace(/([A-Z])/g, ' $1')} is now ${config[feature] ? 'enabled' : 'disabled'}`,
            title: 'YouTube Cleaner',
            timeout: 2000
        });
        updateMenu();  // Refresh menu to reflect the new state
    };

    // Selectors for elements to be removed or interacted with
    const SELECTORS = {
        ads: '#masthead-ad, .ytp-ad-module, ytd-banner-promo-renderer, ytd-search-pyv-renderer',
        popups: 'ytd-popup-container, tp-yt-paper-dialog, #dialog, #dialog-container, .ytp-ad-overlay-slot',
        recommendations: '#related, #secondary, #comments',
        endScreen: '.ytp-ce-element',
        continueWatching: 'yt-formatted-string.style-scope.ytd-button-renderer',
        notificationToast: 'yt-notification-action-renderer',
        ageRestriction: '.ytp-error, .yt-playability-error-supported-renderers',
    };

    // Function to remove ads, popups, recommendations, and notification toasts
    const cleanYouTube = () => {
        if (config.removeAds) {
            requestAnimationFrame(() => {
                document.querySelectorAll(SELECTORS.ads).forEach(el => el.remove());
                document.querySelectorAll(SELECTORS.popups).forEach(el => el.remove());
                document.querySelectorAll(SELECTORS.recommendations).forEach(el => el.remove());
                document.querySelectorAll(SELECTORS.notificationToast).forEach(el => el.remove());
                document.querySelectorAll(SELECTORS.endScreen).forEach(el => el.style.display = 'none');
            });
        }
    };

    // Skip "Are you still watching?" interruptions
    const skipPlaybackInterruptions = () => {
        if (config.skipContinueWatching) {
            setInterval(() => {
                const continueBtn = document.querySelector(SELECTORS.continueWatching);
                if (continueBtn) continueBtn.click();
            }, 5000);
        }
    };

    // Bypass age restriction on restricted content
    const bypassAgeRestriction = () => {
        if (config.bypassAgeRestriction) {
            const currentVideoId = new URLSearchParams(window.location.search).get('v');
            if (!currentVideoId) return;

            fetch(`https://www.youtube.com/get_video_info?video_id=${currentVideoId}`)
                .then(res => res.text())
                .then(data => {
                    const params = new URLSearchParams(data);
                    if (params.get('playabilityStatus') === 'AGE_VERIFICATION_REQUIRED') {
                        const restrictionElement = document.querySelector(SELECTORS.ageRestriction);
                        if (restrictionElement) restrictionElement.remove();
                    }
                });
        }
    };

    // Observe DOM for changes and clean dynamically loaded content
    const observeDOM = () => {
        if (config.removeAds) {
            const observer = new MutationObserver(() => cleanYouTube());
            observer.observe(document.body, { childList: true, subtree: true });
        }
    };

    // Initialize the core features
    const init = () => {
        cleanYouTube();              // Clean up on initial load
        skipPlaybackInterruptions(); // Start monitoring for "Are you still watching?" interruptions
        bypassAgeRestriction();      // Bypass age restrictions on videos
        observeDOM();                // Observe for new dynamic content
    };

    document.addEventListener('DOMContentLoaded', init);

    const menuCommands = {};
    const updateMenu = () => {
        // Unregister old commands before updating
        Object.keys(menuCommands).forEach(key => GM_unregisterMenuCommand(menuCommands[key]));

        // Register new menu commands reflecting the current config state
        menuCommands.removeAds = GM_registerMenuCommand(`Toggle Ad Removal (${config.removeAds ? 'ON' : 'OFF'})`, () => toggleFeature('removeAds'));
        menuCommands.skipContinueWatching = GM_registerMenuCommand(`Toggle Skip Continue Watching (${config.skipContinueWatching ? 'ON' : 'OFF'})`, () => toggleFeature('skipContinueWatching'));
        menuCommands.bypassAgeRestriction = GM_registerMenuCommand(`Toggle Age Restriction Bypass (${config.bypassAgeRestriction ? 'ON' : 'OFF'})`, () => toggleFeature('bypassAgeRestriction'));
    };

    if (typeof GM_registerMenuCommand !== 'undefined') {
        updateMenu();  // Initialize the menu on load
    }

})();