NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Cruncyroll Hard Subs > No Subs // @namespace http://tampermonkey.net/ // @version 0.1 // @description Use hard subs if soft subs are not availible // @author Netaware // @license MIT // @match https://www.crunchyroll.com/* // @grant unsafeWindow // ==/UserScript== (function() { 'use strict'; var changedVilosPlayer = false; var origTokenList = DOMTokenList.prototype.add; var origVilosPlayer; // This is a setter function. // It reads the config, and if no ${lang} soft subs are availible, // it deletes the soft subs config, forcing the player to use hard subs. function scanConfig(config) { try { var lang= this.player.language; var soft_subs_lang = config.subtitles.map(sub => sub.language); if(!soft_subs_lang.includes(lang)) { console.log('[hs > ns] deleting soft subtitles'); delete config.subtitles; }; } catch (error) { console.log('[hs > ns] had trouble reading config'); console.log(error); }; // set "vilos.config.media" to its real (possibly modified) value. Object.defineProperty(this, 'media', {value:config}); }; unsafeWindow.DOMTokenList.prototype.add = function(item) { // Is "VilosPlayer" defined and have we already made our changes? if(unsafeWindow.VilosPlayer !== undefined && !changedVilosPlayer) { changedVilosPlayer = true; origVilosPlayer = unsafeWindow.VilosPlayer; // wrap "VilosPlayer" to set "vilos.config.media" to a setter function unsafeWindow.VilosPlayer = function() { origVilosPlayer.call(this,...arguments); Object.defineProperty(this.config, 'media', {set: scanConfig}); }; } return origTokenList.call(this, ...arguments); }; })(); // Plan of attack: // If the video player is given a configuration which has // hard subs and soft subs, then the player will only play soft subs. // This is the case even if the customer's perfered language is $LANG, // there are hard subs in $LANG, and no soft subs in $LANG. // This prevents the customer from viewing subs in $LANG even // if such subs are availible as hard subs. // Therefore we need to intercept the configuration before it is passed // to the video player, check if soft subs aren't availible in $LANG, // and delete soft subs from the configuration if so. // The relevent bits of Crunchyroll's code is as follows: // <script src=".../vilosplayer.js"> // //inside vilosplayer.js // function VilosPlayer(...) { ... } // Constructs a vilos player object // </script> // <script> // (function () { // anon function all the stuff we care about happens in // function buildPlayer(...) { // var vilos = new VilosPlayer(); // vilos.config.player.language = ...; // the customer's preferred language // ... // vilos.config.media = { ... }; // where the data we want to read/modify is // ... // } // // function setStylingWideAspectRatio() { // ... // (...).classList.add(...) // our chance to change global scope // ... // } // ... // document.addEventListener(..., function(e) { // setStylingWideAspectRatio(); // change VilosPlayer here // var vilosPlayer = buildPlayer(...); // need to change VilosPlayer before this is called // vilosPlayer.load(...); // we want to read and change the config before here // }); // }()); // </script> // So we want to read and maybe modify "vilos.config.media" // before "vilosPlayer.load" is called. // Since all this is encapsulated in an anon function, // the only way we can do this is to change the global scope. // Changing the "VilosPlayer" constructor to make "vilos.config.media" // a setter function should do exactly what we want. // Unfortunatly, this script runs before "VilosPlayer" is defined. // If we try to change "window.VilosPlayer" to a setter, // we get thrown an error because VilosPlayer uses function declaration syntax. // HOWEVER, the "SetStylingWideAspectRatio" function calls // (...).classList.add(...), which we can override by setting // "DOMTokenList.prototype.add". // So in conclusion, we change the "DOMTokenList.prototype.add" // so that it checks if "VilosPlayer" is defined when it is called. // If it is, then "VilosPlayer" is wrapped in a function which // makes "vilos.config.media" a setter. // This allows us to read and change the value of "vilos.config.media" when it is defined. // Yea, I know this is brittle. I can't think of a better way though.