Hide watched videos on YouTube

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

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==UserScript==
// @name         Hide watched videos on YouTube
// @version      1.6
// @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", "yt-lockup-view-model"];
    const thumbWatchedCheckSelectors = ["ytd-thumbnail-overlay-resume-playback-renderer", "ytd-thumbnail-overlay-bottom-panel-renderer", "yt-thumbnail-bottom-overlay-view-model", ".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,
    .${thumbWatchedClass} .yt-thumbnail-view-model,
    .${thumbWatchedClass} yt-thumbnail-view-model,
    .${thumbWatchedClass} yt-lockup-metadata-view-model {
        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 if (thumb.matches(":hover")) {
                debugLogInterval("Not clearing class - mouse on hover");
                debugLogInterval(thumb);
            }
            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
        });
    }
})();