// ==UserScript==
// @name Bye Bye YouTube Ads - Improved (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
// ==/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
}
})();