3 msยท
Maybe slightly tangential since this is about Safari - but have ๐ฌ๐ผ๐๐ง๐๐ฏ๐ฒ ๐ฎ๐ฑ๐ ๐ฟ๐ฒ๐ฐ๐ฒ๐ป๐๐น๐ ๐ฟ๐ฒ๐ฎ๐ฝ๐ฝ๐ฒ๐ฎ๐ฟ๐ฒ๐ฑ ๐ถ๐ป ๐๐ต๐ฟ๐ผ๐บ๐ฒ? The declarativeW
by exikyut 5y ago
Maybe slightly tangential since this is about Safari - but have ๐ฌ๐ผ๐๐ง๐๐ฏ๐ฒ ๐ฎ๐ฑ๐ ๐ฟ๐ฒ๐ฐ๐ฒ๐ป๐๐น๐ ๐ฟ๐ฒ๐ฎ๐ฝ๐ฝ๐ฒ๐ฎ๐ฟ๐ฒ๐ฑ ๐ถ๐ป ๐๐ต๐ฟ๐ผ๐บ๐ฒ?
The declarativeWebRequest YouTube adblocker I threw together in half an hour a few months ago recently stopped working on my end too.
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).
Here's what works for me... and here's hoping it still works tomorrow :)
โถ 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);
})();
Notes:
- 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. :)
- 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.
- 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 https://greasyfork.org/en/scripts/32626-disable-youtube-video-ads/code https://greasyfork.org/en/scripts/32626-disable-youtube-vide... to hook JSON.parse instead (koooool). Hooking Fetch, or at least YouTube's use of Fetch, was thankfully not that hard.
The above is quite brittle, depends on how YouTube works now, and may get caught in the crossfire of unrelated site changes. ยฏ\_(ใ)_/ยฏ*
- exikyut 5y agoYay; the /player endpoint can also use XHR. Add the following before the 'return' in the JSON.parse intercept: if (typeof data['adPlacements'] !== 'undefined') { data.adPlacements = []; data.playerAds = []; }