Add new scriptlet — `trusted-replace-xhr-response`
I think that it could be useful in case of some SSAI, like for example here - https://github.com/AdguardTeam/AdguardFilters/issues/44461
Steps to reproduce
- Use AdGuard extension which doesn't support
$replacerules - Add this rule to user rules (it looks like that it's old exception and I will probably remove it soon, but anyway):
@@||player.theplatform.com^$jsinject,badfilter
- Go to - https://www.nbc.com/the-six-million-dollar-man/video/the-privacy-of-the-mind/3762893 US IP may be required
- Play video (ads is not blocked)
I guess this link may be useful - https://stackoverflow.com/questions/26447335/how-can-i-modify-the-xmlhttprequest-responsetext-received-by-another-function
Example basing on a code from one of the answers from the link above:
(() => {
var open_prototype = XMLHttpRequest.prototype.open,
intercept_response = function (urlpattern, callback) {
XMLHttpRequest.prototype.open = function () {
arguments['1'].match(urlpattern) && this.addEventListener('readystatechange', function (event) {
if (this.readyState === 4) {
var response = callback(event.target.responseText);
Object.defineProperty(this, 'response', {
writable: true
});
Object.defineProperty(this, 'responseText', {
writable: true
});
this.response = this.responseText = response;
}
});
return open_prototype.apply(this, arguments);
};
};
intercept_response(/manifest\..*\.theplatform\.com\/.*\/.*\.m3u8\?.*|manifest\..*\.theplatform\.com\/.*\/*\.meta.*/, function (response) {
var new_response = response.replaceAll(/#EXTINF:.*\n.*tvessaiprod\.nbcuni\.com\/video\/[\s\S]*?#EXT-X-DISCONTINUITY|#EXT-X-VMAP-AD-BREAK[\s\S]*?#EXT-X-ENDLIST/g, '');
console.log("Test: ", new_response)
return new_response;
});
})();
player.theplatform.com#%#(()=>{var e,t,s=XMLHttpRequest.prototype.open;e=/manifest\..*\.theplatform\.com\/.*\/.*\.m3u8\?.*|manifest\..*\.theplatform\.com\/.*\/*\.meta.*/,t=function(e){e=e.replaceAll(/#EXTINF:.*\n.*tvessaiprod\.nbcuni\.com\/video\/[\s\S]*?#EXT-X-DISCONTINUITY|#EXT-X-VMAP-AD-BREAK[\s\S]*?#EXT-X-ENDLIST/g,"");return console.log("Test: ",e),e},XMLHttpRequest.prototype.open=function(){return arguments[1].match(e)&&this.addEventListener("readystatechange",function(e){4===this.readyState&&(e=t(e.target.responseText),Object.defineProperty(this,"response",{writable:!0}),Object.defineProperty(this,"responseText",{writable:!0}),this.response=this.responseText=e)}),s.apply(this,arguments)}})();
Or by using Proxy:
(() => {
const adsURL = /manifest\..*\.theplatform\.com\/.*\/.*\.m3u8\?.*|manifest\..*\.theplatform\.com\/.*\/*\.meta.*/;
const adsRegex = /#EXTINF:.*\n.*tvessaiprod\.nbcuni\.com\/video\/[\s\S]*?#EXT-X-DISCONTINUITY|#EXT-X-VMAP-AD-BREAK[\s\S]*?#EXT-X-ENDLIST/g;
const wrapper = async (target, thisArg, args) => {
const xhrURL = args[1];
if (typeof xhrURL !== 'string' || xhrURL.length === 0) {
return Reflect.apply(target, thisArg, args);
}
if (xhrURL.match(adsURL)) {
thisArg.addEventListener('readystatechange', function (event) {
if (thisArg.readyState === 4) {
const response = thisArg.response;
Object.defineProperty(thisArg, 'response', {
writable: true
});
Object.defineProperty(thisArg, 'responseText', {
writable: true
});
const responseWithoutAds = response.replaceAll(adsRegex, "");
thisArg.response = responseWithoutAds;
thisArg.responseText = responseWithoutAds;
}
});
}
return Reflect.apply(target, thisArg, args);
};
const handler = {
apply: wrapper
};
window.XMLHttpRequest.prototype.open = new Proxy(window.XMLHttpRequest.prototype.open, handler);
})();
Rule:
player.theplatform.com#%#(()=>{window.XMLHttpRequest.prototype.open=new Proxy(window.XMLHttpRequest.prototype.open,{apply:async(a,b,c)=>{const d=c[1];return"string"!=typeof d||0===d.length?Reflect.apply(a,b,c):(d.match(/manifest\..*\.theplatform\.com\/.*\/.*\.m3u8\?.*|manifest\..*\.theplatform\.com\/.*\/*\.meta.*/)&&b.addEventListener("readystatechange",function(){if(4===b.readyState){const a=b.response;Object.defineProperty(b,"response",{writable:!0}),Object.defineProperty(b,"responseText",{writable:!0});const c=a.replaceAll(/#EXTINF:.*\n.*tvessaiprod\.nbcuni\.com\/video\/[\s\S]*?#EXT-X-DISCONTINUITY|#EXT-X-VMAP-AD-BREAK[\s\S]*?#EXT-X-ENDLIST/g,"");b.response=c,b.responseText=c}}),Reflect.apply(a,b,c))}})})();
Should be rather straightforward to implement, but this one also needs to be a "trusted" scriptlet.
Hi. In fact, that would be very cool! :thumbsup:
However, could we consider the possibility of having in agtrusted- scriptlets, for simplicity and consistency, the equivalent of these (with identical or similar name)? :
$replace-> in a scriptlet version, possiblyagtrusted-replace(therefore limited to XMLHttpRequest and fetch requests),$hls-> in a scriptlet version, possiblyagtrusted-hls(logically limited to XMLHttpRequest and possibly fetch requests).
Examples for $hls:
Click to see them …
Removes all segments with the matching URL
||example.org^$hls=\/videoplayback^?*&source=dclk_video_ads
:arrow_right_hook: example.org#%#//scriptlet('agtrusted-hls', '\/videoplayback^?*&source=dclk_video_ads')
Achieves more or less the same with a regex instead of a URL pattern
||example.org^$hls=/\/videoplayback\/?\?.*\&source=dclk_video_ads/i
:a: :arrow_right_hook: example.org#%#//scriptlet('agtrusted-hls', '/\/videoplayback\/?\?.*\&source=dclk_video_ads/i')
:b: :arrow_right_hook: example.org#%#//scriptlet('agtrusted-hls', '/\/videoplayback\/?\?.*\&source=dclk_video_ads/', 'i')
:bulb: B is prettier but less practical for quick copy/paste manual porting. And also less flexible for the future (addition of parameter).
:warning: Automatic porting during list generation can also be considered, however can be problematic because a proper video ads removal sometimes requires json-prune as a complement (due to the fact that no complete porting of $replace possible, i.e. for JSON data present in files loaded via a simple <script> like webapp-[…].js), so in automated generation it would end up with a half-baked solution.
Removes all segments which have the matching tag
||example.org^$hls=/#UPLYNK-SEGMENT:.*\,ad/t
:a: :arrow_right_hook: example.org#%#//scriptlet('agtrusted-hls', '/#UPLYNK-SEGMENT:.*\,ad/t')
:b: :arrow_right_hook: example.org#%#//scriptlet('agtrusted-hls', '/#UPLYNK-SEGMENT:.*\,ad/', 't')
(Same remark regarding parameters.)
Click to see them (whitelisting part) …
"Disable all $hls rules for responses from URLs matching ||example.org^"
@@||example.org^$hls
:arrow_right_hook: example.org#@%#//scriptlet('agtrusted-hls')
"Disable all $hls rules with the value of the hls modifier equal to text for responses from URLs matching ||example.org^"
@@||example.org^$hls=text
:arrow_right_hook: example.org#@%#//scriptlet('agtrusted-hls', 'text')
:arrow_right_hook: example.org#@%#//scriptlet('agtrusted-hls', 'rulescontaining:te') (broader, as an idea of possible evolution :bulb:)
:bulb: I tried to keep it simple. However, naturally, an additional parameter (possibly optional for the case of agtrusted-hls, although a really speed optimized HLS auto-detection is not so easy I would say) to limit to certain URLs (e.g. certain specific '.m3u8' URLs only for example) for personal reasons or for optimization reasons remains ideal. This, in addition to other conceivable optimizations, such as the use of: [$domain=example.org,path=/player.html]#%#//scriptlet('agtrusted-hls', '[…]').
:information_source: Just for info, besides a fix to the current $replace system (meanwhile fixed by @AdamWr), I had developed (successfully) during my tests a version modifying XMLHttpRequest.prototype.open end of year 2021 for pluto.tv (used in combination with a json-prune rule). I finally gave up the idea of committing this because with the Manifest V3, this JS rule would not have lasted long … Just a short year, until January 2023. So I was planning instead to make this very request for a scriptlet. Therefore, this issue comes at the right time. Thank you @AdamWr! :thumbsup: :relaxed:
@AdamWr The video https://www.nbc.com/the-six-million-dollar-man/video/the-privacy-of-the-mind/3762893 is not available anymore. Any other video that can be used as an example?
I guess that any other free video from the same website.
For example this one - https://www.nbc.com/thats-my-jam/video/keke-palmer-saweetie-vs-joel-mchale-william/9000235331