// ==UserScript== // @name YouTube - Filters // @version 2.3.0 // @description Filter out unwanted content on YouTube to enhance your browsing experience. (Currently is able to filter videos based on age and members-only status) // @author Journey Over // @license MIT // @match *://*.youtube.com/* // @match *://*.youtube-nocookie.com/* // @require https://cdn.jsdelivr.net/gh/StylusThemes/Userscripts@0171b6b6f24caea737beafbc2a8dacd220b729d8/libs/utils/utils.min.js // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @run-at document-body // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com // @homepageURL https://github.com/StylusThemes/Userscripts // @downloadURL https://github.com/StylusThemes/Userscripts/raw/main/userscripts/youtube-filters.user.js // @updateURL https://github.com/StylusThemes/Userscripts/raw/main/userscripts/youtube-filters.user.js // ==/UserScript== (async function() { 'use strict'; // ---------- Constants ---------- const TITLE_SELECTORS = [ 'a#video-title', 'h3 .yt-lockup-metadata-view-model__title span.yt-core-attributed-string', '.yt-lockup-view-model__content-image span.yt-core-attributed-string', 'span.yt-core-attributed-string[role="text"]', 'a.yt-lockup-metadata-view-model__title span.yt-core-attributed-string', 'a.yt-lockup-metadata-view-model__title', 'yt-formatted-string#video-title', 'yt-formatted-string[id="video-title"]', 'yt-formatted-string[class="style-scope ytd-video-renderer"]', 'a#video-title-link span.yt-core-attributed-string', 'span.ytp-modern-videowall-still-info-title' ]; const VIDEO_SELECTORS = [ 'ytd-rich-item-renderer', 'yt-lockup-view-model', 'ytd-grid-video-renderer', 'ytd-video-renderer', 'ytd-compact-video-renderer', 'ytd-playlist-video-renderer', 'ytd-playlist-panel-video-renderer', 'ytd-radio-renderer', 'ytd-reel-item-renderer', 'ytd-reel-video-renderer', 'a.ytp-modern-videowall-still' ]; const AGE_SELECTORS = [ 'span.inline-metadata-item.style-scope.ytd-video-meta-block', 'span.yt-content-metadata-view-model__metadata-text', 'span.ytp-modern-videowall-still-view-count-and-date-info' ]; const MEMBERS_SELECTORS = [ '.badge.badge-style-type-members-only', 'badge-shape[aria-label*="Members only" i]', '.yt-badge-shape--commerce .yt-badge-shape__text', '.yt-badge-shape__text' ]; const MEMBERS_REGEX = /\bmembers\s*[- ]?\s*only\b/i; // ---------- Settings ---------- let DEBUG_ENABLED = GM_getValue('DEBUG_ENABLED', false); const logger = Logger('YT - Filters', { debug: DEBUG_ENABLED }); let AGE_THRESHOLD = GM_getValue('AGE_THRESHOLD', { value: 4, unit: 'years' }); let MEMBERS_ONLY_ENABLED = GM_getValue('MEMBERS_ONLY_ENABLED', false); let AGE_FILTERING_ENABLED = GM_getValue('AGE_FILTERING_ENABLED', true); const processedVideos = new WeakSet(); // ---------- Utility Functions ---------- function convertToYears(value, unit) { const conversions = { minutes: 525600, hours: 8760, days: 365, weeks: 52, months: 12, years: 1 }; return value / (conversions[unit] || 1); } function matchesAnySelector(element, selectors) { return selectors.some(selector => element.matches(selector)); } function queryAll(root, selectors) { return root.querySelectorAll(selectors.join(',')); } // ---------- Video Processing ---------- function getVideoAgeTextAndYears(videoElement) { for (const ageElement of queryAll(videoElement, AGE_SELECTORS)) { const ageText = (ageElement.textContent || '').trim(); if (/\bago\b/i.test(ageText)) { const ageMatch = ageText.match(/(\d+)\s+(minute|hour|day|week|month|year)s?\s+ago/i); if (ageMatch) { const ageValue = parseInt(ageMatch[1], 10); const ageUnit = ageMatch[2].toLowerCase(); const ageInYears = convertToYears(ageValue, ageUnit.includes('minute') ? 'minutes' : ageUnit.includes('hour') ? 'hours' : ageUnit.includes('day') ? 'days' : ageUnit.includes('week') ? 'weeks' : ageUnit.includes('month') ? 'months' : 'years'); return { text: ageText, years: ageInYears }; } return { text: ageText, years: 0 }; } } return { text: 'Unknown', years: 0 }; } function getVideoTitle(videoElement) { for (const titleSelector of TITLE_SELECTORS) { const titleElement = videoElement.querySelector(titleSelector); if (titleElement && titleElement.innerText.trim()) { return titleElement.innerText.trim(); } } return ''; } function hideVideo(videoElement, reason) { for (const selector of VIDEO_SELECTORS) { const videoContainer = videoElement.closest(selector); if (videoContainer) { try { videoContainer.setAttribute('hidden', 'true'); } catch { videoContainer.style.display = 'none'; } } } logger.debug(`Hidden "${getVideoTitle(videoElement)}" (${reason})`); } // ---------- Age Filtering ---------- function filterVideoByAge(videoElement) { if (processedVideos.has(videoElement)) return; const { text: ageText, years: ageYears } = getVideoAgeTextAndYears(videoElement); if (ageText === 'Unknown') return; processedVideos.add(videoElement); videoElement.dataset.processed = 'true'; const thresholdInYears = convertToYears(AGE_THRESHOLD.value, AGE_THRESHOLD.unit); if (ageYears >= thresholdInYears) { hideVideo(videoElement, ageText); } } // ---------- Members-Only Filtering ---------- function isMembersOnlyBadge(badge) { if (badge.classList.contains('badge-style-type-members-only')) return true; const label = badge.getAttribute('aria-label') || badge.textContent || ''; return MEMBERS_REGEX.test(label); } function removeMembersOnlyVideo(badge) { const videoElement = badge.closest(VIDEO_SELECTORS.join(',')); if (videoElement) { videoElement.remove(); logger.debug(`Removed Members-only "${getVideoTitle(videoElement)}"`); } } function pruneMembersShelf(root = document) { for (const shelf of root.querySelectorAll('ytd-shelf-renderer')) { const title = (shelf.querySelector('#title')?.textContent || '').trim(); const subtitle = (shelf.querySelector('#subtitle')?.textContent || '').trim(); if (MEMBERS_REGEX.test(title) || /videos available to members/i.test(subtitle)) { shelf.remove(); } } } function scanForMembersOnly(root = document) { for (const badge of queryAll(root, MEMBERS_SELECTORS)) { if (isMembersOnlyBadge(badge)) { removeMembersOnlyVideo(badge); } } pruneMembersShelf(root); } // ---------- Observers ---------- async function observeNewVideos() { while (true) { try { const unprocessedVideos = [...document.querySelectorAll( VIDEO_SELECTORS.map(selector => `${selector}:not([data-processed])`).join(',') )]; for (const videoElement of unprocessedVideos) { // Age filtering only on non-channel pages if (AGE_FILTERING_ENABLED && !window.location.href.includes('@')) { filterVideoByAge(videoElement); } } if (MEMBERS_ONLY_ENABLED) { pruneMembersShelf(); } } catch (error) { logger.error(error); } await new Promise(resolve => setTimeout(resolve, 50)); } } function observeMembersOnly() { const observer = new MutationObserver(mutations => { for (const mutation of mutations) { if (mutation.type === 'childList') { for (const node of mutation.addedNodes) { if (!(node instanceof Element)) continue; if (matchesAnySelector(node, MEMBERS_SELECTORS) && isMembersOnlyBadge(node)) { removeMembersOnlyVideo(node); } else { scanForMembersOnly(node); } } } } }); observer.observe(document.documentElement, { childList: true, subtree: true }); const rescan = () => setTimeout(() => scanForMembersOnly(document), 50); window.addEventListener('yt-navigate-finish', rescan); window.addEventListener('yt-page-data-updated', rescan); } // ---------- Initialization ---------- observeNewVideos(); if (MEMBERS_ONLY_ENABLED) { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { scanForMembersOnly(); observeMembersOnly(); }); } else { scanForMembersOnly(); observeMembersOnly(); } } // ---------- Settings UI ---------- function toggleSwitch(element) { element.classList.toggle('active'); } function handleToggle(element) { toggleSwitch(element); element.setAttribute('aria-checked', element.classList.contains('active')); } function openSettingsMenu() { if (document.getElementById('yt-filters-settings')) return; const overlay = document.createElement('div'); overlay.id = 'yt-filters-overlay'; overlay.style = `position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.6);backdrop-filter:blur(12px);display:flex;align-items:center;justify-content:center;z-index:10000;opacity:0;animation:overlayFadeIn 0.4s ease forwards;`; const modal = document.createElement('div'); modal.id = 'yt-filters-settings'; modal.style = `background:linear-gradient(135deg,#1a1a1a 0%,#2d2d2d 100%);color:#e4e4e4;border-radius:20px;width:420px;max-width:95vw;max-height:85vh;overflow:hidden;box-shadow:0 25px 50px rgba(0,0,0,0.5),0 0 0 1px rgba(255,255,255,0.1);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;transform:translateY(30px) scale(0.95);opacity:0;animation:modalSlideIn 0.4s cubic-bezier(0.34,1.56,0.64,1) 0.1s forwards;display:flex;flex-direction:column;`; modal.innerHTML = `

