您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Allow Plex to connect to MPV Shim running on the same computer without a local Plex server.
当前为
// ==UserScript== // @name MPV Shim Local Connection // @version 1 // @grant GM.xmlHttpRequest // @include https://app.plex.tv/* // @connect 127.0.0.1 // @description Allow Plex to connect to MPV Shim running on the same computer without a local Plex server. // @namespace https://greatest.deepsurf.us/users/456605 // ==/UserScript== function messageHandler(event) { let message; try { message = JSON.parse(event.data); } catch(_) { return; } if (message.eventName != "gm_xhr_send") return; let parsedURL = new URL(message.url); parsedURL.host = "127.0.0.1:3000"; parsedURL.protocol = "http:"; GM.xmlHttpRequest({ method: 'GET', url: parsedURL.toString(), headers: { "X-Plex-Client-Identifier": parsedURL.searchParams.get("X-Plex-Client-Identifier") }, onload: function (result) { window.postMessage(JSON.stringify({ eventName: "gm_xhr_recv", response: result.responseText, headers: result.responseHeaders, id: message.id }), "*"); } }); } window.addEventListener("message", messageHandler, false); function main () { // From https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript function uuidv4() { return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) ); } let clientId = localStorage.getItem('gmpx_uuid'); if (!clientId) { clientId = uuidv4(); localStorage.setItem('gmpx_uuid', clientId); } window.gmpx_eventHandlers = {}; window.gmpx_id = 0; const parser = new DOMParser(); const serializer = new XMLSerializer(); function gmpx_messageHandler(event) { let message; try { message = JSON.parse(event.data); } catch(_) { return; } if (message.eventName != "gm_xhr_recv") return; window.gmpx_eventHandlers[message.id](message); window.gmpx_eventHandlers[message.id] = undefined; } window.addEventListener("message", gmpx_messageHandler, false); function intercept(url, responseText) { if (url == "") return; let parsedURL = new URL(url); if (parsedURL.pathname == "/clients") { const xml = parser.parseFromString(responseText, "text/xml"); const s = xml.createElement("Server") s.setAttribute("name", "local (direct)"); s.setAttribute("host", "127.0.0.1"); s.setAttribute("address", "127.0.0.1"); s.setAttribute("port", "3000"); s.setAttribute("machineIdentifier", clientId); s.setAttribute("version", "1.0"); s.setAttribute("protocol", "plex"); s.setAttribute("product", "Plex MPV Shim"); s.setAttribute("deviceClass", "pc"); s.setAttribute("protocolVersion", "1"); s.setAttribute("protocolCapabilities", "timeline,playback,navigation,playqueues"); xml.children[0].appendChild(s); return serializer.serializeToString(xml); } else { return responseText; } } // From https://stackoverflow.com/questions/26447335/ // Please note: This is very dirty in the way it works. Don't expect it to work perfectly in all areas. (function() { // create XMLHttpRequest proxy object var oldXMLHttpRequest = XMLHttpRequest; // define constructor for my proxy object XMLHttpRequest = function() { var actual = new oldXMLHttpRequest(); var self = this; self.override = false; 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) { self.responseText = actual.responseText; } } if (self.onreadystatechange) { return self.onreadystatechange(); } }; // add all proxy getters/setters ["response", "upload", "ontimeout, timeout", "withCredentials", "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 ["statusText", "status", "readyState", "responseURL", "responseType"].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", "abort", "getResponseHeader", "overrideMimeType", "setRequestHeader"].forEach(function(item) { Object.defineProperty(self, item, { value: function() { if (self.override) { return; } return actual[item].apply(actual, arguments); } }); }); self.open = function() { if (arguments[0] == "GET" && (new URL(arguments[1])).searchParams.get("X-Plex-Target-Client-Identifier") == clientId) { const url = arguments[1]; self.override = true; self._readyState = 1; self._send = function() { const id = window.gmpx_id++; self._responseURL = url; self._responseType = ""; window.gmpx_eventHandlers[id] = function(result) { self._readyState = 4; self._status = 200; self._statusText = "OK"; self.responseText = result.response; self.headers = result.headers; if (self.onreadystatechange) { self.onreadystatechange(); } if (self._onload) { self._onload(); } }; window.postMessage(JSON.stringify({ eventName: "gm_xhr_send", url: url, id: id }), "*"); } } else { return actual.open.apply(actual, arguments); } } self.send = function() { if (self.override) { self._send(); } else { return actual.send.apply(actual, arguments); } } self.getAllResponseHeaders = function() { if (self.override) { const headers = self.headers.split("\r\n"); for (let i = 0; i < headers.length; i++) { if (headers[i].indexOf("x-plex-client-identifier") >= 0) { headers[i] = "x-plex-client-identifier: " + clientId; } } return headers.join("\r\n"); } else { return actual.getAllResponseHeaders.apply(actual, arguments); } } Object.defineProperty(self, "responseXML", { get: function() { if (self.override) { return parser.parseFromString(self.responseText, "text/xml"); } else { return actual[item]; } } }); Object.defineProperty(self, "onload", { get: function() { if (self.override) return self._onload; return actual.onload;}, set: function(val) { if (self.override) self._onload = val; else actual.onload = val;} }); } })(); } // From https://stackoverflow.com/questions/2303147/ var script = document.createElement('script'); script.appendChild(document.createTextNode('('+ main +')();')); (document.body || document.head || document.documentElement).appendChild(script);