Members-Only Remover

Filters Members-only entries out of YouTube API responses, and removes the members-only shelf.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Members-Only Remover 
// @namespace    https://example.com/memonly
// @version      1.0
// @description  Filters Members-only entries out of YouTube API responses, and removes the members-only shelf.
// @match        https://www.youtube.com/*
// @match        https://youtube.com/*
// @grant        none
// @author       Mr005k 
// @license      MIT
// @run-at       document-start
// ==/UserScript==

(() => {
  'use strict';

  // ---------- Detection ----------
  const MEM_RE = /\bmembers\s*[- ]?\s*only\b/i;

  function extractText(obj) {
    if (!obj) return '';
    if (typeof obj === 'string') return obj;
    if (obj.simpleText) return String(obj.simpleText);
    if (Array.isArray(obj.runs)) return obj.runs.map(r => r && r.text || '').join('');
    if (obj.text) return extractText(obj.text);
    if (obj.label) return String(obj.label);
    return '';
  }

  function nodeLooksMembersOnly(o) {
    if (!o || typeof o !== 'object') return false;
    // Direct style flags used by YT JSON
    if (typeof o.style === 'string' && o.style.includes('MEMBERS_ONLY')) return true;
    if (typeof o.badgeStyle === 'string' && o.badgeStyle.includes('MEMBERS_ONLY')) return true;

    // Textual labels
    if (MEM_RE.test(extractText(o))) return true;

    return false;
  }

  function deepHasMembersOnly(o, depth = 0) {
    if (depth > 6 || !o) return false;
    if (nodeLooksMembersOnly(o)) return true;

    if (Array.isArray(o)) {
      for (const it of o) if (deepHasMembersOnly(it, depth + 1)) return true;
      return false;
    }
    if (typeof o === 'object') {
      for (const k in o) {
        // Skip huge binary-ish fields
        if (k === 'playerResponse' || k === 'responseContext') continue;
        if (deepHasMembersOnly(o[k], depth + 1)) return true;
      }
    }
    return false;
  }

  // Remove any array item whose subtree advertises "Members only"
  function scrubJSON(x, depth = 0) {
    if (depth > 8 || x == null) return x;
    if (Array.isArray(x)) {
      const out = [];
      for (const it of x) {
        if (deepHasMembersOnly(it)) continue;
        out.push(scrubJSON(it, depth + 1));
      }
      return out;
    }
    if (typeof x === 'object') {
      for (const k in x) x[k] = scrubJSON(x[k], depth + 1);
    }
    return x;
  }

  // ---------- Network interception (fetch + XHR) ----------
  const shouldFilterURL = url =>
    typeof url === 'string' &&
    /\/youtubei\/v1\/(browse|search|next|reel|guide)/.test(url);

  // fetch
  const _fetch = window.fetch;
  window.fetch = async function(input, init) {
    const res = await _fetch(input, init);
    try {
      const url = (typeof input === 'string' ? input : input.url) || res.url || '';
      if (!shouldFilterURL(url)) return res;

      const clone = res.clone();
      const data = await clone.json();
      const scrubbed = scrubJSON(data);
      // If nothing changed, pass original response
      if (JSON.stringify(data) === JSON.stringify(scrubbed)) return res;

      const body = JSON.stringify(scrubbed);
      const headers = new Headers(res.headers);
      headers.set('content-type', 'application/json; charset=UTF-8');
      return new Response(body, { status: res.status, statusText: res.statusText, headers });
    } catch (_) {
      return res; // fail open
    }
  };

  // XHR (some pages still use it)
  const _open = XMLHttpRequest.prototype.open;
  const _send = XMLHttpRequest.prototype.send;
  XMLHttpRequest.prototype.open = function(method, url, async, user, pass) {
    this.__yt_url = url;
    return _open.apply(this, arguments);
  };
  XMLHttpRequest.prototype.send = function() {
    this.addEventListener('readystatechange', function() {
      if (this.readyState !== 4) return;
      try {
        if (!shouldFilterURL(this.__yt_url)) return;
        const text = this.responseText;
        const json = JSON.parse(text);
        const scrubbed = scrubJSON(json);
        const newText = JSON.stringify(scrubbed);
        if (newText !== text) {
          Object.defineProperty(this, 'responseText', { value: newText });
          Object.defineProperty(this, 'response', { value: newText });
        }
      } catch (_) {}
    });
    return _send.apply(this, arguments);
  };

  // ---------- DOM fallback (strict) ----------
  const ITEM_SEL = [
    'ytd-rich-item-renderer',
    'yt-lockup-view-model',
    'ytd-video-renderer',
    'ytd-compact-video-renderer',
    'ytd-grid-video-renderer',
    'ytd-playlist-video-renderer',
    'ytd-playlist-panel-video-renderer',
    'ytd-radio-renderer',
    'ytd-reel-item-renderer',
    'ytd-reel-video-renderer'
  ].join(',');

  const POLYMER_BADGE = '.badge.badge-style-type-members-only';
  const VM_BADGE_TEXT = '.yt-badge-shape--commerce .yt-badge-shape__text';

  function isStrictBadge(el) {
    if (!(el instanceof Element)) return false;
    if (el.matches(POLYMER_BADGE)) return true;
    if (el.matches(VM_BADGE_TEXT) && MEM_RE.test(el.textContent || '')) return true;
    return false;
  }

  function dropTileFromBadge(badge) {
    const item = badge.closest(ITEM_SEL);
    if (item) item.remove();
  }

  function pruneMembersShelf(root = document) {
    // Remove the channel home shelf titled "Members-only videos"
    document.querySelectorAll('ytd-shelf-renderer').forEach(shelf => {
      const title = (shelf.querySelector('#title')?.textContent || '').trim();
      const subtitle = (shelf.querySelector('#subtitle')?.textContent || '').trim();
      if (MEM_RE.test(title) || /videos available to members/i.test(subtitle)) {
        shelf.remove();
      }
    });
  }

  function scanDOM(root = document) {
    root.querySelectorAll(POLYMER_BADGE).forEach(dropTileFromBadge);
    root.querySelectorAll(VM_BADGE_TEXT).forEach(n => {
      if (MEM_RE.test(n.textContent || '')) dropTileFromBadge(n);
    });
    pruneMembersShelf(root);
  }

  function observeDOM() {
    const mo = new MutationObserver(muts => {
      for (const m of muts) {
        if (m.type === 'childList') {
          for (const n of m.addedNodes) {
            if (!(n instanceof Element)) continue;
            if (isStrictBadge(n)) dropTileFromBadge(n);
            else scanDOM(n);
          }
        }
      }
    });
    mo.observe(document.documentElement, { childList: true, subtree: true });
    const rescan = () => setTimeout(() => { scanDOM(document); }, 50);
    window.addEventListener('yt-navigate-finish', rescan);
    window.addEventListener('yt-page-data-updated', rescan);
  }

  // Boot
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', () => { scanDOM(); observeDOM(); });
  } else {
    scanDOM(); observeDOM();
  }
})();