// ==UserScript==
// @name Plex Letterboxd links
// @namespace http://tampermonkey.net/
// @description Add Letterboxd link to its corresponding Plex page
// @author CarnivalHipster
// @match https://app.plex.tv/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=plex.tv
// @license MIT
// @grant GM_xmlhttpRequest
// @version 1.0
// ==/UserScript==
(function() {
'use strict';
const letterboxdImg = 'https://www.google.com/s2/favicons?sz=64&domain=letterboxd.com';
var lastTitle = undefined;
var lastYear = undefined;
//Function Definitions
function extractData() {
const titleElement = document.querySelector('h1[data-testid="metadata-title"]');
const yearElement = document.querySelector('span[data-testid="metadata-line1"]');
if (titleElement) {
const title = titleElement.textContent.trim() || titleElement.innerText.trim();
if (title !== lastTitle) {
lastTitle = title;
console.log('The title is:', lastTitle);
}
} else {
lastTitle = ''; // Reset if no title is found
}
if (yearElement) {
const text = yearElement.textContent.trim() || yearElement.innerText.trim();
const match = text.match(/\b\d{4}\b/);
if (match && match[0] !== lastYear) {
lastYear = match[0];
console.log('The year is:', lastYear);
}
} else {
lastYear = ''; // Reset if no year is found
}
}
function checkLink(url) {
return new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method: 'HEAD',
url: url,
onload: function(response) {
if (response.status >= 200 && response.status < 300) {
resolve({url: url, status: response.status, accessible: true});
} else {
resolve({url: url, status: response.status, accessible: false});
}
},
onerror: function() {
reject(new Error(url + ' could not be reached or is blocked by CORS policy.'));
}
});
});
}
function updateOrCreateLetterboxdIcon(link) {
const existingIcon = document.querySelector('.letterboxd-icon');
const metadataElement = document.querySelector('div[data-testid="metadata-children"]');
if (existingIcon) {
existingIcon.href = link;
} else if (metadataElement) {
const icon = document.createElement('img');
icon.src = letterboxdImg;
icon.alt = 'Letterboxd Icon';
icon.classList.add('letterboxd-icon');
icon.style.cssText = 'width: 24px; height: 24px; cursor: pointer;';
const linkElement = document.createElement('a');
linkElement.href = link;
//linkElement.target = '_blank'; // Open in a new tab
linkElement.appendChild(icon);
metadataElement.insertAdjacentElement('afterend', linkElement);
}
}
function buildDefaultLetterboxdUrl(title, year) {
const titleSlug = title.trim().toLowerCase()
.replace(/&/g, 'and')
.replace(/[^\w\s-]/g, '')
.replace(/\s+/g, '-');
const letterboxdBaseUrl = 'https://letterboxd.com/film/';
return `${letterboxdBaseUrl}${titleSlug}-${year}/`;
}
function removeYearFromUrl(url) {
const yearPattern = /-\d{4}(?=\/$)/;
return url.replace(yearPattern, '');
}
function replaceFilmWithDirector(url) {
return url.replace('film','director');
}
function buildLetterboxdUrl(title, year) {
let defaultUrl = buildDefaultLetterboxdUrl(title, year);
return checkLink(defaultUrl).then(result => {
if (result.accessible) {
console.log(result.url, 'is accessible, status:', result.status);
return result.url;
} else {
console.log(result.url, 'is not accessible, status:', result.status);
let yearRemovedUrl = removeYearFromUrl(result.url);
console.log('Trying URL without year:', yearRemovedUrl);
return checkLink(yearRemovedUrl).then(yearRemovedResult => {
if (yearRemovedResult.accessible) {
console.log(yearRemovedUrl, 'is accessible, status:', yearRemovedResult.status);
return yearRemovedUrl;
} else {
console.log(yearRemovedUrl, 'is not accessible, status:', yearRemovedResult.status);
let directorUrl = replaceFilmWithDirector(yearRemovedUrl);
console.log('Trying director URL:', directorUrl);
return directorUrl;
}
});
}
}).catch(error => {
console.error('Error after checking both film and year:', error.message);
let newUrl = removeYearFromUrl(defaultUrl);
let directorUrl = replaceFilmWithDirector(newUrl);
console.log('Fallback director URL:', directorUrl);
return directorUrl;
});
}
// Main
if(document.readyState === 'complete' || document.readyState === 'loaded' || document.readyState === 'interactive') {
main();
} else {
document.addEventListener('DOMContentLoaded', main);
}
function main() {
function observerCallback(mutationsList, observer) {
extractData();
if (lastTitle && lastYear) {
buildLetterboxdUrl(lastTitle, lastYear).then(url => {
updateOrCreateLetterboxdIcon(url);
}).catch(error => {
console.error('Error building Letterboxd URL:', error);
});
} else {
console.log('Title or year not found, not updating Letterboxd icon.');
}
}
const observer = new MutationObserver(observerCallback);
observer.observe(document.body, {
childList: true,
characterData: true,
subtree: true
});
}
})();