// ==UserScript==
// @name Letterboxd to IMDb Links with Ratings and Additional Sites
// @name:tr Letterboxd Puan ve Ek Site Linkleri
// @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
// @description:tr Letterboxd Puan ve Ek Site Linkleri Sunar
// @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
});
})();