Last active
January 31, 2024 19:33
-
-
Save JonnyWong16/ec660094e4cd7233f1dd96b4b4d62e21 to your computer and use it in GitHub Desktop.
Open the Plex metadata XML info for any media page with Ctrl+I
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 Open Plex Metadata XML | |
// @namespace https://app.plex.tv | |
// @version 1.3 | |
// @description Open the Plex metadata XML info for any media page with Ctrl+I. | |
// @author JonnyWong16 | |
// @homepage https://gist.github.com/JonnyWong16/ec660094e4cd7233f1dd96b4b4d62e21 | |
// @downloadURL https://gist.github.com/JonnyWong16/ec660094e4cd7233f1dd96b4b4d62e21/raw/openPlexMetadataXML.user.js | |
// @updateURL https://gist.github.com/JonnyWong16/ec660094e4cd7233f1dd96b4b4d62e21/raw/openPlexMetadataXML.user.js | |
// @match https://app.plex.tv/* | |
// @grant none | |
// ==/UserScript== | |
// Based on https://github.com/piplongrun/piplongrun.github.io/blob/master/plxdwnld/bookmarklet.js | |
(function() { | |
'use strict'; | |
const clientIdRegex = new RegExp("(?:server|media)\/([a-f0-9]{40})|provider\/(tv\.plex\.provider\.(?:discover|metadata|vod))"); | |
const apiResourceUrl = "https://plex.tv/api/resources?includeHttps=1&X-Plex-Token={token}"; | |
const accessTokenXpath = "//Device[@clientIdentifier='{clientId}']/@accessToken"; | |
const baseUriXpath = "//Device[@clientIdentifier='{clientId}']/Connection[@local='0']/@uri"; | |
const includeUrlParams = { | |
checkFiles: 1, | |
includeAllConcerts: 1, | |
includeBandwidths: 1, | |
includeChapters: 1, | |
includeChildren: 1, | |
includeConcerts: 1, | |
includeExternalMedia: 1, | |
includeExtras: 1, | |
includeGeolocation: 1, | |
includeLoudnessRamps: 1, | |
includeMarkers: 1, | |
includeOnDeck: 1, | |
includePopularLeaves: 1, | |
includePreferences: 1, | |
includeRelated: 1, | |
includeRelatedCount: 1, | |
includeReviews: 1, | |
includeStations: 1 | |
} | |
const includeUrlParamsServer = { | |
includeFields: 'thumbBlurHash,artBlurHash' | |
} | |
let myPlexAccessToken = localStorage.myPlexAccessToken; | |
const getXML = function(url, callback) { | |
const request = new XMLHttpRequest(); | |
request.onreadystatechange = function() { | |
if (request.readyState == 4 && request.status == 200) { | |
callback(request.responseXML); | |
} | |
}; | |
request.open('GET', url); | |
request.send(); | |
}; | |
const openXML = function(baseUri, accessToken, urlKey, server) { | |
let XMLUrlParams = includeUrlParams; | |
if (server) { | |
XMLUrlParams = { | |
...XMLUrlParams, | |
...includeUrlParamsServer | |
} | |
} | |
XMLUrlParams['X-Plex-Token'] = accessToken; | |
const [uriKey, uriParams] = decodeURIComponent(urlKey).split('?'); | |
if (uriParams) { | |
XMLUrlParams = { | |
...Object.fromEntries(new URLSearchParams(uriParams)), | |
...XMLUrlParams | |
} | |
} | |
const XMLUrl = new URL(baseUri); | |
XMLUrl.pathname = uriKey; | |
XMLUrl.search = new URLSearchParams(XMLUrlParams).toString(); | |
window.open(XMLUrl.toString()); | |
}; | |
const viewXML = function() { | |
const clientId = clientIdRegex.exec(window.location.href); | |
const urlParams = new URLSearchParams(window.location.href.split('?')[1]); | |
const urlKey = urlParams.get('key'); | |
if (clientId && clientId.length == 3 && urlKey) { | |
if (clientId[2] === 'tv.plex.provider.discover' || clientId[2] === 'tv.plex.provider.metadata' || clientId[2] === 'tv.plex.provider.vod') { | |
const baseUri = 'https://metadata.provider.plex.tv'; | |
const accessToken= myPlexAccessToken; | |
openXML(baseUri, accessToken, urlKey); | |
} else { | |
getXML(apiResourceUrl.replace('{token}', myPlexAccessToken), function(xml) { | |
const baseUriNode = xml.evaluate(baseUriXpath.replace('{clientId}', clientId[1]), xml, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); | |
const accessTokenNode = xml.evaluate(accessTokenXpath.replace('{clientId}', clientId[1]), xml, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); | |
if (accessTokenNode.singleNodeValue && baseUriNode.singleNodeValue) { | |
const baseUri = baseUriNode.singleNodeValue.textContent; | |
const accessToken = accessTokenNode.singleNodeValue.textContent; | |
openXML(baseUri, accessToken, urlKey, true); | |
} else { | |
alert('Unable to find a valid accessToken.'); | |
} | |
}); | |
} | |
} else { | |
alert('You are currently not viewing a media page.'); | |
} | |
} | |
document.addEventListener('keydown', function(e) { | |
// Press Ctrl+I | |
if (e.keyCode == 73 && !e.shiftKey && e.ctrlKey && !e.altKey && !e.metaKey) { | |
if (typeof myPlexAccessToken != 'undefined') { | |
viewXML(); | |
} | |
} | |
}, false); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment