Youtube Music auto dismiss liked song notification

Automatically dismiss the liked song notification after some seconds

// ==UserScript==
// @name         Youtube Music auto dismiss liked song notification
// @license      MIT
// @namespace    https://github.com/thecodeisLOVE
// @homepageURL  https://github.com/thecodeisLOVE/ytmusic-auto-dismiss
// @version      0.3
// @description  Automatically dismiss the liked song notification after some seconds
// @author       thecodeisLOVE
// @icon         https://music.youtube.com/favicon.ico
// @match        https://music.youtube.com/*
// @run-at       document-end
// @grant        none
// ==/UserScript==

const DISMISS_DELAY_LIKED = 1000;
const DISMISS_DELAY_LIBRARY = 1000;
const DISMISS_DELAY_PLAYLIST = 5000;
const DISMISS_DELAY_GENERAL = 5000;
const DESTROY_DELAY = 2000;
const DESTROY_DELAY_TEXT = 7000;

const ENABLE_DEBUG_LOGGING = true;

function timeStamp() {
    const now = new Date();
    const pad = (num, size) => String(num).padStart(size, "0");
    
    const hours = pad(now.getHours(), 2);
    const minutes = pad(now.getMinutes(), 2);
    const seconds = pad(now.getSeconds(), 2);
    const milliseconds = pad(now.getMilliseconds(), 3);
    
    return `${hours}:${minutes}:${seconds}.${milliseconds}`;
}

function contextPrint(message) {
    const messageTime = () => `[${message}] ${timeStamp()} -`
    
    const log = function(...args) {console.log(messageTime(), ...args)};
    const warn = function(...args) {console.warn(messageTime(), ...args)};
    const error = function(...args) {console.error(messageTime(), ...args)};
    const info = function(...args) {console.info(messageTime(), ...args)};
    
    var debug = function(...args) {console.debug(messageTime(), ...args)};
    if (!ENABLE_DEBUG_LOGGING) {
        debug = function() {};
    }
    
    return {log, warn, error, info, debug};
}

const {log, warn, error, info, debug} = contextPrint("YT Music auto dismiss");

function observeChildren(element, config, callback) {
    const observer = new MutationObserver((mutationsList) => {
        for (const mutation of mutationsList) {
            if (mutation.type === "childList") {
                callback(Array.from(mutation.addedNodes));
            }
        }
    });
    if (!config) {config = { childList: true, subtree: false };}
    observer.observe(element, config);
    return observer;
}

function observeChildrenWithFilter(element, config, filter, callback) {
    return observeChildren(element, config, addedNodes => 
        callback(addedNodes.filter(filter)
    ));
}

function observeChildrenWithTags(element, config, filterTags, callback) {
    return observeChildrenWithFilter(element, config, node => 
        node.nodeType === Node.ELEMENT_NODE && 
        filterTags.includes(node.tagName.toLowerCase()),
        callback
    );
}

function observeAndHandle(element, tags, fun) {
    observeChildrenWithTags(element, null, tags, nodes => nodes.forEach(node => fun(node)))
}

function dismissNotification(notification) {
    const dismissButton = notification.querySelector("#button");
    
    if (!dismissButton) {
        warn("Dismiss button not found");
        return;
    }
    
    debug("Dismissing notification.");
    
    const activeElement = document.activeElement;
    debug("Element in focus", activeElement);
    
    dismissButton.click();
    
    if (activeElement && activeElement.focus) {
        debug("Restoring focus to element", activeElement);
        activeElement.focus();
    }
}

function destroyNotification(notification) {
    if (!notification.parentNode) {
        warn("Notification element already removed");
        return;
    }
    
    debug("Destroying notification element.");
    notification.parentNode.removeChild(notification);
}

function getDismissDelay(notification) {
    const textElement = notification.querySelector("#text");
    
    if (!textElement) {
        warn("Could not find text element.");
        return;
    }
    
    const textContent = textElement.textContent.toLowerCase()
    
    if (textContent === "saved to liked music") {
        debug("Saved to liked music notification");
        return DISMISS_DELAY_LIKED;
    } else if (textContent == "added to library") {
        debug("Added to library notification");
        return DISMISS_DELAY_LIBRARY;
    } else if (textContent == "removed from library") {
        debug("Removed from library notification");
        return DISMISS_DELAY_LIBRARY;
    } else if (textContent.startsWith("saved to")) {
        debug("Added to playlist notification");
        return DISMISS_DELAY_PLAYLIST;
    } else if (textContent === "this track is already in the playlist") {
        debug("Already in playlist notification");
        return DISMISS_DELAY_PLAYLIST;
    } else {
        debug(`Text content (not yet matched): "${textContent}"`);
        return DISMISS_DELAY_GENERAL;
    }
}

function handleActionNotification(notification) {
    debug("Action notification detected.");
    
    setTimeout(() => {
        dismissNotification(notification);
        setTimeout(() => destroyNotification(notification), DESTROY_DELAY, notification);
    }, getDismissDelay(notification));
}

function handleTextNotification(notification) {
    debug("Text notification detected.");
    
    setTimeout(() => destroyNotification(notification), DESTROY_DELAY_TEXT);
}



(function() {
    "use strict";
    
    debug("Auto dismiss is active.");
    
    const popupContainer = document.getElementsByTagName("ytmusic-popup-container").item(0);
    
    if (!popupContainer) {
        warn("Could not find popup container.");
        return;
    }
    
    const actionNotificationTags = ["ytmusic-notification-action-renderer"];
    observeAndHandle(popupContainer, actionNotificationTags, handleActionNotification);
    
    const textNotificationTags = ["ytmusic-notification-text-renderer"];
    observeAndHandle(popupContainer, textNotificationTags, handleTextNotification);
    
})();