// ==UserScript== // @name Plex GUID Grabber // @namespace @soitora/plex-guid-grabber // @description Grab the GUID of a Plex entry on demand // @version 3.5.0 // @license MPL-2.0 // @icon https://app.plex.tv/desktop/favicon.ico // @homepageURL https://soitora.com/Plex-GUID-Grabber/ // @include *:32400/* // @include *://plex.*/* // @include https://app.plex.tv/* // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/js-yaml/4.1.0/js-yaml.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.11/clipboard.min.js // @require https://cdn.jsdelivr.net/npm/sweetalert2@11 // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_getResourceText // @grant GM_setClipboard // @run-at document-end // @downloadURL https://update.greasyfork.icu/scripts/525305/Plex%20GUID%20Grabber.user.js // @updateURL https://update.greasyfork.icu/scripts/525305/Plex%20GUID%20Grabber.meta.js // ==/UserScript== GM_addStyle(`button[id$="-guid-button"], button[id$="-yaml-button"] { margin-right: 4px; } button[id$="-guid-button"]:not([id="imdb-guid-button"]):hover img, button[id$="-yaml-button"]:not([id="imdb-yaml-button"]):hover img { filter: invert(100%) grayscale(100%) contrast(120%); } button[id="imdb-guid-button"]:hover img, button[id="imdb-yaml-button"]:hover img { filter: grayscale(100%) contrast(120%); } button[id="imdb-guid-button"] img, button[id="imdb-yaml-button"] img { width: 30px !important; height: 30px !important; } .pgg-toast-container { min-width: 400px !important; max-width: 800px !important; } .pgg-toast-yaml { white-space: pre-wrap; font-family: monospace; } `); // Initialize GM values if they don't exist function initializeGMValues() { if (GM_getValue("USE_SOCIAL_BUTTONS") === undefined) { GM_setValue("USE_SOCIAL_BUTTONS", true); console.log(LOG_PREFIX, LOG_STYLE, "Created USE_SOCIAL_BUTTONS storage"); } if (GM_getValue("SOCIAL_BUTTON_SEPARATION") === undefined) { GM_setValue("SOCIAL_BUTTON_SEPARATION", true); console.log(LOG_PREFIX, LOG_STYLE, "Created SOCIAL_BUTTON_SEPARATION storage"); } if (GM_getValue("USE_PAS") === undefined) { GM_setValue("USE_PAS", false); console.log(LOG_PREFIX, LOG_STYLE, "Created USE_PAS storage"); } if (GM_getValue("TMDB_API_READ_ACCESS_TOKEN") === undefined) { GM_setValue("TMDB_API_READ_ACCESS_TOKEN", ""); console.log(LOG_PREFIX, LOG_STYLE, "Created TMDB_API_READ_ACCESS_TOKEN storage"); } if (GM_getValue("TMDB_LANGUAGE") === undefined) { GM_setValue("TMDB_LANGUAGE", "en-US"); console.log(LOG_PREFIX, LOG_STYLE, "Created TMDB_LANGUAGE storage"); } if (GM_getValue("TVDB_API_KEY") === undefined) { GM_setValue("TVDB_API_KEY", ""); console.log(LOG_PREFIX, LOG_STYLE, "Created TVDB_API_KEY storage"); } if (GM_getValue("TVDB_SUBSCRIBER_PIN") === undefined) { GM_setValue("TVDB_SUBSCRIBER_PIN", ""); console.log(LOG_PREFIX, LOG_STYLE, "Created TVDB_SUBSCRIBER_PIN storage"); } if (GM_getValue("TVDB_LANGUAGE") === undefined) { GM_setValue("TVDB_LANGUAGE", "eng"); console.log(LOG_PREFIX, LOG_STYLE, "Created TVDB_LANGUAGE storage"); } } // SweetAlert2 Toast const Toast = Swal.mixin({ toast: true, position: "bottom-right", showConfirmButton: false, timer: 5000, timerProgressBar: true, width: "auto", customClass: { container: "pgg-toast-container", }, }); // Variables let rightButtonContainer = null; // Constants const LOG_PREFIX = "%c🔍 PGG"; const DEBUG_PREFIX = "%c🔍 PGG %cDebug"; const ERROR_PREFIX = "%c🔍 PGG %cError"; const LOG_STYLE = "color: cyan;"; const COLOR_GREEN = "color: lime; font-weight: bold;"; const COLOR_CYAN = "color: cyan; font-weight: bold;"; const ERROR_STYLE = "color: cyan; font-weight: bold; background-color: red;"; const DEBOUNCE_DELAY = 100; const BUTTON_FADE_DELAY = 50; const BUTTON_MARGIN = "8px"; // User configuration - Set these values in your userscript manager const USE_SOCIAL_BUTTONS = GM_getValue("USE_SOCIAL_BUTTONS", true); const SOCIAL_BUTTON_SEPARATION = GM_getValue("SOCIAL_BUTTON_SEPARATION", true); const USE_PAS = GM_getValue("USE_PAS", false); const TMDB_API_READ_ACCESS_TOKEN = GM_getValue("TMDB_API_READ_ACCESS_TOKEN", ""); const TMDB_LANGUAGE = GM_getValue("TMDB_LANGUAGE", "en-US"); const TVDB_API_KEY = GM_getValue("TVDB_API_KEY", ""); const TVDB_SUBSCRIBER_PIN = GM_getValue("TVDB_SUBSCRIBER_PIN", ""); const TVDB_LANGUAGE = GM_getValue("TVDB_LANGUAGE", "eng"); // Initialize console.log(LOG_PREFIX, LOG_STYLE, "Plex GUID Grabber v3.5.0"); initializeGMValues(); const siteConfig = { plex: { id: "plex-guid-button", name: "Plex", icon: "https://raw.githubusercontent.com/Soitora/Plex-GUID-Grabber/main/.github/images/plex.webp", buttonLabel: "Copy Plex GUID", visible: ["album", "artist", "movie", "season", "episode", "show"], isYamlButton: false, isSocialButton: false, }, imdb: { id: "imdb-social-button", name: "IMDb", icon: "https://raw.githubusercontent.com/Soitora/Plex-GUID-Grabber/main/.github/images/imdb.webp", buttonLabel: "Open IMDB", visible: ["movie", "show"], isYamlButton: false, isSocialButton: true, }, tmdb: { id: "tmdb-social-button", name: "TMDB", icon: "https://raw.githubusercontent.com/Soitora/Plex-GUID-Grabber/main/.github/images/tmdb-small.webp", buttonLabel: "Open TMDB", visible: ["movie", "show"], isYamlButton: false, isSocialButton: true, }, tvdb: { id: "tvdb-social-button", name: "TVDB", icon: "https://raw.githubusercontent.com/Soitora/Plex-GUID-Grabber/main/.github/images/tvdb.webp", buttonLabel: "Open TVDB", visible: ["movie", "show"], isYamlButton: false, isSocialButton: true, }, mbid: { id: "musicbrainz-social-button", name: "MusicBrainz", icon: "https://raw.githubusercontent.com/Soitora/Plex-GUID-Grabber/main/.github/images/musicbrainz.webp", buttonLabel: "Open MusicBrainz", visible: ["album", "artist"], isYamlButton: false, isSocialButton: true, }, anidb: { id: "anidb-social-button", name: "AniDB", icon: "https://raw.githubusercontent.com/Soitora/Plex-GUID-Grabber/main/.github/images/anidb.webp", buttonLabel: "Open AniDB", visible: ["show", "movie"], isYamlButton: false, isSocialButton: true, }, youtube: { id: "youtube-social-button", name: "YouTube", icon: "https://raw.githubusercontent.com/Soitora/Plex-GUID-Grabber/main/.github/images/youtube.webp", buttonLabel: "Open YouTube", visible: ["movie", "show", "episode"], isYamlButton: false, isSocialButton: true, }, tmdbYaml: { id: "tmdb-yaml-button", name: "TMDB YAML", icon: "https://raw.githubusercontent.com/Soitora/Plex-GUID-Grabber/main/.github/images/tmdb-pas.webp", buttonLabel: "Copy TMDB YAML", visible: ["movie", "show"], isYamlButton: true, isSocialButton: false, }, tvdbYaml: { id: "tvdb-yaml-button", name: "TVDB YAML", icon: "https://raw.githubusercontent.com/Soitora/Plex-GUID-Grabber/main/.github/images/tvdb-pas.webp", buttonLabel: "Copy TVDB YAML", visible: ["movie", "show"], isYamlButton: true, isSocialButton: false, }, }; function handleButtons(metadata, pageType, guid) { const leftButtonContainer = $(document).find(".PageHeaderLeft-pageHeaderLeft-GB_cUK"); const rightButtonContainer = $(document).find(".PageHeaderRight-pageHeaderRight-j9Yjqh"); console.debug(DEBUG_PREFIX, COLOR_CYAN, COLOR_GREEN, "Button container found:", rightButtonContainer.length > 0); if (!rightButtonContainer.length || $("#" + siteConfig.plex.id).length) return; const $directory = $(metadata).find("Directory, Video").first(); const title = $directory.attr("parentTitle") || $directory.attr("title"); const buttons = createButtonsConfig(guid, pageType, metadata); Object.entries(buttons).forEach(([site, { handler, config }]) => { if (config.visible.includes(pageType)) { if (config.isYamlButton && !USE_PAS) return; let shouldShow = true; if (config.isYamlButton) { const apiSite = site === "tmdbYaml" ? "tmdb" : "tvdb"; shouldShow = !!guid[apiSite]; } const $button = createButtonElement(config, shouldShow, guid[site], title); if ($button) { if (site === "plex") { $button.on("click", () => handlePlexButtonClick(guid[site], config, title)); } else if (config.isYamlButton) { $button.on("click", async () => handleYamlButtonClick(metadata, site, pageType, guid, title)); } else { $button.on("click", (e) => handler(e)); } appendButtonToContainer($button, config, rightButtonContainer, leftButtonContainer); setTimeout(() => { $button.css("opacity", 1); }, BUTTON_FADE_DELAY); } } }); } function createButtonsConfig(guid, pageType, metadata) { return Object.keys(siteConfig).reduce((acc, site) => { acc[site] = { handler: (event) => handleButtonClick(event, site, guid[site], pageType, metadata), config: siteConfig[site], }; return acc; }, {}); } function createButtonElement(config, shouldShow, guid, title) { if (!USE_SOCIAL_BUTTONS && config.isSocialButton) { return null; } const buttonClasses = ["_1v4h9jl0", "_76v8d62", "_76v8d61", "_76v8d68", "tvbry61", "_76v8d6g", "_76v8d6h", "_1v25wbq1g", "_1v25wbq18"].join(" "); const imageContainerClasses = ["_1h4p3k00", "_1v25wbq8", "_1v25wbq1w", "_1v25wbq1g", "_1v25wbq1c", "_1v25wbq14", "_1v25wbq3g", "_1v25wbq2g"].join(" "); return $("