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.8
// @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=';
    const processedPosters = new WeakSet();
 
    function fetchPythonAnywhereData(filmLink, callback) {
        GM_xmlhttpRequest({
            method: 'GET',
            url: API_URL + encodeURIComponent(filmLink),
            onload: function(response) {
                try {
                    const data = JSON.parse(response.responseText);
                    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 addRatingsAndLinks(data, $posterElement) {
        if (!data || !data.imdbID) {
            console.warn('Received invalid data or no imdbID for poster:', $posterElement.html());
            return;
        }
 
        const imdbId = data.imdbID;
        const imdbRating = data.IMDbSc;
        const rtRating = data.RT_Score;
        const mcRating = data.MetaSc;
        const letterboxdRating = data.Lbx;
 
        // Ratings div (üst kısım)
        const ratingsDiv = $('<div>').css({
            'background-color': 'rgba(0, 0, 0, 0.7)', 'padding': '2px 3px', 'border-radius': '5px',
            'display': 'flex', 'align-items': 'center', 'justify-content': 'center', 'margin-top': '2px',
            'font-size': '10px', 'position': 'absolute', 'top': '0', 'left': '0', 'width': '100%',
            'white-space': 'nowrap', 'overflow': 'hidden', 'box-sizing': 'border-box', 'z-index': '2'
        });
 
        if (letterboxdRating && letterboxdRating !== 'N/A') {
            ratingsDiv.append(createRatingElement(data['Letterboxd_Link'], 'https://i.imgur.com/YSiskZp.png', letterboxdRating, '12.5px'));
        }
        if (imdbRating && imdbRating !== 'N/A') {
            ratingsDiv.append(createRatingElement(`https://www.imdb.com/title/${imdbId}`, 'https://upload.wikimedia.org/wikipedia/commons/6/69/IMDB_Logo_2016.svg', imdbRating, '12.5px'));
        }
        if (rtRating && rtRating !== 'N/A') {
            ratingsDiv.append(createRatingElement(`https://www.rottentomatoes.com/search?search=${encodeURIComponent(data.Title)}`, 'https://upload.wikimedia.org/wikipedia/commons/5/5b/Rotten_Tomatoes.svg', rtRating, '12.5px'));
        }
        if (mcRating && mcRating !== 'N/A') {
            ratingsDiv.append(createRatingElement(`https://www.metacritic.com/search/all/${encodeURIComponent(data.Title)}/results`, 'https://upload.wikimedia.org/wikipedia/commons/2/20/Metacritic.svg', mcRating, '12.5px'));
        }
 
        // Links div (alt kısım)
        const linksDiv = $('<div>').css({
            'background-color': 'rgba(0, 0, 0, 0.7)', 'padding': '1px 1px', 'border-radius': '3px',
            'display': 'flex', 'align-items': 'center', 'justify-content': 'center', 'font-size': '10px',
            'position': 'absolute', 'bottom': '0', 'left': '0', 'width': '100%', 'white-space': 'nowrap',
            'overflow': 'hidden', 'box-sizing': 'border-box', 'z-index': '2'
        });
 
        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)}`;
 
        linksDiv.append(createIconLink(ekşiLink, 'https://i.imgur.com/k5K7m9h.png', 'Ekşi Sözlük', '10px'));
        linksDiv.append(createIconLink(traktLink, 'https://i.imgur.com/adN3cCW.png', 'Trakt', '12.5px'));
        linksDiv.append(createIconLink(sinefilLink, 'https://i.imgur.com/Z8E36pP.png', 'Sinefil', '12.5px'));
 
        // Elementleri afişe ekle
        $posterElement.css('position', 'relative').append(ratingsDiv).append(linksDiv);
    }
 
    function createRatingElement(link, imgSrc, rating, size) {
        const ratingContainer = $('<div>').css({'display': 'flex', 'align-items': 'center', 'margin-right': '5px'});
        const icon = $('<img>').attr('src', imgSrc).css({'width': size, 'vertical-align': 'middle', 'margin-right': '2px'});
        const ratingText = $('<span>').text(rating).css({'color': '#fff', 'font-weight': 'bold'});
        return ratingContainer.append(icon).append(ratingText).wrap('<a>').parent().attr('href', link).attr('target', '_blank');
    }
 
    function createIconLink(link, imgSrc, altText, size) {
        return $('<a>').attr('href', link).attr('target', '_blank')
            .append($('<img>').attr('src', imgSrc).attr('alt', altText).css({'width': size, 'vertical-align': 'middle', 'margin': '0 2px'}));
    }
 
    function processAllFilms() {
        // DEĞİŞİKLİK 1: Yeni afiş konteyner seçicisi. data-target-link niteliği olan tüm react-component'leri hedefliyoruz.
        const POSTER_SELECTOR = 'div.react-component[data-target-link]';
 
        $(POSTER_SELECTOR).each(function() {
            const $posterComponent = $(this); // Bu artık div.react-component
 
            if (processedPosters.has($posterComponent[0])) {
                return;
            }
 
            // DEĞİŞİKLİK 2: Linki doğrudan bu elementin kendisinden alıyoruz.
            const filmLink = $posterComponent.attr('data-target-link');
 
            if (filmLink && filmLink.startsWith('/')) {
                const fullUrl = `https://letterboxd.com${filmLink}`;
 
                // DEĞİŞİKLİK 3: Puanlamaları ekleyeceğimiz görsel afiş elementini (.film-poster) bulup fonksiyona onu gönderiyoruz.
                const $visualPoster = $posterComponent.find('.film-poster');
                if ($visualPoster.length > 0) {
                    fetchPythonAnywhereData(fullUrl, data => {
                        addRatingsAndLinks(data, $visualPoster);
                        processedPosters.add($posterComponent[0]);
                    });
                }
            }
        });
    }
 
    // Sayfa içeriği değiştikçe betiğin tekrar çalışmasını sağlayan gözlemci
    const observer = new MutationObserver(function(mutations) {
        clearTimeout(this.timeout);
        this.timeout = setTimeout(processAllFilms, 500);
    });
 
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
 
    // İlk sayfa yüklenmesi için
    $(document).ready(function() {
        setTimeout(processAllFilms, 1000); // Sayfanın tam oturması için küçük bir gecikme eklendi
    });
 
})();