// ==UserScript==
// @name YouTube Ad Skipper
// @name:en YouTube Ad Skipper
// @name:vi YouTube Ad Skipper
// @name:zh-cn YouTube 广告跳过器
// @name:zh-tw YouTube 廣告跳過器
// @name:ja YouTube 広告スキッパー
// @name:ko YouTube 광고 건너뛰기
// @name:es YouTube Ad Skipper
// @name:ru Пропускатель рекламы YouTube
// @name:id YouTube Ad Skipper
// @name:hi YouTube विज्ञापन स्किपर
// @namespace http://tampermonkey.net/
// @version 4.8.1
// @description Skip ads on YouTube automatically. Hides ad elements, mutes ads during playback, removes ad slots, and provides an option to track total skipped ad time. Includes anti-adblock detection countermeasures.
// @description:en Skip ads on YouTube automatically. Hides ad elements, mutes ads during playback, removes ad slots, and provides an option to track total skipped ad time. Includes anti-adblock detection countermeasures.
// @description:vi Tự động bỏ qua quảng cáo trên YouTube. Ẩn các thành phần quảng cáo, tắt tiếng quảng cáo trong khi phát lại, xóa vị trí quảng cáo và cung cấp tùy chọn để theo dõi tổng thời gian quảng cáo đã bỏ qua. Bao gồm các biện pháp đối phó với tính năng phát hiện chặn quảng cáo.
// @description:zh-cn 自动跳过 YouTube 上的广告。隐藏广告元素,在播放过程中将广告静音,移除广告位,并提供一个选项来跟踪已跳过的广告总时间。 包括反广告拦截检测对策。
// @description:zh-tw 自動跳過 YouTube 上的廣告。隱藏廣告元素,在播放過程中將廣告靜音,移除廣告位,並提供一個選項來追蹤已跳過的廣告總時間。 包括反廣告封鎖偵測對策。
// @description:ja YouTube の広告を自動的にスキップします。広告要素を非表示にし、再生中に広告をミュートし、広告スロットを削除し、スキップした広告の総時間を追跡するオプションを提供します。広告ブロック検出対策が含まれています。
// @description:ko YouTube에서 광고를 자동으로 건너뜁니다. 광고 요소를 숨기고, 재생 중에 광고를 음소거하고, 광고 슬롯을 제거하고, 건너뛴 광고의 총 시간을 추적하는 옵션을 제공합니다. 광고 차단 감지 대책이 포함되어 있습니다.
// @description:es Omite automáticamente los anuncios en YouTube. Oculta los elementos publicitarios, silencia los anuncios durante la reproducción, elimina los espacios publicitarios y ofrece una opción para realizar un seguimiento del tiempo total de anuncios omitidos. Incluye contramedidas de detección antibloqueo de anuncios.
// @description:ru Автоматически пропускает рекламу на YouTube. Скрывает рекламные элементы, отключает звук рекламы во время воспроизведения, удаляет рекламные блоки и предоставляет возможность отслеживать общее время пропущенной рекламы. Включает контрмеры для обнаружения блокировки рекламы.
// @description:id Lewati iklan secara otomatis di YouTube. Sembunyikan elemen iklan, nonaktifkan suara iklan selama pemutaran, hapus slot iklan, dan berikan opsi untuk melacak total waktu iklan yang dilewati. Termasuk tindakan pencegahan deteksi anti-adblock.
// @description:hi YouTube पर विज्ञापनों को स्वचालित रूप से छोड़ें। विज्ञापन तत्वों को छुपाता है, प्लेबैक के दौरान विज्ञापनों को म्यूट करता है, विज्ञापन स्लॉट को हटाता है, और स्किप किए गए कुल विज्ञापन समय को ट्रैक करने का विकल्प प्रदान करता है। इसमें विज्ञापन-रोकथाम का पता लगाने के उपाय शामिल हैं।
// @author RenjiYuusei
// @license MIT
// @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @match https://*.youtube.com/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
const DEFAULT_CONFIG = {
skipAds: true,
hideAds: true,
muteAds: true,
trackSkippedTime: true,
hideMethod: 'remove', // 'remove' or 'display:none'
debugMode: false
};
let config = { ...DEFAULT_CONFIG, ...GM_getValue('adSkipperConfig', {}) };
let totalSkippedTime = GM_getValue('totalSkippedTime', 0);
let adObserver = null;
let currentVideoId = null;
let originalVideoVolume = null;
let lastSkipTime = 0;
const AD_SELECTORS = {
skippable: [
'.ytp-ad-skip-button', '.ytp-ad-skip-button-modern', '.videoAdUiSkipButton',
'[id^="visit-advertiser"]', '.ytp-ad-overlay-close-button', '.ytp-ad-button-icon',
'.ytp-ad-text-overlay-skip-button', '.ytp-ad-player-close-button', '#ad', '.ytp-ad-persistent-panel'
],
hideable: [
'.ad-showing', '.ytp-ad-module', '.ytp-ad-overlay-closing', 'ytd-ad-slot-renderer',
'#ad-display-slot', '.ytd-promoted-video-renderer', 'ytd-display-ad-renderer',
'ytd-banner-promo-renderer', '[id^="ad-text"]', '[id^="ad-image"]',
'.ytd-watch-next-secondary-results-renderer.ytd-item-section-renderer',
'ytd-in-feed-ad-layout-renderer', 'ytd-promoted-sparkles-web-renderer',
'#shorts-player-ad-slot-container', '.html5-video-player.ad-showing', '#masthead-ad',
'.ytp-ad-persistent-panel'
]
};
const log = (message) => config.debugMode && console.log(`[YouTube Ad Skipper] ${message}`);
const skipAd = () => {
const now = Date.now();
if (now - lastSkipTime < 1000) return; // Debounce
for (const selector of AD_SELECTORS.skippable) {
const button = document.querySelector(selector);
if (button) {
button.click();
log('Clicked skip button:', selector);
lastSkipTime = now;
return true;
}
}
const video = document.querySelector('video');
if (video && isAdPlaying(video)) {
video.currentTime = video.duration || 0;
log('Skipped/Forwarded ad');
if (config.muteAds) {
muteAd(video);
}
lastSkipTime = now;
return true;
} else if (video && config.muteAds && !isAdPlaying(video) && originalVideoVolume !== null) {
unmuteAd(video);
}
return false;
};
const isAdPlaying = (video) => {
return document.querySelector('.ad-showing') !== null || (video?.src && video.src.includes('/ads/'));
};
const muteAd = (video) => {
if (originalVideoVolume === null) {
originalVideoVolume = video.volume;
}
video.volume = 0;
log('Muted ad');
};
const unmuteAd = (video) => {
video.volume = originalVideoVolume;
originalVideoVolume = null;
log('Restored video volume');
};
const hideAds = () => {
if (!config.hideAds) return;
const applyHideStyle = () => {
AD_SELECTORS.hideable.forEach(selector => {
Array.from(document.querySelectorAll(selector)).forEach(el => {
if (config.hideMethod === 'remove') {
el.remove();
} else {
el.style.setProperty('display', 'none', 'important');
}
});
});
};
applyHideStyle();
new MutationObserver(applyHideStyle).observe(document.body, { childList: true, subtree: true });
log('Ad hiding applied');
};
const handleVideoChange = () => {
const newVideoId = new URLSearchParams(window.location.search).get('v');
if (newVideoId && newVideoId !== currentVideoId) {
currentVideoId = newVideoId;
log(`New video detected: ${currentVideoId}`);
if (adObserver) {
adObserver.disconnect();
}
initializeAdSkipper();
}
};
const initializeAdSkipper = () => {
hideAds();
adObserver = new MutationObserver(() => {
skipAd();
handleVideoChange();
});
adObserver.observe(document.body, { childList: true, subtree: true });
log('Ad skipper initialized');
skipAd(); // Initial ad skip
};
const saveConfig = () => {
GM_setValue('adSkipperConfig', config);
log('Configuration saved');
};
const toggleFeature = (feature) => {
config[feature] = !config[feature];
saveConfig();
log(`Feature '${feature}' toggled: ${config[feature]}`);
location.reload();
};
const setHideMethod = (method) => {
config.hideMethod = method;
saveConfig();
log(`Hide method set to: ${method}`);
location.reload();
};
const formatTime = (seconds) => {
const hrs = Math.floor(seconds / 3600);
const mins = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
return `${hrs}h ${mins}m ${secs}s`;
};
GM_registerMenuCommand('Toggle Ad Skipping', () => toggleFeature('skipAds'));
GM_registerMenuCommand('Toggle Ad Hiding', () => toggleFeature('hideAds'));
GM_registerMenuCommand('Toggle Ad Muting', () => toggleFeature('muteAds'));
GM_registerMenuCommand('Toggle Time Tracking', () => toggleFeature('trackSkippedTime'));
GM_registerMenuCommand('Set Hide Method (Display: none)', () => setHideMethod('display:none'));
GM_registerMenuCommand('Set Hide Method (Remove)', () => setHideMethod('remove'));
GM_registerMenuCommand('Toggle Debug Mode', () => toggleFeature('debugMode'));
GM_registerMenuCommand('Show Total Skipped Time', () => alert(`Total ad time skipped: ${formatTime(totalSkippedTime)}`));
GM_registerMenuCommand('Reset Total Skipped Time', () => {
totalSkippedTime = 0;
GM_setValue('totalSkippedTime', 0);
alert('Total skipped time has been reset to 0');
});
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeAdSkipper);
} else {
initializeAdSkipper();
}
// Anti-adblock detection countermeasure (less aggressive) - This is now less critical because of improved ad detection. Consider removing for simplicity.
try {
const originalSetInterval = window.setInterval;
window.setInterval = (func, delay, ...args) => {
if (delay === 1000 && (func.toString().includes('adTimeout') || func.toString().includes('yt.ads.signals'))) {
log('Blocked potential adblock detection interval.');
return originalSetInterval(() => {}, delay);
}
return originalSetInterval(func, delay, ...args);
};
} catch (error) {
log('Error overriding setInterval:', error);
}
})();