// ==UserScript== // @name Show Letterboxd rating // @description Show Letterboxd rating on imdb.com, metacritic.com, rottentomatoes.com, BoxOfficeMojo, Amazon, Google Play, allmovie.com, Wikipedia, themoviedb.org, fandango.com, thetvdb.com, save.tv, argenteam.net // @namespace cuzi // @icon https://a.ltrbxd.com/logos/letterboxd-mac-icon.png // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM.xmlHttpRequest // @grant GM.setValue // @grant GM.getValue // @require https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js // @license GPL-3.0-or-later; https://www.gnu.org/licenses/gpl-3.0.txt // @version 21 // @connect letterboxd.com // @match https://play.google.com/store/movies/details/* // @match https://www.amazon.ca/* // @match https://www.amazon.co.jp/* // @match https://www.amazon.co.uk/* // @match https://smile.amazon.co.uk/* // @match https://www.amazon.com.au/* // @match https://www.amazon.com.mx/* // @match https://www.amazon.com/* // @match https://smile.amazon.com/* // @match https://www.amazon.de/* // @match https://smile.amazon.de/* // @match https://www.amazon.es/* // @match https://www.amazon.fr/* // @match https://www.amazon.in/* // @match https://www.amazon.it/* // @match https://www.imdb.com/title/* // @match https://www.serienjunkies.de/* // @match https://www.boxofficemojo.com/movies/* // @match https://www.boxofficemojo.com/release/* // @match https://www.allmovie.com/movie/* // @match https://en.wikipedia.org/* // @match https://www.fandango.com/* // @match https://www.flixster.com/movie/* // @match https://www.themoviedb.org/movie/* // @match https://www.rottentomatoes.com/m/* // @match https://rottentomatoes.com/m/* // @match https://www.metacritic.com/movie/* // @match https://www.nme.com/reviews/movie/* // @match https://www.nme.com/reviews/film-reviews/* // @match https://itunes.apple.com/* // @match https://www.tvhoard.com/* // @match https://thetvdb.com/movies/* // @match https://rlsbb.ru/*/ // @match https://www.sho.com/* // @match https://psa.pm/* // @match https://www.save.tv/* // @match https://argenteam.net/* // @downloadURL none // ==/UserScript== /* global GM, $ */ const baseURL = 'https://letterboxd.com' const baseURL_search = baseURL + '/s/autocompletefilm?q={query}&limit=20×tamp={timestamp}' const baseURL_openTab = baseURL + '/search/{query}/' const baseURL_ratingHistogram = baseURL + '/csi{url}rating-histogram/' const cacheExpireAfterHours = 4 function minutesSince (time) { const seconds = ((new Date()).getTime() - time.getTime()) / 1000 return seconds > 60 ? parseInt(seconds / 60) + ' min ago' : 'now' } function fixLetterboxdURLs (html) { return html.replace(/ cacheExpireAfterHours * 60 * 60 * 1000) { delete cache[prop] } } // Check cache or request new content if (url in cache) { // Use cached response handleSearchResponse(cache[url], forceList) } else { GM.xmlHttpRequest({ method: 'GET', url, onload: function (response) { // Save to chache response.time = (new Date()).toJSON() // Chrome fix: Otherwise JSON.stringify(cache) omits responseText const newobj = {} for (const key in response) { newobj[key] = response[key] } newobj.responseText = response.responseText cache[url] = newobj GM.setValue('cache', JSON.stringify(cache)) handleSearchResponse(response, forceList) }, onerror: function (response) { console.log('Letterboxd GM.xmlHttpRequest Error: ' + response.status + '\nURL: ' + url + '\nResponse:\n' + response.responseText) } }) } } function handleSearchResponse (response, forceList) { // Handle GM.xmlHttpRequest response const result = JSON.parse(response.responseText) if (forceList && (result.result === false || !result.data || !result.data.length)) { window.alert('Letterboxd userscript\n\nNo results for ' + current.query) } else if (result.result === false || !result.data || !result.data.length) { console.log('Letterboxd: No results for ' + current.query) } else if (!forceList && result.data.length === 1) { loadMovieRating(result.data[0]) } else { // Sort results by closest match function matchQuality (title, year, originalTitle) { if (title === current.query && year === current.year) { return 105 + year } if (originalTitle && originalTitle === current.query && year === current.year) { return 104 + year } if (title === current.query && current.year) { return 103 - Math.abs(year - current.year) } if (originalTitle && originalTitle === current.query && current.year) { return 102 - Math.abs(year - current.year) } if (title.replace(/\(.+\)/, '').trim() === current.query && current.year) { return 101 - Math.abs(year - current.year) } if (originalTitle && originalTitle.replace(/\(.+\)/, '').trim() === current.query && current.year) { return 100 - Math.abs(year - current.year) } if (title === current.query) { return 12 } if (originalTitle && originalTitle === current.query) { return 11 } if (title.replace(/\(.+\)/, '').trim() === current.query) { return 10 } if (originalTitle && originalTitle.replace(/\(.+\)/, '').trim() === current.query) { return 9 } if (title.startsWith(current.query)) { return 8 } if (originalTitle && originalTitle.startsWith(current.query)) { return 7 } if (current.query.indexOf(title) !== -1) { return 6 } if (originalTitle && current.query.indexOf(originalTitle) !== -1) { return 5 } if (title.indexOf(current.query) !== -1) { return 4 } if (originalTitle && originalTitle.indexOf(current.query) !== -1) { return 3 } if (current.query.toLowerCase().indexOf(title.toLowerCase()) !== -1) { return 2 } if (title.toLowerCase().indexOf(current.query.toLowerCase()) !== -1) { return 1 } return 0 } result.data.sort(function (a, b) { if (!a.hasOwnProperty('matchQuality')) { a.matchQuality = matchQuality(a.name, a.releaseYear, a.originalName) } if (!b.hasOwnProperty('matchQuality')) { b.matchQuality = matchQuality(b.name, b.releaseYear, b.originalName) } return b.matchQuality - a.matchQuality }) if (!forceList && result.data.length > 1 && result.data[0].matchQuality > 100 && result.data[1].matchQuality < result.data[0].matchQuality) { loadMovieRating(result.data[0]) } else { showMovieList(result.data, new Date(response.time)) } } } function showMovieList (arr, time) { // Show a small box in the right lower corner $('#mcdiv321letterboxd').remove() const div = $('
').appendTo(document.body) div.css({ position: 'fixed', bottom: 0, right: 0, minWidth: 100, maxHeight: '80%', overflow: 'auto', backgroundColor: '#fff', border: '2px solid #bbb', borderRadius: ' 6px', boxShadow: '0 0 3px 3px rgba(100, 100, 100, 0.2)', color: '#000', padding: ' 3px', zIndex: '5010001', fontFamily: 'Helvetica,Arial,sans-serif' }) const imgFrame = function imgFrameFct (image125, scale) { if (!image125) { return } const id = 'iframeimg' + Math.random() const mWidth = 180.0 * scale - 45.0 const mHeight = 180.0 * scale - 25 let html = ' ' html += '
' GM.xmlHttpRequest({ method: 'GET', url: baseURL + image125, onload: function (response) { const html = '' + response.responseText if (html.indexOf('empty-poster-')) { const emptyPoster = html.match(/src="(https:\/\/\S+)"/)[1] const width = html.match(/data-image-width="(\d+)"/)[1] const height = html.match(/data-image-height="(\d+)"/)[1] const filmId = html.match(/data-film-id="(\d+)"/)[1] const cacheBustingKey = html.match(/data-cache-busting-key="(\w+)"/)[1] const dashTitle = html.match(/data-target-link="\/film\/(\S+)\/"/)[1] const slashFilmId = filmId.toString().split('').join('/') const emptyImg = new Image() emptyImg.src = emptyPoster emptyImg.style.maxWidth = mWidth + 'px' emptyImg.style.maxHeight = mHeight + 'px' document.getElementById(id).parentNode.replaceChild(emptyImg, document.getElementById(id)) const img = new Image() img.onload = function () { emptyImg.parentNode.replaceChild(img, emptyImg) } img.style.maxWidth = mWidth + 'px' img.style.maxHeight = mHeight + 'px' img.src = `https://a.ltrbxd.com/resized/film-poster/${slashFilmId}/${filmId}-${dashTitle}-0-${width}-0-${height}-crop.jpg?k=${cacheBustingKey}` } else { document.getElementById(id).src = 'data:text/html;charset=utf-8,' + escape(html) } } }) return html } // First result const first = $('
' + imgFrame(arr[0].image125, 0.75) + '
' + arr[0].name + (arr[0].originalTitle ? ' [' + arr[0].originalTitle + ']' : '') + (arr[0].releaseYear ? ' (' + arr[0].releaseYear + ')' : '') + '
').click(selectMovie).appendTo(div) first[0].dataset.movie = JSON.stringify(arr[0]) // Shall the following results be collapsed by default? let more = null if ((arr.length > 1 && arr[0].matchQuality > 10) || arr.length > 10) { $('More results...').appendTo(div).click(function () { more.css('display', 'block'); this.parentNode.removeChild(this) }) more = $('
').appendTo(div) } else { more = $('
').appendTo(div) } // More results for (let i = 1; i < arr.length; i++) { const entry = $('
' + imgFrame(arr[i].image125, 0.5) + '
' + arr[i].name + (arr[i].originalTitle ? ' [' + arr[i].originalTitle + ']' : '') + (arr[0].releaseYear ? ' (' + arr[0].releaseYear + ')' : '') + '
').click(selectMovie).appendTo(more) entry[0].dataset.movie = JSON.stringify(arr[i]) } // Footer const sub = $('
').appendTo(div) $('').appendTo(sub) $('@letterboxd.com').appendTo(sub) $('').appendTo(sub).click(function () { document.body.removeChild(this.parentNode.parentNode) }) } function selectMovie (ev) { ev.preventDefault() $('#mcdiv321letterboxd').html('Loading...') const data = JSON.parse(this.dataset.movie) loadMovieRating(data) addToWhiteList(data.url) } async function loadMovieRating (data) { // Load page from letterboxd if ('name' in data) { current.query = data.name } if ('releaseYear' in data) { current.year = data.releaseYear } const url = baseURL_ratingHistogram.replace('{url}', data.url) const cache = JSON.parse(await GM.getValue('cache', '{}')) // Delete cached values, that are expired for (const prop in cache) { if ((new Date()).getTime() - (new Date(cache[prop].time)).getTime() > cacheExpireAfterHours * 60 * 60 * 1000) { delete cache[prop] } } // Check cache or request new content if (url in cache) { // Use cached response showMovieRating(cache[url], data.url, data) } else { GM.xmlHttpRequest({ method: 'GET', url, onload: function (response) { // Save to chache response.time = (new Date()).toJSON() // Chrome fix: Otherwise JSON.stringify(cache) omits responseText const newobj = {} for (const key in response) { newobj[key] = response[key] } newobj.responseText = response.responseText cache[url] = newobj GM.setValue('cache', JSON.stringify(cache)) showMovieRating(newobj, data.url, data) }, onerror: function (response) { console.log('GM.xmlHttpRequest Error: ' + response.status + '\nURL: ' + url + '\nResponse:\n' + response.responseText) } }) } } function showMovieRating (response, letterboxdUrl, otherData) { // Show a small box in the right lower corner const time = new Date(response.time) $('#mcdiv321letterboxd').remove() const div = $('
').appendTo(document.body) div.css({ position: 'fixed', bottom: 0, right: 0, width: 230, minHeight: 44, color: '#789', padding: ' 3px', zIndex: '5010001', fontFamily: 'Helvetica,Arial,sans-serif' }) const CSS = `` $(CSS).appendTo(div) const section = $(fixLetterboxdURLs(response.responseText)).appendTo(div) section.find('h2').remove() let identName = current.query let identYear = current.year ? ' (' + current.year + ')' : '' let identOriginalName = '' let identDirector = '' if (otherData) { if ('name' in otherData && otherData.name) { identName = otherData.name } if ('year' in otherData && otherData.year) { identYear = ' (' + otherData.year + ')' } if ('originalName' in otherData && otherData.originalName) { identOriginalName = ' "' + otherData.originalName + '"' } if ('directors' in otherData) { identDirector = [] for (let i = 0; i < otherData.directors.length; i++) { if ('name' in otherData.directors[i]) { identDirector.push(otherData.directors[i].name) } } if (identDirector) { identDirector = '
Dir. ' + identDirector.join(', ') + '' } else { identDirector = '' } } } // Footer const sub = $('').appendTo(div) $('' + identName + identOriginalName + identYear + identDirector + '').appendTo(sub) $('
').appendTo(sub) $('').appendTo(sub) $('@letterboxd.com').appendTo(sub) $('').appendTo(sub).click(function () { document.getElementById('mcdiv321letterboxd').remove() }) $('🙅').appendTo(sub).click(function () { removeFromWhiteList() searchMovie(current.query, current.type, current.year, true) }) $('').appendTo(sub) } const Always = () => true const sites = { googleplay: { host: ['play.google.com'], condition: Always, products: [ { condition: () => ~document.location.href.indexOf('/movies/details/'), type: 'movie', data: () => document.querySelector('*[itemprop=name]').textContent } ] }, imdb: { host: ['imdb.com'], condition: () => !~document.location.pathname.indexOf('/mediaviewer') && !~document.location.pathname.indexOf('/mediaindex') && !~document.location.pathname.indexOf('/videoplayer'), products: [ { condition: function () { const e = document.querySelector("meta[property='og:type']") if (e && e.content === 'video.movie') { return true } else if (document.querySelector('[data-testid="hero-title-block__title"]') && !document.querySelector('[data-testid="hero-subnav-bar-left-block"] a[href*="episodes/"]')) { // New design 2020-12 return true } return false }, type: 'movie', data: function () { let year = null let name = null let jsonld = null if (document.querySelector('[data-testid="hero-title-block__title"]')) { // New design 2020-12 const m = document.title.match(/\s+\((\d{4})\)/) if (m) { year = parseInt(m[1]) } return [document.querySelector('[data-testid="hero-title-block__title"]').textContent, year] } if (document.querySelector('#titleYear')) { year = parseInt(document.querySelector('#titleYear a').firstChild.textContent) } if (document.querySelector("meta[property='og:title']") && document.querySelector("meta[property='og:title']").content) { // English title, this is the prefered title for Rottentomatoes' search name = document.querySelector("meta[property='og:title']").content.trim() if (name.indexOf('- IMDb') !== -1) { name = name.replace('- IMDb', '').trim() } name = name.replace(/\(\d{4}\)/, '').trim() } if (document.querySelector('script[type="application/ld+json"]')) { // Original title and release year jsonld = parseLDJSON(['name', 'datePublished']) if (name === null) { name = jsonld[0] } if (year === null) { year = parseInt(jsonld[1].match(/\d{4}/)[0]) } } if (name !== null && year !== null) { return [name, year] // Use original title } if (document.querySelector('.originalTitle') && document.querySelector('.title_wrapper h1')) { return [document.querySelector('.title_wrapper h1').firstChild.textContent.trim(), year] // Use localized title } else if (document.querySelector('h1[itemprop=name]')) { // Movie homepage (New design 2015-12) return [document.querySelector('h1[itemprop=name]').firstChild.textContent.trim(), year] } else if (document.querySelector('*[itemprop=name] a') && document.querySelector('*[itemprop=name] a').firstChild.textContent) { // Subpage of a move return [document.querySelector('*[itemprop=name] a').firstChild.textContent.trim(), year] } else if (document.querySelector('.title-extra[itemprop=name]')) { // Movie homepage: sub-/alternative-/original title return [document.querySelector('.title-extra[itemprop=name]').firstChild.textContent.replace(/"/g, '').trim(), year] } else if (document.querySelector('*[itemprop=name]')) { // Movie homepage (old design) return [document.querySelector('*[itemprop=name]').firstChild.textContent.trim(), year] } else { const rm = document.title.match(/(.+?)\s+(\(\d+\))? - IMDb/) return [rm[1], rm[2]] } } } ] }, metacritic: { host: ['www.metacritic.com'], condition: () => document.querySelector("meta[property='og:type']"), products: [{ condition: () => document.querySelector("meta[property='og:type']").content === 'video.movie', type: 'movie', data: function () { let year = null if (document.querySelector('.release_year')) { year = parseInt(document.querySelector('.release_year').firstChild.textContent) } else if (document.querySelector('.release_data .data')) { year = document.querySelector('.release_data .data').textContent.match(/(\d{4})/)[1] } return [document.querySelector("meta[property='og:title']").content, year] } }] }, amazon: { host: ['amazon.'], condition: Always, products: [{ condition: () => document.querySelector('[data-automation-id=title]'), type: 'movie', data: () => document.querySelector('[data-automation-id=title]').textContent.trim().replace(/\[.{1,8}\]/, '') }] }, BoxOfficeMojo: { host: ['boxofficemojo.com'], condition: () => Always, products: [ { condition: () => document.location.pathname.startsWith('/release/'), type: 'movie', data: function () { let year = null const cells = document.querySelectorAll('#body .mojo-summary-values .a-section span') for (let i = 0; i < cells.length; i++) { if (~cells[i].innerText.indexOf('Release Date')) { year = parseInt(cells[i].nextElementSibling.textContent.match(/\d{4}/)[0]) break } } return [document.querySelector('meta[name=title]').content, year] } }, { condition: () => ~document.location.search.indexOf('id=') && document.querySelector('#body table:nth-child(2) tr:first-child b'), type: 'movie', data: function () { let year = null try { const tds = document.querySelectorAll('#body table:nth-child(2) tr:first-child table table table td') for (let i = 0; i < tds.length; i++) { if (~tds[i].innerText.indexOf('Release Date')) { year = parseInt(tds[i].innerText.match(/\d{4}/)[0]) break } } } catch (e) { } return [document.querySelector('#body table:nth-child(2) tr:first-child b').firstChild.textContent, year] } }] }, AllMovie: { host: ['allmovie.com'], condition: () => document.querySelector('h2.movie-title'), products: [{ condition: () => document.querySelector('h2.movie-title'), type: 'movie', data: () => document.querySelector('h2.movie-title').firstChild.textContent.trim() }] }, 'en.wikipedia': { host: ['en.wikipedia.org'], condition: Always, products: [{ condition: function () { if (!document.querySelector('.infobox .summary')) { return false } const r = /\d\d\d\d films/ return $('#catlinks a').filter((i, e) => e.firstChild.textContent.match(r)).length }, type: 'movie', data: () => document.querySelector('.infobox .summary').firstChild.textContent }] }, fandango: { host: ['fandango.com'], condition: () => document.querySelector("meta[property='og:title']"), products: [{ condition: Always, type: 'movie', data: () => document.querySelector("meta[property='og:title']").content.match(/(.+?)\s+\(\d{4}\)/)[1].trim() }] }, flixster: { host: ['www.flixster.com'], condition: () => Always, products: [{ condition: () => parseLDJSON('@type') === 'Movie', type: 'movie', data: () => parseLDJSON('name', (j) => (j['@type'] === 'Movie')) }] }, themoviedb: { host: ['themoviedb.org'], condition: () => document.querySelector("meta[property='og:type']"), products: [{ condition: () => document.querySelector("meta[property='og:type']").content === 'movie', type: 'movie', data: function () { let year = null try { year = parseInt(document.querySelector('.release_date').innerText.match(/\d{4}/)[0]) } catch (e) {} return [document.querySelector("meta[property='og:title']").content, year] } }] }, rottentomatoes: { host: ['rottentomatoes.com'], condition: Always, products: [{ condition: () => document.location.pathname.startsWith('/m/'), type: 'movie', data: () => document.querySelector('h1').firstChild.textContent } ] }, nme: { host: ['nme.com'], condition: () => document.location.pathname.startsWith('/reviews/'), products: [{ condition: () => document.querySelector('.tdb-breadcrumbs a[href*="/reviews/film-reviews"]'), type: 'movie', data: function () { let year = null try { year = parseInt(document.querySelector('*[itemprop=datePublished]').content.match(/\d{4}/)[0]) } catch (e) {} try { return [document.title.match(/[‘'](.+?)[’']/)[1], year] } catch (e) { try { return [document.querySelector('h1.tdb-title-text').textContent.match(/[‘'](.+?)[’']/)[1], year] } catch (e) { return [document.querySelector('h1').textContent.match(/:\s*(.+)/)[1].trim(), year] } } } }] }, TheTVDB: { host: ['thetvdb.com'], condition: Always, products: [{ condition: () => document.location.pathname.startsWith('/movies/'), type: 'movie', data: () => document.getElementById('series_title').firstChild.textContent.trim() }] }, itunes: { host: ['itunes.apple.com'], condition: Always, products: [{ condition: () => ~document.location.href.indexOf('/movie/'), type: 'movie', data: () => parseLDJSON('name', (j) => (j['@type'] === 'Movie')) }] }, TVHoard: { host: ['tvhoard.com'], condition: Always, products: [{ condition: () => document.location.pathname.split('/').length === 3 && document.location.pathname.split('/')[1] === 'titles' && !document.querySelector('app-root title-secondary-details-panel .seasons') && document.querySelector('app-root title-page-container h1.title a'), type: 'movie', data: () => [document.querySelector('app-root title-page-container h1.title a').textContent.trim(), document.querySelector('app-root title-page-container title-primary-details-panel h1.title .year').textContent.trim().substring(1, 5)] }] }, RlsBB: { host: ['rlsbb.ru'], condition: () => document.querySelectorAll('.post').length === 1, products: [ { condition: () => document.querySelector('#post-wrapper .entry-meta a[href*="/category/movies/"]'), type: 'movie', data: () => document.querySelector('h1.entry-title').textContent.match(/(.+?)\s+\d{4}/)[1].trim() }] }, showtime: { host: ['sho.com'], condition: Always, products: [ { condition: () => parseLDJSON('@type') === 'Movie', type: 'movie', data: () => parseLDJSON('name', (j) => (j['@type'] === 'Movie')) }] }, psapm: { host: ['psa.pm'], condition: Always, products: [ { condition: () => document.location.pathname.startsWith('/movie/'), type: 'movie', data: function () { const title = document.querySelector('h1').textContent.trim() const m = title.match(/(.+)\((\d+)\)$/) if (m) { return [m[1].trim(), parseInt(m[2])] } else { return title } } }] }, 'save.tv': { host: ['save.tv'], condition: () => document.location.pathname.startsWith('/STV/M/obj/archive/'), products: [ { condition: () => document.location.pathname.startsWith('/STV/M/obj/archive/'), type: 'movie', data: function () { let title = null if (document.querySelector("span[data-bind='text:OrigTitle']")) { title = document.querySelector("span[data-bind='text:OrigTitle']").textContent } else { title = document.querySelector("h2[data-bind='text:Title']").textContent } let year = null if (document.querySelector("span[data-bind='text:ProductionYear']")) { year = parseInt(document.querySelector("span[data-bind='text:ProductionYear']").textContent) } return [title, year] } } ] }, aRGENTeaM: { host: ['argenteam.net'], condition: Always, products: [ { condition: () => document.location.pathname.startsWith('/movie/'), type: 'movie', data: function () { const partes = document.title.split('•') const SinArgenteam = partes[1].trim() const SoloTitulo = SinArgenteam.split('(')[0].trim() const Year = SinArgenteam.split('(')[1].split(')')[0] return [SoloTitulo, Year] } } ] } } function main () { let dataFound = false for (const name in sites) { const site = sites[name] if (site.host.some(function (e) { return ~this.indexOf(e) }, document.location.hostname) && site.condition()) { for (let i = 0; i < site.products.length; i++) { if (site.products[i].condition()) { // Try to retrieve item name from page let data try { data = site.products[i].data() } catch (e) { data = false console.error(`ShowLetterboxd: Error in data() of site='${name}', type='${site.products[i].type}'`) console.error(e) } if (data) { if (Array.isArray(data) && data[1]) { searchMovie(data[0].trim(), site.products[i].type, parseInt(data[1])) } else { searchMovie(data.trim(), site.products[i].type) } dataFound = true } break } } break } } return dataFound } async function adaptForRottentomatoesScript () { // Move this container above the rottentomatoes container and if the meta container is on the right side above both const letterC = document.getElementById('mcdiv321letterboxd') const metaC = document.getElementById('mcdiv123') const rottenC = document.getElementById('mcdiv321rotten') if (!letterC || (!metaC && !rottenC)) { return } const letterBounds = letterC.getBoundingClientRect() let bottom = 0 if (metaC) { const metaBounds = metaC.getBoundingClientRect() if (Math.abs(metaBounds.right - letterBounds.right) < 20 && metaBounds.top > 20) { bottom += metaBounds.height } } if (rottenC) { const rottenBounds = rottenC.getBoundingClientRect() if (Math.abs(rottenBounds.right - letterBounds.right) < 20 && rottenBounds.top > 20) { bottom += rottenBounds.height } } if (bottom > 0) { letterC.style.bottom = bottom + 'px' } } (function () { const firstRunResult = main() let lastLoc = document.location.href let lastContent = document.body.innerText let lastCounter = 0 function newpage () { if (lastContent === document.body.innerText && lastCounter < 15) { window.setTimeout(newpage, 500) lastCounter++ } else { lastContent = document.body.innerText lastCounter = 0 const re = main() if (!re) { // No page matched or no data found window.setTimeout(newpage, 1000) } } } window.setInterval(function () { adaptForRottentomatoesScript() if (document.location.href !== lastLoc) { lastLoc = document.location.href $('#mcdiv321letterboxd').remove() window.setTimeout(newpage, 1000) } }, 500) if (!firstRunResult) { // Initial run had no match, let's try again there may be new content window.setTimeout(main, 2000) } })()