Hide watched videos on YouTube

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

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

You will need to install an extension such as Tampermonkey to install this script.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

// ==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
        });
    }
})();