Add movie ratings to IMDB links [adopted]

Adds movie ratings and number of voters to links on IMDB. Modified version of http://userscripts.org/scripts/show/96884

当前为 2020-07-21 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Add movie ratings to IMDB links [adopted]
  3. // @description Adds movie ratings and number of voters to links on IMDB. Modified version of http://userscripts.org/scripts/show/96884
  4. // @author StackOverflow community (especially Brock Adams)
  5. // @version 2015-11-24-13-joeytwiddle
  6. // @license MIT
  7. // @match *://www.imdb.com/*
  8. // @grant GM_xmlhttpRequest
  9. // @grant unsafeWindow
  10. // @namespace https://greasyfork.org/users/8615
  11. // @derived-from https://greasyfork.org/en/scripts/2033-add-imdb-rating-votes-next-to-all-imdb-movie-series-links-improved
  12. // ==/UserScript==
  13. // Special Thanks to Brock Adams for this script: http://stackoverflow.com/questions/23974801/gm-xmlhttprequest-data-is-being-placed-in-the-wrong-places/23992742
  14.  
  15. var maxLinksAtATime = 100; //-- pages can have 100's of links to fetch. Don't spam server or browser.
  16. var skipEpisodes = true; //-- I only want to see ratings for movies or TV shows, not TV episodes
  17. var showAsStar = false; //-- Add IMDB stars instead of a colored pills. Looks more consistent with the site, but provides less info.
  18. var addRatingToTitle = true; //-- Puts the rating in the browser's title bar (thus rating will be kept in any bookmarks you make)
  19.  
  20. var $ = unsafeWindow.$;
  21.  
  22. var fetchedLinkCnt = 0;
  23.  
  24. function processIMDB_Links () {
  25. //--- Get only links that could be to IMBD movie/TV pages.
  26. var linksToIMBD_Shows = document.querySelectorAll ("a[href*='/title/']");
  27.  
  28. for (var J = 0, L = linksToIMBD_Shows.length; J < L; J++) {
  29. var currentLink = linksToIMBD_Shows[J];
  30.  
  31. /*--- Strict tests for the correct IMDB link to keep from spamming the page
  32. with erroneous results.
  33. */
  34. if ( ! /^(?:www\.)?IMDB\.com$/i.test (currentLink.hostname)
  35. || ! /^\/title\/tt\d+\/?$/i.test (currentLink.pathname)
  36. )
  37. continue;
  38.  
  39. // I am beginning to think a whitelist might be better than this blacklist!
  40.  
  41. // Skip thumbnails on the search results page
  42. if ($(currentLink).closest('.primary_photo').length) {
  43. continue;
  44. }
  45.  
  46. // Skip thumbnails in the six recommendations area of a title page
  47. if ($(currentLink).closest('.rec_item').length) {
  48. continue;
  49. }
  50.  
  51. // Skip top-rated episodes on the right-hand sidebar of TV series pages; they already display a rating anyway!
  52. if ($(currentLink).closest('#top-rated-episodes-rhs').length) {
  53. continue;
  54. }
  55.  
  56. // Skip thumbnail of title at top of Season page
  57. if ($(currentLink).find(':only-child').prop('tagName') === 'IMG') {
  58. continue;
  59. }
  60.  
  61. // Skip the thumbnail of each episode on a season page (episode names still processed)
  62. if ($(currentLink).closest('.image').length) {
  63. continue;
  64. }
  65.  
  66. // Skip thumbnails in "Known For" section of actor pages
  67. if ($(currentLink).closest('.known-for').length && $(currentLink).find('img').length) {
  68. continue;
  69. }
  70.  
  71. // Skip episodes on actor pages
  72. if (skipEpisodes && $(currentLink).closest('.filmo-episodes').length) {
  73. continue;
  74. }
  75.  
  76. if (! currentLink.getAttribute ("data-gm-fetched") ){
  77. if (fetchedLinkCnt >= maxLinksAtATime){
  78. //--- Position the "continue" button.
  79. continueBttn.style.display = 'inline';
  80. currentLink.parentNode.insertBefore (continueBttn, currentLink);
  81. break;
  82. }
  83.  
  84. fetchTargetLink (currentLink); //-- AJAX-in the ratings for a given link.
  85.  
  86. //---Mark the link with a data attribute, so we know it's been fetched.
  87. currentLink.setAttribute ("data-gm-fetched", "true");
  88. fetchedLinkCnt++;
  89. }
  90. }
  91. }
  92.  
  93. function fetchTargetLink (linkNode) {
  94. //--- This function provides a closure so that the callbacks can work correctly.
  95.  
  96. /*--- Must either call AJAX in a closure or pass a context.
  97. But Tampermonkey does not implement context correctly!
  98. (Tries to JSON serialize a DOM node.)
  99. */
  100. GM_xmlhttpRequest ( {
  101. method: 'get',
  102. url: linkNode.href,
  103. //context: linkNode,
  104. onload: function (response) {
  105. prependIMDB_Rating (response, linkNode);
  106. },
  107. onload: function (response) {
  108. prependIMDB_Rating (response, linkNode);
  109. },
  110. onabort: function (response) {
  111. prependIMDB_Rating (response, linkNode);
  112. }
  113. } );
  114. }
  115.  
  116. function prependIMDB_Rating (resp, targetLink) {
  117. var isError = true;
  118. var ratingTxt = "** Unknown Error!";
  119. var colnumber = 0;
  120. var justrate = 'RATING_NOT_FOUND';
  121.  
  122. if (resp.status != 200 && resp.status != 304) {
  123. ratingTxt = '** ' + resp.status + ' Error!';
  124. }
  125. else {
  126. // Example value: ["Users rated this 8.5/10 (", "8.5/10"]
  127. //var ratingM = resp.responseText.match (/Users rated this (.*) \(/);
  128. // Example value: ["(1,914 votes) -", "1,914"]
  129. //var votesM = resp.responseText.match (/\((.*) votes\) -/);
  130.  
  131. var doc = document.createElement('div');
  132. doc.innerHTML = resp.responseText;
  133. var elem = doc.querySelector('.title-overview .vital .ratingValue strong');
  134. var title = elem && elem.title || '';
  135.  
  136. var ratingT = title.replace(/ based on .*$/, '');
  137. var votesT = title.replace(/.* based on /, '').replace(/ user ratings/, '');
  138.  
  139. // The code below expects arrays (originally returned by string match)
  140. var ratingM = [ratingT, ratingT + "/10"];
  141. var votesM = [votesT, votesT];
  142.  
  143. //console.log('ratingM', ratingM);
  144. //console.log('votesM', votesM);
  145.  
  146. if (/\(awaiting \d+ votes\)|\(voting begins after release\)|in development,/i.test (resp.responseText) ) {
  147. ratingTxt = "NR";
  148. isError = false;
  149. colnumber = 0;
  150. } else {
  151. if (ratingM && ratingM.length > 1 && votesM && votesM.length > 1) {
  152. isError = false;
  153.  
  154. justrate = ratingM[1].substr(0, ratingM[1].indexOf("/"));
  155.  
  156. var votes = votesM[1];
  157. //console.log('votes:', votes);
  158. var votesNum = Number( votes.replace(/,/,'','') );
  159. var commas_found = votes.match(/,/,'g');
  160. if (commas_found && commas_found.length === 1) {
  161. votes = votes.replace(/,.../, 'k');
  162. } else if (commas_found && commas_found.length === 2) {
  163. votes = votes.replace(/,.*,.*/, 'M');
  164. }
  165.  
  166. // ratingTxt = ratingM[1] + " - " + votesM[1];
  167. ratingTxt = "<strong>" + justrate + "</strong>" + " / " + votes;
  168. colnumber = Math.round(justrate);
  169. }
  170. }
  171. }
  172.  
  173.  
  174. // NOTE: I switched from <b> to <strong> simply because on Season pages, the rating injected after episode titles was getting uglified by an IMDB CSS rule: .list_item .info b { font-size: 15px; }
  175. targetLink.setAttribute("title", "Rated " + ratingTxt.replace(/<\/*strong>/g,'').replace(/\//,'by') + " users." );
  176.  
  177. if (justrate < 5) {
  178. return;
  179. }
  180.  
  181.  
  182. // Slowly approach full opacity as votesNum increases. 10,000 votes results in opacity 0.5 (actually 0.6 when adjusted).
  183. var opacity = 1 - 1 / (1 + 0.0001 * votesNum);
  184. // Actually let's not start from 0; we may still want to see the numbers!
  185. opacity = 0.2 + 0.8*opacity;
  186. // Don't use too many decimal points; it's ugly!
  187. //opacity = Math.round(opacity * 10000) / 10000;
  188. opacity = opacity.toFixed(3);
  189.  
  190. var color = ["#Faa", "#Faa","#Faa", "#Faa","#Faa", "#F88","#Faa", "#ff7","#7e7", "#5e5", "#0e0", "#ddd"];
  191. var resltSpan = document.createElement ("span");
  192. // resltSpan.innerHTML = '<b><font style="border-radius: 5px;padding: 1px;border: #575757 solid 1px; background-color:' + color[colnumber] + ';">' + ' [' + ratingTxt + '] </font></b>&nbsp;';
  193. // resltSpan.innerHTML = '<b><font style="background-color:' + justrate + '">' + ' [' + ratingTxt + '] </font></b>&nbsp;';
  194. // I wanted vertical padding 1px but then the element does not fit in the "also liked" area, causing the top border to disappear! Although reducing the font size to 70% is an alternative.
  195. resltSpan.innerHTML = '&nbsp;<font style="font-weight: normal;font-size: 80%;opacity: '+opacity+';border-radius: 3px;padding: 0.1em 0.6em;border: rgba(0,0,0,0.1) solid 1px; background-color:' + color[colnumber] + ';color: black;">' + '' + ratingTxt + '</font>';
  196.  
  197. if (showAsStar) {
  198. resltSpan.innerHTML = `
  199. <div class="ipl-rating-star" style="font-weight: normal">
  200. <span class="ipl-rating-star__star">
  201. <svg class="ipl-icon ipl-star-icon " xmlns="http://www.w3.org/2000/svg" fill="#000000" height="24" viewBox="0 0 24 24" width="24">
  202. <path d="M0 0h24v24H0z" fill="none"></path>
  203. <path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"></path>
  204. <path d="M0 0h24v24H0z" fill="none"></path>
  205. </svg>
  206. </span>
  207. <span class="ipl-rating-star__rating">${justrate}</span>
  208. </div>
  209. `;
  210. }
  211.  
  212. if (isError)
  213. resltSpan.style.color = 'red';
  214.  
  215. //var targetLink = resp.context;
  216. //console.log ("targetLink: ", targetLink);
  217.  
  218. //targetLink.parentNode.insertBefore (resltSpan, targetLink);
  219. targetLink.parentNode.insertBefore (resltSpan, targetLink.nextSibling);
  220. }
  221.  
  222. //--- Create the continue button
  223. var continueBttn = document.createElement ("button");
  224. continueBttn.innerHTML = "Get more ratings";
  225.  
  226. continueBttn.addEventListener ("click", function (){
  227. fetchedLinkCnt = 0;
  228. continueBttn.style.display = 'none';
  229. processIMDB_Links ();
  230. },
  231. false
  232. );
  233.  
  234. processIMDB_Links ();
  235.  
  236. if (addRatingToTitle) {
  237. const foundRating = document.querySelectorAll('.ratingValue [itemprop=ratingValue]');
  238. if (foundRating.length === 1) {
  239. const rating = foundRating[0].textContent;
  240. if (String(Number(rating)) === rating) {
  241. document.title = `(${rating}) ` + document.title;
  242. }
  243. }
  244. }