A userscript that improves YouTube's notification system by:
- Eliminating the annoying
Important
category by moving all notifications from it intoMore notifications
- Sorting all notifications by recency (newest first)
- Reducing the need to check multiple notification categories
// ==UserScript==
// @name Better YouTube Notifications Organizer
// @namespace https://rschu.me/
// @homepage https://rschu.me/
// @version 1.0.0
// @encoding utf-8
// @description Combines and sorts YouTube notifications by recency
// @author Robin Schulz
// @match *://*.youtube.com/*
// @compatible chrome
// @compatible firefox
// @compatible opera
// @compatible safari
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
const observeElement = (callback) => {
const observer = new MutationObserver((mutationsList, observer) => {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
callback();
}
}
});
if (document.body) {
observer.observe(document.body, {
childList: true,
subtree: true,
});
} else {
new MutationObserver((mutations, obs) => {
if (document.body) {
obs.disconnect();
observer.observe(document.body, {
childList: true,
subtree: true,
});
}
}).observe(document.documentElement, {
childList: true
});
}
return observer;
};
const convertTimeToMinutes = (timeString) => {
if (!timeString) {
return Infinity;
}
timeString = timeString.trim().toLowerCase();
if (timeString === 'just now') {
return 0;
}
const match = timeString.match(/(\d+)\s+(minute|hour|day|week|month|year)s?\s+ago/);
if (!match) {
return Infinity;
}
const value = parseInt(match[1], 10);
const unit = match[2];
switch (unit) {
case 'minute': return value;
case 'hour': return value * 60;
case 'day': return value * 24 * 60;
case 'week': return value * 7 * 24 * 60;
case 'month': return value * 30 * 24 * 60;
case 'year': return value * 365 * 24 * 60;
default: return Infinity;
}
};
const moveImportantNotifications = () => {
const notificationsPanel = document.querySelector('ytd-multi-page-menu-renderer[menu-style="multi-page-menu-style-type-notifications"]');
if (!notificationsPanel) {
return;
}
const sections = notificationsPanel.querySelectorAll('yt-multi-page-menu-section-renderer');
if (sections.length < 2) {
return;
}
let importantSection = null;
let moreNotificationsSection = null;
for (const section of sections) {
const title = section.querySelector('#section-title yt-formatted-string');
if (!title) {
continue;
}
const titleText = title.textContent.trim();
if (titleText === 'Important') {
importantSection = section;
} else if (titleText === 'More notifications') {
moreNotificationsSection = section;
}
}
if (importantSection && moreNotificationsSection) {
const importantNotifications = Array.from(importantSection.querySelectorAll('ytd-notification-renderer'));
if (importantNotifications.length === 0) {
return;
}
const targetContainer = moreNotificationsSection.querySelector('#items');
if (!targetContainer) {
return;
}
importantNotifications.forEach(notification => {
targetContainer.insertBefore(notification, targetContainer.firstChild);
});
importantSection.style.display = 'none';
const allNotifications = Array.from(targetContainer.querySelectorAll('ytd-notification-renderer'));
const sortedNotifications = [...allNotifications];
sortedNotifications.sort((a, b) => {
const timeA = a.querySelector('.metadata yt-formatted-string:last-child');
const timeB = b.querySelector('.metadata yt-formatted-string:last-child');
const minutesA = convertTimeToMinutes(timeA ? timeA.textContent : '');
const minutesB = convertTimeToMinutes(timeB ? timeB.textContent : '');
return minutesA - minutesB;
});
for (let i = 0; i < sortedNotifications.length; i++) {
const element = sortedNotifications[i];
targetContainer.insertBefore(element, targetContainer.children[i]);
}
}
};
observeElement(() => {
const notificationsPanel = document.querySelector('ytd-multi-page-menu-renderer[menu-style="multi-page-menu-style-type-notifications"]');
if (notificationsPanel && notificationsPanel.offsetParent !== null) {
setTimeout(moveImportantNotifications, 200);
}
});
document.addEventListener('click', function(e) {
if (e.target.closest('yt-icon-button#button.style-scope.ytd-notification-topbar-button-renderer') ||
e.target.closest('button.style-scope.ytd-notification-topbar-button-renderer')) {
setTimeout(moveImportantNotifications, 300);
}
}, true);
setTimeout(moveImportantNotifications, 500);
})();