YouTube Filters

Customize your video filtering experience

⏱️

Age Filter

Hide videos older than your specified threshold

Filter out videos based on upload age

🎬

Content Filters

Control what types of content to filter out

Hide videos that require channel membership

🔧

Advanced Settings

Developer options and debugging tools

Enable detailed logging for troubleshooting

`; overlay.appendChild(modal); document.body.appendChild(overlay); // Toggle switches with keyboard support const membersToggle = document.getElementById('members-only-toggle'); const debugToggle = document.getElementById('debug-toggle'); const ageToggle = document.getElementById('age-filtering-toggle'); membersToggle.addEventListener('click', () => handleToggle(membersToggle)); membersToggle.addEventListener('keydown', (event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); handleToggle(membersToggle); } }); debugToggle.addEventListener('click', () => handleToggle(debugToggle)); debugToggle.addEventListener('keydown', (event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); handleToggle(debugToggle); } }); ageToggle.addEventListener('click', () => handleToggle(ageToggle)); ageToggle.addEventListener('keydown', (event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); handleToggle(ageToggle); } }); document.getElementById('save-settings').addEventListener('click', () => { const thresholdValue = parseFloat(document.getElementById('age-threshold').value); const thresholdUnit = document.getElementById('age-unit').value; AGE_THRESHOLD = { value: thresholdValue, unit: thresholdUnit }; GM_setValue('AGE_THRESHOLD', AGE_THRESHOLD); MEMBERS_ONLY_ENABLED = membersToggle.classList.contains('active'); GM_setValue('MEMBERS_ONLY_ENABLED', MEMBERS_ONLY_ENABLED); AGE_FILTERING_ENABLED = ageToggle.classList.contains('active'); GM_setValue('AGE_FILTERING_ENABLED', AGE_FILTERING_ENABLED); DEBUG_ENABLED = debugToggle.classList.contains('active'); GM_setValue('DEBUG_ENABLED', DEBUG_ENABLED); // Update logger if debug changed if (logger.debug !== DEBUG_ENABLED) { logger.debug = DEBUG_ENABLED; } closeModal(); }); document.getElementById('close-settings').addEventListener('click', closeModal); overlay.addEventListener('click', event => { if (event.target === overlay) closeModal(); }); function closeModal() { modal.style.animation = 'modalSlideIn 0.3s ease reverse'; overlay.style.animation = 'overlayFadeIn 0.3s ease reverse'; setTimeout(() => overlay.remove(), 300); } } GM_registerMenuCommand('Open YouTube Filters Settings', openSettingsMenu); })();