/** * MUSE Video Ad Scriptlets v2 * Injected via initialization_script BEFORE any page JavaScript runs. * * UNIVERSAL approach — not YouTube-specific. Works by: * 1. Patching JSON.parse/Response.json to prune known ad properties * 2. Blocking known ad-serving fetch/XHR patterns * 3. Blocking VAST/VPAID/IMA SDK script loading * 4. Auto-skipping video ads when detected * 5. Collapsing blank ad containers * * TIMING IS CRITICAL: runs before ANY page JavaScript. */ (function() { 'use strict'; // ═══════════════════════════════════════════════════════════════════════════════ // UNIVERSAL JSON PRUNE — removes ad-related properties from ALL parsed JSON // Works on YouTube, Dailymotion, Reddit, news sites, any site using JSON APIs // ═══════════════════════════════════════════════════════════════════════════════ const AD_JSON_KEYS = new Set([ 'adPlacements', 'playerAds', 'adSlots', 'adBreaks', 'adBreakServiceRenderer', 'promotedSparklesWebRenderer', 'promotedVideoRenderer', 'adRenderer', 'displayAdRenderer', 'promoted', 'sponsoredAd', 'sponsoredContent', 'ad_placements', 'player_ads', 'ad_slots', 'ad_breaks', 'vastUrl', 'vastXml', 'vpaidUrl', 'adTagUrl', 'adUnit', 'adSource', ]); const AD_JSON_NESTED = [ 'auxiliaryUi.messageRenderers.enforcementMessageViewModel', 'overlay.playerBarActionRenderer', ]; function deepPruneAdKeys(obj, depth) { if (!obj || typeof obj !== 'object' || depth > 8) return; if (Array.isArray(obj)) { obj.forEach(item => deepPruneAdKeys(item, depth + 1)); return; } for (const key of Object.keys(obj)) { if (AD_JSON_KEYS.has(key)) { delete obj[key]; } else if (typeof obj[key] === 'object') { deepPruneAdKeys(obj[key], depth + 1); } } } function pruneNested(obj, path) { const parts = path.split('.'); let target = obj; for (let i = 0; i < parts.length - 1; i++) { if (!target || typeof target !== 'object') return; target = target[parts[i]]; } if (target && typeof target === 'object') { delete target[parts[parts.length - 1]]; } } // Patch JSON.parse — universal const _JSONParse = JSON.parse; JSON.parse = function() { const result = _JSONParse.apply(this, arguments); if (result && typeof result === 'object') { deepPruneAdKeys(result, 0); for (const path of AD_JSON_NESTED) { pruneNested(result, path); } } return result; }; // Patch Response.prototype.json — universal for fetch APIs const _responseJson = Response.prototype.json; Response.prototype.json = function() { return _responseJson.apply(this, arguments).then(data => { if (data && typeof data === 'object') { deepPruneAdKeys(data, 0); for (const path of AD_JSON_NESTED) { pruneNested(data, path); } } return data; }); }; // ═══════════════════════════════════════════════════════════════════════════════ // UNIVERSAL FETCH/XHR BLOCK — blocks ad-related network requests on all sites // ═══════════════════════════════════════════════════════════════════════════════ const AD_URL_PATTERNS = [ '/pagead/', '/ptracking', '/api/stats/ads', '/ad_break', 'doubleclick.net', 'googlesyndication.com', 'googleadservices.com', 'imasdk.googleapis.com', 'securepubads.g.doubleclick.net', 'amazon-adsystem.com', 'twitchsvc.net', '/ads/bid', '/ads/log', '/ads/event', 'ad.doubleclick.net', 'pagead2.googlesyndication.com', 'stats.g.doubleclick.net', '2mdn.net', '/api/stats/qoe?adformat', 'play.google.com/log', 'video-ad-stats', '/adunit', '/vast/', ]; function isAdUrl(url) { if (!url) return false; return AD_URL_PATTERNS.some(p => url.includes(p)); } const _fetch = window.fetch; window.fetch = function(input, init) { const url = typeof input === 'string' ? input : (input instanceof Request ? input.url : ''); if (isAdUrl(url)) { return Promise.resolve(new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } })); } return _fetch.apply(this, arguments); }; const _xhrOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(method, url) { this.__muse_blocked = isAdUrl(url); return _xhrOpen.apply(this, arguments); }; const _xhrSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.send = function() { if (this.__muse_blocked) { Object.defineProperty(this, 'readyState', { get: () => 4, configurable: true }); Object.defineProperty(this, 'status', { get: () => 200, configurable: true }); Object.defineProperty(this, 'responseText', { get: () => '{}', configurable: true }); Object.defineProperty(this, 'response', { get: () => '{}', configurable: true }); setTimeout(() => { if (typeof this.onload === 'function') this.onload(new Event('load')); }, 0); return; } return _xhrSend.apply(this, arguments); }; // ═══════════════════════════════════════════════════════════════════════════════ // UNIVERSAL SCRIPT BLOCK — prevent ad SDK loading on all sites // ═══════════════════════════════════════════════════════════════════════════════ const AD_SCRIPT_PATTERNS = [ 'imasdk.googleapis.com', 'securepubads', 'pagead2.googlesyndication', 'adsbygoogle', 'googletag', 'gpt.js', 'pubads', 'amazon-adsystem.com/aax', 'moat', 'doubleclick.net/tag', ]; const _createElement = document.createElement.bind(document); document.createElement = function(tag) { const el = _createElement(tag); if (tag.toLowerCase() === 'script') { const _setSrc = Object.getOwnPropertyDescriptor(HTMLScriptElement.prototype, 'src')?.set; if (_setSrc) { Object.defineProperty(el, 'src', { set(value) { if (typeof value === 'string' && AD_SCRIPT_PATTERNS.some(p => value.includes(p))) { return; // silently block } _setSrc.call(this, value); }, get() { return el.getAttribute('src') || ''; }, configurable: true, }); } } return el; }; // ═══════════════════════════════════════════════════════════════════════════════ // VIDEO AD SKIP — auto-skip/fast-forward video ads when detected (all sites) // ═══════════════════════════════════════════════════════════════════════════════ function setupVideoAdSkipper() { const skipSelectors = [ '.ytp-ad-skip-button', '.ytp-ad-skip-button-modern', '.ytp-skip-ad-button', '[class*="skip-button"]', '[class*="skipButton"]', '[class*="ad-skip"]', 'button[class*="skip"]', ]; const adContainerSelectors = [ '.ytp-ad-module', '.ytp-ad-overlay-container', '.ytp-ad-text-overlay', '#player-ads', '#masthead-ad', '.ytd-promoted-sparkles-web-renderer', '.ytd-display-ad-renderer', '.ytd-promoted-video-renderer', '.ytd-ad-slot-renderer', '.ytd-in-feed-ad-layout-renderer', '[class*="ad-container"]', '[class*="ad-banner"]', '[class*="ad-slot"]', '.video-ads', '.ad-container', '#ad-display', ]; const observer = new MutationObserver(() => { // Auto-click skip buttons for (const sel of skipSelectors) { const btn = document.querySelector(sel); if (btn && btn.offsetParent !== null) { btn.click(); } } // Collapse ad containers (remove blank spaces) for (const sel of adContainerSelectors) { document.querySelectorAll(sel).forEach(el => { if (el.offsetHeight > 0) { el.style.display = 'none'; el.style.height = '0'; el.style.overflow = 'hidden'; el.style.margin = '0'; el.style.padding = '0'; } }); } // Fast-forward video ads (player showing ad state) const player = document.querySelector('.html5-video-player'); if (player && player.classList.contains('ad-showing')) { player.classList.remove('ad-showing'); const video = player.querySelector('video'); if (video && video.duration && isFinite(video.duration) && video.duration < 120) { video.currentTime = video.duration; video.playbackRate = 16; } } }); function startObserving() { if (document.body) { observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class'] }); } else { document.addEventListener('DOMContentLoaded', () => { observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class'] }); }); } } startObserving(); // Also run periodically to catch delayed ads setInterval(() => { for (const sel of adContainerSelectors) { document.querySelectorAll(sel).forEach(el => { el.style.display = 'none'; }); } // Skip any active video ad const player = document.querySelector('.html5-video-player.ad-showing'); if (player) { const video = player.querySelector('video'); if (video && video.duration && isFinite(video.duration)) { video.currentTime = video.duration; } } }, 1000); } setupVideoAdSkipper(); // ═══════════════════════════════════════════════════════════════════════════════ // TWITCH HLS MANIFEST PRUNING — remove ad segments from stream // ═══════════════════════════════════════════════════════════════════════════════ if (location.hostname.includes('twitch.tv')) { const origFetch = window.fetch; window.fetch = function(input, init) { const url = typeof input === 'string' ? input : (input instanceof Request ? input.url : ''); if (url.includes('.m3u8')) { return origFetch.apply(this, arguments).then(response => { const clone = response.clone(); return clone.text().then(text => { if (text.includes('stitched-ad') || text.includes('advertisement')) { const cleaned = text.split('\n').filter(line => { if (line.includes('#EXT-X-DATERANGE') && (line.includes('stitched-ad') || line.includes('advertisement'))) return false; if (line.includes('#EXT-X-SCTE35-OUT')) return false; return true; }).join('\n'); return new Response(cleaned, { status: response.status, statusText: response.statusText, headers: response.headers }); } return response; }); }); } return origFetch.apply(this, arguments); }; } // ═══════════════════════════════════════════════════════════════════════════════ // COSMETIC: Inject CSS to collapse ad containers immediately // ═══════════════════════════════════════════════════════════════════════════════ const adCss = ` .ytp-ad-module, .ytp-ad-overlay-container, .ytp-ad-text-overlay, #player-ads, #masthead-ad, .ytd-promoted-sparkles-web-renderer, .ytd-display-ad-renderer, .ytd-promoted-video-renderer, .ytd-ad-slot-renderer, .ytd-in-feed-ad-layout-renderer, .ytd-banner-promo-renderer, ytd-rich-item-renderer:has(.ytd-ad-slot-renderer), [class*="ad-container"], [class*="ad-banner"], .video-ads, .ad-container, #ad-display, .ad-showing .ytp-ad-player-overlay, .ytp-ad-action-interstitial { display: none !important; height: 0 !important; max-height: 0 !important; overflow: hidden !important; margin: 0 !important; padding: 0 !important; opacity: 0 !important; pointer-events: none !important; } `; const style = document.createElement('style'); style.id = '__muse_ad_collapse'; style.textContent = adCss; (document.head || document.documentElement).appendChild(style); })();