Last active
September 22, 2023 16:12
-
-
Save x0a/ccbc8aeb7e917fe5b49ce321293f4b18 to your computer and use it in GitHub Desktop.
Forces the highest quality video on Reddit
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==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