diff --git a/content.css b/content.css index 14c7116..ffe890d 100644 --- a/content.css +++ b/content.css @@ -1,2 +1,40 @@ /* GoaBlockAD - Content CSS */ -/* We inject styles via JS to allow toggling, but this file is reserved for static overrides if needed. */ \ No newline at end of file +/* We inject styles via JS to allow toggling, but this file is reserved for static overrides if needed. */ + +/* YouTube: hide the video and overlays while an ad is playing so the user + doesn't see the brief flash before our auto-skip kicks in. The video + element keeps playing underneath so fast-forward still works. */ +.html5-video-player.ad-showing video, +.html5-video-player.ad-interrupting video { + opacity: 0 !important; +} + +.html5-video-player.ad-showing .ytp-ad-player-overlay, +.html5-video-player.ad-showing .ytp-ad-player-overlay-layout, +.html5-video-player.ad-showing .ytp-ad-image-overlay, +.html5-video-player.ad-showing .ytp-ad-overlay-container, +.html5-video-player.ad-showing .ytp-ad-text-overlay, +.html5-video-player.ad-showing .ytp-ad-preview-container, +.html5-video-player.ad-interrupting .ytp-ad-player-overlay, +.html5-video-player.ad-interrupting .ytp-ad-player-overlay-layout { + display: none !important; +} + +/* Hide every overlay/UI piece YouTube layers on top of an ad: the entire ad + module, the "promoted/sponsored" cards, action companions, surveys, etc. + The skip button is preserved in case the click programmatique is ignored + and the user wants to click it manually. */ +.video-ads, +.ytp-ad-module, +ytd-player[hidden-by-goablockad], +.ytp-ad-action-interstitial, +.ytp-ad-action-interstitial-background-container, +.ytp-ad-action-interstitial-card, +.ytp-ad-action-interstitial-image, +.ytp-ad-action-interstitial-slot, +.ytp-flyout-cta, +.ytp-ad-survey, +.ytp-ad-survey-questions, +ytmusic-ad-slot-renderer { + display: none !important; +} \ No newline at end of file diff --git a/content.js b/content.js index fd50f0e..3487474 100644 --- a/content.js +++ b/content.js @@ -13,7 +13,27 @@ const adSelectors = [ '.ad-container', '.adsbox', '#ad-sidebar', - '.sponsored-content' + '.sponsored-content', + // YouTube + 'ytd-display-ad-renderer', + 'ytd-promoted-video-renderer', + 'ytd-promoted-sparkles-web-renderer', + 'ytd-in-feed-ad-layout-renderer', + 'ytd-ad-slot-renderer', + 'ytd-banner-promo-renderer', + 'ytd-statement-banner-renderer', + 'ytd-companion-slot-renderer', + 'ytd-action-companion-ad-renderer', + 'ytd-merch-shelf-renderer', + 'ytd-rich-section-renderer:has(ytd-statement-banner-renderer)', + '#masthead-ad', + '#player-ads', + '.ytp-ad-overlay-slot', + '.ytp-ad-overlay-container', + '.ytp-featured-product', + '.ytp-suggested-action', + 'tp-yt-paper-dialog:has(yt-mealbar-promo-renderer)', + 'ytmusic-statement-banner-renderer' ]; function applyCosmeticFiltering() { @@ -63,3 +83,159 @@ chrome.storage.onChanged.addListener((changes, namespace) => { applyCosmeticFiltering(); } }); + +// ───────────────────────────────────────────── +// YouTube video ad auto-skip +// Runs only on YouTube. Clicks the skip button when available, and fast-forwards +// non-skippable ads by jumping the player to the end of the ad clip. +// Independent of the cosmetic toggle — gated only by the master `enabled` flag. +// ───────────────────────────────────────────── +(() => { + const host = location.hostname; + if (!/(^|\.)youtube\.com$/.test(host) && !/(^|\.)youtube-nocookie\.com$/.test(host)) return; + + const LOG = '[GoaBlockAD/yt]'; + const SKIP_SELECTORS = [ + '.ytp-ad-skip-button', + '.ytp-ad-skip-button-modern', + '.ytp-skip-ad-button', + '.ytp-ad-skip-button-container button', + '.ytp-skip-ad button', + 'button[class*="ytp-ad-skip"]', + 'button[class*="ytp-skip-ad"]' + ]; + + let enabled = true; + chrome.storage.local.get(['enabled'], (r) => { + enabled = r.enabled !== false; + console.log(LOG, 'init, enabled =', enabled); + }); + chrome.storage.onChanged.addListener((changes, namespace) => { + if (namespace === 'local' && changes.enabled) { + enabled = changes.enabled.newValue !== false; + } + }); + + function isAdPlaying(player) { + return !!player && ( + player.classList.contains('ad-showing') || + player.classList.contains('ad-interrupting') + ); + } + + function clickSkipButtons() { + for (const sel of SKIP_SELECTORS) { + const btns = document.querySelectorAll(sel); + for (const btn of btns) { + try { btn.click(); } catch (_) { /* ignore */ } + } + } + } + + // Per-video state. Reset whenever the underlying