Plex Letterboxd links

Add Letterboxd link to its corresponding Plex film's page

目前为 2023-12-30 提交的版本。查看 最新版本

// ==UserScript==
// @name         Plex Letterboxd links
// @namespace    http://tampermonkey.net/
// @description  Add Letterboxd link to its corresponding Plex film's 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
// @connect      letterboxd.com
// @version 	 1.2
// ==/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);
            return newUrl;
        });
    }



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

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址