// ==UserScript== // @name Letterboxd Friend Ratings Analyzer // @namespace http://tampermonkey.net/ // @version 3.2.1 // @description Analyze ratings from friends on Letterboxd, including paginated ratings, and show a histogram below the global one. // @author https://github.com/liam-h // @match https://letterboxd.com/film/* // @grant none // @license GPLv3 // @run-at document-end // @downloadURL none // ==/UserScript== const username = "YOUR_USERNAME_HERE"; // Fetch all ratings, including paginated pages const fetchAllRatings = (user, film) => { let page = 1; const allRatings = []; return (function fetchPage() { return fetchRatingsPage(user, film, page).then(ratingsFromPage => { allRatings.push(...ratingsFromPage); if (ratingsFromPage.length > 0) { page++; return fetchPage(); } return allRatings; }); })(); }; // Fetch ratings from a single page as a one-liner const fetchRatingsPage = (user, film, page) => fetch(`https://letterboxd.com/${user}/friends/film/${film}/rated/.5-5/page/${page}`) .then(response => response.text()) .then(html => Array.from(new DOMParser().parseFromString(html, 'text/html').querySelectorAll('.person-table.film-table tbody tr')) .map(row => { const ratingClass = [...row.querySelector('.film-detail-meta .rating').classList] .find(cls => cls.startsWith('rated-')); return ratingClass ? parseFloat(ratingClass.replace('rated-', '')) / 2 : null; }).filter(Boolean) ); // Construct the friends' rating histogram with links const constructHistogram = (ratings, user, film) => { const bins = Array(10).fill(0); ratings.forEach(rating => bins[Math.floor((rating - 0.5) * 2)]++); const maxCount = Math.max(...bins); return `
★★★★★
`; }; // Place histogram below the global one, adding links const placeHistogram = (histogramHtml, averageRating, user, film, count) => { const globalHistogramSection = document.querySelector('.ratings-histogram-chart'); if (globalHistogramSection) { const friendsRatingsLink = `/${user}/friends/film/${film}/rated/.5-5/`; const friendsHistogramSection = document.createElement('section'); friendsHistogramSection.classList.add('section', 'ratings-histogram-chart'); friendsHistogramSection.innerHTML = `

Ratings from friends ${averageRating}

${histogramHtml} `; globalHistogramSection.parentNode.insertBefore(friendsHistogramSection, globalHistogramSection.nextSibling); } }; // Main function to run the script fetchAllRatings(username, film = window.location.href.split('/').slice(-2, -1)[0]) .then(ratings => { if (ratings.length) { const averageRating = (ratings.reduce((sum, rating) => sum + rating, 0) / ratings.length).toFixed(1); placeHistogram(constructHistogram(ratings, username, film), averageRating, username, film, ratings.length); } });