// ==UserScript==
// @name Letterboxd External Ratings
// @namespace https://github.com/soyguijarro/userscripts
// @description Adds ratings of film from external sites to film pages
// @copyright 2015, Ramón Guijarro (http://soyguijarro.com)
// @homepageURL https://github.com/soyguijarro/userscripts
// @supportURL https://github.com/soyguijarro/userscripts/issues
// @icon https://raw.githubusercontent.com/soyguijarro/userscripts/master/img/letterboxd_icon.png
// @license GPLv3; http://www.gnu.org/licenses/gpl.html
// @version 1.8
// @include *://letterboxd.com/film/*
// @include *://letterboxd.com/film/*/crew/*
// @include *://letterboxd.com/film/*/studios/*
// @include *://letterboxd.com/film/*/genres/*
// @exclude *://letterboxd.com/film/*/views/*
// @exclude *://letterboxd.com/film/*/lists/*
// @exclude *://letterboxd.com/film/*/likes/*
// @exclude *://letterboxd.com/film/*/fans/*
// @exclude *://letterboxd.com/film/*/ratings/*
// @exclude *://letterboxd.com/film/*/reviews/*
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @grant unsafeWindow
// ==/UserScript==
var ratingsData = { "IMDb": {origRatingMax: 10, isLoaded: false},
"Metascore": {origRatingMax: 100, isLoaded: false},
"Tomatometer": {isLoaded: false} };
function updateRatingElt(site) {
var ratingElts = document.querySelectorAll("section.ratings-external a"),
ratingElt = ratingElts[Object.keys(ratingsData).indexOf(site)],
ratingInnerElt = ratingElt.firstElementChild,
ratingData = ratingsData[site];
if (ratingData.isLoaded) {
ratingInnerElt.classList.remove("spinner");
if (ratingData.origRating && ratingData.origRating !== "" &&
ratingData.origRating !== 0 && !isNaN(ratingData.origRating)) {
if (localStorage.origRatingsMode === "true") {
ratingInnerElt.removeAttribute("class");
ratingInnerElt.textContent = ratingData.origRating +
((ratingData.origRatingMax) ? ("/" + ratingData.origRatingMax) : "%");
} else {
ratingInnerElt.className = "rating rated-" +
Math.round(ratingData.oneToTenRating);
}
ratingElt.href = ratingData.url;
ratingElt.style.cursor = "pointer";
} else {
ratingInnerElt.removeAttribute("class");
ratingInnerElt.textContent = "N/A";
}
}
}
function createRatingsSection(callback) {
var sidebarElt = document.getElementsByClassName("sidebar")[0],
ratingsSectionElt = document.createElement("section"),
modeToggleElt = document.createElement("ul"),
modeToggleInnerElt = document.createElement("li"),
modeToggleInnerInnerElt = document.createElement("a"),
ratingElt,
ratingInnerElt,
cssRules = "section.ratings-external {\
margin-top: 20px;\
}\
section.ratings-external a {\
display: block;\
font-size: 12px;\
line-height: 1.5;\
margin-bottom: 0.5em;\
}\
section.ratings-external span {\
text-align: right;\
position: absolute;\
right: 0;\
color: #6C3;\
}\
section.ratings-external span.spinner {\
background: url('" + getSpinnerImageUrl() + "');\
height: 12px;\
width: 12px;\
margin: 3px 0;\
}";
function getSpinnerImageUrl() {
var spinnersObj = unsafeWindow.globals.spinners;
for (var prop in spinnersObj) {
if (/spinner_12/.test(prop)) {
return spinnersObj[prop];
}
}
return null;
}
function getModeToggleButtonText() {
var ratingsModeName =
(localStorage.origRatingsMode === "true") ? "five-star" : "original";
return "Show " + ratingsModeName + " ratings";
}
function toggleRatingsMode(evt) {
evt.preventDefault();
localStorage.origRatingsMode = !(localStorage.origRatingsMode === "true");
modeToggleInnerInnerElt.textContent = getModeToggleButtonText();
for (var i = 0; i < Object.keys(ratingsData).length; i++) {
updateRatingElt(Object.keys(ratingsData)[i]);
}
}
// Set up section to be inserted in page
ratingsSectionElt.className = "section ratings-external";
// Set up section elements that will contain ratings
for (var i = 0; i < Object.keys(ratingsData).length; i++) {
ratingElt = document.createElement("a");
ratingInnerElt = document.createElement("span");
ratingElt.textContent = Object.keys(ratingsData)[i];
ratingElt.className = "rating-green";
ratingInnerElt.className = "spinner";
ratingElt.style.cursor = "default";
ratingElt.appendChild(ratingInnerElt);
ratingsSectionElt.appendChild(ratingElt);
}
// Set up ratings mode toggle button
modeToggleElt.className = "box-link-list box-links";
modeToggleInnerInnerElt.className = "box-link";
modeToggleInnerInnerElt.href = "#";
modeToggleInnerInnerElt.textContent = getModeToggleButtonText();
modeToggleInnerInnerElt.addEventListener("click", toggleRatingsMode, false);
modeToggleInnerElt.appendChild(modeToggleInnerInnerElt);
modeToggleElt.appendChild(modeToggleInnerElt);
ratingsSectionElt.appendChild(modeToggleElt);
// Insert section in page
sidebarElt.insertBefore(ratingsSectionElt, sidebarElt.lastElementChild);
GM_addStyle(cssRules);
callback();
}
function fillRatingsSection() {
var moreDetailsElt = document.querySelector("section.col-main p.text-link"),
imdbIdMatch = moreDetailsElt.innerHTML.
match(/http:\/\/www\.imdb.com\/title\/tt(\d+)\//),
rottenApiReqBaseUrl = "http://api.rottentomatoes.com/api/public/v1.0/",
rottenApiReqParams = "movie_alias.json?type=imdb&id=",
rottenApiReqUrl,
imdbUrl,
imdbId;
function updateRatingData(site, origRating, oneToTenRating, url) {
ratingsData[site].origRating = origRating;
ratingsData[site].oneToTenRating = oneToTenRating;
ratingsData[site].url = url;
ratingsData[site].isLoaded = true;
updateRatingElt(site);
}
function getIMDbAndMetaRatings(res) {
var parser = new DOMParser(),
dom = parser.parseFromString(res.responseText, "text/html"),
ratingsElt = dom.getElementById("title-overview-widget");
function getIMDbRating() {
var imdbRating,
imdbRatingElt = ratingsElt.querySelector("span[itemprop=ratingValue]");
if (imdbRatingElt) {
imdbRating = parseFloat(imdbRatingElt.textContent);
updateRatingData("IMDb", imdbRating, imdbRating, imdbUrl);
} else {
updateRatingData("IMDb", null);
}
}
function getMetaRating() {
var metaRating,
metaRatingElt = ratingsElt.querySelector(".metacriticScore span");
if (metaRatingElt) {
metaRating = parseFloat(metaRatingElt.textContent);
GM_xmlhttpRequest({
method: "GET",
url: imdbUrl + "criticreviews", // Metacritic reviews page on IMDb
onload: function (res) {
var pageContent,
metaUrl;
dom = parser.parseFromString(res.responseText, "text/html");
pageContent = dom.getElementById("main").innerHTML;
metaUrl = pageContent.
match(/<a.*href="(.*?)".*>See all \d+ reviews/)[1];
updateRatingData("Metascore", metaRating,
metaRating / 10, metaUrl);
}
});
} else {
updateRatingData("Metascore", null);
}
}
if (ratingsElt) {
getIMDbRating();
getMetaRating();
} else {
updateRatingData("IMDb", null);
updateRatingData("Metascore", null);
}
}
function getRottenRating(res) {
var json = JSON.parse(res.responseText),
rottenId,
rottenUrl,
rottenRating;
if (json) {
if (json.id && json.ratings && !json.error) {
rottenUrl = "http://www.rottentomatoes.com/m/" + json.id;
rottenRating = json.ratings.critics_score;
if (rottenRating > 0) {
updateRatingData("Tomatometer", rottenRating,
rottenRating / 10, rottenUrl);
} else {
updateRatingData("Tomatometer", null);
}
} else {
updateRatingData("Tomatometer", null);
}
}
}
if (imdbIdMatch) {
imdbUrl = imdbIdMatch[0];
imdbId = imdbIdMatch[1];
rottenApiReqUrl = rottenApiReqBaseUrl + rottenApiReqParams + imdbId;
GM_xmlhttpRequest({
method: "GET",
url: imdbUrl,
onload: getIMDbAndMetaRatings
});
GM_xmlhttpRequest({
method: "GET",
url: rottenApiReqUrl,
onload: getRottenRating
});
} else {
updateRatingData("IMDb", null);
updateRatingData("Metascore", null);
updateRatingData("Tomatometer", null);
}
}
localStorage.origRatingsMode = (localStorage.origRatingsMode || true);
createRatingsSection(fillRatingsSection);