您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Sorts all notifications chronologically and hides section titles & borders.
// ==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(); })();