// ==UserScript==
// @name Instant YouTube Ad Skipper @ Gurveer
// @name:ar تخطي إعلانات يوتيوب تلقائيًا @ Gurveer
// @name:bg Автоматично Пропускане на Реклами в YouTube
// @name:es Omitir Anuncios de YouTube Automáticamente @ Gurveer
// @name:fr Passer Automatiquement les Publicités YouTube
// @name:hi यूट्यूब विज्ञापन स्वतः छोड़ें
// @name:id Lewati Iklan YouTube Secara Otomatis
// @name:ja YouTube広告を自動でスキップ
// @name:ko 유튜브 광고 자동 스킵
// @name:nl Automatisch YouTube-Advertenties Overslaan
// @name:pt-BR Pular Anúncios do YouTube Automaticamente
// @name:ru Автоматический Пропуск Рекламы YouTube
// @name:vi Bỏ Qua Quảng Cáo YouTube Tự Động
// @name:zh-CN 自动跳过YouTube广告
// @name:zh-TW 自動略過YouTube廣告
// @namespace https://github.com/gurr-i/browser-scripts
// @version 8.0.3
// @description Instantly skips YouTube ads with low detection risk. Includes customizable options, update alerts, strong error management, and mute state fixes.
// @description:ar تخطي إعلانات يوتيوب فورًا مع تقليل مخاطر الكشف. يشمل إعدادات قابلة للتخصيص، تنبيهات التحديث، إدارة أخطاء قوية، وإصلاحات لحالة الصمت.
// @description:bg Пропуска рекламите в YouTube незабавно с минимален риск от откриване. Включва персонализирани настройки, известия за актуализация, стабилна обработка на грешки и корекции за възстановяване на заглушаването.
// @description:en Instantly skips YouTube ads with low detection risk. Includes customizable options, update alerts, strong error management, and mute state fixes.
// @description:es Omite anuncios de YouTube al instante con bajo riesgo de detección. Incluye opciones personalizables, alertas de actualización, gestión robusta de errores y correcciones para el estado de silencio.
// @description:fr Passe automatiquement les publicités YouTube avec un faible risque de détection. Inclut des options personnalisables, des alertes de mise à jour, une gestion robuste des erreurs et des corrections pour l’état muet.
// @description:hi यूट्यूब विज्ञापनों को तुरंत छोड़ दें, जिसमें पता लगने का जोखिम कम हो। इसमें अनुकूलन योग्य विकल्प, अपडेट अलर्ट, मजबूत त्रुटि प्रबंधन और म्यूट स्थिति सुधार शामिल हैं।
// @description:id Lewati iklan YouTube secara instan dengan risiko deteksi rendah. Termasuk opsi yang dapat disesuaikan, peringatan pembaruan, pengelolaan kesalahan yang kuat, dan perbaikan untuk status bisu.
// @description:ja YouTube広告を瞬時に自動スキップし、検出リスクを低減。カスタマイズ可能なオプション、更新アラート、強力なエラー管理、ミュート状態の修正を含む。
// @description:ko 유튜브 광고를 즉시 자동으로 건너뛰며 탐지 위험이 낮음. 사용자 맞춤 설정, 업데이트 알림, 강력한 오류 관리, 음소거 상태 수정 포함.
// @description:nl Sla YouTube-advertenties direct automatisch over met laag detectierisico. Bevat aanpasbare opties, updatewaarschuwingen, robuuste foutafhandeling en fixes voor mute-status.
// @description:pt-BR Pula anúncios do YouTube instantaneamente com baixo risco de detecção. Inclui opções personalizáveis, alertas de atualização, gerenciamento robusto de erros e correções para o estado de mudo.
// @description:ru Мгновенно пропускает рекламу YouTube с низким риском обнаружения. Включает настраиваемые опции, уведомления об обновлениях, надежное управление ошибками и исправления для состояния звука.
// @description:vi Bỏ qua quảng cáo YouTube ngay lập tức với rủi ro phát hiện thấp. Bao gồm tùy chọn tùy chỉnh, cảnh báo cập nhật, quản lý lỗi mạnh mẽ và sửa lỗi cho trạng thái tắt tiếng.
// @description:zh-CN 即时自动跳过YouTube广告,检测风险低。包括可定制选项、更新提醒、强大的错误管理和静音状态修复。
// @description:zh-TW 即時自動跳過YouTube廣告,偵測風險低。包含可自訂選項、更新提醒、強大的錯誤管理及靜音狀態修復。
// @author Gurveer
// @icon https://raw.githubusercontent.com/gurr-i/browser-scripts/main/assets/icons/youtube-ads-skipper.png
// @match https://www.youtube.com/*
// @match https://m.youtube.com/*
// @match https://music.youtube.com/*
// @exclude https://studio.youtube.com/*
// @grant none
// @license MIT
// @compatible firefox
// @compatible chrome
// @compatible opera
// @compatible safari
// @compatible edge
// @noframes
// @homepage https://github.com/gurr-i/browser-scripts/tree/main/scripts/Auto-Skip-YouTube-Ads
// ==/UserScript==
(function() {
'use strict';
let videoPlayer;
// Selectors for interface ads
const adSelectors = [
'#masthead-ad', // Top banner ad on homepage
'ytd-rich-item-renderer.style-scope.ytd-rich-grid-row #content:has(.ytd-display-ad-renderer)', // Homepage video grid ad
'.video-ads.ytp-ad-module', // Player bottom ad
'tp-yt-paper-dialog:has(yt-mealbar-promo-renderer)', // Membership promo ad on video page
'ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-ads"]', // Recommended ad on video page
'#related #player-ads', // Sidebar promo ad in comments
'#related ytd-ad-slot-renderer', // Sidebar video ad in comments
'ytd-ad-slot-renderer', // Search page ad
'yt-mealbar-promo-renderer', // Membership recommendation ad
'ytd-popup-container:has(a[href="/premium"])', // Premium ad popup
'ad-slot-renderer', // Third-party ad on mobile
'ytm-companion-ad-renderer', // Skippable video ad link on mobile
];
window.debugMode = false; // Debugging toggle
/**
* Format a Date object to a readable string
* @param {Date} date - Standard date object
* @returns {string} Formatted date string
*/
function formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
/**
* Log messages for debugging
* @param {string} message - Message to log
*/
function debugLog(message) {
if (!window.debugMode) return;
console.log(window.location.href);
console.log(`${formatDate(new Date())} ${message}`);
}
/**
* Create a style element for script execution flag
* @param {string} flagId - Flag identifier
*/
function setExecutionFlag(flagId) {
const style = document.createElement('style');
style.id = flagId;
(document.head || document.body).appendChild(style);
}
/**
* Retrieve execution flag element
* @param {string} flagId - Flag identifier
* @returns {Element|null}
*/
function getExecutionFlag(flagId) {
return document.getElementById(flagId);
}
/**
* Check if execution flag is set
* @param {string} flagId - Flag identifier
* @returns {boolean}
*/
function hasExecutionFlag(flagId) {
if (getExecutionFlag(flagId)) {
return true;
}
setExecutionFlag(flagId);
return false;
}
/**
* Create style element to hide ads
* @param {string} flagId - Flag identifier
*/
function createAdBlockStyle(flagId) {
if (hasExecutionFlag(flagId)) {
debugLog('Ad-blocking style already applied');
return;
}
const style = document.createElement('style');
(document.head || document.body).appendChild(style);
style.appendChild(document.createTextNode(generateAdBlockCss(adSelectors)));
debugLog('Ad-blocking style applied successfully');
}
/**
* Generate CSS to hide ads
* @param {string[]} selectors - Array of CSS selectors
* @returns {string} CSS text
*/
function generateAdBlockCss(selectors) {
return selectors.map(selector => `${selector}{display:none!important}`).join(' ');
}
/**
* Simulate touch event for mobile
*/
function simulateTouch() {
const touch = new Touch({
identifier: Date.now(),
target: this,
clientX: 12,
clientY: 34,
radiusX: 56,
radiusY: 78,
rotationAngle: 0,
force: 1
});
const touchStart = new TouchEvent('touchstart', {
bubbles: true,
cancelable: true,
view: window,
touches: [touch],
targetTouches: [touch],
changedTouches: [touch]
});
this.dispatchEvent(touchStart);
const touchEnd = new TouchEvent('touchend', {
bubbles: true,
cancelable: true,
view: window,
touches: [],
targetTouches: [],
changedTouches: [touch]
});
this.dispatchEvent(touchEnd);
}
/**
* Get video element
*/
function fetchVideoElement() {
videoPlayer = document.querySelector('.ad-showing video') || document.querySelector('video');
}
/**
* Resume video playback if paused
*/
function resumePlayback() {
if (videoPlayer?.paused && videoPlayer.currentTime < 1) {
videoPlayer.play();
debugLog('Resumed video playback');
}
}
/**
* Remove ad blocker popup and overlay
*/
function clearOverlay() {
const premiumPopups = [...document.querySelectorAll('ytd-popup-container')];
const targetPopups = premiumPopups.filter(popup => popup.querySelector('a[href="/premium"]'));
if (targetPopups.length > 0) {
targetPopups.forEach(popup => popup.remove());
debugLog('Removed ad blocker popup');
}
const backdrops = document.querySelectorAll('tp-yt-iron-overlay-backdrop');
const targetBackdrop = Array.from(backdrops).find(backdrop => backdrop.style.zIndex === '2201');
if (targetBackdrop) {
targetBackdrop.className = '';
targetBackdrop.removeAttribute('opened');
debugLog('Closed overlay backdrop');
}
}
/**
* Skip video ads
* @param {MutationRecord[]} mutations - MutationObserver records
* @param {MutationObserver} observer - Observer instance
*/
function bypassAd(mutations, observer) {
const skipBtn = document.querySelector('.ytp-ad-skip-button') ||
document.querySelector('.ytp-skip-ad-button') ||
document.querySelector('.ytp-ad-skip-button-modern');
const shortAd = document.querySelector('.video-ads.ytp-ad-module .ytp-ad-player-overlay') ||
document.querySelector('.ytp-ad-button-icon');
if ((skipBtn || shortAd) && !window.location.href.includes('https://m.youtube.com/')) {
videoPlayer.muted = true;
}
if (skipBtn) {
const delay = 0.5;
setTimeout(bypassAd, delay * 1000);
if (videoPlayer.currentTime > delay) {
videoPlayer.currentTime = videoPlayer.duration;
debugLog('Forced skip for special account ad');
return;
}
skipBtn.click();
simulateTouch.call(skipBtn);
debugLog('Clicked skip ad button');
} else if (shortAd) {
videoPlayer.currentTime = videoPlayer.duration;
debugLog('Force-ended short ad');
}
}
/**
* Monitor and remove in-player ads
* @param {string} flagId - Flag identifier
*/
function blockPlayerAds(flagId) {
if (hasExecutionFlag(flagId)) {
debugLog('Player ad blocker already running');
return;
}
const target = document.body;
const config = { childList: true, subtree: true };
const observer = new MutationObserver(() => {
fetchVideoElement();
clearOverlay();
bypassAd();
resumePlayback();
});
observer.observe(target, config);
debugLog('Player ad blocker started successfully');
}
/**
* Main execution function
*/
function initialize() {
createAdBlockStyle('adBlockStyle');
blockPlayerAds('playerAdBlock');
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initialize);
debugLog('YouTube ad skipper scheduled');
} else {
initialize();
debugLog('YouTube ad skipper executed immediately');
}
/**
* Resume video and remove popups
*/
function resumeAndClear() {
const video = document.querySelector('video.html5-main-video');
if (video?.paused) {
video.play();
debugLog('Resumed paused video');
}
}
/**
* Remove popup and backdrop elements
* @param {Node} node - DOM node to check
*/
function clearPopup(node) {
const popup = node.querySelector('.ytd-popup-container > .ytd-popup-container > .ytd-enforcement-message-view-model');
if (popup) {
popup.parentNode.remove();
debugLog('Removed popup element');
const backdrops = document.getElementsByTagName('tp-yt-iron-overlay-backdrop');
for (let i = backdrops.length - 1; i >= 0; i--) {
backdrops[i].remove();
}
resumeAndClear();
}
if (node.tagName.toLowerCase() === 'tp-yt-iron-overlay-backdrop') {
node.remove();
debugLog('Removed backdrop element');
resumeAndClear();
}
}
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
Array.from(mutation.addedNodes)
.filter(node => node.nodeType === 1)
.forEach(node => clearPopup(node));
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
})();