6 |
7 |
8 |
9 | **This add-on also supports the mobile Letterboxd website and has been tested to work on Firefox for Android.** Other Android browsers that support add-ons/extensions should work, but have not been tested.
10 |
11 | ## Features
12 | - Film ratings: displays film ratings from other websites, by default:
13 | - IMDb
14 | - Rotten Tomatoes
15 | - Metacritic
16 | - MyAnimeList
17 | - AniList
18 | - CinemaScore
19 | - Additional film rating sites, disabled by default:
20 | - SensCritique
21 | - MUBI
22 | - FilmAffinity
23 | - Simkl
24 | - AlloCiné
25 | - Additional top film rankings: Display the rankings from "They Shoot Pictures, Don't They?" top 1000 and "BFI Sight and Sound" top 250
26 | - Info box on people pages: A Wikipedia-like info box on people pages for the birth dates, death dates, and years active
27 | - MPA film ratings: Display the film's MPA rating
28 | - Wide release date: Display the full wide release date on hover of the film year
29 | - Duration: Converts the duration to hours and minutes on hover of the duration
30 | - Budget and Box Office: Display budget and box office numbers in the details tabs
31 | - Search: option to default the search to filter to films only
32 |
33 |
34 | ## Installation
35 | ### Firefox
36 | [Firefox Add-ons](https://addons.mozilla.org/en-US/firefox/addon/letterboxd-extras/)
37 |
38 | XPI file for manual installation available for each release on the [releases tab](https://github.com/duncanlang/Letterboxd-Extras/releases).
39 |
40 | ### Chromium
41 | [Chrome Web Store](https://chromewebstore.google.com/detail/letterboxd-extras/edhldpamlnkpekapihiolppcdppgeice)
42 |
43 | [Edge Add-ons](https://microsoftedge.microsoft.com/addons/detail/letterboxd-extras/khnodkkceaakcafenlmnbbjgfkhjmbgh)
44 |
45 |
46 | ## Issues or Suggestions
47 | Any issues or suggestions, please [create an issue on Github](https://github.com/duncanlang/Letterboxd-Extras/issues).
48 |
49 | Any suggestion for new rating sites will be considered, but may not be possible due to lack of APIs or anti-scraping rules.
50 |
51 | ## Screenshots
52 | Additional ratings from IMDb, Rotten Tomatoes, Metacritic, and Cinemascore in the sidebar, new links at the bottom, MPA rating at the top
53 |
54 |
55 | Extra details for Rotten Tomatoes and Metacritic expanded in the sidebar
56 |
57 |
58 | More ratings from SensCritique, MUBI, filmaffinity, and SIMKL, rankings on the left sidebar, and budget and box office in the details tab
59 |
60 |
61 | More ratings from Allocine
62 |
63 |
64 | MyAnimeList and AniList ratings for Anime
65 |
66 |
67 | Info box below photo of the actor, displaying the birth and death dates as well as the years active
68 |
69 |
--------------------------------------------------------------------------------
/pack.bat:
--------------------------------------------------------------------------------
1 | "C:\Program Files\7-Zip\7z.exe" a -tzip Letterboxd-Extras.zip images/* icon16.png icon32.png icon128.png jquery-3.6.0.min.js letterboxd-extras.user.js manifest.json options.html options.js package.json polyfill.js background.js google2letterboxd.js restore.html
--------------------------------------------------------------------------------
/screenshots/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duncanlang/Letterboxd-Extras/0e0fb6c300231e6eb9b07131166f5b7cb785e59b/screenshots/1.png
--------------------------------------------------------------------------------
/screenshots/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duncanlang/Letterboxd-Extras/0e0fb6c300231e6eb9b07131166f5b7cb785e59b/screenshots/2.png
--------------------------------------------------------------------------------
/screenshots/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duncanlang/Letterboxd-Extras/0e0fb6c300231e6eb9b07131166f5b7cb785e59b/screenshots/3.png
--------------------------------------------------------------------------------
/screenshots/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duncanlang/Letterboxd-Extras/0e0fb6c300231e6eb9b07131166f5b7cb785e59b/screenshots/4.png
--------------------------------------------------------------------------------
/screenshots/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duncanlang/Letterboxd-Extras/0e0fb6c300231e6eb9b07131166f5b7cb785e59b/screenshots/5.png
--------------------------------------------------------------------------------
/screenshots/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duncanlang/Letterboxd-Extras/0e0fb6c300231e6eb9b07131166f5b7cb785e59b/screenshots/6.png
--------------------------------------------------------------------------------
/screenshots/promo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duncanlang/Letterboxd-Extras/0e0fb6c300231e6eb9b07131166f5b7cb785e59b/screenshots/promo.png
--------------------------------------------------------------------------------
/screenshots/ratings-logos.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duncanlang/Letterboxd-Extras/0e0fb6c300231e6eb9b07131166f5b7cb785e59b/screenshots/ratings-logos.png
--------------------------------------------------------------------------------
/src/build chrome.lnk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duncanlang/Letterboxd-Extras/0e0fb6c300231e6eb9b07131166f5b7cb785e59b/src/build chrome.lnk
--------------------------------------------------------------------------------
/src/build firefox.lnk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duncanlang/Letterboxd-Extras/0e0fb6c300231e6eb9b07131166f5b7cb785e59b/src/build firefox.lnk
--------------------------------------------------------------------------------
/src/build.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | setlocal enabledelayedexpansion
3 |
4 | :: Ask for target if not passed
5 | if "%1"=="" (
6 | echo Usage: build.bat chrome ^| firefox
7 | exit /b 1
8 | )
9 |
10 | set TARGET=%1
11 | set SOURCE_DIR=%~dp0
12 | set COMMON_DIR=%SOURCE_DIR%common
13 | set TARGET_DIR=%SOURCE_DIR%%TARGET%
14 | set DIST_DIR=%SOURCE_DIR%dist\%TARGET%
15 |
16 | :: Clean previous build
17 | if exist "%DIST_DIR%" (
18 | rmdir /s /q "%DIST_DIR%"
19 | )
20 | mkdir "%DIST_DIR%"
21 |
22 | :: Copy common files
23 | xcopy /e /i /y "%COMMON_DIR%" "%DIST_DIR%"
24 |
25 | :: Copy manifest
26 | copy /y "%TARGET_DIR%" "%DIST_DIR%"
27 |
28 | echo Build complete: %DIST_DIR%
--------------------------------------------------------------------------------
/src/chrome/colour_scheme.css:
--------------------------------------------------------------------------------
1 | @media (prefers-color-scheme: light) {
2 | body {
3 | --body-text-color: #000;
4 | --body-back-color: #ffffff;
5 | --body-text-alt-color: #7d7d7d;
6 | }
7 | }
8 |
9 | @media (prefers-color-scheme: dark) {
10 | body {
11 | --body-text-color: #f4f4f4;
12 | --body-back-color: rgb(32, 33, 36); /* --google-grey-900 */
13 | --body-text-alt-color: #a3a2a7;
14 | }
15 | }
--------------------------------------------------------------------------------
/src/chrome/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 3,
3 | "name": "Letterboxd Extras",
4 | "description": "Displays additional scores on Letterboxd (IMDB, Rotten Tomatoes, Metacritic).",
5 | "version": "3.17.0",
6 | "minimum_chrome_version": "88",
7 |
8 | "action": {
9 | "default_title": "Letterboxd Extras Settings",
10 | "default_popup": "options.html?type=action",
11 | "default_icon": {
12 | "16": "icon16.png",
13 | "32": "icon32.png",
14 | "128": "icon128.png"
15 | }
16 | },
17 |
18 | "icons": {
19 | "128": "icon128.png"
20 | },
21 |
22 | "options_ui": {
23 | "page": "options.html",
24 | "browser_style": true,
25 | "open_in_tab": true
26 | },
27 |
28 | "permissions": [
29 | "storage",
30 | "scripting"
31 | ],
32 |
33 | "host_permissions": [
34 | "https://www.imdb.com/*",
35 | "https://letterboxd.com/*",
36 | "https://*.imdb.com/*",
37 | "https://www.rottentomatoes.com/*",
38 | "https://www.boxofficemojo.com/*",
39 | "https://webapp.cinemascore.com/*",
40 | "https://query.wikidata.org/*",
41 | "https://www.metacritic.com/*",
42 | "https://api.jikan.moe/*",
43 | "https://graphql.anilist.co/*"
44 | ],
45 |
46 | "optional_host_permissions": [
47 | "https://api.mubi.com/*",
48 | "https://apollo.senscritique.com/*",
49 | "https://*.filmaffinity.com/*",
50 | "https://www.theyshootpictures.com/*",
51 | "https://www.bfi.org.uk/*",
52 | "https://www.allocine.fr/*",
53 | "https://api.simkl.com/*",
54 | "https://www.google.com/search*",
55 | "https://www.doesthedogdie.com/*",
56 | "https://markuapi.apn.leapcell.app/*",
57 | "https://kinopoiskapiunofficial.tech/api/*"
58 | ],
59 |
60 | "background": {
61 | "service_worker": "background.js",
62 | "type": "module"
63 | },
64 |
65 | "content_scripts": [
66 | {
67 | "matches": [ "https://letterboxd.com/*" ],
68 | "js": [ "polyfill.js", "letterboxd-extras.user.js" ],
69 | "run_at": "document_start"
70 | }
71 | ],
72 |
73 | "web_accessible_resources": [
74 | {
75 | "resources": [ "images/*"],
76 | "matches": [ "https://letterboxd.com/*" ],
77 | "use_dynamic_url": true
78 | }
79 | ]
80 | }
81 |
--------------------------------------------------------------------------------
/src/common/background.js:
--------------------------------------------------------------------------------
1 | const isFirefox = typeof browser !== "undefined" && typeof browser.runtime !== "undefined";
2 | const isChrome = typeof chrome !== "undefined" && typeof browser === "undefined";
3 |
4 |
5 | if (isChrome)
6 | var browser = chrome;
7 |
8 | browser.runtime.onMessage.addListener((msg, sender, response) => {
9 | // Logging
10 | browser.storage.sync.get('options', (data) => {
11 | if (data != null){
12 | var options = data.options;
13 | if (options != null && options.hasOwnProperty('console-log') && options['console-log'] == true) {
14 | console.log("Letterboxd Extras | " + msg.url);
15 | }
16 | }
17 | });
18 |
19 | if (msg.type == null){
20 | msg.type = "";
21 | }
22 |
23 | if (msg.name == "GETDATA") { // Standard call
24 | var options = null;
25 | if (msg.options != null)
26 | options = msg.options;
27 |
28 | if (msg.url.includes('kinopoiskapiunofficial.tech') && msg.options == null){
29 | // do not steal pls :(
30 | var options = {
31 | method: 'GET',
32 | headers: {
33 | 'X-API-KEY': 'd761642c-8182-4167-b8db-cae260ade0db'
34 | }
35 | };
36 | }
37 |
38 | try {
39 | (async () => {
40 | // Check for permission before call
41 | if (await CheckForPermission(msg.url)){
42 | fetch(encodeURI(msg.url), options).then(async function (res) {
43 | var errors = null;
44 |
45 | // Check for errors
46 | if (res.status !== 200) {
47 | if (res.errors != null)
48 | errors = res.errors;
49 |
50 | response({ response: null, url: null, status: res.status, errors: errors })
51 | return;
52 | }
53 |
54 | // Get response body
55 | var resData = null;
56 | if (msg.type == "JSON"){
57 | await res.json().then(function (data) {
58 | resData = data;
59 | });
60 | }else{
61 | await res.text().then(function (data) {
62 | resData = data;
63 | });
64 | }
65 |
66 | response({ response: resData, url: res.url, status: res.status });
67 | });
68 | }else{
69 | response({ response: null, url: msg.url, status: 0, errors: ["No permission found matching url: " + msg.url] });
70 | }
71 | })();
72 | } catch (exception){
73 | response({ response: null, url: null, status: 0 })
74 | }
75 |
76 | } else if (msg.name == "RESETSETTINGS") { // Reset saved settings
77 | return (async () => {
78 | var options = {};
79 | await browser.storage.sync.set({ options });
80 | await InitDefaultSettings();
81 | return true;
82 | })();
83 |
84 | } else if (msg.name == "GETPERMISSIONS") { // Get all permissions
85 | return (async () => {
86 | var permissions = await browser.permissions.getAll();
87 | return permissions;
88 | })();
89 | }
90 |
91 | return true;
92 | });
93 |
94 | async function registerContentScripts() {
95 | await browser.storage.sync.get('options', async (data) => {
96 | if (data != null){
97 | var storedSettings = data.options;
98 | if (storedSettings != null && storedSettings.hasOwnProperty("google") && storedSettings["google"] === true) {
99 | const script = {
100 | id: 'google2letterboxd',
101 | js: ['google2letterboxd.js'],
102 | matches: ['https://www.google.com/search*'],
103 | };
104 | await browser.scripting.registerContentScripts([script]).catch(console.error);
105 | }
106 | }
107 | });
108 | }
109 |
110 | // InitDefaultSettings - Run every update/install to make sure all settings are initilized
111 | async function InitDefaultSettings() {
112 | // Get options from sync
113 | var options = {};
114 | const data = await browser.storage.sync.get('options');
115 | if (data != null && data.options != null) {
116 | Object.assign(options, data.options);
117 | }
118 |
119 | if (options == null)
120 | options = {};
121 |
122 | // mpa-enabled -> content-ratings
123 | if (options['mpa-enabled'] != null){
124 | if (options['mpa-enabled'] === true){
125 | options['content-ratings'] = 'mpaa';
126 | }else{
127 | options['content-ratings'] = 'none';
128 | }
129 | options['mpa-enabled'] = null;
130 | }
131 |
132 | // Default enabled settings
133 | if (options['imdb-enabled'] == null) options['imdb-enabled'] = true;
134 | if (options['tomato-enabled'] == null) options['tomato-enabled'] = true;
135 | if (options['metacritic-enabled'] == null) options['metacritic-enabled'] = true;
136 | if (options['mal-enabled'] == null) options['mal-enabled'] = true;
137 | if (options['al-enabled'] == null) options['al-enabled'] = true;
138 | if (options['cinema-enabled'] == null) options['cinema-enabled'] = true;
139 | if (options['mojo-link-enabled'] == null) options['mojo-link-enabled'] = true;
140 | if (options['wiki-link-enabled'] == null) options['wiki-link-enabled'] = true;
141 | if (options['tomato-critic-enabled'] == null) options['tomato-critic-enabled'] = true;
142 | if (options['tomato-audience-enabled'] == null) options['tomato-audience-enabled'] = true;
143 | if (options['metacritic-critic-enabled'] == null) options['metacritic-critic-enabled'] = true;
144 | if (options['metacritic-users-enabled'] == null) options['metacritic-users-enabled'] = true;
145 | if (options['metacritic-mustsee-enabled'] == null) options['metacritic-mustsee-enabled'] = true;
146 | if (options['sens-favorites-enabled'] == null) options['sens-favorites-enabled'] = true;
147 | if (options['allocine-critic-enabled'] == null) options['allocine-critic-enabled'] = true;
148 | if (options['allocine-users-enabled'] == null) options['allocine-users-enabled'] = true;
149 | if (options['content-ratings'] == null) options['content-ratings'] = 'mpaa';
150 |
151 | // Default disabled settings
152 | if (options['rt-default-view'] == null) options['rt-default-view'] = "hide";
153 | if (options['critic-default'] == null) options['critic-default'] = "all";
154 | if (options['audience-default'] == null) options['audience-default'] = "all";
155 | if (options['meta-default-view'] == null) options['meta-default-view'] = "hide";
156 | if (options['senscritique-enabled'] == null) options['senscritique-enabled'] = false;
157 | if (options['mubi-enabled'] == null) options['mubi-enabled'] = false;
158 | if (options['filmaff-enabled'] == null) options['filmaff-enabled'] = false;
159 | if (options['simkl-enabled'] == null) options['simkl-enabled'] = false;
160 | if (options['allocine-enabled'] == null) options['allocine-enabled'] = false;
161 | if (options['allocine-default-view'] == null) options['allocine-default-view'] = "user";
162 | if (options['search-redirect'] == null) options['search-redirect'] = false;
163 | if (options['tspdt-enabled'] == null) options['tspdt-enabled'] = false;
164 | if (options['bfi-enabled'] == null) options['bfi-enabled'] = false;
165 | if (options['convert-ratings'] == null) options['convert-ratings'] = "false";
166 | if (options['mpa-convert'] == null) options['mpa-convert'] = false;
167 | if (options['open-same-tab'] == null) options['open-same-tab'] = false;
168 | if (options['replace-fans'] == null) options['replace-fans'] = "false";
169 | if (options['hide-ratings-enabled'] == null) options['hide-ratings-enabled'] = false;
170 | if (options['tooltip-show-details'] == null) options['tooltip-show-details'] = false;
171 | if (options['google'] == null) options['google'] = false;
172 | if (options['boxoffice-enabled'] == null) options['boxoffice-enabled'] = false;
173 | if (options['ddd-enabled'] == null) options['ddd-enabled'] = false;
174 | if (options['ddd-apikey'] == null) options['ddd-apikey'] = '';
175 | if (options['kinopoisk-enabled'] == null) options['kinopoisk-enabled'] = false;
176 | if (options['kinopoisk-apikey'] == null) options['kinopoisk-apikey'] = '';
177 | if (options['filmarks-enabled'] == null) options['filmarks-enabled'] = false;
178 |
179 | if (options["convert-ratings"] === true) {
180 | options["convert-ratings"] = "5";
181 | }
182 |
183 |
184 | // Save
185 | await browser.storage.sync.set({ options });
186 | }
187 |
188 | async function InitLocalStorage(){
189 | // Get options from sync
190 | var options = {};
191 | const data = await browser.storage.local.get('options');
192 | if (data != null && data.options != null) {
193 | Object.assign(options, data.options);
194 | }
195 |
196 | if (options['hide-lost-films'] == null) options['hide-lost-films'] = 'show';
197 |
198 | // Save
199 | await browser.storage.local.set({ options });
200 | }
201 |
202 | // Convert storage.local to storage.sync (Firefox)
203 | async function ConvertLocalToSync() {
204 | // Get from local
205 | var options = await browser.storage.local.get().then(function (storedSettings) {
206 | return storedSettings;
207 | });
208 |
209 | // Clear the (now) unused local storage
210 | await browser.storage.local.clear();
211 |
212 | // Save
213 | await browser.storage.sync.set({ options });
214 | }
215 |
216 | browser.runtime.onStartup.addListener(registerContentScripts);
217 |
218 | browser.runtime.onInstalled.addListener(async (details) => {
219 | if (details.reason == 'install') {
220 | // Init the default settings
221 | await InitDefaultSettings();
222 | await InitLocalStorage();
223 | }
224 | else if (details.reason == 'update') {
225 | // Convert from previous versions
226 | var version = details.previousVersion.split('.');
227 |
228 | if (parseInt(version[0]) == 3 && parseInt(version[1]) < 16 && isFirefox) {
229 | await ConvertLocalToSync();
230 | }
231 |
232 | // Init default settings
233 | await InitDefaultSettings();
234 | await InitLocalStorage();
235 | }
236 | else if (details.reason == 'browser_update' || details.reason == 'chrome_update') {
237 | // Do nothing
238 | }
239 |
240 | // Make sure to register content scripts
241 | registerContentScripts();
242 | });
243 |
244 | async function CheckForPermission(url) {
245 | // Wrap chrome API in a Promise
246 | const perms = await new Promise(resolve => {
247 | chrome.permissions.getAll(resolve);
248 | });
249 |
250 | // Loop through granted origins and check against the given URL
251 | for (const pattern of perms.origins) {
252 | try {
253 | const urlPattern = new URLPattern({
254 | protocol: pattern.split("://")[0],
255 | hostname: pattern.split("://")[1].split("/")[0],
256 | pathname: pattern.split("/").slice(3).join("/") || "*"
257 | });
258 |
259 | if (urlPattern.test(url)) {
260 | return true;
261 | }
262 | } catch (e) {
263 | console.warn("Invalid pattern in permissions:", pattern, e);
264 | }
265 | }
266 |
267 | return false;
268 | }
269 |
--------------------------------------------------------------------------------
/src/common/google2letterboxd.js:
--------------------------------------------------------------------------------
1 |
2 | google2letterboxd();
3 |
4 | async function google2letterboxd(){
5 | // Get storage
6 | var options = await browser.storage.sync.get().then(function (storedSettings) {
7 | storedSettings = storedSettings['options'];
8 | if (storedSettings["convert-ratings"] === true){
9 | storedSettings["convert-ratings"] = "5";
10 | }
11 | return storedSettings;
12 | });
13 |
14 | // Verify the setting is enabled
15 | if (options['google'] == null || options['google'] === false){
16 | // This shouldn't happen
17 | return;
18 | }
19 |
20 | // Find element with IMDb
21 | let imdbElements = document.querySelectorAll('a span[aria-hidden="true"]')
22 | imdbElements = Array.from(imdbElements).filter(elm => elm.textContent.trim() === 'IMDb')
23 |
24 | imdbElements.forEach(imdbElement => {
25 | // Find the closest tag (which is the parent link element)
26 | const imdbLink = imdbElement.closest('a')
27 |
28 | let letterboxdElement = imdbLink.parentElement.querySelectorAll('a span[aria-hidden="true"]')
29 | letterboxdElement = Array.from(letterboxdElement).find(elm => elm.textContent.trim() === 'Letterboxd')
30 | if (!letterboxdElement) {
31 |
32 | // Extract the IMDb ID from the href attribute
33 | const imdbUrl = imdbLink.getAttribute('href')
34 | const imdbId = imdbUrl.split('title/').pop().replace('/','')
35 | const letterboxdUrl = `https://letterboxd.com/imdb/${imdbId}`
36 |
37 | // Remove Ratings heading
38 | let headingElement = document.querySelectorAll('span[role="heading"]')
39 | headingElement = Array.from(headingElement).find(elm => elm.textContent.trim() === 'Ratings')
40 | if (headingElement)
41 | headingElement.parentElement.style.display = "none"
42 |
43 | // Remove links if there are too many
44 | if (imdbLink.parentElement.childElementCount >= 5) {
45 | for (let i = imdbLink.parentElement.childElementCount - 1; i >= 3; i--)
46 | imdbLink.parentElement.children[i].remove()
47 | }
48 |
49 | // Add divider
50 | let dividerElement = imdbLink.parentElement.querySelector(':scope > div')
51 | if (dividerElement) {
52 | dividerElement = dividerElement.cloneNode(true)
53 | imdbLink.parentElement.appendChild(dividerElement)
54 | }
55 |
56 | // Construct the letterboxdLink
57 | const letterboxdLink = imdbLink.cloneNode(true)
58 | letterboxdLink.href = letterboxdUrl
59 |
60 | imdbLink.parentElement.appendChild(letterboxdLink)
61 | imdbLink.parentElement.style = "overflow-x:auto; overflow-y:hidden; justify-content:space-between;"
62 |
63 | const spans = letterboxdLink.querySelectorAll('span')
64 | const span1 = Array.from(spans).find(span => span.textContent.includes('/10'))
65 | if (options['convert-ratings'] != null && options['convert-ratings'] === "10"){
66 | span1.textContent = '/10'
67 | }else{
68 | span1.textContent = '/5'
69 | }
70 | const span2 = Array.from(spans).find(span => span.textContent.trim() === 'IMDb')
71 | span2.textContent = 'Letterboxd'
72 |
73 | const img = letterboxdLink.querySelector('img')
74 | if (img)
75 | img.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABxklEQVQ4jaWTvWtTYRTGf+fNtUGJcLV+cFsIiEiDt5SipVKKIB0SBAUpROgiDnGQDkLwD8gfIN1sFwcdxI9AF0ExQ8giSF20xEgVpSSkGbRwlQym5N7jYI25sYUGn+k9h/M8POd5OUIPUnPzrvrtTKAkgfh2u2qEgkSsey8f3X3fPS9/Hul0bsCTxgJwU1VNrzCAiATAkq1ONp/PbXUE0uncgEfjhaIzOxH/EUKKNs7FfD63ZQA8aSzslQyg6My2WyQ1N+8Gfnt1N9u7uhAJTMQas9RvZ1TVTB1c50ysTq1l89xL8OOQsDHZAmBoJcoBbx9BYgrso1D/hFkvG/XbGStQklePvOXG8ZWO+ik3yqXUIH5UAahN/+TKx1ts7h//PTB6Hl4/g3elpAHis4PlkD1n9jYjseFOPRIb5vr0UGgmGLsAEO9r751ggOry5mio2Vi+w1qz3qnXmnXuv9oIE1dLAFXLCIWn38ZP11r23xA/nOBcJRzid+8hJvGlO0SMSOG/vzHyufzm60l34hgw2afAYuHJ0gMDYKuTFaS4ZzJStNXJAkQAKpWSf9a9/LglzcMiMkHXkfXaFpFFG+da6Ji60e85/wJ5/LY0w2PuAAAAAABJRU5ErkJggg=="
76 |
77 | // Fetch the Letterboxd page and extract the rating
78 | fetch(letterboxdUrl)
79 | .then(response => response.text())
80 | .then(text => {
81 | // Use regex to find the ratingValue
82 | const ratingMatch = text.match(/"ratingValue"\s*:\s*(\d+(\.\d+)?)/)
83 | if (ratingMatch) {
84 | const rating = parseFloat(ratingMatch[1])
85 | if (options['convert-ratings'] != null && options['convert-ratings'] === "10"){
86 | span1.textContent = `${(rating*2).toFixed(1)}/10`
87 | }else{
88 | span1.textContent = `${rating.toFixed(1)}/5`
89 | }
90 | } else if (/IMDB ID not found/i.test(text)) {
91 | letterboxdLink.remove()
92 | if (dividerElement)
93 | dividerElement.remove()
94 | if (headingElement)
95 | headingElement.parentElement.style.display = "block"
96 | } else {
97 | console.error('Letterboxd rating not found.')
98 | }
99 | })
100 | .catch(error => {
101 | console.error('Error fetching Letterboxd page:', error)
102 | })
103 |
104 | }else if (options['convert-ratings'] != null && options['convert-ratings'] === "10"){
105 | const spans = letterboxdElement.parentElement.querySelectorAll('span')
106 | const span1 = Array.from(spans).find(span => span.textContent.includes('/5'))
107 |
108 | var rating = parseFloat(span1.textContent.split('/')[0]);
109 | span1.textContent = `${(rating*2).toFixed(1)}/10`
110 | }
111 | })
112 | }
--------------------------------------------------------------------------------
/src/common/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duncanlang/Letterboxd-Extras/0e0fb6c300231e6eb9b07131166f5b7cb785e59b/src/common/icon128.png
--------------------------------------------------------------------------------
/src/common/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duncanlang/Letterboxd-Extras/0e0fb6c300231e6eb9b07131166f5b7cb785e59b/src/common/icon16.png
--------------------------------------------------------------------------------
/src/common/icon32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duncanlang/Letterboxd-Extras/0e0fb6c300231e6eb9b07131166f5b7cb785e59b/src/common/icon32.png
--------------------------------------------------------------------------------
/src/common/icon512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duncanlang/Letterboxd-Extras/0e0fb6c300231e6eb9b07131166f5b7cb785e59b/src/common/icon512.png
--------------------------------------------------------------------------------
/src/common/images/bfi-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/common/images/filmarks-logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/common/images/kinopoisk-logo-eng.svg:
--------------------------------------------------------------------------------
1 |
55 |
--------------------------------------------------------------------------------
/src/common/images/kinopoisk-logo-rus.svg:
--------------------------------------------------------------------------------
1 |
50 |
--------------------------------------------------------------------------------
/src/common/images/mal-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duncanlang/Letterboxd-Extras/0e0fb6c300231e6eb9b07131166f5b7cb785e59b/src/common/images/mal-logo.png
--------------------------------------------------------------------------------
/src/common/images/sens-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duncanlang/Letterboxd-Extras/0e0fb6c300231e6eb9b07131166f5b7cb785e59b/src/common/images/sens-logo.png
--------------------------------------------------------------------------------
/src/common/images/simkl-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duncanlang/Letterboxd-Extras/0e0fb6c300231e6eb9b07131166f5b7cb785e59b/src/common/images/simkl-logo.png
--------------------------------------------------------------------------------
/src/common/images/tomato-audience-hot.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/common/images/tomato-audience-no-score.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/common/images/tomato-audience-stale.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/common/images/tomato-audience-verified-hot.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/common/images/tomato-critic-certified-fresh.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/common/images/tomato-critic-fresh.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/common/images/tomato-critic-no-score.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/common/images/tomato-critic-rotten.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/common/options.css:
--------------------------------------------------------------------------------
1 | body {
2 | --body-text-color: #000;
3 | --body-back-color: #ffffff;
4 | --body-text-alt-color: #7d7d7d;
5 |
6 | color: var(--body-text-color);
7 | background-color: var(--body-back-color);
8 |
9 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
10 |
11 | min-width: 450px;
12 | }
13 |
14 | h3,
15 | label,
16 | p,
17 | a,
18 | input#export,
19 | input#import,
20 | input#reset,
21 | input#importbutton,
22 | input#importpicker,
23 | input#requestall {
24 | margin-left: 15px;
25 | margin-right: 15px;
26 | }
27 |
28 | input#export,
29 | input#import {
30 | margin-top: 5px;
31 | }
32 |
33 | input#reset {
34 | margin-top: 25px;
35 | }
36 |
37 | p {
38 | margin-top: 0px;
39 | font-style: italic;
40 | font-size: 11px;
41 | width: 95%;
42 | max-width: 450px;
43 | color: var(--body-text-alt-color);
44 | }
45 |
46 | a {
47 | color: rgb(51, 167, 255);
48 | width: 95%;
49 | text-decoration: none;
50 | }
51 |
52 | a:hover {
53 | opacity: 50%;
54 | text-decoration: underline;
55 | }
56 |
57 | label {
58 | display: inline-block;
59 | width: 175px;
60 | margin-bottom: 10px;
61 | }
62 |
63 | .div-request-permission,
64 | .div-request-contentscript {
65 | display: none;
66 | }
67 |
68 | .div-request-permission label,
69 | .div-request-contentscript label {
70 | font-size: 15px;
71 | }
72 |
73 | .disabled {
74 | pointer-events: none;
75 | opacity: 50%;
76 | }
77 |
78 | .hyperlink {
79 | color: rgb(51, 167, 255);
80 | }
--------------------------------------------------------------------------------
/src/common/options.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | You are missing some permissions needed for certain enabled settings.
18 | 19 |Letterboxd Extras uses an unoffical API to get the ratings from Kinopoisk. If you encounter issues with rate limiting, you can enter your own key.
213 |Defaults the Letterboxd search to only show results for films, rather than showing all results.
242 |An API key is supposed to be mandatory for the DDD API, however, the API call seems to work without. If this does not work, you can try to use your own API key.
312 |Converts ratings to different rating scales. Affects all ratings except for Cinemascore and Rotten Tomatoes percentage.
327 |This will return back to the settings to allow you to request any missing permissions.
102 |