YouTube Remove Important Notifications

Sorts all notifications chronologically and hides section titles & borders.

As of 19.10.2025. See ბოლო ვერსია.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name            YouTube Remove Important Notifications
// @namespace       https://github.com/crazyrabbit0
// @version         1.0
// @description     Sorts all notifications chronologically and hides section titles & borders.
// @author          CrazyRabbit
// @match           http://*.youtube.com/*
// @match           https://*.youtube.com/*
// @icon            https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @grant           none
// @license         GPL-3.0-or-later; http://www.gnu.org/licenses/gpl-3.0.txt
// @copyright       2023+, CrazyRabbit (https://github.com/crazyrabbit0)
// @homepageURL     https://github.com/crazyrabbit0/UserScripts
// @supportURL      https://github.com/crazyrabbit0/UserScripts/issues/new?assignees=crazyrabbit0&labels=help+wanted&template=&title=YouTube%20Remove%20Important%20Notifications%20-%20Issue
// @run-at          document-start
// @noframes
// ==/UserScript==

(function () {
    'use strict';

    // YouTube DOM structure selectors
    const NOTIFICATION_SELECTORS = {
        button: 'ytd-notification-topbar-button-renderer',
        panel: 'ytd-multi-page-menu-renderer #sections',
        section: 'yt-multi-page-menu-section-renderer',
        title: '#section-title',
        items: '#items',
        item: 'ytd-notification-renderer',
        time: 'div.metadata yt-formatted-string:last-of-type'
    };

    // Constants for timing
    const BUTTON_POLL_INTERVAL = 200;    // ms - interval for checking if the button is available
    const OBSERVER_RETRY_DELAY = 200;    // ms - delay before retrying to setup observer
    const MERGE_DEBOUNCE_DELAY = 200;    // ms - delay for debouncing merge operations

    // Global variables to manage the MutationObserver and debounce timer
    let notificationObserver = null;
    let debounceTimer = null;

    /**
     * Parses YouTube's time element (e.g., "5 hours ago", "2 days ago") into minutes for chronological sorting
     * Handles common time units: seconds, minutes, hours, days, weeks, months, years
     * Returns Infinity for invalid time strings to ensure they appear at the end of sorted results
     * @param {string} timeElement - The time element to parse (e.g., "5 hours ago")
     * @returns {number} Notification time in minutes or Infinity if parsing fails
     */
    const parseTimeToMinutes = (timeElement) => {
        if (!timeElement) return Infinity;

        // Clean and split the string into parts
        const string = timeElement?.textContent.toLowerCase();
        const parts = string.split(' ');

        // Parse the numeric value
        const value = parseInt(parts[0], 10);
        if (isNaN(value)) return Infinity;

        // Normalize the unit by removing 's' if it's plural
        const unit = parts[1].endsWith('s') ? parts[1].slice(0, -1) : parts[1];

        // Lookup map for converting various time units to minutes
        const timeMultipliers = new Map([
            ['second', 1 / 60],    // Convert seconds to fractional minutes
            ['minute', 1],       // Minutes stay as-is
            ['hour', 60],        // Hours to minutes
            ['day', 1440],       // Days to minutes (24 * 60)
            ['week', 10080],     // Weeks to minutes (7 * 24 * 60)
            ['month', 43200],    // Months to minutes (30 * 24 * 60)
            ['year', 525600]     // Years to minutes (365 * 24 * 60)
        ]);

        // Direct lookup for the normalized unit
        const multiplier = timeMultipliers.get(unit);
        return multiplier !== undefined ? value * multiplier : Infinity;
    };

    /**
     * Sorts all notifications chronologically across the 'Important' and 'Main' sections
     * Also hides the section titles.
     * @param {HTMLElement} panel - The container element holding notification sections
     */
    const sortNotifications = (panel) => {
        notificationObserver.disconnect(); // Disconnect to prevent recursive triggers

        const sections = panel.querySelectorAll(NOTIFICATION_SELECTORS.section);
        if (sections.length < 2) return; // Need both 'Important' and 'Main' sections to proceed

        // Collect all notifications and their parsed times, while hiding section titles and borders
        const notificationsToSort = [];
        sections.forEach(section => {
            // Hide section titles and border
            section.querySelector(NOTIFICATION_SELECTORS.title).style.display = 'none';
            section.style.border = 'none';

            const items = section.querySelector(NOTIFICATION_SELECTORS.items);
            const notifications = items.querySelectorAll(NOTIFICATION_SELECTORS.item);
            if (!notifications.length) return; // No notifications found

            // Gather all notifications
            for (const notification of notifications) {
                const timeElement = notification.querySelector(NOTIFICATION_SELECTORS.time);

                // Create an object with the DOM element and its parsed time
                notificationsToSort.push({
                    element: notification,
                    time: parseTimeToMinutes(timeElement)
                });
            }
        });

        // Sort notifications by time (newest first)
        notificationsToSort.sort((a, b) => a.time - b.time);

        // Extract only the DOM elements from the sorted notifications
        const sortedNotifications = notificationsToSort.map(notification => notification.element);

        // Add notifications back into sections
        sections.forEach((section, index) => {
            const items = section.querySelector(NOTIFICATION_SELECTORS.items);

            // For the 'Important' section (index 0), add only the first two notifications
            // For the 'Main' section (index 1), add the remaining notifications
            const notificationsToAdd = (index === 0) ? sortedNotifications.slice(0, 2) : sortedNotifications.slice(2);
            items.prepend(...notificationsToAdd);
        });
    };

    /**
     * Creates and attaches a MutationObserver to monitor changes in the notification container
     * Uses debouncing to prevent excessive merge operations when multiple notifications are added rapidly
     * The observer watches for childList changes (new notifications being added) and triggers merge with a delay
     */
    const setupNotificationObserver = () => {
        const panel = document.querySelector(NOTIFICATION_SELECTORS.panel);
        // Retry after configured delay, if the notification panel isn't rendered yet
        if (!panel) {
            setTimeout(setupNotificationObserver, OBSERVER_RETRY_DELAY);
            return;
        }

        // Check if any mutation added nodes, then trigger merge with debounce
        notificationObserver = new MutationObserver((mutations) => {
            if (!mutations.some(mutation => mutation.addedNodes.length > 0)) return; // No relevant changes

            // Debounce ensures merge only runs once after a batch of changes complete
            clearTimeout(debounceTimer);
            debounceTimer = setTimeout(() => sortNotifications(panel), MERGE_DEBOUNCE_DELAY);
        });

        // Observe section childList changes (element additions/removals)
        notificationObserver.observe(panel, {
            childList: true,
            subtree: false
        });
    };

    /**
     * Polls the DOM to find the notification button and attaches a click listener.
     */
    const initialize = () => {
        const buttonInterval = setInterval(() => {
            const button = document.querySelector(NOTIFICATION_SELECTORS.button);
            if (!button) return; // No button found

            clearInterval(buttonInterval);

            // Attach the click listener to initialize the notification observer
            button.addEventListener('click', () => {
                if (notificationObserver) notificationObserver.disconnect(); // Disconnect any existing observer
                setupNotificationObserver();
            });
        }, BUTTON_POLL_INTERVAL);
    };

    // Start polling for the notification button
    initialize();

})();