YouTube: Hide "Members Only" Videos

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

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey, Greasemonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

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.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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