Skip to content

Instantly share code, notes, and snippets.

@antronic
Last active August 11, 2024 11:42
Show Gist options
  • Save antronic/b9eaeb6ad4a61ddff0190cade75c8572 to your computer and use it in GitHub Desktop.
Save antronic/b9eaeb6ad4a61ddff0190cade75c8572 to your computer and use it in GitHub Desktop.
Grab the GUID of a Plex entry on demand
// ==UserScript==
// @name Plex GUID Grabber
// @namespace plex-guid-grabber
// @include http://plex.*/*
// @include https://plex.*/*
// @include *:32400/*
// @version 1.0.3
// @description Grab the GUID of a Plex entry on demand
// @icon https://app.plex.tv/desktop/favicon.ico
// @homepageURL https://gist.githubusercontent.com/antronic/b9eaeb6ad4a61ddff0190cade75c8572/raw/70453287de9400e2677da29036d28b81402d77a7/try.js
// @downloadURL https://gist.githubusercontent.com/antronic/b9eaeb6ad4a61ddff0190cade75c8572/raw/70453287de9400e2677da29036d28b81402d77a7/try.js
// @require https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.11/clipboard.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js
// @resource TOASTR_CSS https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css
// @grant GM_addStyle
// @grant GM_getResourceText
// ==/UserScript==
(function () {
"use strict";
const plexServer = window.location.origin;
let buttonContainer = null;
let clipboard = null;
// Add Toastr CSS
GM_addStyle(GM_getResourceText("TOASTR_CSS"));
// Configure Toastr
toastr.options = {
"closeButton": false,
"debug": false,
"newestOnTop": false,
"progressBar": true,
"positionClass": "toast-top-right",
"preventDuplicates": true,
"onclick": null,
"showDuration": "300",
"hideDuration": "1000",
"timeOut": "5000",
"extendedTimeOut": "1000",
"showEasing": "swing",
"hideEasing": "linear",
"showMethod": "fadeIn",
"hideMethod": "fadeOut"
};
function log(message) {
console.log(`PLEX_GUID_GRABBER: ${message}`);
}
log("Script initialized");
// Function to get GUID
async function getGUID() {
const posterElement = document.querySelector("[class^=MetadataSimplePosterCard-card-]");
if (!posterElement) {
toastr.warning("No Plex item selected. Please select an item and try again.");
return null;
}
const details = extractMetadataDetails(posterElement);
if (!details) {
toastr.error("Unable to extract metadata details. Please try again with a different item.");
return null;
}
const { metadataKey, token } = details;
const metadataUrl = `${plexServer}${metadataKey}?X-Plex-Token=${token}`;
try {
const response = await fetch(metadataUrl);
const text = await response.text();
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(text, "text/xml");
const directoryElement = xmlDoc.querySelector("Directory");
if (directoryElement) {
return directoryElement.getAttribute("guid");
} else {
throw new Error("Directory element not found in XML");
}
} catch (error) {
console.error("Plex GUID Grabber: Error fetching metadata:", error);
toastr.error("Error fetching GUID. Please try again.");
return null;
}
}
// Function to extract metadata key and token from poster element
function extractMetadataDetails(element) {
const linkElement = element.querySelector("a[class^=PosterCardLink-link-]");
const imgElement = element.querySelector("img");
if (linkElement && imgElement) {
const imgURL = new URL(imgElement.src);
const token = imgURL.searchParams.get("X-Plex-Token");
const urlParam = imgURL.searchParams.get("url");
const metadataKey = decodeURIComponent(urlParam).split("/thumb/")[0];
return { metadataKey, token };
}
return null;
}
// Function to add the GUID button
function addGUIDButton() {
if (buttonContainer && !document.getElementById("guid-button")) {
const button = document.createElement("button");
button.id = "guid-button";
button.setAttribute("aria-label", "Get and Copy GUID");
button.className = "_1v4h9jl0 _76v8d62 _76v8d61 _76v8d68 tvbry61 _76v8d6g _76v8d6h _1v25wbq1g _1v25wbq18";
button.innerHTML = `
<div class="_1h4p3k00 _1v25wbq8 _1v25wbq1w _1v25wbqg _1v25wbq1g _1v25wbq1c _1v25wbq14 _1v25wbq3g _1v25wbq2g">
<svg aria-hidden="true" class="rkbrtb0 rkbrtb1 rkbrtb3 _1v25wbq5k" height="24" width="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(2,2) scale(0.8333)">
<path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" fill="currentColor" fill-rule="evenodd"></path>
</g>
</svg>
</div>
`;
button.addEventListener('click', handleButtonClick);
buttonContainer.prepend(button);
log("GUID button added successfully");
}
}
// Function to handle button click
async function handleButtonClick() {
const guid = await getGUID();
if (guid) {
if (!clipboard) {
clipboard = new ClipboardJS('#guid-button', {
text: function () {
return guid;
}
});
clipboard.on('success', function (e) {
toastr.success(guid, "GUID copied successfully!");
e.clearSelection();
});
log("Clipboard.js initialized");
// Trigger the copy action for the first click
clipboard.onClick({ currentTarget: document.getElementById('guid-button') });
} else {
// For subsequent clicks, manually trigger the copy and show toast
navigator.clipboard.writeText(guid).then(function () {
toastr.success(guid, "GUID copied successfully!");
}).catch(function (err) {
toastr.error('Failed to copy GUID: ' + err);
});
}
}
}
// Function to check for button container and add button
function checkForButtonContainer() {
buttonContainer = document.querySelector(".PageHeaderRight-pageHeaderRight-j9Yjqh");
if (buttonContainer) {
addGUIDButton();
observer.disconnect();
log("Button container found and button added. Stopped observing for DOM changes.");
}
}
// Create and start the observer
const observer = new MutationObserver(checkForButtonContainer);
observer.observe(document.body, { childList: true, subtree: true });
log("Started observing for DOM changes");
// Initial check for button container
checkForButtonContainer();
log("Script setup complete");
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment