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.