Greasy Fork is available in English.

Hide watched videos on YouTube

Adds an ability to hide thumbnails of videos you've already watched.

// ==UserScript==
// @name         Hide watched videos on YouTube
// @version      1.3
// @description  Adds an ability to hide thumbnails of videos you've already watched.
// @author       yojc
// @match        https://www.youtube.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @grant        none
// @license MIT
// @namespace https://greasyfork.com
// ==/UserScript==

(function() {

    const debugMode = false;
    const debugModeInterval = false;
    const debugModeErrorOnly = false;

    let lastUrl = "";
    let lastCheck = 0;
    let debugFiredTimes = 0;

    const checkInterval = 100;
    const bodyClassName = "hide-watched-videos";
    const buttonId = "toggleHideButton";
    const buttonClass = "toggle-hide-button";
    const buttonHideText = "Hide watched";
    const buttonShowText = "Show watched";
    const thumbProcessedClass = "thumb-processed";
    const thumbWatchedClass = "thumb-watched";
    const thumbTags = ["ytd-grid-video-renderer", "ytd-rich-item-renderer", "ytd-compact-video-renderer", "ytd-compact-radio-renderer"];
    const thumbWatchedCheckSelectors = ["ytd-thumbnail-overlay-resume-playback-renderer", "ytd-thumbnail-overlay-bottom-panel-renderer", ".ytd-thumbnail-overlay-time-status-renderer[aria-label=Upcoming]"];

    function debugLog(msg) {
        if (debugMode && !debugModeErrorOnly) {
            console.log(msg);
        }
    }
    function debugLogInterval(msg) {
        if (debugMode && debugModeInterval && !debugModeErrorOnly) {
            console.log(msg);
        }
    }
    function debugErr(msg) {
        if (debugMode) {
            console.error(msg);
        }
    }
    function debugErrInterval(msg) {
        if (debugMode && debugModeInterval) {
            console.error(msg);
        }
    }

    const greyedOutThumbnails = `
    .${thumbWatchedClass} #thumbnail > yt-image,
    .${thumbWatchedClass} #details,
    .${thumbWatchedClass} .details,
    .${thumbWatchedClass} ytd-thumbnail-overlay-resume-playback-renderer {
        opacity: 0.5;
        filter: grayscale(0.75);
    }
    `;

    const stylesheet = `
    .${buttonClass} {
        position: fixed;
        bottom: 1rem;
        right: 1rem;
        min-height: var(--paper-item-min-height,48px);
        padding: 0 2rem;
        background-color: #3f3f3f;
        border-radius: 10px;
        border: none;
        cursor: pointer;
        color: rgb(241, 241, 241);
        font-family: "Roboto","Arial",sans-serif;
        font-size: 1.4rem;
        line-height: 2rem;
        font-weight: 500;
        transition: all 0.5s;
        z-index: 999;
    }

    .${buttonClass}:hover {
        background: #272727;
        transition: all 0.5s;
    }
/*
    .${thumbWatchedClass} ytd-thumbnail {
        pointer-events: none;
    }
*/
    ${localStorage.getItem("dontGreyOutWatchedThumbnails") === "true" ? "" : greyedOutThumbnails}

    .${bodyClassName} .${thumbWatchedClass} {
        display: none !important;
    }

    @media all and (display-mode: fullscreen) {
        .${buttonClass} {
            display: none;
        }
    }
    `;

/*
    .${thumbWatchedClass} #thumbnail::before {
        content: "Watched";
        position: absolute;
        left: 0;
        right: 0;
        text-align: center;
        font-size: 2rem;
        text-transform: uppercase;
        font-family: monospace;
        background: #000;
        z-index: 3;
        opacity: 0.75;
    }
 */

    function appendStylesheet(stylesheet) {
        debugLog("Appending stylesheet");

        let head = document.getElementsByTagName("head")[0];
        let s = document.createElement("style");
        s.setAttribute("type", "text/css");
        s.appendChild(document.createTextNode(stylesheet));
        head.appendChild(s);
    }

    function appendButton() {
        debugLog("Appending toggle button");

        let actionButton = document.createElement("button");
        actionButton.id = buttonId;
        actionButton.classList.add(buttonClass);
        actionButton.textContent = buttonHideText;
        actionButton.addEventListener("click",() => toggleHide());
        document.body.appendChild(actionButton);
    }

    function toggleHide() {
        debugLog("Toggling hide");

        if (document.body.classList.contains(bodyClassName)) {
            document.getElementById(buttonId).textContent = buttonHideText;
            document.body.classList.remove(bodyClassName);
        }
        else {
            document.getElementById(buttonId).textContent = buttonShowText;
            document.body.classList.add(bodyClassName);
        }
    }

    function checkIfWatched() {
        let thumbSelector;

        if (lastUrl === window.location.href) {
            thumbSelector = thumbTags.join(`, `);
        }
        else {
            thumbSelector = thumbTags.join(`:not(.${thumbWatchedClass}), `) + `:not(.${thumbWatchedClass})`;
        }

        for (const thumb of document.querySelectorAll(thumbSelector)) {
            debugLogInterval("Processing thumb");
            debugLogInterval(thumb);
            //thumb.classList.add(thumbProcessedClass);

            if (thumb.querySelector(thumbWatchedCheckSelectors.join(", "))) {
                debugLogInterval("This video was watched");
                debugLogInterval(thumb);

                for (const selector of thumbWatchedCheckSelectors) {
                   debugLogInterval(selector);
                   debugLogInterval(thumb.querySelector(selector));
                }

                thumb.classList.add(thumbWatchedClass);
            }
            else {
                thumb.classList.remove(thumbWatchedClass);
            }
        }

        lastUrl = window.location.href;
    }

    if (window.top===window.self) {
        appendStylesheet(stylesheet);

        appendButton();

        const observer = new MutationObserver(function(mutations) {
            if (Date.now() - lastCheck > checkInterval) {
                lastCheck = Date.now();
                if (debugMode) {
                    debugFiredTimes++
                    console.log(`Observer fired for the ${debugFiredTimes} time`);
                }
                checkIfWatched();
            }
        });

        observer.observe(document.querySelector("body"), {
            childList: true,
            subtree: true
        });
    }
})();