Maybe slightly tangential since this is about Safari - but have 𝗬𝗼𝘂𝗧𝘂𝗯𝗲 𝗮𝗱𝘀 𝗿𝗲𝗰𝗲𝗻𝘁𝗹𝘆 𝗿𝗲𝗮𝗽𝗽𝗲𝗮𝗿𝗲𝗱 𝗶𝗻 𝗖𝗵𝗿𝗼𝗺𝗲?<p>The declarativeWebRequest YouTube adblocker I threw together in half an hour a few months ago recently stopped working on my end too.<p>Opting out of targeted ads serves you the bottom of the barrel, which was enough motivation to waste a day of JS spelunking (YouTube's changes "conveniently" make declarative blocking nonviable).<p>Here's what works for me... and here's hoping it still works tomorrow :)<p><pre><code> ▶ manifest.json:
{
"name": "ytadblock",
"description": "(license: CC0)",
"version": "2.0",
"manifest_version": 2,
"declarative_net_request": {
"rule_resources": [{
"id": "1", "enabled": true, "path": "ytadblock.json"
}]
},
"permissions": [
"declarativeNetRequest",
"*://youtube.com/*",
"*://www.youtube.com/*"
],
"content_scripts": [
{
"matches": ["https://*.youtube.com/*"],
"js": ["contentscript.js"],
"run_at": "document_start"
}
],
"web_accessible_resources": ["inject.js"]
}
▶ ytadblock.json:
[{
"id": 1,
"priority": 1,
"action": { "type": "block" },
"condition": {
"regexFilter": ".*\\.googlevideo\\.com/.*ctier=",
"resourceTypes": [
"main_frame", "sub_frame", "script", "image", "xmlhttprequest", "media", "other"
]
}
}]
▶ contentscript.js:
console.log('hi from contentscript');
var s = document.createElement('script');
s.src = chrome.runtime.getURL('inject.js');
s.onload = function() { this.remove(); }
document.documentElement.prepend(s);
▶ inject.js:
'use strict';
(function() {
var parse = JSON.parse;
JSON.parse = function(input) {
var data = parse(input);
if (typeof data['playerResponse'] !== 'undefined') {
data.playerResponse.adPlacements = [];
data.playerResponse.playerAds = [];
}
return data;
}
var fetch_ = fetch;
fetch = async function(...args) {
let response = await fetch_(...args);
let text = await response.text();
if (response.url.match(/\/player\?/)) {
try {
var json = JSON.parse(text);
if (typeof json['playerAds'] !== 'undefined') {
json.adPlacements = [];
json.playerAds = [];
}
text = JSON.stringify(json);
} catch (e) {}
}
return new Response(text, {
status: response.status,
statusText: response.statusText,
headers: response.headers
});
};
var trap = {
get(target, key) {
if (key == 'create' || key == 'createAlternate') {
return function(...args) {
args[1].args.raw_player_response.adPlacements = [];
args[1].args.raw_player_response.playerAds = [];
return target[key](...args);
}
} else if (typeof target[key] === 'object' && (key == 'player' || key == 'Application')) {
return new Proxy(target[key], trap);
} else {
return target[key];
}
}
};
var yt = new Proxy({}, trap);
})();
</code></pre>
Notes:<p>- Ad video requests can actually be distinguished by the "ctier" parameter in the xxx.googlevideo.com URL. (I presume "c" is related to the "CSI" that seems to be everywhere on Google properties.) The sole declarative block is intended as a fallback in case the JavaScript hooks fail; you'll know if you hit these, because ad videos will spin for 5 seconds before they fail - and there might be MoRe ThAn OnE - and this may drive you very, very crazy. :)<p>- Initial page loads now contain a JavaScript blob with content and ad video info. After a lot of stumbling around I got the idea to turn the `yt` object into a Proxy and intercept the yt.player.Application.createAlternate() function, which said blob is passed into as an argument.<p>- YouTube seems to be using a mixture of requests to ".../player?" via Fetch and XHR POSTed requests to ".../watch?.*pbj=1" to fetch next-video-info when you click video links. Hooking XHR proved... depressingly tricky (...wow...), so I got the idea from <a href="https://greasyfork.org/en/scripts/32626-disable-youtube-video-ads/code" rel="nofollow">https://greasyfork.org/en/scripts/32626-disable-youtube-vide...</a> to hook JSON.parse instead (koooool). Hooking Fetch, or at least YouTube's use of Fetch, was thankfully not that hard.<p>The above is quite brittle, depends on how YouTube works <i>now</i>, and may get caught in the crossfire of unrelated site changes. ¯\_(ツ)_/¯*