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();
})();