JustPaste.it

Letterboxd to IMDb Links with Ratings and Additional Sites

// ==UserScript==
// @name         Letterboxd to IMDb Links with Ratings and Additional Sites
// @name:tr      Letterboxd Puan ve Ek Site Linkleri
// @namespace    http://tampermonkey.net/
// @version      5.2
// @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
// @description:tr  Letterboxd Puan ve Ek Site Linkleri Sunar
// @author       sheeper
// @match        https://letterboxd.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @connect      sheeper.pythonanywhere.com
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// @downloadURL https://update.greasyfork.org/scripts/547078/Letterboxd%20to%20IMDb%20Links%20with%20Ratings%20and%20Additional%20Sites.user.js
// @updateURL https://update.greasyfork.org/scripts/547078/Letterboxd%20to%20IMDb%20Links%20with%20Ratings%20and%20Additional%20Sites.meta.js
// ==/UserScript==
 
(function() {
    'use strict';
 
    const API_URL = 'http://162.19.226.37:5005/movie_by_letterboxd?link=';
    const processedPosters = new WeakSet();
 
    const defaultConfig = {
        showLetterboxd: true,
        showImdb: true,
        showTmdb: true,
        showMetacritic: true,
        showRotten: true,
        showTrakt: true,
        showEksi: true,
        showSinefil: true,
        showRuntime: true,
        showPlot: true,
        plotLang: 'en',
        uiLang: 'en'
    };
 
    const userConfig = Object.assign({}, defaultConfig, GM_getValue('letterboxd_ext_settings', {}));
 
    const i18n = {
        en: {
            settingsTitle: 'Extension Settings',
            showLetterboxd: 'Show Letterboxd Rating',
            showImdb: 'Show IMDb Rating',
            showTmdb: 'Show TMDB Rating',
            showMeta: 'Show Metacritic Rating',
            showRotten: 'Show Rotten Tomatoes',
            showTrakt: 'Show Trakt Rating',
            showEksi: 'Show Ekşi Sözlük Icon',
            showSinefil: 'Show Sinefil Icon',
            showRuntime: 'Show Runtime',
            showPlot: 'Show Plot on Hover (Popup)',
            plotLangLabel: 'Plot Language:',
            plotLangTrEn: 'Turkish (English fallback)',
            plotLangTr: 'Turkish only',
            plotLangEn: 'English only',
            uiLangLabel: 'Extension Language:',
            uiLangTr: 'Türkçe',
            uiLangEn: 'English',
            btnCancel: 'Cancel',
            btnSave: 'Save and Reload',
            settingsMenuName: '⚙️ Settings',
            trueScoreTitle: 'True Score (Weighted Average)'
        },
        tr: {
            settingsTitle: 'Eklenti Ayarları',
            showLetterboxd: 'Letterboxd Puanı Göster',
            showImdb: 'IMDb Puanı Göster',
            showTmdb: 'TMDB Puanı Göster',
            showMeta: 'Metacritic Puanı Göster',
            showRotten: 'Rotten Tomatoes Göster',
            showTrakt: 'Trakt Puanı Göster',
            showEksi: 'Ekşi Sözlük İkonunu Göster',
            showSinefil: 'Sinefil İkonunu Göster',
            showRuntime: 'Film Süresini (Runtime) Göster',
            showPlot: 'Afiş Üzerinde Özet Göster (Popup)',
            plotLangLabel: 'Özet Dili:',
            plotLangTrEn: 'Türkçe (Yoksa İngilizce)',
            plotLangTr: 'Sadece Türkçe',
            plotLangEn: 'Sadece İngilizce',
            uiLangLabel: 'Eklenti Dili:',
            uiLangTr: 'Türkçe',
            uiLangEn: 'English',
            btnCancel: 'İptal',
            btnSave: 'Kaydet ve Yenile',
            settingsMenuName: '⚙️ Ayarlar',
            trueScoreTitle: 'True Score (Ağırlıklı Ortalama)'
        }
    };
 
    const t = i18n[userConfig.uiLang] || i18n['en'];
 
    function saveConfig() {
        GM_setValue('letterboxd_ext_settings', userConfig);
        window.location.reload();
    }
 
    GM_registerMenuCommand(t.settingsMenuName, openSettingsModal);
 
    function openSettingsModal() {
        if ($('#ext-settings-modal').length > 0) return;
 
        const modalHtml = `
            <div id="ext-settings-modal" style="position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.85);z-index:9999999;display:flex;align-items:center;justify-content:center;-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;">
                <div style="background:#2c3440;color:#fff;padding:25px;border-radius:10px;width:350px;font-size:14px;box-shadow:0 10px 30px rgba(0,0,0,0.8);">
                    <h2 style="color:#00e054;0;text-align:center;">${t.settingsTitle}</h2>
 
                    <label style="display:block;5px;color:#8aa8c1;font-weight:bold;">${t.uiLangLabel}</label>
                    <select id="cfg-ui-lang" style="width:100%;padding:8px;background:#14181c;color:#fff;border:1px solid #445566;border-radius:4px;15px;outline:none;">
                        <option value="en" ${userConfig.uiLang === 'en' ? 'selected' : ''}>${t.uiLangEn}</option>
                        <option value="tr" ${userConfig.uiLang === 'tr' ? 'selected' : ''}>${t.uiLangTr}</option>
                    </select>
 
                    <label style="display:block;10px;cursor:pointer;"><input type="checkbox" id="cfg-letterboxd" ${userConfig.showLetterboxd ? 'checked' : ''}> ${t.showLetterboxd}</label>
                    <label style="display:block;10px;cursor:pointer;"><input type="checkbox" id="cfg-imdb" ${userConfig.showImdb ? 'checked' : ''}> ${t.showImdb}</label>
                    <label style="display:block;10px;cursor:pointer;"><input type="checkbox" id="cfg-tmdb" ${userConfig.showTmdb ? 'checked' : ''}> ${t.showTmdb}</label>
                    <label style="display:block;10px;cursor:pointer;"><input type="checkbox" id="cfg-meta" ${userConfig.showMetacritic ? 'checked' : ''}> ${t.showMeta}</label>
                    <label style="display:block;10px;cursor:pointer;"><input type="checkbox" id="cfg-rotten" ${userConfig.showRotten ? 'checked' : ''}> ${t.showRotten}</label>
                    <label style="display:block;10px;cursor:pointer;"><input type="checkbox" id="cfg-trakt" ${userConfig.showTrakt ? 'checked' : ''}> ${t.showTrakt}</label>
                    <label style="display:block;10px;cursor:pointer;"><input type="checkbox" id="cfg-eksi" ${userConfig.showEksi ? 'checked' : ''}> ${t.showEksi}</label>
                    <label style="display:block;10px;cursor:pointer;"><input type="checkbox" id="cfg-sinefil" ${userConfig.showSinefil ? 'checked' : ''}> ${t.showSinefil}</label>
                    <label style="display:block;10px;cursor:pointer;"><input type="checkbox" id="cfg-runtime" ${userConfig.showRuntime ? 'checked' : ''}> ${t.showRuntime}</label>
                    <label style="display:block;15px;cursor:pointer;"><input type="checkbox" id="cfg-plot" ${userConfig.showPlot ? 'checked' : ''}> ${t.showPlot}</label>
 
                    <label style="display:block;5px;color:#8aa8c1;font-weight:bold;">${t.plotLangLabel}</label>
                    <select id="cfg-lang" style="width:100%;padding:8px;background:#14181c;color:#fff;border:1px solid #445566;border-radius:4px;20px;outline:none;">
                        <option value="tr" ${userConfig.plotLang === 'tr' ? 'selected' : ''}>${t.plotLangTrEn}</option>
                        <option value="tr_only" ${userConfig.plotLang === 'tr_only' ? 'selected' : ''}>${t.plotLangTr}</option>
                        <option value="en" ${userConfig.plotLang === 'en' ? 'selected' : ''}>${t.plotLangEn}</option>
                    </select>
 
                    <div style="display:flex;justify-content:space-between;">
                        <button id="btn-cfg-cancel" style="padding:8px 15px;background:#445566;color:#fff;border:none;border-radius:4px;cursor:pointer;">${t.btnCancel}</button>
                        <button id="btn-cfg-save" style="padding:8px 15px;background:#00e054;color:#14181c;border:none;border-radius:4px;cursor:pointer;font-weight:bold;">${t.btnSave}</button>
                    </div>
                </div>
            </div>
        `;
 
        $('body').append(modalHtml);
 
        $('#btn-cfg-cancel').on('click', function() {
            $('#ext-settings-modal').remove();
        });
 
        $('#btn-cfg-save').on('click', function() {
            userConfig.showLetterboxd = $('#cfg-letterboxd').is(':checked');
            userConfig.showImdb = $('#cfg-imdb').is(':checked');
            userConfig.showTmdb = $('#cfg-tmdb').is(':checked');
            userConfig.showMetacritic = $('#cfg-meta').is(':checked');
            userConfig.showRotten = $('#cfg-rotten').is(':checked');
            userConfig.showTrakt = $('#cfg-trakt').is(':checked');
            userConfig.showEksi = $('#cfg-eksi').is(':checked');
            userConfig.showSinefil = $('#cfg-sinefil').is(':checked');
            userConfig.showRuntime = $('#cfg-runtime').is(':checked');
            userConfig.showPlot = $('#cfg-plot').is(':checked');
            userConfig.plotLang = $('#cfg-lang').val();
            userConfig.uiLang = $('#cfg-ui-lang').val();
            saveConfig();
        });
    }
 
    function fetchPythonAnywhereData(filmLink, callback, force = false) {
        let url = API_URL + encodeURIComponent(filmLink);
        if (force) {
            url += '&force=true';
        }
        GM_xmlhttpRequest({
            method: 'GET',
            url: url,
            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);
            }
        });
    }
 
    // --- Global Plot Tooltip Oluşturma ---
    const globalPlotTooltip = $('<div>').addClass('global-plot-tooltip-ext').css({
        'position': 'absolute', 'width': '250px',
        'background-color': 'rgba(20, 24, 28, 0.98)', 'color': '#8aa8c1',
        'padding': '12px', 'box-sizing': 'border-box', 'z-index': '999999',
        'font-size': '12px', 'line-height': '1.5', 'border-radius': '6px',
        'border': '1px solid #445566', 'box-shadow': '0 4px 15px rgba(0,0,0,0.7)',
        'opacity': '0', 'transition': 'opacity 0.2s', 'pointer-events': 'none',
        'display': 'none'
    });
    $('body').append(globalPlotTooltip);
 
    function showGlobalTooltip($el, text) {
        globalPlotTooltip.text(text).css('display', 'block');
 
        const offset = $el.offset();
        const width = $el.outerWidth();
 
        let leftPos = offset.left + width + 10;
        let topPos = offset.top - 10;
 
        // Sağ ekrandan taşıyorsa sola al
        if (leftPos + 260 > $(window).width()) {
            leftPos = offset.left - 260;
        }
 
        globalPlotTooltip.css({'left': leftPos + 'px', 'top': topPos + 'px'});
        setTimeout(() => globalPlotTooltip.css('opacity', '1'), 10);
    }
 
    function hideGlobalTooltip() {
        globalPlotTooltip.css('opacity', '0');
        setTimeout(() => {
            if(globalPlotTooltip.css('opacity') === '0') globalPlotTooltip.css('display', 'none');
        }, 200);
    }
 
    function addRatingsAndLinks(data, $posterElement, filmLink) {
        if (!data || data.error) {
            return;
        }
 
        // Temizleme işlemi (Yenileme yapıldığında eski ögeleri silmek için)
        $posterElement.find('.ratings-overlay-ext, .links-overlay-ext, .hover-overlay-ext, .plot-overlay-ext').remove();
        $posterElement.off('mouseenter.ext mouseleave.ext');
 
        const imdbRating = data.IMDbSc;
        const rtRating = data.RT_Score;
        const metacriticRating = data.MetaSc;
        const letterboxdRating = data.Lbx;
        const traktRating = data.TraktSc;
        const tmdbRating = data.TMDBSc;
        const traktId = data.TraktID;
        const tmdbId = data.TMDBID;
        const runtime = data.Runtime;
 
        const ratingsDiv = $('<div>').addClass('ratings-overlay-ext').css({
            'background-color': 'rgba(0, 0, 0, 0.7)', 'padding': '1px 1px', 'border-radius': '4px',
            'display': 'flex', 'align-items': 'center', 'justify-content': 'center', 'flex-wrap': 'nowrap',
            'margin-bottom': '1px', 'font-size': '9.9px', 'overflow': 'hidden', 'min-width': '100%',
            'position': 'absolute', 'top': '0', 'left': '0', 'z-index': '2'
        });
 
        if (userConfig.showLetterboxd && letterboxdRating !== null && letterboxdRating !== undefined && letterboxdRating !== 'N/A' && letterboxdRating !== '') {
            ratingsDiv.append(createRatingElement(`https://letterboxd.com/film/${filmLink}/`, 'https://a.ltrbxd.com/logos/letterboxd-decal-dots-pos-rgb-500px.png', letterboxdRating, '12.1px'));
        }
        if (userConfig.showImdb && imdbRating !== null && imdbRating !== undefined && imdbRating !== 'N/A' && imdbRating !== '') {
            ratingsDiv.append(createRatingElement(`https://www.imdb.com/title/${data.imdbID}`, 'https://upload.wikimedia.org/wikipedia/commons/6/69/IMDB_Logo_2016.svg', imdbRating, '12.1px'));
        }
        if (userConfig.showMetacritic && metacriticRating !== null && metacriticRating !== undefined && metacriticRating !== 'N/A' && metacriticRating !== '') {
            ratingsDiv.append(createRatingElement(`https://www.metacritic.com/search/${encodeURIComponent(data.Title)}`, 'https://upload.wikimedia.org/wikipedia/commons/c/ce/Metacritic_logo_original.svg', metacriticRating, '12.1px'));
        }
        if (userConfig.showRotten && rtRating !== null && rtRating !== undefined && rtRating !== 'N/A' && rtRating !== '') {
            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.1px'));
        }
 
        // True Score Hesaplama (100 üzerinden)
        let totalScore = 0;
        let scoreCount = 0;
        if (imdbRating && !isNaN(imdbRating)) { totalScore += parseFloat(imdbRating) * 10; scoreCount++; }
        if (letterboxdRating && !isNaN(letterboxdRating)) { totalScore += parseFloat(letterboxdRating) * 20; scoreCount++; }
        if (rtRating && !isNaN(rtRating)) { totalScore += parseFloat(rtRating); scoreCount++; }
        if (metacriticRating && !isNaN(metacriticRating)) { totalScore += parseFloat(metacriticRating); scoreCount++; }
        if (traktRating && !isNaN(traktRating)) { totalScore += parseFloat(traktRating); scoreCount++; }
        if (tmdbRating && !isNaN(tmdbRating)) { totalScore += parseFloat(tmdbRating); scoreCount++; }
 
        let trueScoreHtml = null;
        if (scoreCount > 0) {
            const trueScore = Math.round(totalScore / scoreCount);
            trueScoreHtml = $('<span>').text(trueScore).css({
                'background-color': '#d4af37', 'color': '#14181c', 'padding': '1px 3px',
                'border-radius': '4px', 'font-size': '9px', 'font-weight': 'bold',
                'margin': '0 2px', 'box-shadow': '0 0 2px rgba(0,0,0,0.5)'
            }).attr('title', t.trueScoreTitle);
        }
 
        // Links div (alt kısım)
        const linksDiv = $('<div>').addClass('links-overlay-ext').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': '9px',
            '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 sinefilLink = `https://www.sinefil.com/ara/${encodeURIComponent(data.imdbID)}`;
 
        if (userConfig.showEksi) {
            linksDiv.append(createIconLink(ekşiLink, 'https://i.imgur.com/k5K7m9h.png', 'Ekşi Sözlük', '11px'));
        }
 
        if (userConfig.showTrakt) {
            if (traktRating !== null && traktRating !== undefined && traktRating !== 'N/A' && traktRating !== '') {
                const traktLink = traktId ? `https://trakt.tv/movies/${traktId}` : `https://trakt.tv/search/imdb?query=${data.imdbID}`;
                linksDiv.append(createRatingElement(traktLink, 'https://i.imgur.com/adN3cCW.png', traktRating, '11px'));
            } else {
                const traktLink = `https://trakt.tv/search/imdb?query=${encodeURIComponent(data.imdbID)}`;
                linksDiv.append(createIconLink(traktLink, 'https://i.imgur.com/adN3cCW.png', 'Trakt', '11px'));
            }
        }
 
        // Tam ortaya True Score
        if (trueScoreHtml) {
            linksDiv.append(trueScoreHtml);
        }
 
        if (userConfig.showTmdb) {
            if (tmdbRating !== null && tmdbRating !== undefined && tmdbRating !== 'N/A' && tmdbRating !== '') {
                const tmdbLink = tmdbId ? `https://www.themoviedb.org/movie/${tmdbId}` : `https://www.themoviedb.org/search?query=${encodeURIComponent(data.Title)}`;
                linksDiv.append(createRatingElement(tmdbLink, 'https://play-lh.googleusercontent.com/nbpjjTT9eAxpRjvFIIm6qnLIdQVN7YGww8BBLJNrPw5a7uortAASMyGlZPQL7kvM0YVnjvRLPoiOU-thyf9jAQ', tmdbRating, '11px'));
            } else {
                const tmdbLink = `https://www.themoviedb.org/search?query=${encodeURIComponent(data.Title)}`;
                linksDiv.append(createIconLink(tmdbLink, 'https://play-lh.googleusercontent.com/nbpjjTT9eAxpRjvFIIm6qnLIdQVN7YGww8BBLJNrPw5a7uortAASMyGlZPQL7kvM0YVnjvRLPoiOU-thyf9jAQ', 'TMDB', '11px'));
            }
        }
 
        if (userConfig.showSinefil) {
            linksDiv.append(createIconLink(sinefilLink, 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAMAAABF0y+mAAAAIVBMVEVHcExnbnlnbnlnbnlnbnlnbnlnbnlnbnlnbnlnbnlnbnmmlNfFAAAACnRSTlMAtVsSSC/pcNWfSr0WdAAAAIlJREFUKJHNkckVwCAIRGVxo/+CE4MoRgtwLvr4OCyGcLWIkeORMIgKNs5JpmBlVVaRY+4Zh7JSb1nxO4qxIgdlZfnERHS4M3uL/xuFea0vwyU9Tpq2dnJgZoQBwcO+G7CrNgszuy1zzNKipF2jgzQ8enFbWh0FYk9LfXA1szxGbF+IaK6kgev0AP0rCpjJWVvFAAAAAElFTkSuQmCC', 'Sinefil', '13px'));
        }
 
        $posterElement.css('position', 'relative').append(ratingsDiv).append(linksDiv);
 
        // Hover Overlay (Süre ve Yenile)
        const hoverDiv = $('<div>').addClass('hover-overlay-ext').css({
            'position': 'absolute', 'top': '25px', 'left': '0', 'width': '100%',
            'display': 'flex', 'justify-content': 'space-between', 'padding': '0 3px',
            'box-sizing': 'border-box', 'z-index': '3', 'opacity': '0', 'transition': 'opacity 0.2s',
            'pointer-events': 'none'
        });
 
        const leftSpace = $('<div>');
        if (userConfig.showRuntime && runtime) {
            const hours = Math.floor(runtime / 60);
            const mins = runtime % 60;
            let runtimeStr = '';
            if (hours > 0) runtimeStr += `${hours}h`;
            if (mins > 0 || hours === 0) runtimeStr += `${mins}m`;
            leftSpace.append($('<span>').text(runtimeStr).css({
                'background-color': 'rgba(0,0,0,0.8)', 'color': '#fff', 'padding': '2px 4px',
                'border-radius': '3px', 'font-size': '10px', 'font-weight': 'bold',
                'box-shadow': '0 0 2px rgba(0,0,0,0.5)'
            }));
        }
        hoverDiv.append(leftSpace);
 
        const refreshBtn = $('<span>').html('&#x21bb;').css({
            'background-color': 'rgba(0,0,0,0.8)', 'color': '#fff', 'padding': '1px 5px',
            'border-radius': '3px', 'font-size': '12px', 'cursor': 'pointer', 'pointer-events': 'auto',
            'box-shadow': '0 0 2px rgba(0,0,0,0.5)', 'display': 'inline-block'
        }).on('click', function(e) {
            e.preventDefault();
            e.stopPropagation();
            $(this).css('transform', 'rotate(360deg)').css('transition', 'transform 1s');
            $(this).css('pointer-events', 'none'); // Disable during fetch
 
            fetchPythonAnywhereData(filmLink, function(newData) {
                addRatingsAndLinks(newData, $posterElement, filmLink);
            }, true);
        });
 
        const rightSpace = $('<div>').append(refreshBtn);
        hoverDiv.append(rightSpace);
 
        $posterElement.append(hoverDiv);
 
        if (userConfig.showPlot) {
            let plotFetched = false;
            let hoverTimer;
            let plotContent = '';
 
            $posterElement.on('mouseenter.ext', function() {
                hoverDiv.css('opacity', '1');
 
                hoverTimer = setTimeout(() => {
                    if (!plotFetched && data.imdbID) {
                        $.get(`http://127.0.0.1:5005/movie_plot/${data.imdbID}?lang=${userConfig.plotLang}`, function(plotData) {
                            if(plotData && plotData.plot && plotData.plot !== 'Film özeti bulunamadı.') {
                                plotContent = plotData.plot;
                                showGlobalTooltip($posterElement, plotContent);
                            }
                            plotFetched = true;
                        }).fail(function() {
                            plotFetched = true;
                        });
                    } else if (plotFetched && plotContent !== '') {
                        showGlobalTooltip($posterElement, plotContent);
                    }
                }, 600);
 
            }).on('mouseleave.ext', function() {
                hoverDiv.css('opacity', '0');
                clearTimeout(hoverTimer);
                hideGlobalTooltip();
            });
        } else {
            $posterElement.on('mouseenter.ext', function() {
                hoverDiv.css('opacity', '1');
            }).on('mouseleave.ext', function() {
                hoverDiv.css('opacity', '0');
            });
        }
    }
 
    function createRatingElement(link, imgSrc, rating, size) {
        const ratingContainer = $('<div>').css({'display': 'flex', 'align-items': 'center', 'margin-right': '2px'});
        const icon = $('<img>').attr('src', imgSrc).css({'width': size, 'vertical-align': 'middle', 'margin-right': '1px'});
        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').css('text-decoration', 'none');
    }
 
    function createIconLink(link, imgSrc, altText, size) {
        const iconContainer = $('<div>').css({'display': 'flex', 'align-items': 'center', 'margin-right': '1px', 'cursor': 'pointer'});
        const icon = $('<img>').attr('src', imgSrc).css({'width': size, 'vertical-align': 'middle'}).attr('title', altText);
        return iconContainer.append(icon).wrap('<a>').parent().attr('href', link).attr('target', '_blank').css('text-decoration', 'none');
    }
 
    function processAllFilms() {
        // Yeni afiş konteyner seçicisi. Tüm olası poster varyasyonlarını yakalıyoruz.
        const POSTER_SELECTOR = 'div.react-component[data-target-link], div.react-component[data-item-link], div.film-poster[data-film-link]';
 
        $(POSTER_SELECTOR).each(function() {
            const $posterComponent = $(this);
 
            if (processedPosters.has($posterComponent[0])) {
                return;
            }
 
            // Linki bulmak için olası tüm nitelikleri kontrol et
            let rawLink = $posterComponent.attr('data-item-link') || $posterComponent.attr('data-target-link') || $posterComponent.attr('data-film-link');
            let filmLink = rawLink;
 
            // Linkte /film/ varsa her zaman base formata (/film/film-adi/) çevir (Kullanıcı inceleme sayfalarındaki hatayı önler)
            if (rawLink) {
                const match = rawLink.match(/\/film\/([^\/?#]+)/);
                if (match) {
                    filmLink = `/film/${match[1]}/`;
                }
            }
 
            if (filmLink && filmLink.startsWith('/')) {
                const fullUrl = `https://letterboxd.com${filmLink}`;
 
                const $visualPoster = $posterComponent.find('.film-poster');
                if ($visualPoster.length > 0) {
                    fetchPythonAnywhereData(fullUrl, data => {
                        addRatingsAndLinks(data, $visualPoster, fullUrl);
                        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
    });
 
})();