NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Trakt.tv External Letterboxd Link // @namespace https://trakt.tv/ // @version 1.1 // @description Adds a Letterboxd link to Trakt.tv movie pages external section. // @author You // @match https://trakt.tv/* // @grant none // @license MIT // @updateURL https://openuserjs.org/meta/sp1ti/Trakt.tv_External_Letterboxd_Link.meta.js // @downloadURL https://openuserjs.org/install/sp1ti/Myanimelist.net_links_on_trakt.tv.user.js // @icon https://huere.net/i/extensions/trakt-letterboxd-icon-128.png // ==/UserScript== (function () { "use strict"; let lastUrl = location.href; // Function to check if we're on a movie page function isMoviePage() { return location.pathname.startsWith("/movies/"); } // Function to add the Letterboxd link function addLetterboxdLink() { if (!isMoviePage()) return; // Only run on movie pages const tmdbLink = document.getElementById("external-link-tmdb"); if (tmdbLink) { const tmdbIdMatch = tmdbLink.href.match(/\/(\d+)$/); if (tmdbIdMatch) { const tmdbId = tmdbIdMatch[1]; const letterboxdUrl = `https://letterboxd.com/tmdb/${tmdbId}`; const externalList = document.querySelector("ul.external"); if (externalList && !externalList.querySelector(`a[href="${letterboxdUrl}"]`)) { const letterboxdLink = document.createElement("a"); letterboxdLink.href = letterboxdUrl; letterboxdLink.target = "_blank"; letterboxdLink.textContent = "Letterboxd"; tmdbLink.classList.forEach((cls) => letterboxdLink.classList.add(cls)); letterboxdLink.style.marginLeft = "5px"; const lastLi = externalList.querySelector("li:last-of-type"); if (lastLi) lastLi.appendChild(letterboxdLink); } } } } // Observe the external links section for updates function observeExternalLinks() { const externalList = document.querySelector("ul.external"); if (!externalList) return; const observer = new MutationObserver(() => addLetterboxdLink()); observer.observe(externalList, { childList: true }); } // Detect URL changes using history API interception function interceptHistoryMethods() { const originalPushState = history.pushState; const originalReplaceState = history.replaceState; function handleNavigationChange() { requestAnimationFrame(() => { if (location.href !== lastUrl) { lastUrl = location.href; if (isMoviePage()) { addLetterboxdLink(); observeExternalLinks(); } } }); } history.pushState = function (...args) { originalPushState.apply(history, args); handleNavigationChange(); }; history.replaceState = function (...args) { originalReplaceState.apply(history, args); handleNavigationChange(); }; window.addEventListener("popstate", handleNavigationChange); } // Run script immediately and set up observers function initialize() { if (isMoviePage()) { addLetterboxdLink(); observeExternalLinks(); } interceptHistoryMethods(); } initialize(); })();