YouTube: Hide "Members Only" Videos

Hides all YouTube videos with the "Members only" badge.

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name         YouTube: Hide "Members Only" Videos
// @namespace    https://github.com/krrrrrrk/youtube-hide-members-only
// @version      1.0
// @description  Hides all YouTube videos with the "Members only" badge.
// @author       krrrrrrk
// @match        https://www.youtube.com/*
// @run-at       document-idle
// @grant        none
// @license      MIT
// @homepageURL  https://github.com/Krrrrrrk/YouTube-Hide-Members-only-videos
// ==/UserScript==

(function () {
  "use strict";

  // --- Config --------------------------------------------------------------
  // Badge text to match (case-insensitive). Add translations if you need them.
  const BADGE_TEXTS = [
    "Members only",
    // "Miembros solamente", "Nur für Mitglieder", etc.
  ].map(s => s.toLowerCase());

  // Which container tags we consider a "video tile"
  const VIDEO_TILE_SELECTORS = [
    // Classic & search
    "ytd-video-renderer",
    "ytd-grid-video-renderer",
    "ytd-compact-video-renderer",
    "ytd-playlist-video-renderer",
    "ytd-rich-item-renderer",
    "ytd-reel-item-renderer",         // Shorts in some contexts
    "ytd-radio-renderer",
    // New(er) lockup model you pasted
    ".yt-lockup-view-model",
  ].join(",");

  // A few known badge containers/leafs that may contain the text
  const BADGE_SELECTORS = [
    ".yt-badge-shape__text",
    ".yt-content-metadata-view-model__badge",
    "yt-badge-view-model",
    "badge-shape",
    // Fallback: anything in content metadata blocks
    ".yt-content-metadata-view-model",
  ].join(",");

  // --- Helpers -------------------------------------------------------------
  const lc = s => (s || "").toLowerCase();

  function isMembersOnlyBadge(el) {
    const txt = lc(el.textContent || "");
    return BADGE_TEXTS.some(t => txt.includes(t));
  }

  function findVideoTile(start) {
    if (!start) return null;
    // climb up to a recognized tile container
    return start.closest(VIDEO_TILE_SELECTORS);
  }

  function hideTile(tile) {
    if (!tile || tile.dataset._tmMembersHidden === "1") return;
    tile.style.display = "none";
    tile.dataset._tmMembersHidden = "1";
  }

  function processNode(root) {
    if (!root || root.nodeType !== 1) return;

    // Fast path: if the node itself is a badge, check it
    if (root.matches && root.matches(BADGE_SELECTORS) && isMembersOnlyBadge(root)) {
      const tile = findVideoTile(root);
      if (tile) hideTile(tile);
      return;
    }

    // Otherwise, look inside for any badges
    const badges = root.querySelectorAll(BADGE_SELECTORS);
    for (const b of badges) {
      if (isMembersOnlyBadge(b)) {
        const tile = findVideoTile(b);
        if (tile) hideTile(tile);
      }
    }
  }

  // Debounced rescans help when Polymer reflows a lot at once.
  let pending = false;
  function rescanSoon() {
    if (pending) return;
    pending = true;
    requestAnimationFrame(() => {
      pending = false;
      processNode(document.body);
    });
  }

  // Initial pass
  processNode(document.body);

  // Watch for dynamic additions
  const mo = new MutationObserver(mutations => {
    for (const m of mutations) {
      // Process added nodes (tiles/badges often arrive as fragments)
      m.addedNodes && m.addedNodes.forEach(node => processNode(node));
      // If text changed inside a badge, rescan
      if (m.type === "characterData") rescanSoon();
    }
  });

  mo.observe(document.documentElement || document, {
    childList: true,
    subtree: true,
    characterData: true,
  });

  // Also rescan on route changes (YouTube SPA navigation)
  const pushState = history.pushState;
  const replaceState = history.replaceState;
  function onNav() { setTimeout(() => processNode(document.body), 300); }
  history.pushState = function () { pushState.apply(this, arguments); onNav(); };
  history.replaceState = function () { replaceState.apply(this, arguments); onNav(); };
  window.addEventListener("yt-navigate-finish", onNav);
  window.addEventListener("popstate", onNav);
})();