// ==UserScript== // @name Add Plex Link to Letterboxd // @namespace http://tampermonkey.net/ // @license MIT // @version 3.1 // @description Adds a Plex link to Letterboxd watch section // @match https://letterboxd.com/film/* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @connect * // @downloadURL none // ==/UserScript== (function() { 'use strict'; const plexServerIp = GM_getValue('plexServerIp') || prompt("Please enter your Plex server IP:"); const plexToken = GM_getValue('plexToken') || prompt("Please enter your Plex token:"); // Store the IP and token if they were just provided if (!GM_getValue('plexServerIp')) { GM_setValue('plexServerIp', plexServerIp); } if (!GM_getValue('plexToken')) { GM_setValue('plexToken', plexToken); } const plexBaseUrl = `http://${plexServerIp}:32400`; // Ensure HTTP // Function to get the rating_key from Plex for a specific movie title function getRatingKey(movieTitle, movieYear) { const sanitizedTitle = movieTitle .trim() .toLowerCase() .replace(/\u00A0/g, ' ') .replace(/\s+/g, ' '); const url = `${plexBaseUrl}/library/sections/1/all?X-Plex-Token=${plexToken}&title=${encodeURIComponent(sanitizedTitle)}&year=${encodeURIComponent(movieYear)}`; console.log(`Fetching URL: ${url}`); // Log the URL used for fetching the title return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: url, onload: function(response) { if (response.status === 200) { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(response.responseText, "application/xml"); const metadata = xmlDoc.getElementsByTagName("Video"); if (metadata.length > 0) { const ratingKey = metadata[0].getAttribute("ratingKey"); console.log(`Found ratingKey: ${ratingKey}`); // Log found ratingKey resolve(ratingKey); } else { console.log("No metadata found for the title."); resolve(null); } } else { reject(`Error fetching from Plex: ${response.statusText}`); } }, onerror: function(error) { reject(`Request failed: ${error}`); } }); }); } // Function to add the Plex link if the rating_key is found async function addPlexLink() { const titleElement = document.querySelector('h1[class*="headline"]'); const yearElement = document.querySelector('div.releaseyear'); const movieTitle = titleElement ? titleElement.innerText : ''; const movieYear = yearElement ? yearElement.innerText.trim() : ''; if (!movieTitle) { console.log("No movie title found."); return; } console.log(`Searching for movie title: ${movieTitle} (${movieYear})`); // Log movie title try { const ratingKey = await getRatingKey(movieTitle, movieYear); if (ratingKey) { const plexLinkHTML = `

Plex Plex

`; const servicesSection = document.querySelector('section.services'); if (servicesSection) { const existingLink = document.getElementById('service-plex'); if (!existingLink) { servicesSection.insertAdjacentHTML('afterbegin', plexLinkHTML); console.log("Plex link added as the first item in services section."); } else { console.log("Plex link already exists."); } } else { console.log("Services section not found. Attempting to replace 'Not streaming' message."); replaceNotStreamingMessage(ratingKey); } } else { console.log(`No rating key found for: ${movieTitle}`); removePlexLink(); // Remove the Plex link if not found replaceNotStreamingMessage(); } } catch (error) { console.error(error); } } // Function to replace the "Not streaming" message with the Plex link function replaceNotStreamingMessage(ratingKey) { const notStreamingDiv = document.querySelector('section.watch-panel.js-watch-panel div.other.-message.js-not-streaming'); if (notStreamingDiv) { const plexLinkHTML = `

Plex Plex

`; notStreamingDiv.outerHTML = plexLinkHTML; console.log("Replaced 'Not streaming' message with Plex link."); } else { console.log("No 'Not streaming' message found."); } } // Function to remove the Plex link if the movie is not found function removePlexLink() { const existingLink = document.getElementById('service-plex'); if (existingLink) { existingLink.remove(); // Remove the Plex link if it exists console.log("Removed Plex link as the movie was not found."); } } // Wait for the page to load the necessary elements before adding the Plex link const observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (document.querySelector('section.services') || document.querySelector('section.watch-panel.js-watch-panel div.other.-message.js-not-streaming')) { addPlexLink(); observer.disconnect(); // Stop observing after the Plex link is added } }); }); // Start observing changes in the DOM observer.observe(document.body, { childList: true, subtree: true }); })();