Hide Members Only Filter and whitelist for youtube

Auto-hide "Members only" videos from untrusted channels on YouTube with a toggle to re-show/hide them. Optionally leave layout gap with config flag (handles sidebar safely too). Includes fallback logic for non-existent parent elements. Updated to handle change in YouTube sidebar.

// ==UserScript==
// @name         Hide Members Only Filter and whitelist for youtube
// @namespace    http://tampermonkey.net/
// @version      2.7
// @description  Auto-hide "Members only" videos from untrusted channels on YouTube with a toggle to re-show/hide them. Optionally leave layout gap with config flag (handles sidebar safely too). Includes fallback logic for non-existent parent elements. Updated to handle change in YouTube sidebar.
// @author       Aonnymous
// @match        https://www.youtube.com/*
// @grant        GM_registerMenuCommand
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // === Config ===
    const WHITELIST = [
        'channel name 1',
        'Some Channel',
        'Trusted Creator'
    ];


    /**
     * LEAVE_BLANK_SPACE_WHEN_HIDDEN:
     * For channel page video/live tab
     * - true  => hides only video element (layout gap)
     * - false => tries to hide parent container (no layout gap),
     *            but still hides video if parent is unavailable
     */
    const LEAVE_BLANK_SPACE_WHEN_HIDDEN = true;

    const SCAN_INTERVAL_FAST = 2000;              // Every 2s for first minute
    const FAST_SCAN_DURATION = 60000;             // Fast mode for 1 minute
    const MAX_RUNS_PER_MINUTE = 4;                // Slow mode limit
    const DEBUG_BORDER_STYLE = '2px solid red';   // Red border on hidden
    const AUTO_ENABLE_DELAY_MS = 4500;            // Auto-start 4.5s in

    // === State ===
    let scanInterval = null;
    let filteringEnabled = false;
    let scanStartTime = null;
    let lastRunTimestamp = 0;
    let runsThisMinute = 0;

    // === Utilities ===

    const CLEANED_WHITELIST = WHITELIST.map(name => name.trim().toLowerCase());

    function log(...args) {
        console.log('[YT-MembersFilter]', ...args);
    }

    function isWhitelisted(name) {
        const lc = name.trim().toLowerCase();
        return CLEANED_WHITELIST.some(w => lc.indexOf(w) !== -1);
    }

    function getTopLevelVideoElements() {
        const sidebarVideos = Array.from(document.querySelectorAll('ytd-compact-video-renderer'))
            .filter(el => el.closest('ytd-compact-video-renderer') === el);

        const richItems = Array.from(document.querySelectorAll('div#content.style-scope.ytd-rich-item-renderer'));

        // New layout: yt-lockup-view-model elements
        const newLayoutItems = Array.from(document.querySelectorAll('yt-lockup-view-model'));

        return [...sidebarVideos, ...richItems, ...newLayoutItems];
    }

    function getChannelNameFromElement(vid) {
        // Try old layout first
        const oldLayoutChannel = vid.querySelector('.ytd-channel-name');
        if (oldLayoutChannel?.textContent?.trim()) {
            return oldLayoutChannel.textContent.trim();
        }

        // Try new layout with yt-content-metadata-view-model
        const newLayoutChannel = vid.querySelector('.yt-content-metadata-view-model__metadata-text');
        if (newLayoutChannel?.textContent?.trim()) {
            return newLayoutChannel.textContent.trim();
        }

        // Fallback: search for any text that looks like a channel name
        const text = vid.textContent || '';
        const lines = text.split('\n').map(line => line.trim()).filter(line => line.length > 0);
        if (lines.length > 1) {
            return lines[1]; // Channel name is often second line
        }

        return '';
    }

    function hasMembersOnlyBadge(vid) {
        // Check for "Members only" text anywhere in the element
        if (vid.textContent?.includes('Members only')) {
            return true;
        }

        // Check for new layout badge specifically
        const badgeTexts = vid.querySelectorAll('.yt-badge-shape__text');
        for (const badge of badgeTexts) {
            if (badge.textContent?.trim() === 'Members only') {
                return true;
            }
        }

        return false;
    }

    function scanAndHide() {
        const now = Date.now();
        const inFastMode = now - scanStartTime < FAST_SCAN_DURATION;

        if (!inFastMode) {
            if (now - lastRunTimestamp > 60000) {
                runsThisMinute = 0;
                lastRunTimestamp = now;
            }

            if (runsThisMinute >= MAX_RUNS_PER_MINUTE) {
                log('Throttled: max scans this minute reached.');
                return;
            }

            runsThisMinute++;
        }

        const videos = getTopLevelVideoElements();

        if (!videos.length) {
            log('No video elements found.');
            return;
        }

        let hiddenCount = 0;

        videos.forEach(vid => {
            if (vid.dataset._ytMembersFiltered === 'true') return;

            if (!hasMembersOnlyBadge(vid)) return;

            const channelName = getChannelNameFromElement(vid);

            if (!isWhitelisted(channelName)) {
                // Always hide the video element
                vid.style.display = 'none';
                vid.style.border = DEBUG_BORDER_STYLE;
                vid.dataset._ytMembersFiltered = 'true';

                if (!LEAVE_BLANK_SPACE_WHEN_HIDDEN) {
                    // Try to hide the parent (to leave blank space), but catch any errors
                    try {
                        const parentRenderer = vid.closest('ytd-rich-item-renderer, yt-lockup-view-model');
                        if (parentRenderer) {
                            parentRenderer.hidden = true;
                            parentRenderer.style.border = DEBUG_BORDER_STYLE;
                            parentRenderer.dataset._ytMembersFiltered = 'true';
                        }
                    } catch (err) {
                        log('Could not hide parent element:', err);
                    }
                }

                hiddenCount++;
                log(`Hid video from: "${channelName}"`);
            } else {
                log(`Whitelisted video from: "${channelName}"`);
            }
        });

        if (hiddenCount) {
            log(`Hidden videos this scan: ${hiddenCount}`);
        }
    }

    function startScanning() {
        if (scanInterval) clearInterval(scanInterval);
        scanStartTime = Date.now();
        runsThisMinute = 0;
        lastRunTimestamp = 0;

        scanInterval = setInterval(scanAndHide, SCAN_INTERVAL_FAST);
        log('Started scanning every 2s for 1 minute...');

        setTimeout(() => {
            if (scanInterval) clearInterval(scanInterval);
            log('Fast scan finished. Throttled scanning active.');
            scanInterval = setInterval(scanAndHide, 15000);
        }, FAST_SCAN_DURATION);
    }

    function stopScanningAndShowAll() {
        if (scanInterval) clearInterval(scanInterval);
        scanInterval = null;

        const hiddenEls = document.querySelectorAll('[data-_yt-members-filtered="true"]');
        hiddenEls.forEach(el => {
            el.style.display = '';
            el.style.border = '';
            el.hidden = false;
            delete el.dataset._ytMembersFiltered;
        });

        log('Stopped scanning and revealed previously hidden videos.');
    }

    function toggleFiltering() {
        filteringEnabled = !filteringEnabled;

        if (filteringEnabled) {
            log('Filtering ENABLED.');
            startScanning();
        } else {
            log('Filtering DISABLED.');
            stopScanningAndShowAll();
        }
    }

    // === Tampermonkey Menu ===
    GM_registerMenuCommand('Toggle Member Filter On/Off', toggleFiltering);

    // === Auto Start After Delay ===
    setTimeout(() => {
        if (!filteringEnabled) {
            filteringEnabled = true;
            log('Auto-starting filtering after delay...');
            startScanning();
        }
    }, AUTO_ENABLE_DELAY_MS);

    log('YouTube Member Filter script loaded. Will auto-start in ~4.5s. Toggle anytime via Tampermonkey menu.');
})();