musealpha / src-tauri /resources /scripts /video_ad_scriptlets.js
asdf98's picture
Upload 112 files
3d7d9b5 verified
/**
* 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);
})();