Letterboxd External Ratings

Adds ratings of film from external sites to film pages

当前为 2015-10-05 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Letterboxd External Ratings
  3. // @namespace https://github.com/soyguijarro/userscripts
  4. // @description Adds ratings of film from external sites to film pages
  5. // @copyright 2015, Ramón Guijarro (http://soyguijarro.com)
  6. // @homepageURL https://github.com/soyguijarro/userscripts
  7. // @supportURL https://github.com/soyguijarro/userscripts/issues
  8. // @icon https://raw.githubusercontent.com/soyguijarro/userscripts/master/img/letterboxd_icon.png
  9. // @license GPLv3; http://www.gnu.org/licenses/gpl.html
  10. // @version 1.6
  11. // @include http://letterboxd.com/film/*
  12. // @include http://letterboxd.com/film/*/crew/*
  13. // @include http://letterboxd.com/film/*/studios/*
  14. // @include http://letterboxd.com/film/*/genres/*
  15. // @exclude http://letterboxd.com/film/*/views/*
  16. // @exclude http://letterboxd.com/film/*/lists/*
  17. // @exclude http://letterboxd.com/film/*/likes/*
  18. // @exclude http://letterboxd.com/film/*/fans/*
  19. // @exclude http://letterboxd.com/film/*/ratings/*
  20. // @exclude http://letterboxd.com/film/*/reviews/*
  21. // @grant GM_addStyle
  22. // @grant GM_xmlhttpRequest
  23. // @grant unsafeWindow
  24. // ==/UserScript==
  25.  
  26. var ratingsData = { "IMDb": {origRatingMax: 10, isLoaded: false},
  27. "Metascore": {origRatingMax: 100, isLoaded: false},
  28. "Tomatometer": {isLoaded: false} };
  29.  
  30. function updateRatingElt(site) {
  31. var ratingElts = document.querySelectorAll("section.ratings-external a"),
  32. ratingElt = ratingElts[Object.keys(ratingsData).indexOf(site)],
  33. ratingInnerElt = ratingElt.firstElementChild,
  34. ratingData = ratingsData[site];
  35.  
  36. if (ratingData.isLoaded) {
  37. ratingInnerElt.classList.remove("spinner");
  38.  
  39. if (ratingData.origRating && ratingData.origRating !== "" &&
  40. ratingData.origRating !== 0 && !isNaN(ratingData.origRating)) {
  41. if (localStorage.origRatingsMode === "true") {
  42. ratingInnerElt.removeAttribute("class");
  43. ratingInnerElt.textContent = ratingData.origRating +
  44. ((ratingData.origRatingMax) ? ("/" + ratingData.origRatingMax) : "%");
  45. } else {
  46. ratingInnerElt.className = "rating rated-" +
  47. Math.round(ratingData.oneToTenRating);
  48. }
  49. ratingElt.href = ratingData.url;
  50. ratingElt.style.cursor = "pointer";
  51. } else {
  52. ratingInnerElt.removeAttribute("class");
  53. ratingInnerElt.textContent = "N/A";
  54. }
  55. }
  56. }
  57.  
  58. function createRatingsSection(callback) {
  59. var sidebarElt = document.getElementsByClassName("sidebar")[0],
  60. ratingsSectionElt = document.createElement("section"),
  61. modeToggleElt = document.createElement("ul"),
  62. modeToggleInnerElt = document.createElement("li"),
  63. modeToggleInnerInnerElt = document.createElement("a"),
  64. ratingElt,
  65. ratingInnerElt,
  66. cssRules = "section.ratings-external {\
  67. margin-top: 20px;\
  68. }\
  69. section.ratings-external a {\
  70. display: block;\
  71. font-size: 12px;\
  72. line-height: 1.5;\
  73. margin-bottom: 0.5em;\
  74. }\
  75. section.ratings-external span {\
  76. text-align: right;\
  77. position: absolute;\
  78. right: 0;\
  79. color: #6C3;\
  80. }\
  81. section.ratings-external span.spinner {\
  82. background: url('" + getSpinnerImageUrl() + "');\
  83. height: 12px;\
  84. width: 12px;\
  85. margin: 3px 0;\
  86. }";
  87.  
  88. function getSpinnerImageUrl() {
  89. var spinnersObj = unsafeWindow.globals.spinners;
  90.  
  91. for (var prop in spinnersObj) {
  92. if (/spinner_12/.test(prop)) {
  93. return spinnersObj[prop];
  94. }
  95. }
  96. return null;
  97. }
  98.  
  99. function getModeToggleButtonText() {
  100. var ratingsModeName =
  101. (localStorage.origRatingsMode === "true") ? "five-star" : "original";
  102. return "Show " + ratingsModeName + " ratings";
  103. }
  104.  
  105. function toggleRatingsMode(evt) {
  106. evt.preventDefault();
  107.  
  108. localStorage.origRatingsMode = !(localStorage.origRatingsMode === "true");
  109. modeToggleInnerInnerElt.textContent = getModeToggleButtonText();
  110.  
  111. for (var i = 0; i < Object.keys(ratingsData).length; i++) {
  112. updateRatingElt(Object.keys(ratingsData)[i]);
  113. }
  114. }
  115. // Set up section to be inserted in page
  116. ratingsSectionElt.className = "section ratings-external";
  117.  
  118. // Set up section elements that will contain ratings
  119. for (var i = 0; i < Object.keys(ratingsData).length; i++) {
  120. ratingElt = document.createElement("a");
  121. ratingInnerElt = document.createElement("span");
  122. ratingElt.textContent = Object.keys(ratingsData)[i];
  123. ratingElt.className = "rating-green";
  124. ratingInnerElt.className = "spinner";
  125. ratingElt.style.cursor = "default";
  126.  
  127. ratingElt.appendChild(ratingInnerElt);
  128. ratingsSectionElt.appendChild(ratingElt);
  129. }
  130.  
  131. // Set up ratings mode toggle button
  132. modeToggleElt.className = "box-link-list box-links";
  133. modeToggleInnerInnerElt.className = "box-link";
  134. modeToggleInnerInnerElt.href = "#";
  135. modeToggleInnerInnerElt.textContent = getModeToggleButtonText();
  136. modeToggleInnerInnerElt.addEventListener("click", toggleRatingsMode, false);
  137. modeToggleInnerElt.appendChild(modeToggleInnerInnerElt);
  138.  
  139. modeToggleElt.appendChild(modeToggleInnerElt);
  140. ratingsSectionElt.appendChild(modeToggleElt);
  141.  
  142. // Insert section in page
  143. sidebarElt.insertBefore(ratingsSectionElt, sidebarElt.lastElementChild);
  144. GM_addStyle(cssRules);
  145.  
  146. callback();
  147. }
  148.  
  149. function fillRatingsSection() {
  150. var moreDetailsElt = document.querySelector("section.col-main p.text-link"),
  151. imdbIdMatch = moreDetailsElt.innerHTML.
  152. match(/http:\/\/www\.imdb.com\/title\/tt(\d+)\//),
  153. rottenApiReqBaseUrl = "http://api.rottentomatoes.com/api/public/v1.0/",
  154. rottenApiReqParams = "movie_alias.json?type=imdb&id=",
  155. rottenApiReqUrl,
  156. imdbUrl,
  157. imdbId;
  158.  
  159. function updateRatingData(site, origRating, oneToTenRating, url) {
  160. ratingsData[site].origRating = origRating;
  161. ratingsData[site].oneToTenRating = oneToTenRating;
  162. ratingsData[site].url = url;
  163. ratingsData[site].isLoaded = true;
  164.  
  165. updateRatingElt(site);
  166. }
  167.  
  168. function getIMDbAndMetaRatings(res) {
  169. var parser = new DOMParser(),
  170. dom = parser.parseFromString(res.responseText, "text/html"),
  171. ratingsElt = dom.getElementById("overview-top");
  172. function getIMDbRating() {
  173. var imdbRating,
  174. imdbRatingElt = ratingsElt.
  175. getElementsByClassName("star-box-giga-star")[0];
  176.  
  177. if (imdbRatingElt) {
  178. imdbRating = parseFloat(imdbRatingElt.textContent);
  179. updateRatingData("IMDb", imdbRating, imdbRating, imdbUrl);
  180. } else {
  181. updateRatingData("IMDb", null);
  182. }
  183. }
  184.  
  185. function getMetaRating() {
  186. var metaRatingMatch = ratingsElt.textContent.
  187. match(/Metascore: (\d+)\/100/);
  188.  
  189. if (metaRatingMatch) {
  190. GM_xmlhttpRequest({
  191. method: "GET",
  192. url: imdbUrl + "criticreviews", // Metacritic reviews page on IMDb
  193. onload: function (res) {
  194. var metaRating = metaRatingMatch[1],
  195. pageContent,
  196. metaUrl;
  197.  
  198. dom = parser.parseFromString(res.responseText, "text/html");
  199. pageContent = dom.getElementById("main").innerHTML;
  200. metaUrl = pageContent.
  201. match(/<a.*href="(.*?)".*>See all \d+ reviews/)[1];
  202.  
  203. updateRatingData("Metascore", metaRating,
  204. metaRating / 10, metaUrl);
  205. }
  206. });
  207. } else {
  208. updateRatingData("Metascore", null);
  209. }
  210. }
  211.  
  212. if (ratingsElt) {
  213. getIMDbRating();
  214. getMetaRating();
  215. } else {
  216. updateRatingData("IMDb", null);
  217. updateRatingData("Metascore", null);
  218. }
  219. }
  220.  
  221. function getRottenRating(res) {
  222. var json = JSON.parse(res.responseText),
  223. rottenId,
  224. rottenUrl,
  225. rottenRating;
  226. if (json) {
  227. if (json.id && json.ratings && !json.error) {
  228. rottenUrl = "http://www.rottentomatoes.com/m/" + json.id;
  229. rottenRating = json.ratings.critics_score;
  230.  
  231. if (rottenRating > 0) {
  232. updateRatingData("Tomatometer", rottenRating,
  233. rottenRating / 10, rottenUrl);
  234. } else {
  235. updateRatingData("Tomatometer", null);
  236. }
  237. } else {
  238. updateRatingData("Tomatometer", null);
  239. }
  240. }
  241. }
  242.  
  243. if (imdbIdMatch) {
  244. imdbUrl = imdbIdMatch[0];
  245. imdbId = imdbIdMatch[1];
  246. rottenApiReqUrl = rottenApiReqBaseUrl + rottenApiReqParams + imdbId;
  247.  
  248. GM_xmlhttpRequest({
  249. method: "GET",
  250. url: imdbUrl,
  251. onload: getIMDbAndMetaRatings
  252. });
  253.  
  254. GM_xmlhttpRequest({
  255. method: "GET",
  256. url: rottenApiReqUrl,
  257. onload: getRottenRating
  258. });
  259. } else {
  260. updateRatingData("IMDb", null);
  261. updateRatingData("Metascore", null);
  262. updateRatingData("Tomatometer", null);
  263. }
  264. }
  265.  
  266. localStorage.origRatingsMode = (localStorage.origRatingsMode || true);
  267. createRatingsSection(fillRatingsSection);