Skip to content

Instantly share code, notes, and snippets.

@x0a
Last active September 22, 2023 16:12
Show Gist options
  • Save x0a/ccbc8aeb7e917fe5b49ce321293f4b18 to your computer and use it in GitHub Desktop.
Save x0a/ccbc8aeb7e917fe5b49ce321293f4b18 to your computer and use it in GitHub Desktop.
Forces the highest quality video on Reddit
// ==UserScript==
// @name Force highest resolution on Reddit
// @namespace http://tampermonkey.net/
// @version 0.2
// @description Parses the mpd file sent to reddit's video player and removes all but the highest resolution representations for each video period
// @author x0a
// @run-at document-start
// @match https://www.reddit.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=reddit.com
// @grant none
// ==/UserScript==
(function() {
'use strict';
const origOpen = XMLHttpRequest.prototype.open;
const origSend = XMLHttpRequest.prototype.send;
const highestResOnly = xml => {
const periods = Array.from(xml.querySelectorAll('Period'));
// for each period, find the highest bandwidth representation and cut out all the rest.
periods.forEach(period => {
const representations = period.querySelectorAll('AdaptationSet[contentType="video"] Representation');
const highest = Array.from(representations)
.map(el => ~~el.getAttribute('bandwidth'))
.reduce((highest, current) => current > highest ? current : highest, 0);
representations.forEach(representation => {
if(representation.getAttribute('bandwidth') !== '' + highest){
representation.remove(); // remove representations that dont match the highest bandwidth
}
})
})
return xml
}
const highestResOnlyText = string => {
const parser = new DOMParser();
const xml = parser.parseFromString(string, "text/xml");
const fixed = highestResOnly(xml);
return fixed.documentElement.outerHTML;
}
XMLHttpRequest.prototype.open = function () {
const [method, url, async, user, password] = arguments;
if(url.indexOf('/DASHPlaylist.mpd?') !== -1){
console.log('Found', url, this);
this.overrideSend = true;
}
return origOpen.apply(this, [method,
url,
async === undefined ? true : async,
user,
password]);
}
XMLHttpRequest.prototype.send = function () {
if(this.overrideSend){
const self = this;
const lastLoad = self.onload;
const lastLoadEnd = self.onloadend;
let modified = false;
const hookModifications = () => {
// since we happen to know that reddit doesn't set the XMLHttpRequest.responseType property on this request, I'm not bothering to handle responseText/response differently. they are both strings.
// similarly, reddit appears to use the onload event first rather than onloadend, so I'm not bothering to handle these seperately.
// but the code is there in case this changes.
if(modified) return;
const responseText = highestResOnlyText(self.responseText);
const response = responseText;
if(self.responseXML) {
try{
highestResOnly(self.responseXML);
}catch(e){
console.error('Could not parse response XML', e);
}
}
Object.defineProperties(self, {
response: {
get: function() {
console.error('Someone just tried to access me, check stack trace if you want to dig deeper');
return response;
}
},
responseText: {
get: function() {
console.error('Someone just tried to access me');
return responseText;
}
}
});
modified = true;
}
self.onload = function(){
hookModifications();
return lastLoad.apply(this, arguments);
}
self.onloadend = function(){
hookModifications(); // in theory, this is already done but meh.
return lastLoadEnd.apply(this, arguments);
}
}
return origSend.apply(this, arguments);
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment