// ==UserScript== // @name Bye Bye YouTube Ads - Improved (Updated October Update) // @version 3.1 // @description Skip YouTube ads automatically, and block ads more effectively (desktop only). Updated for more robust detection. // @author DishantX // @match *://www.youtube.com/* // @exclude *://www.youtube.com/*/music* // @exclude *://music.youtube.com/* // @exclude *://m.youtube.com/* // @icon https://tenor.com/view/manifest-meditate-pepe-gif-12464108004541162266 // @license MIT // @namespace https://greasyfork.org/users/1467023 // @run-at document-idle // @grant none // @downloadURL none // ==/UserScript== (() => { 'use strict'; const LOG = false; // set true for debugging in console const log = (...args) => { if (LOG) console.log('[ByeByeYTAds]', ...args); }; // --- 1) CSS: hide common ad overlay elements (non-destructive selectors) --- const css = ` /* in-player overlays/cards/promos */ .ytp-ad-overlay, .ytp-ad-player-overlay, .ytp-featured-product, .ytp-ad-image-overlay, #player-ads, ytd-companion-ad-renderer, ytd-display-ad-renderer, ytd-banner-promo-renderer, ytd-promoted-sparkles-text-renderer, ytd-ad-slot-renderer { display: none !important; pointer-events: none !important; } /* promoted badges that sometimes overlay thumbnails */ .ytd-promoted-sparkles-text-renderer, .ytp-ce-element { display: none !important; } /* don't hide site-critical elements — be conservative */ `; const styleTag = document.createElement('style'); styleTag.setAttribute('data-bbye-ads', '1'); styleTag.textContent = css; (document.head || document.documentElement).appendChild(styleTag); // --- utilities --- function queryAllButtons() { return Array.from(document.querySelectorAll('button, a')); } function isSkipishButton(el) { if (!el || el.nodeType !== 1) return false; try { const aria = (el.getAttribute && el.getAttribute('aria-label')) || ''; const txt = (el.textContent || '').trim(); const classes = (el.className || '').toLowerCase(); // aria label or visible text that indicates a skip/close action if (/skip ad|skipads|skip ad(s)?|skip|close ad|close overlay|dismiss ad/i.test(aria + ' ' + txt)) { return true; } // known class fragments if (classes.includes('ytp-ad-skip') || classes.includes('overlay-close') || classes.includes('ad-overlay-close') || classes.includes('ad-close')) { return true; } } catch (e) { // ignore } return false; } function clickSkipButtons() { const buttons = queryAllButtons().filter(isSkipishButton); if (buttons.length === 0) return false; for (const b of buttons) { try { b.click(); log('Clicked skipish button', b); } catch (e) { log('Click failed', e, b); } } return true; } function closeAdOverlays() { const sel = [ '.ytp-ad-overlay-close-button', 'button[aria-label*="Close ad"]', 'button[aria-label*="close ad"]' ]; for (const s of sel) { const el = document.querySelector(s); if (el) { try { el.click(); log('Closed overlay with', s); } catch(e){/*ignore*/ } return true; } } return false; } // Fast-forward / jump strategies function jumpAdToEnd(video) { if (!video) video = document.querySelector('video'); if (!video) return false; const dur = Number(video.duration); if (!isFinite(dur) || dur <= 0) return false; // only jump when there's a meaningful distance to skip if (video.currentTime >= dur - 0.5) return false; try { // attempt to jump to end video.currentTime = Math.max(0, dur - 0.05); // try to play (some players may pause on assignment) video.play().catch(()=>{}); log('jumped to end', video.currentTime, dur); return true; } catch (e) { log('jumpAdToEnd failed', e); return false; } } function speedUpAd(video) { if (!video) video = document.querySelector('video'); if (!video) return false; try { const prev = video.playbackRate || 1; // Try a big speed to finish the ad quickly. // Some players restrict this — that's why it's a fallback. video.playbackRate = Math.max(prev, 16); setTimeout(() => { try { video.playbackRate = prev; } catch (e) {} }, 1200); log('temporarily sped playbackRate to skip ad'); return true; } catch (e) { log('speedUpAd failed', e); return false; } } // Heuristic: detect presence of ad using several signals function isAdPlaying() { try { const player = document.getElementById('movie_player'); if (player && player.classList && player.classList.contains('ad-showing')) return true; // look for known ad elements if (document.querySelector('.ytp-ad-player-overlay, .ytp-ad-overlay, ytd-display-ad-renderer, ytd-companion-ad-renderer')) return true; // skip button presence implies ad context const foundSkip = queryAllButtons().some(isSkipishButton); if (foundSkip) return true; // If video is present and has a "ad" text overlays or elements near the player const adBadge = document.querySelector('ytd-promoted-sparkles-text-renderer, .ytp-ce-element'); if (adBadge) return true; } catch (e) { // ignore detection errors } return false; } // Main monitor function: try strategies in order function handleAdEvent() { if (!isAdPlaying()) return false; log('Ad detected -> acting'); // 1) Click any skip-like buttons if (clickSkipButtons()) return true; // 2) Close overlays if (closeAdOverlays()) return true; // 3) Jump video to end (most reliable for unskippable ads) const vid = document.querySelector('video'); if (jumpAdToEnd(vid)) return true; // 4) Speed up as last resort if (speedUpAd(vid)) return true; return false; } // Observe player class changes (movie_player) and DOM additions function setupObservers() { const player = document.getElementById('movie_player'); if (player) { try { const mo = new MutationObserver(muts => { for (const m of muts) { if (m.type === 'attributes' && m.attributeName === 'class') { if (player.classList.contains('ad-showing')) { log('player class ad-showing observed'); setTimeout(handleAdEvent, 50); } } if (m.addedNodes && m.addedNodes.length) { // small delay allows YouTube to add skip buttons setTimeout(handleAdEvent, 60); } } }); mo.observe(player, { attributes: true, attributeFilter: ['class'], childList: true, subtree: true }); log('Attached observer to movie_player'); } catch (e) { log('Failed to observe movie_player', e); } } // Observe document for dynamic ad nodes try { const bodyObserver = new MutationObserver((muts) => { for (const m of muts) { if (m.addedNodes && m.addedNodes.length) { setTimeout(handleAdEvent, 80); break; } } }); bodyObserver.observe(document.documentElement || document.body, { childList: true, subtree: true }); log('Attached global DOM observer'); } catch (e) { log('Failed to attach global observer', e); } } // Periodic poll as fallback (lightweight) const POLL_MS = 900; let pollHandle = null; function startPolling() { if (pollHandle) clearInterval(pollHandle); pollHandle = setInterval(() => { try { if (isAdPlaying()) { handleAdEvent(); } } catch (e) { /* swallow */ } }, POLL_MS); log('Polling started', POLL_MS); } // Hook into navigation events in YouTube single-page navigation function setupNavigationHooks() { document.addEventListener('yt-navigate-finish', () => { setTimeout(() => { handleAdEvent(); }, 300); }, { passive: true }); // some sites/older clients use pushState const pushStateOrig = history.pushState; history.pushState = function () { try { pushStateOrig.apply(this, arguments); } catch (e) { /* ignore */ } setTimeout(handleAdEvent, 300); }; } // init function init() { log('ByeByeYTAds init'); setupObservers(); setupNavigationHooks(); startPolling(); // One-off immediate attempt (in case the ad is already present) setTimeout(handleAdEvent, 400); } // If DOM not ready wait a bit if (document.readyState === 'complete' || document.readyState === 'interactive') { init(); } else { window.addEventListener('DOMContentLoaded', init, { once: true }); setTimeout(() => { if (!pollHandle) init(); }, 1500); // fallback } })();