// ==UserScript== // @name Show Subtitle/Audio Names and Media File Segment for Plex // @version 2.5 // @grant none // @include https://app.plex.tv/* // @description Add the subtitle, video, audio track titles and media version segment (stuff in {}) to the Plex Web app. // @namespace https://greasyfork.org/users/456605 // @license MIT; https://spdx.org/licenses/MIT.html#licenseText // @downloadURL https://update.greasyfork.icu/scripts/398883/Show%20SubtitleAudio%20Names%20and%20Media%20File%20Segment%20for%20Plex.user.js // @updateURL https://update.greasyfork.icu/scripts/398883/Show%20SubtitleAudio%20Names%20and%20Media%20File%20Segment%20for%20Plex.meta.js // ==/UserScript== function main () { function getMediaTitle(media) { let parts = media.Part; for (let k = 0; k < parts.length; k++) { if (!parts[k].hasOwnProperty("file")) continue; const format = parts[k].file.match(".*{([^}]*)}.*$"); if (format != null) return format[1]; } return "Unknown"; } function intercept(url, responseText) { if (url.indexOf("/library/metadata/") == -1 && url.indexOf("/status/sessions") == -1) return responseText; let response = JSON.parse(responseText); if (!response.hasOwnProperty("MediaContainer") || !response.MediaContainer.hasOwnProperty("Metadata")) return responseText; const meta = response.MediaContainer.Metadata; for (let i = 0; i < meta.length; i++) { if (!meta[i].hasOwnProperty("Media")) continue; let medias = meta[i].Media; for (let j = 0; j < medias.length; j++) { if (!medias[j].hasOwnProperty("Part")) continue; if (!medias[j].hasOwnProperty("title")) medias[j].title = getMediaTitle(medias[j]); let parts = medias[j].Part; for (let k = 0; k < parts.length; k++) { if (!parts[k].hasOwnProperty("Stream")) continue; let streams = parts[k].Stream; for (let l = 0; l < streams.length; l++) { if (!streams[l].hasOwnProperty("displayTitle") || !streams[l].hasOwnProperty("title")) continue; streams[l].displayTitle = streams[l].displayTitle + " (" + streams[l].title + ")"; } } } } return JSON.stringify(response); } // From https://stackoverflow.com/questions/26447335/ (function() { // create XMLHttpRequest proxy object var oldXMLHttpRequest = XMLHttpRequest; // define constructor for my proxy object XMLHttpRequest = function() { var actual = new oldXMLHttpRequest(); var self = this; this.onreadystatechange = null; // this is the actual handler on the real XMLHttpRequest object actual.onreadystatechange = function() { if (this.readyState == 4 && (actual.responseType == '' || actual.responseType == 'text')) { try { self._responseText = intercept(actual.responseURL, actual.responseText); } catch (err) { console.error(err); } } if (self.onreadystatechange) { return self.onreadystatechange(); } }; // add all proxy getters/setters ["status", "statusText", "responseType", "response", "readyState", "responseXML", "upload", "ontimeout, timeout", "withCredentials", "onload", "onerror", "onprogress"].forEach(function(item) { Object.defineProperty(self, item, { get: function() {return actual[item];}, set: function(val) {actual[item] = val;} }); }); // add all proxy getters/setters ["responseText"].forEach(function(item) { Object.defineProperty(self, item, { get: function() { if (self.hasOwnProperty("_" + item)) { return self["_" + item]; } else { return actual[item]; } }, set: function(val) {actual[item] = val;} }); }); // add all pure proxy pass-through methods ["addEventListener", "send", "open", "abort", "getAllResponseHeaders", "getResponseHeader", "overrideMimeType", "setRequestHeader"].forEach(function(item) { Object.defineProperty(self, item, { value: function() {return actual[item].apply(actual, arguments);} }); }); } })(); } // From https://stackoverflow.com/questions/2303147/ var script = document.createElement('script'); script.appendChild(document.createTextNode('('+ main +')();')); (document.body || document.head || document.documentElement).appendChild(script);