JustPaste.it

Letterboxd to IMDb Links with Ratings and Additional Sites

// ==UserScript==
// @name         Letterboxd to IMDb Links with Ratings and Additional Sites
// @namespace    http://tampermonkey.net/
// @version      4.0
// @description  Add IMDb, Rotten Tomatoes, Metacritic ratings, and links to Ekşi Sözlük, Trakt, and Sinefil for Letterboxd films using PythonAnywhere API, with console logs for debugging missing information issues
// @author       sheeper
// @match        https://letterboxd.com/*
// @grant        GM_xmlhttpRequest
// @connect      sheeper.pythonanywhere.com
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// ==/UserScript==
 
(function() {
    'use strict';
 
    const API_URL = 'https://sheeper.pythonanywhere.com/movie_by_letterboxd?link=';
 
    function fetchPythonAnywhereData(filmLink, callback) {
        console.log(`Fetching data for: ${filmLink}`);
        GM_xmlhttpRequest({
            method: 'GET',
            url: API_URL + encodeURIComponent(filmLink),
            onload: function(response) {
                try {
                    const data = JSON.parse(response.responseText);
                    console.log(`Data fetched for: ${filmLink}`, data);
                    callback(data);
                } catch (error) {
                    console.error(`Error parsing response for: ${filmLink}`, error);
                }
            },
            onerror: function(error) {
                console.error(`Error fetching data from PythonAnywhere for: ${filmLink}`, error);
            }
        });
    }
 
    function addRatingsFromPythonAnywhereData(data, filmPoster) {
        if (data && data.imdbID) {
            const imdbId = data.imdbID;
            const imdbRating = data.IMDbSc;
            const rtRating = data['RT Score'];
            const mcRating = data.MetaSc;
            const letterboxdRating = data.Lbx;
 
            const ratingsDiv = $('<div>')
                .css('background-color', 'rgba(0, 0, 0, 0.7)')
                .css('padding', '2px 3px') // Reduced padding
                .css('border-radius', '5px')
                .css('display', 'flex')
                .css('align-items', 'center')
                .css('margin-top', '2px') // Reduced margin
                .css('font-size', '10px') // Reduced font size
                .css('position', 'absolute')
                .css('top', '0')
                .css('left', '0')
                .css('width', '100%')
                .css('justify-content', 'space-around');
 
            if (letterboxdRating && letterboxdRating !== 'N/A') {
                const letterboxdLogo = $('<a>')
                    .attr('href', data['Letterboxd Link'])
                    .attr('target', '_blank')
                    .append(
                        $('<img>')
                            .attr('src', 'https://i.imgur.com/YSiskZp.png')
                            .attr('alt', 'Letterboxd Logo')
                            .css('width', '12.5px') // Increased size
                            .css('vertical-align', 'middle')
                            .css('margin-right', '1px')
                    );
                const letterboxdRatingText = $('<span>')
                    .text(letterboxdRating)
                    .css('vertical-align', 'middle')
                    .css('color', '#fff')
                    .css('margin-right', '5px'); // Reduced margin
                ratingsDiv.append(letterboxdLogo).append(letterboxdRatingText);
            }
 
            if (imdbRating && imdbRating !== 'N/A') {
                const imdbLogo = $('<a>')
                    .attr('href', `https://www.imdb.com/title/${imdbId}`)
                    .attr('target', '_blank')
                    .append(
                        $('<img>')
                            .attr('src', 'https://upload.wikimedia.org/wikipedia/commons/6/69/IMDB_Logo_2016.svg')
                            .attr('alt', 'IMDb Logo')
                            .css('width', '12.5px') // Increased size
                            .css('vertical-align', 'middle')
                            .css('margin-right', '1px')
                    );
                const imdbRatingText = $('<span>')
                    .text(imdbRating)
                    .css('vertical-align', 'middle')
                    .css('color', '#fff')
                    .css('margin-right', '5px'); // Reduced margin
                ratingsDiv.append(imdbLogo).append(imdbRatingText);
            }
 
            if (rtRating && rtRating !== 'N/A') {
                const rtLogo = $('<a>')
                    .attr('href', `https://www.rottentomatoes.com/search?search=${encodeURIComponent(data.Title)}`)
                    .attr('target', '_blank')
                    .append(
                        $('<img>')
                            .attr('src', 'https://upload.wikimedia.org/wikipedia/commons/thumb/5/5b/Rotten_Tomatoes.svg/2019px-Rotten_Tomatoes.svg.png')
                            .attr('alt', 'Rotten Tomatoes Logo')
                            .css('width', '12.5px') // Increased size
                            .css('vertical-align', 'middle')
                            .css('margin-right', '1px')
                    );
                const rtRatingText = $('<span>')
                    .text(rtRating)
                    .css('vertical-align', 'middle')
                    .css('color', '#fff')
                    .css('margin-right', '5px'); // Reduced margin
                ratingsDiv.append(rtLogo).append(rtRatingText);
            }
 
            if (mcRating && mcRating !== 'N/A') {
                const mcLogo = $('<a>')
                    .attr('href', `https://www.metacritic.com/search/all/${encodeURIComponent(data.Title)}/results`)
                    .attr('target', '_blank')
                    .append(
                        $('<img>')
                            .attr('src', 'https://upload.wikimedia.org/wikipedia/commons/2/20/Metacritic.svg')
                            .attr('alt', 'Metacritic Logo')
                            .css('width', '12.5px') // Increased size
                            .css('vertical-align', 'middle')
                            .css('margin-right', '1px')
                    );
                const mcRatingText = $('<span>')
                    .text(mcRating)
                    .css('vertical-align', 'middle')
                    .css('color', '#fff')
                    .css('margin-right', '5px'); // Reduced margin
                ratingsDiv.append(mcLogo).append(mcRatingText);
            }
 
            const ekşiLink = `https://eksisozluk.com/?q=${encodeURIComponent(data.Title)}`;
            const traktLink = `https://trakt.tv/search/imdb?query=${encodeURIComponent(imdbId)}`;
            const sinefilLink = `https://www.sinefil.com/ara/${encodeURIComponent(imdbId)}`;
 
            filmPoster.css('position', 'relative').append(ratingsDiv);
 
            // Links div
            const linksDiv = $('<div>')
                .css('background-color', 'rgba(0, 0, 0, 0.7)')
                .css('padding', '1px 1px') // Reduced padding
                .css('border-radius', '3px')
                .css('display', 'flex')
                .css('align-items', 'center')
                .css('font-size', '10px') // Reduced font size
                .css('position', 'absolute')
                .css('bottom', '0')
                .css('left', '0')
                .css('width', '100%')
                .css('justify-content', 'space-around');
 
            const ekşiLogo = $('<a>')
                .attr('href', ekşiLink)
                .attr('target', '_blank')
                .append(
                    $('<img>')
                        .attr('src', 'https://i.imgur.com/k5K7m9h.png')
                        .attr('alt', 'Ekşi Sözlük Logo')
                        .css('width', '10px') // Increased size
                        .css('vertical-align', 'middle')
                        .css('margin', '0 1px')
                );
 
            const traktLogo = $('<a>')
                .attr('href', traktLink)
                .attr('target', '_blank')
                .append(
                    $('<img>')
                        .attr('src', 'https://i.imgur.com/adN3cCW.png')
                        .attr('alt', 'Trakt Logo')
                        .css('width', '12.5px') // Increased size
                        .css('vertical-align', 'middle')
                        .css('margin', '0 1px')
                );
 
            const sinefilLogo = $('<a>')
                .attr('href', sinefilLink)
                .attr('target', '_blank')
                .append(
                    $('<img>')
                        .attr('src', 'https://i.imgur.com/Z8E36pP.png')
                        .attr('alt', 'Sinefil Logo')
                        .css('width', '12.5px') // Increased size
                        .css('vertical-align', 'middle')
                        .css('margin', '0 1px')
                );
 
            linksDiv.append(ekşiLogo).append(traktLogo).append(sinefilLogo);
            filmPoster.append(linksDiv);
        }
    }
 
    function processAllFilms() {
        const filmLinks = new Set();
        $('li.poster-container').each(function() {
            const lazyLoadElement = $(this).find('.really-lazy-load, .react-component');
            let filmLink = lazyLoadElement.attr('data-target-link');
            if (!filmLink) {
                filmLink = lazyLoadElement.attr('data-film-link');
            }
 
            if (!filmLink) {
                filmLink = lazyLoadElement.find('a').attr('href');
            }
 
            if (filmLink) {
                filmLinks.add(`https://letterboxd.com${filmLink}`);
            } else {
                console.warn('No film link found for poster container:', $(this).html());
            }
        });
 
        filmLinks.forEach(filmLink => {
            fetchPythonAnywhereData(filmLink, data => {
                const filmPoster = $(`li.poster-container:has(.really-lazy-load[data-target-link="${filmLink.replace('https://letterboxd.com', '')}"]), li.poster-container:has(.really-lazy-load[data-film-link="${filmLink.replace('https://letterboxd.com', '')}"]), li.poster-container:has(.react-component[data-film-link="${filmLink.replace('https://letterboxd.com', '')}"])`);
                if (filmPoster.length > 0) {
                    addRatingsFromPythonAnywhereData(data, filmPoster);
                } else {
                    console.warn('No poster container found for film link:', filmLink);
                }
            });
        });
    }
 
    function ensureAllPosterContainersLoaded(callback) {
        const checkInterval = setInterval(() => {
            const posterContainersLoaded = $('li.poster-container').length;
            if (posterContainersLoaded > 0) {
                clearInterval(checkInterval);
                console.log('All poster containers loaded.');
                callback();
            }
        }, 500);
    }
 
    function processNewFilms() {
        new MutationObserver((mutationsList) => {
            for (const mutation of mutationsList) {
                const addedNodes = Array.from(mutation.addedNodes);
                addedNodes.filter(node => node.nodeType === Node.ELEMENT_NODE).forEach(node => {
                    if ($(node).find('.poster-container').length) {
                        ensureAllPosterContainersLoaded(processAllFilms);
                    }
                });
            }
        }).observe(document.body, { childList: true, subtree: true });
    }
 
    $(document).ready(function() {
        ensureAllPosterContainersLoaded(processAllFilms);
        processNewFilms();
    });
})();