🏠 Home 

คุณต้องเข้าสู่ระบบหรือลงทะเบียนก่อนดำเนินการต่อ

Show Letterboxd rating

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


Installer dette script?
Skaberens foreslåede script

Du vil måske også kunne lide Show Rottentomatoes meter


Installer dette script
// ==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
// @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.7.1/jquery.min.js
// @license     GPL-3.0-or-later; https://www.gnu.org/licenses/gpl-3.0.txt
// @version     27
// @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.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://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://thetvdb.com/movies/*
// @match       https://rlsbb.ru/*/
// @match       https://www.sho.com/*
// @match       https://www.gog.com/*
// @match       https://psa.wf/*
// @match       https://www.save.tv/*
// @match       https://www.wikiwand.com/*
// @match       https://trakt.tv/*
// @match       http://localhost:7878/*
// ==/UserScript==
/* global GM, $, Image, DOMParser */
/* jshint asi: true, esversion: 8 */
const baseURL = 'https://letterboxd.com'
const baseURLsearch = baseURL + '/s/autocompletefilm?q={query}&limit=20&timestamp={timestamp}'
const baseURLopenTab = baseURL + '/search/{query}/'
const baseURLratingHistogram = baseURL + '/csi{url}rating-histogram/'
const baseURLposter = baseURL + '/ajax/poster{film_url}std/70x105/'
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(/<a /g, '<a target="_blank" ').replace(/href="\//g, 'href="' + baseURL + '/').replace(/src="\//g, 'src="' + baseURL + '/')
}
function filterUniversalUrl (url) {
try {
url = url.match(/http.+/)[0]
} catch (e) { }
try {
url = url.replace(/https?:\/\/(www.)?/, '')
} catch (e) { }
if (url.startsWith('imdb.com/') && url.match(/(imdb\.com\/\w+\/\w+\/)/)) {
// Remove movie subpage from imdb url
return url.match(/(imdb\.com\/\w+\/\w+\/)/)[1]
} else if (url.startsWith('boxofficemojo.com/') && url.indexOf('id=') !== -1) {
// Keep the important id= on
try {
const parts = url.split('?')
const page = parts[0] + '?'
const idparam = parts[1].match(/(id=.+?)(\.|&)/)[1]
return page + idparam
} catch (e) {
return url
}
} else {
// Default: Remove parameters
return url.split('?')[0].split('&')[0]
}
}
const parseLDJSONCache = {}
function parseLDJSON (keys, condition) {
if (document.querySelector('script[type="application/ld+json"]')) {
const xmlEntitiesElement = document.createElement('div')
const xmlEntitiesPattern = /&(?:#x[a-f0-9]+|#[0-9]+|[a-z0-9]+);?/ig
const xmlEntities = function (s) {
s = s.replace(xmlEntitiesPattern, (m) => {
xmlEntitiesElement.innerHTML = m
return xmlEntitiesElement.textContent
})
return s
}
const decodeXmlEntities = function (jsonObj) {
// Traverse through object, decoding all strings
if (jsonObj !== null && typeof jsonObj === 'object') {
Object.entries(jsonObj).forEach(([key, value]) => {
// key is either an array index or object key
jsonObj[key] = decodeXmlEntities(value)
})
} else if (typeof jsonObj === 'string') {
return xmlEntities(jsonObj)
}
return jsonObj
}
const data = []
const scripts = document.querySelectorAll('script[type="application/ld+json"]')
for (let i = 0; i < scripts.length; i++) {
let jsonld
if (scripts[i].innerText in parseLDJSONCache) {
jsonld = parseLDJSONCache[scripts[i].innerText]
} else {
try {
jsonld = JSON.parse(scripts[i].innerText)
parseLDJSONCache[scripts[i].innerText] = jsonld
} catch (e) {
parseLDJSONCache[scripts[i].innerText] = null
continue
}
}
if (jsonld) {
if (Array.isArray(jsonld)) {
data.push(...jsonld)
} else {
data.push(jsonld)
}
}
}
for (let i = 0; i < data.length; i++) {
try {
if (data[i] && data[i] && (typeof condition !== 'function' || condition(data[i]))) {
if (Array.isArray(keys)) {
const r = []
for (let j = 0; j < keys.length; j++) {
r.push(data[i][keys[j]])
}
return decodeXmlEntities(r)
} else if (keys) {
return decodeXmlEntities(data[i][keys])
} else if (typeof condition === 'function') {
return decodeXmlEntities(data[i]) // Return whole object
}
}
} catch (e) {
continue
}
}
return decodeXmlEntities(data)
}
return null
}
async function addToWhiteList (letterboxdUrl) {
const whitelist = JSON.parse(await GM.getValue('whitelist', '{}'))
const docUrl = filterUniversalUrl(document.location.href)
whitelist[docUrl] = letterboxdUrl
await GM.setValue('whitelist', JSON.stringify(whitelist))
}
async function removeFromWhiteList () {
const whitelist = JSON.parse(await GM.getValue('whitelist', '{}'))
const docUrl = filterUniversalUrl(document.location.href)
if (docUrl in whitelist) {
delete whitelist[docUrl]
await GM.setValue('whitelist', JSON.stringify(whitelist))
}
}
const current = {
type: null,
query: null,
year: null
}
async function searchMovie (query, type, year, forceList) {
// Load data from letterboxd search API or from cache
current.type = type
current.query = query
current.year = year
let whitelist = JSON.parse(await GM.getValue('whitelist', '{}'))
if (forceList) {
whitelist = {}
}
const docUrl = filterUniversalUrl(document.location.href)
if (docUrl in whitelist) {
return loadMovieRating({ url: whitelist[docUrl] })
}
const url = baseURLsearch.replace('{query}', encodeURIComponent(query)).replace('{timestamp}', encodeURIComponent(Date.now()))
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
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('ShowLetterboxd: GM.xmlHttpRequest Error: ' + response.status + '\nURL: ' + url + '\nResponse:\n' + response.responseText)
}
})
}
}
function handleSearchResponse (response, forceList) {
// Handle GM.xmlHttpRequest response
const r###lt = JSON.parse(response.responseText)
if (forceList && (r###lt.r###lt === false || !r###lt.data || !r###lt.data.length)) {
window.alert('Letterboxd userscript\n\nNo r###lts for ' + current.query)
} else if (r###lt.r###lt === false || !r###lt.data || !r###lt.data.length) {
console.log('ShowLetterboxd: No r###lts for ' + current.query)
} else if (!forceList && r###lt.data.length === 1) {
loadMovieRating(r###lt.data[0])
} else {
// Sort r###lts 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
}
r###lt.data.sort(function (a, b) {
if (!Object.prototype.hasOwnProperty.call(a, 'matchQuality')) {
a.matchQuality = matchQuality(a.name, a.releaseYear, a.originalName)
}
if (!Object.prototype.hasOwnProperty.call(b, 'matchQuality')) {
b.matchQuality = matchQuality(b.name, b.releaseYear, b.originalName)
}
return b.matchQuality - a.matchQuality
})
if (!forceList && r###lt.data.length > 1 && r###lt.data[0].matchQuality > 100 && r###lt.data[1].matchQuality < r###lt.data[0].matchQuality) {
loadMovieRating(r###lt.data[0])
} else {
showMovieList(r###lt.data, new Date(response.time))
}
}
}
function showMovieList (arr, time) {
// Show a small box in the right lower corner
const parser = new DOMParser()
$('#mcdiv321letterboxd').remove()
const div = $('<div id="mcdiv321letterboxd"></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 (filmUrl, scale) {
if (!filmUrl) {
return
}
console.log(filmUrl)
const url = baseURLposter.replace('{film_url}', filmUrl)
console.log(url)
const id = 'iframeimg' + Math.random()
const mWidth = 180.0 * scale - 45.0
const mHeight = 180.0 * scale - 25
let html = '<iframe id="' + id + '" sandbox scrolling="no" src="' + url + '" marginheight="0" marginwidth="0" style="vertical-align:middle; padding:0px; border:none; display:inline; max-width:125px; margin-top:' + (40.0 * scale - 40.0) + '%; margin-left:' + (40.0 * scale - 40.0) + '%; transform:scale(' + scale + '); transform-origin:bottom right"></iframe> '
html += '<div style="position:absolute;top:0px;left:0px;width:' + mWidth + 'px;height:' + mHeight + 'px"></div> '
GM.xmlHttpRequest({
method: 'GET',
url,
onload: function (response) {
const html = '<base href="' + baseURL + '">' + response.responseText
if (html.indexOf('srcset=')) {
const doc = parser.parseFromString(html, 'text/html')
const image = doc.querySelector('img.image')
const img = new Image()
img.src = image.src
img.srcset = image.srcset
img.style.maxWidth = mWidth + 'px'
img.style.maxHeight = mHeight + 'px'
document.getElementById(id).parentNode.replaceChild(img, document.getElementById(id))
} else {
document.getElementById(id).src = 'data:text/html;charset=utf-8,' + encodeURIComponent(html)
}
}
})
return html
}
// First r###lt
console.log(arr[0])
const first = $('<div style="position:relative"><a style="font-size:small; color:#136CB2; " href="' + baseURL + arr[0].url + '">' + imgFrame(arr[0].url, 0.75) + '<div style="max-width:350px;display:inline-block">' + arr[0].name + (arr[0].originalTitle ? ' [' + arr[0].originalTitle + ']' : '') + (arr[0].releaseYear ? ' (' + arr[0].releaseYear + ')' : '') + '</div></a></div>').click(selectMovie).appendTo(div)
first[0].dataset.movie = JSON.stringify(arr[0])
// Shall the following r###lts be collapsed by default?
let more = null
if ((arr.length > 1 && arr[0].matchQuality > 10) || arr.length > 10) {
$('<span style="color:gray;font-size: x-small">More r###lts...</span>').appendTo(div).click(function () { more.css('display', 'block'); this.parentNode.removeChild(this) })
more = $('<div style="display:none"></div>').appendTo(div)
} else {
more = $('<div></div>').appendTo(div)
}
// More r###lts
for (let i = 1; i < arr.length; i++) {
const entry = $('<div style="position:relative"><a style="font-size:small; color:#136CB2; " href="' + baseURL + arr[i].url + '">' + imgFrame(arr[i].url, 0.5) + '<div style="max-width:350px;display:inline-block">' + arr[i].name + (arr[i].originalTitle ? ' [' + arr[i].originalTitle + ']' : '') + (arr[0].releaseYear ? ' (' + arr[0].releaseYear + ')' : '') + '</div></a></div>').click(selectMovie).appendTo(more)
entry[0].dataset.movie = JSON.stringify(arr[i])
}
// Footer
const sub = $('<div></div>').appendTo(div)
$('<time style="color:#789; font-size: 11px;" datetime="' + time + '" title="' + time.toLocaleTimeString() + ' ' + time.toLocaleDateString() + '">' + minutesSince(time) + '</time>').appendTo(sub)
$('<a style="color:#789; font-size: 11px;" target="_blank" href="' + baseURLopenTab.replace('{query}', encodeURIComponent(current.query)) + '" title="Open Letterboxd">@letterboxd.com</a>').appendTo(sub)
$('<span title="Hide me" style="cursor:pointer; float:right; color:#789; font-size: 11px; padding-left:5px;padding-top:3px">&#10062;</span>').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 = baseURLratingHistogram.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('ShowLetterboxd: 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 = $('<div id="mcdiv321letterboxd"></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 = `<style>
.rating {
display: inline-block;
height: 16px;
background: url(https://s.ltrbxd.com/static/img/sprite.18bffd5e.svg) no-repeat -290px -90px;
background-position-x: -290px;
background-position-y: -90px;
text-indent: 110%;
white-space: nowrap;
overflow: hidden;
}
.rating-green .rating{
background-position:-450px -50px
}
.rating-green .rated-0{
width:0
}
.rating-green .rated-1{
width:13px;
background-position:-515px -50px
}
.rating-green .rated-2{
width:12px
}
.rating-green .rated-3{
width:26px;
background-position:-502px -50px
}
.rating-green .rated-4{
width:25px
}
.rating-green .rated-5{
width:39px;
background-position:-489px -50px
}
.rating-green .rated-6{
width:38px
}
.rating-green .rated-7{
width:52px;
background-position:-476px -50px
}
.rating-green .rated-8{
width:51px
}
.rating-green .rated-9{
width:65px;
background-position:-463px -50px
}
.rating-green .rated-10{
width:64px
}
.rating-green-tiny .rating{
background-position:-350px -380px;
height:9px
}
.rating-green-tiny .rated-2{
width:9px
}
.rating-green-tiny .rated-10{
width:49px
}
.rating-histogram,.rating-histogram ul{
height:44px
}
.rating-histogram .rating-1,.rating-histogram .rating-5{
position:absolute;
bottom:0;
display:block;
height:9px
}
.rating-histogram .rating-1 .rating,.rating-histogram .rating-5 .rating{
display:block
}
.rating-histogram .rating-1{
left:0
}
.rating-histogram .rating-5{
right:0
}
.rating-histogram ul{
width:200px;
left:15px
}
.rating-histogram ul,.rating-histogram ul li{
display:block;
overflow:hidden;
position:absolute;
bottom:0
}
.rating-histogram ul li{
height:1px;
width:30px;
height:100%;
font-size:10px;
line-height:1;
text-indent:110%;
white-space:nowrap;
border-bottom:0px
}
#mcdiv321letterboxd:hover .rating-histogram ul li{
border-bottom:2px solid green
}
.rating-histogram i{
background:#456;
border-top-right-radius:2px;
border-top-left-radius:2px
}
.rating-histogram a,.rating-histogram i{
width:100%;
position:absolute;
bottom:0;
left:0
}
.rating-histogram a{
display:block;
top:0;
right:0;
background:none;
padding:0;
color:#789
}
.rating-histogram a:link,.rating-histogram a:visited{
color:#789;
text-decoration:none
}
.rating-histogram a:hover i{
background-color:#678
}
.ratings-histogram-chart .section-heading{
margin-bottom:15px
}
.ratings-histogram-chart .average-rating{
position:absolute;
top:8px;
left:188px;
z-index:1
}
.ratings-histogram-chart .average-rating .display-rating{
display:block;
font-size:20px;
text-align:center;
color:#789;
margin-left:1px;
line-height:40px;
width:33px;
height:33px;
border-radius:20px;
font-family:Graphik-Light-Web,sans-serif;
font-weight:400
}
.rating-histogram {
overflow:hidden;
color:#9ab;
display:block;
width: 230px;
height: 44px;
position: relative
}
.ratings-histogram-chart .all-link.more-link {
font-size:10px;
position:absolute;
top:0;
left:180px;
}
.rating-histogram.clear {
visibility: visible !important;
}
#mcdiv321letterboxd .footer {
display:none;
}
#mcdiv321letterboxd:hover .footer {
display:block;
}
#mcdiv321letterboxd {
border:none;
border-radius: 0px;
background-color:transparent;
transition:bottom 0.7s, background-color 0.5s, height 0.5s;
}
#mcdiv321letterboxd:hover {
border-radius: 4px;
background-color:rgb(44, 52, 64)
}
/* Fixes/Resets for interfering site css */
#mcdiv321letterboxd .tooltip{
border: none;
box-shadow:none;
background-color:transparent;
opacity:1.0;
white-space: nowrap;
}
</style>`
$(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 = '<br><span style="font-size:10px">Dir. ' + identDirector.join(', ') + '</span>'
} else {
identDirector = ''
}
}
}
// Footer
const sub = $('<div class="footer"></div>').appendTo(div)
$('<span style="color:#789; font-size: 11px">' + identName + identOriginalName + identYear + identDirector + '</span>').appendTo(sub)
$('<br>').appendTo(sub)
$('<time style="color:#789; font-size: 11px;" datetime="' + time + '" title="' + time.toLocaleTimeString() + ' ' + time.toLocaleDateString() + '">' + minutesSince(time) + '</time>').appendTo(sub)
$('<a style="color:#789; font-size: 11px;" target="_blank" href="' + baseURL + letterboxdUrl + '" title="Open Letterboxd">@letterboxd.com</a>').appendTo(sub)
$('<span title="Hide me" style="cursor:pointer; float:right; color:#789; font-size: 11px">&#10062;</span>').appendTo(sub).click(function () {
document.getElementById('mcdiv321letterboxd').remove()
})
$('<span title="Wrong movie!" style="cursor:pointer; float:right; color:#789; font-size: 11px">&#128581;</span>').appendTo(sub).click(function () {
removeFromWhiteList()
searchMovie(current.query, current.type, current.year, true)
})
$('<span style="clear:right">').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__pageTitle"]') && !document.querySelector('[data-testid="hero-subnav-bar-left-block"] a[href*="episodes/"]')) {
return true
}
return false
},
type: 'movie',
data: function () {
let year = null
if (document.querySelector('script[type="application/ld+json"]')) {
const ld = parseLDJSON(['name', 'alternateName', 'datePublished'])
if (ld.length > 2) {
year = parseInt(ld[2].match(/\d{4}/)[0])
}
if (ld.length > 1 && ld[1]) {
console.debug('ShowLetterboxd: Movie ld+json alternateName', ld[1], year)
return [ld[1], year]
}
console.debug('ShowLetterboxd: Movie ld+json name', ld[0], year)
return [ld[0], year]
} else {
const m = document.title.match(/(.+?)\s+(\((\d+)\))? - /)
console.debug('ShowLetterboxd: Movie <title>', [m[1], m[3]])
return [m[1], parseInt(m[3])]
}
}
}
]
},
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}\]/, '')
},
{
condition: () => document.querySelector('#watchNowContainer a[href*="/gp/video/"]'),
type: 'movie',
data: () => document.getElementById('productTitle').textContent.trim()
}]
},
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: ['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' ||
document.querySelector("meta[property='og:type']").content === 'video.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'))
}]
},
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'))
}]
},
gog: {
host: ['www.gog.com'],
condition: () => document.querySelector('.productcard-basics__title'),
products: [{
condition: () => document.location.pathname.split('/').length > 2 && (
document.location.pathname.split('/')[1] === 'movie' ||
document.location.pathname.split('/')[2] === 'movie'),
type: 'movie',
data: () => document.querySelector('.productcard-basics__title').textContent
}]
},
psapm: {
host: ['psa.wf'],
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]
}
}
]
},
wikiwand: {
host: ['www.wikiwand.com'],
condition: Always,
products: [{
condition: function () {
const title = document.querySelector('h1').textContent.toLowerCase()
const subtitle = document.querySelector('h2[class*="subtitle"]') ? document.querySelector('h2[class*="subtitle"]').textContent.toLowerCase() : ''
if (title.indexOf('film') === -1 && !subtitle) {
return false
}
return title.indexOf('film') !== -1 ||
subtitle.indexOf('film') !== -1 ||
subtitle.indexOf('movie') !== -1
},
type: 'movie',
data: () => document.querySelector('h1').textContent.replace(/\((\d{4} )?film\)/i, '').trim()
}]
},
trakt: {
host: ['trakt.tv'],
condition: Always,
products: [
{
condition: () => document.location.pathname.startsWith('/movies/'),
type: 'movie',
data: function () {
const title = Array.from(document.querySelector('.summary h1').childNodes).filter(node => node.nodeType === node.TEXT_NODE).map(node => node.textContent).join(' ').trim()
const year = document.querySelector('.summary h1 .year').textContent
return [title, year]
}
}
]
},
radarr: {
host: ['*'],
condition: () => document.location.pathname.startsWith('/movie/'),
products: [{
condition: () => document.querySelector('[class*="MovieDetails-title"] span'),
type: 'movie',
data: () => {
let year = null
if (document.querySelector('[class*="MovieDetails-yea"] span')) {
year = document.querySelector('[class*="MovieDetails-yea"] span').textContent.trim()
}
return [document.querySelector('[class*="MovieDetails-title"] span').textContent.trim(), 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) || 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)) {
if (data[1]) {
searchMovie(data[0].trim(), site.products[i].type, parseInt(data[1]))
} else {
searchMovie(data.trim(), site.products[i].type)
}
} 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 firstRunR###lt = 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 (!firstRunR###lt) {
// Initial run had no match, let's try again there may be new content
window.setTimeout(main, 2000)
}
})()