Greasy Fork is available in English.
跳过并 column 隐藏 YouTube 广告 (Tiàoguò bìng yǐncáng YouTube guǎnggào)
// ==UserScript==
// @name YouTube Ad-Bypass
// @namespace YouTube_Ad-Bypass_Fckoff
// @version 1.22
// @description:en Skips and hides YouTube and YouTube Music ads
// @description:es Salta y oculta anuncios de YouTube y YouTube Music
// @description:pt Pula e oculta anuncios do YouTube & YouTube Music
// @description:fr Passe et masque las publicités YouTube & YouTube Music
// @description:it Salta e nasconde gli annunci di YouTube & YouTube Music
// @description:de Überspringe und verberge YouTube-Werbung
// @description:hi यूट्यूब विज्ञापनों को छोड़ें और छुपाएं (YouTube vigyapanon ko chhodein aur chhupayein)
// @description:zh-CN 跳过并 column 隐藏 YouTube 广告 (Tiàoguò bìng yǐncáng YouTube guǎnggào)
// @description:ja YouTube 広告をスキップして非表示にします (YouTube kōkoku o sukippu shite hihyōji ni shimasu)
// @description:ru Пропускает и скрывает рекламу на YouTube.
// @author WakeUpNeo
// @match *://www.youtube.com/*
// @match *://music.youtube.com/*
// @run-at document-start
// @grant none
// @license MIT
// @description Salta y oculta anuncios de YouTube
// ==/UserScript==
(function() {
'use strict';
/**
* Configuration object containing CSS selectors for different types of ad elements.
*/
const SELECTORS = {
// Elements that should be visually hidden from the UI
toHide: [
'.ytp-ad-message-container',
'ytd-player-legacy-desktop-watch-ads-renderer',
'ytd-ad-slot-renderer',
'#masthead-ad',
'tp-yt-paper-dialog:has(#feedback.ytd-enforcement-message-view-model)',
'.yt-mealbar-promo-renderer',
'.video-ads',
'.ytp-ad-module',
'.ytp-ad-player-overlay',
'.ad-showing > video',
'.ad-interrupting > video',
'div:has(> div#banner)',
'ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-ads"]',
'ytd-rich-item-renderer:has(ytd-ad-slot-renderer)',
'ytmusic-mealbar-promo-renderer',
'ytd-in-feed-ad-layout-renderer',
'#player-ads',
'.ytd-video-masthead-ad-v3-renderer',
'ytd-ad-selection-preview-renderer',
'.ytp-ad-image-overlay',
'#root.yt-chips-search-renderer-header-v2'
],
// Selectors for the main YouTube video player container
player: [
'#movie_player',
'.html5-video-player'
],
// Classes added by YouTube when an ad is active
adsClasses: [
'.ad-showing',
'.ad-interrupting',
'.ytp-ad-player-overlay'
],
// Selectors for the various "Skip Ad" buttons
skipButtons: [
'.ytp-ad-skip-button-modern',
'.ytp-skip-ad-button',
'.ytp-ad-skip-button',
'.ytp-ad-skip-button-slot',
'.ytp-ad-skip-button-container'
]
};
/**
* Convert arrays of selectors into single comma-separated strings for querySelectorAll/matches usage.
*/
const selectors = Object.fromEntries(
Object.entries(SELECTORS).map(([key, value]) => [key, value.join(', ')])
);
let playerObserver = null;
let video = null;
/**
* Injects a global <style> tag to hide ad-related elements using CSS.
* Uses visibility:hidden and 1px size to avoid breaking layout while making ads invisible.
*/
const injectStyles = () => {
const style = document.createElement('style');
style.textContent = `
${selectors.toHide} {
display: flex !important;
visibility: hidden !important;
opacity: 0 !important;
pointer-events: none !important;
height: 1px !important;
width: 1px !important;
overflow: hidden !important;
}
`;
(document.head || document.documentElement).appendChild(style);
};
/**
* Executes the skipping logic: fast-forwards the video to the end and clicks the skip button.
*/
const skipAction = () => {
if (video) {
if (video.paused) video.play();
video.muted = true; // Mute to avoid sudden loud ad audio
video.playbackRate = 16; // Set max speed to fly through the ad
if (isFinite(video.duration) && video.duration > 0) {
// Jump to the very end of the ad segment
video.currentTime = video.duration - 0.1;
}
}
// Attempt to find and click any available "Skip" button
const btn = document.querySelector(selectors.skipButtons);
if (btn) btn.click();
};
/**
* Checks if the player is currently showing an ad.
* @param {HTMLElement} target - The player element to check for ad-related classes.
*/
const checkAndSkip = (target) => {
// Re-fetch the video element if it's missing or disconnected from DOM
if (!video || !video.isConnected) {
video = document.querySelector('video');
}
if (!video) return;
// If player has ad-related classes, trigger skip; otherwise, reset playback speed
if (target.matches(selectors.adsClasses)) {
skipAction();
} else {
// Restore normal speed if the script had previously accelerated it
if (video.playbackRate > 2) video.playbackRate = 1;
}
};
/**
* Initializes a MutationObserver to watch for changes in the player's class attribute.
* This allows the script to react instantly when an ad starts.
*/
const setupPlayerObserver = () => {
const player = document.querySelector(selectors.player);
if (player && !playerObserver) {
playerObserver = new MutationObserver(() => checkAndSkip(player));
// Monitor class changes which indicate ad transitions
playerObserver.observe(player, { attributes: true, attributeFilter: ['class'] });
// Run initial check
checkAndSkip(player);
}
};
// Event listeners to handle page loads and YouTube's internal navigation (SPA)
window.addEventListener('load', setupPlayerObserver);
window.addEventListener('yt-navigate-finish', setupPlayerObserver);
// Inject CSS as soon as the DOM structure is available
window.addEventListener('DOMContentLoaded', (event) => {
injectStyles();
});
/**
* Fallback mechanism: attempts to initialize the observer every 250ms
* in case 'load' events fire before the player is ready.
*/
let retry = 0;
const fallback = setInterval(() => {
setupPlayerObserver();
// Stop retrying if observer is active or after 10 failed attempts
if (playerObserver || retry > 10) clearInterval(fallback);
retry++;
}, 250);
})();