Show Metacritic.com ratings

Show metacritic metascore and user ratings on: Bandcamp, Apple Itunes (Music), Amazon (Music,Movies,TV Shows), IMDb (Movies), Google Play (Music, Movies), TV.com, Steam, Gamespot (PS4, XONE, PC), Rotten Tomatoes, Serienjunkies, BoxOfficeMojo, allmovie.com, movie.com, Wikipedia (en), themoviedb.org, letterboxd

As of 2015-11-13. See the latest version.

  1. // ==UserScript==
  2. // @name Show Metacritic.com ratings
  3. // @description Show metacritic metascore and user ratings on: Bandcamp, Apple Itunes (Music), Amazon (Music,Movies,TV Shows), IMDb (Movies), Google Play (Music, Movies), TV.com, Steam, Gamespot (PS4, XONE, PC), Rotten Tomatoes, Serienjunkies, BoxOfficeMojo, allmovie.com, movie.com, Wikipedia (en), themoviedb.org, letterboxd
  4. // @namespace cuzi
  5. // @oujs:author cuzi
  6. // @grant GM_xmlhttpRequest
  7. // @grant GM_getResourceURL
  8. // @grant GM_setValue
  9. // @grant GM_getValue
  10. // @grant unsafeWindow
  11. // @require http://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js
  12. // @resource global.min.css http://www.metacritic.com/css/global.min.1446760484.css
  13. // @resource base.min.css http://www.metacritic.com/css/search/base.min.1446760407.css
  14. // @license GNUGPL
  15. // @version 3
  16. // @include https://*.bandcamp.com/*
  17. // @include https://itunes.apple.com/*/album/*
  18. // @include https://play.google.com/store/music/album/*
  19. // @include https://play.google.com/store/movies/details/*
  20. // @include http://www.amazon.com/*
  21. // @include https://www.amazon.com/*
  22. // @include http://www.amazon.co.uk/*
  23. // @include https://www.amazon.co.uk/*
  24. // @include http://www.amazon.fr/*
  25. // @include https://www.amazon.fr/*
  26. // @include http://www.amazon.de/*
  27. // @include https://www.amazon.de/*
  28. // @include http://www.amazon.es/*
  29. // @include https://www.amazon.es/*
  30. // @include http://www.amazon.ca/*
  31. // @include https://www.amazon.ca/*
  32. // @include http://www.amazon.in/*
  33. // @include https://www.amazon.in/*
  34. // @include http://www.amazon.it/*
  35. // @include https://www.amazon.it/*
  36. // @include http://www.amazon.co.jp/*
  37. // @include https://www.amazon.co.jp/*
  38. // @include http://www.amazon.com.mx/*
  39. // @include https://www.amazon.com.mx/*
  40. // @include http://www.amazon.com.au/*
  41. // @include https://www.amazon.com.au/*
  42. // @include http://www.imdb.com/title/*
  43. // @include https://www.imdb.com/title/*
  44. // @include http://store.steampowered.com/app/*
  45. // @include https://store.steampowered.com/app/*
  46. // @include http://www.gamespot.com/*
  47. // @include https://www.gamespot.com/*
  48. // @include http://www.serienjunkies.de/*
  49. // @include https://www.serienjunkies.de/*
  50. // @include http://www.tv.com/shows/*
  51. // @include http://www.rottentomatoes.com/m/*
  52. // @include https://www.rottentomatoes.com/m/*
  53. // @include http://www.rottentomatoes.com/tv/*
  54. // @include https://www.rottentomatoes.com/tv/*
  55. // @include http://www.boxofficemojo.com/movies/*
  56. // @include http://www.allmovie.com/movie/*
  57. // @include https://en.wikipedia.org/*
  58. // @include http://www.movies.com/*/m*
  59. // @include https://www.themoviedb.org/movie/*
  60. // @include https://www.themoviedb.org/tv/*
  61. // @include http://letterboxd.com/film/*
  62. // @include https://letterboxd.com/film/*
  63. // ==/UserScript==
  64.  
  65.  
  66. var baseURL = "http://www.metacritic.com/";
  67.  
  68. var baseURL_music = "http://www.metacritic.com/music/";
  69. var baseURL_movie = "http://www.metacritic.com/movie/";
  70. var baseURL_pcgame = "http://www.metacritic.com/game/pc/";
  71. var baseURL_ps4 = "http://www.metacritic.com/game/playstation-4/";
  72. var baseURL_xone = "http://www.metacritic.com/game/xbox-one/";
  73. var baseURL_tv = "http://www.metacritic.com/tv/";
  74.  
  75. var baseURL_search = "http://www.metacritic.com/search/{type}/{query}/results";
  76. var baseURL_autosearch = "http://www.metacritic.com/autosearch";
  77.  
  78. var mybrowser = "other";
  79. if(~navigator.userAgent.indexOf("Chrome")) {
  80. mybrowser = "chrome";
  81. }
  82.  
  83.  
  84. // http://www.designcouch.com/home/why/2013/05/23/dead-simple-pure-css-loading-spinner/
  85. var CSS = "#mcdiv123 .grespinner{height:16px;width:16px;margin:0 auto;position:relative;-webkit-animation:rotation .6s infinite linear;-moz-animation:rotation .6s infinite linear;-o-animation:rotation .6s infinite linear;animation:rotation .6s infinite linear;border-left:6px solid rgba(0,174,239,.15);border-right:6px solid rgba(0,174,239,.15);border-bottom:6px solid rgba(0,174,239,.15);border-top:6px solid rgba(0,174,239,.8);border-radius:100%}@-webkit-keyframes rotation{from{-webkit-transform:rotate(0)}to{-webkit-transform:rotate(359deg)}}@-moz-keyframes rotation{from{-moz-transform:rotate(0)}to{-moz-transform:rotate(359deg)}}@-o-keyframes rotation{from{-o-transform:rotate(0)}to{-o-transform:rotate(359deg)}}@keyframes rotation{from{transform:rotate(0)}to{transform:rotate(359deg)}}#mcdiv123searchresults .result{font:12px arial,helvetica,serif;border-top-width:1px;border-top-color:#ccc;border-top-style:solid;padding:5px}#mcdiv123searchresults .result .result_type{display:inline}#mcdiv123searchresults .result .result_wrap{float:left;width:100%}#mcdiv123searchresults .result .has_score{padding-left:42px}#mcdiv123searchresults .result .basic_stats{height:1%;overflow:hidden}#mcdiv123searchresults .result h3{font-size:14px;font-weight:700}#mcdiv123searchresults .result a{color:#09f;font-weight:700;text-decoration:none}#mcdiv123searchresults .metascore_w.game.seventyfive,#mcdiv123searchresults .metascore_w.positive,#mcdiv123searchresults .metascore_w.score_favorable,#mcdiv123searchresults .metascore_w.score_outstanding,#mcdiv123searchresults .metascore_w.sixtyone{background-color:#6c3}#mcdiv123searchresults .metascore_w.forty,#mcdiv123searchresults .metascore_w.game.fifty,#mcdiv123searchresults .metascore_w.mixed,#mcdiv123searchresults .metascore_w.score_mixed{background-color:#fc3}#mcdiv123searchresults .metascore_w.negative,#mcdiv123searchresults .metascore_w.score_terrible,#mcdiv123searchresults .metascore_w.score_unfavorable{background-color:red}#mcdiv123searchresults a.metascore_w,#mcdiv123searchresults span.metascore_w{display:inline-block}#mcdiv123searchresults .result .metascore_w{color:#fff!important;font-family:Arial,Helvetica,sans-serif;font-size:17px;font-style:normal!important;font-weight:700!important;height:2em;line-height:2em;text-align:center;vertical-align:middle;width:2em;float:left;margin:0 0 0 -42px}#mcdiv123searchresults .result .more_stats{font-size:10px;color:#444}#mcdiv123searchresults .result .release_date .data{font-weight:700;color:#000}#mcdiv123searchresults ol,#mcdiv123searchresults ul{list-style:none}#mcdiv123searchresults .result li.stat{background:0 0;display:inline;float:left;margin:0;padding:0 6px 0 0;white-space:nowrap}#mcdiv123searchresults .result .deck{margin:3px 0 0}#mcdiv123searchresults .result .basic_stat{display:inline;float:right;overflow:hidden;width:100%}";
  86.  
  87. function name2metacritic(s) {
  88. return s.normalize('NFKD').replace(/\//g,"").replace(/[\u0300-\u036F]/g, '').replace(/&/g,"and").replace(/\W+/g, " ").toLowerCase().trim().replace(/\W+/g,"-");
  89. }
  90. function minutesSince(time) {
  91. var seconds = ((new Date()).getTime() - time.getTime()) / 1000;
  92. return seconds>60?parseInt(seconds/60)+" min ago":"now";
  93. }
  94. function fixMetacriticURLs(html) {
  95. return html.replace(/<a /g,'<a target="_blank" ').replace(/href="\//g,'href="'+baseURL).replace(/src="\//g,'src="'+baseURL);
  96. }
  97. function searchType2metacritic(type) {
  98. return ({
  99. 'movie' : 'movie',
  100. 'pcgame' : 'game',
  101. 'xonegame' : 'game',
  102. 'ps4game' : 'game',
  103. 'music' : 'album',
  104. 'tv' : 'tv'
  105. })[type];
  106. }
  107. function metacritic2searchType(type) {
  108. return ({
  109. "Album" : "music",
  110. "TV" : "tv",
  111. "Movie" : "movie",
  112. "PC Game" : "pcgame",
  113. "PS4 Game" : "ps4game",
  114. "XONE Game" : "onegame",
  115. "WIIU Game" : "xxxxx",
  116. "3DS Game" : "xxxx"
  117. })[type];
  118. }
  119.  
  120.  
  121. function metaScore(score, word) {
  122. var fg,bg,t;
  123. if(score == null) {
  124. fg = "black";
  125. bg = "#ccc";
  126. t = "tbd";
  127. } else if(score >= 75) {
  128. fg = "white";
  129. bg = "#6c3";
  130. t = parseInt(score);
  131. } else if(score < 40) {
  132. fg = "white";
  133. bg = "#f00";
  134. t = parseInt(score);
  135. } else {
  136. fg = "white";
  137. bg = "#fc3";
  138. t = parseInt(score);
  139. }
  140. return '<span title="'+(word?word:'')+'" style="display: inline-block; color: '+fg+';background:'+bg+';font-family: Arial,Helvetica,sans-serif;font-size: 17px;font-style: normal;font-weight: bold;height: 2em;width: 2em;line-height: 2em;text-align: center;vertical-align: middle;">'+t+'</span>';
  141. }
  142.  
  143. function addToMap(url, metaurl) {
  144. var data = JSON.parse(GM_getValue("map","{}"));
  145. var url = url.match(/http.*\d+\//)[0].replace(/https?:\/\/(www.)?/,"");
  146. var metaurl = metaurl.replace(/^http:\/\/(www.)?metacritic\.com\//,"");
  147.  
  148. data[url] = metaurl;
  149. GM_setValue("map", JSON.stringify(data));
  150. }
  151.  
  152. function addToBlacklist(url, metaurl) {
  153. var data = JSON.parse(GM_getValue("black","{}"));
  154. var url = url.match(/http.*\d+\//)[0].replace(/https?:\/\/(www.)?/,"");
  155. var metaurl = metaurl.replace(/^http:\/\/(www.)?metacritic\.com\//,"");
  156.  
  157. data[url] = metaurl;
  158. GM_setValue("black", JSON.stringify(data));
  159. }
  160.  
  161. function isBlacklistedUrl(docurl, metaurl) {
  162. docurl = docurl.replace(/https?:\/\/(www.)?/,"");
  163. metaurl = metaurl.replace(/^http:\/\/(www.)?metacritic\.com\//,"");
  164. metaurl = metaurl.replace(/\/\//g,"/").replace(/\/\//g,"/");; // remove double slash
  165. var data = JSON.parse(GM_getValue("black","{}"));
  166. if(docurl in data) {
  167. if(data[docurl] == metaurl) {
  168. return true;
  169. }
  170. }
  171. return false;
  172. }
  173.  
  174. function isBlacklisted(metaurl) {
  175. return isBlacklistedUrl("" + document.location.host.replace(/^www\./,"") + document.location.pathname + document.location.search, metaurl);
  176. }
  177.  
  178.  
  179.  
  180. function listenForHotkeys(code, cb) {
  181. // Call cb() as soon as the code sequence was typed
  182. var i = 0;
  183. $(document).bind("keydown.listenForHotkeys",function(ev) {
  184. if(document.activeElement == document.body) {
  185. if(ev.key != code[i]) {
  186. i = 0;
  187. } else {
  188. i++;
  189. if(i == code.length) {
  190. ev.preventDefault();
  191. $(document).unbind("keydown.listenForHotkeys");
  192. cb();
  193. }
  194. }
  195. }
  196. });
  197. }
  198.  
  199.  
  200. function metacritic_hoverInfo(url, cb, errorcb) {
  201. // Get the metacritic hover info. Requests are cached.
  202. var handleresponse = function(response, cached) {
  203. if(response.status == 200 && cb) {
  204. if(~response.responseText.indexOf('"jsonRedirect"')) { // {"viewer":{},"mixpanelToken":"6e219fd....","mixpanelDistinctId":"255.255.255.255","omnitureDebug":0,"jsonRedirect":"\/movie\/national-lampoons-vacation"}
  205. var j = JSON.parse(response.responseText);
  206. current.url = baseURL + j["jsonRedirect"];
  207. metacritic_hoverInfo(baseURL + j["jsonRedirect"], cb, errorcb);
  208. } else {
  209. cb(response.responseText, new Date(response.time));
  210. }
  211. } else if(response.status != 200 && errorcb) {
  212. errorcb(response.responseText, new Date(response.time));
  213. if(!cached)
  214. console.log("Show metacritic ratings: Error:"+response.status+"\n"+url);
  215. }
  216. };
  217. var cache = JSON.parse(GM_getValue("hovercache","{}"));
  218. for(var prop in cache) {
  219. // Delete cached values, that are older than 2 hours
  220. if((new Date()).getTime() - (new Date(cache[prop].time)).getTime() > 2*60*60*1000) {
  221. delete cache[prop];
  222. }
  223. }
  224. if(url in cache) {
  225. handleresponse(cache[url], true);
  226. } else {
  227. GM_xmlhttpRequest({
  228. method: "POST",
  229. url: url,
  230. data: "hoverinfo=1",
  231. headers: {
  232. "Referer" : url,
  233. "Content-Type" : "application/x-www-form-urlencoded; charset=UTF-8",
  234. "Host" : "www.metacritic.com",
  235. "User-Agent" : "MetacriticUserscript "+navigator.userAgent,
  236. "X-Requested-With" : "XMLHttpRequest"
  237. },
  238. onload: function(response) {
  239. response.time = (new Date()).toJSON();
  240. cache[url] = response;
  241. GM_setValue("hovercache",JSON.stringify(cache));
  242. handleresponse(response, false);
  243. }
  244. });
  245. }
  246. }
  247. function metacritic_searchResults(url, cb, errorcb) {
  248. // Get metacritic search results. Requests are cached.
  249. var handleresponse = function(response, cached) {
  250. if(response.results.length && cb) {
  251. cb(response.results, new Date(response.time));
  252. } else if(response.results.length == 0 && errorcb) {
  253. errorcb(response.results, new Date(response.time));
  254. }
  255. };
  256. var cache = JSON.parse(GM_getValue("searchcache","{}"));
  257. for(var prop in cache) {
  258. // Delete cached values, that are older than 2 hours
  259. if((new Date()).getTime() - (new Date(cache[prop].time)).getTime() > 2*60*60*1000) {
  260. delete cache[prop];
  261. }
  262. }
  263. if(url in cache) {
  264. handleresponse(cache[url], true);
  265. } else {
  266. GM_xmlhttpRequest({
  267. method: "GET",
  268. url: url,
  269. headers: {
  270. "Referer" : url,
  271. "Content-Type" : "application/x-www-form-urlencoded; charset=UTF-8",
  272. "Host" : "www.metacritic.com",
  273. "User-Agent" : "MetacriticUserscript "+navigator.userAgent,
  274. },
  275. onload: function(response) {
  276. var results = [];
  277. if(!~response.responseText.indexOf("No search results found.")) {
  278. var d = $('<html>').html(response.responseText);
  279. d.find("ul.search_results.module .result").each(function() {
  280. results.push(this.innerHTML);
  281. });
  282. }
  283.  
  284. response = {
  285. time : (new Date()).toJSON(),
  286. results : results,
  287. };
  288. cache[url] = response;
  289. GM_setValue("searchcache",JSON.stringify(cache));
  290. handleresponse(response, false);
  291. },
  292. onerror: function(response) {
  293. alert(response.responseText);
  294. console.log("Show metacritic ratings: Search error: "+response.status+"\n"+url);
  295. handleresponse({
  296. time : (new Date()).toJSON(),
  297. results : [],
  298. }, false);
  299. }
  300. });
  301. }
  302. }
  303.  
  304. function metacritic_showHoverInfo(url) {
  305. if(!url) {
  306. return;
  307. }
  308. metacritic_hoverInfo(url,
  309. // On Success
  310. function(html, time) {
  311. $("#mcdiv123").remove();
  312. var div = $('<div id="mcdiv123"></div>').appendTo(document.body);
  313. div.css({
  314. position:"fixed",
  315. bottom :0,
  316. left: 0,
  317. minWidth: 300,
  318. backgroundColor: "#fff",
  319. border: "2px solid #bbb",
  320. borderRadius:" 6px",
  321. boxShadow: "0 0 3px 3px rgba(100, 100, 100, 0.2)",
  322. color: "#000",
  323. padding:" 3px",
  324. zIndex: "5010001",
  325. });
  326. // Functions for communication between page and iframe
  327. // Mozilla can access parent.document
  328. // Chrome can use postMessage()
  329. var functions = {
  330. "other" : {
  331. "parent": function() {},
  332. "frame" : function sizecorrection() {
  333. var f = parent.document.getElementById('mciframe123');
  334. for(var i =0; f.clientHeight < document.body.scrollHeight && i < 100; i++) {
  335. f.style.width = parseInt(f.style.width)+10+"px";
  336. }
  337. if(f.clientHeight < document.body.scrollHeight) {
  338. f.style.height = parseInt(f.style.height)+15+"px";
  339. f.style.width = "300px";
  340. sizecorrection();
  341. }
  342. }
  343. },
  344. "chrome" : {
  345. "parent" : function() {
  346. var f = parent.document.getElementById('mciframe123');
  347. window.addEventListener("message", function(e){
  348. if("mcimessage1" in e.data) {
  349. f.style.width = parseInt(f.style.width)+10+"px";
  350. } else if("mcimessage2" in e.data) {
  351. f.style.height = parseInt(f.style.height)+15+"px";
  352. f.style.width = "300px";
  353. } else {
  354. return;
  355. }
  356. f.contentWindow.postMessage({
  357. "mcimessage3" : true,
  358. "mciframe123_clientHeight" : f.clientHeight,
  359. "mciframe123_clientWidth" : f.clientWidth,
  360. },'*');
  361. });
  362. },
  363. "frame" : function() {
  364. var i = 0;
  365. window.addEventListener("message", function(e){
  366. if(!("mcimessage3" in e.data)) return;
  367. if(e.data.mciframe123_clientHeight < document.body.scrollHeight && i < 100) {
  368. parent.postMessage({"mcimessage1":1},'*');
  369. i++;
  370. }
  371. if(i >= 100) {
  372. parent.postMessage({"mcimessage2":1},'*')
  373. i = 0;
  374. }
  375. });
  376. parent.postMessage({"mcimessage1":1},'*');
  377. }
  378. }
  379. };
  380. var framesrc = 'data:text/html,';
  381. framesrc += encodeURIComponent('<!DOCTYPE html>\
  382. <html lang="en">\
  383. <head>\
  384. <meta charset="utf-8">\
  385. <title>Metacritic info</title>\
  386. <link rel="stylesheet" href="'+(mybrowser=="chrome"?"data:text/css;base64,":"")+GM_getResourceURL("base.min.css")+'" type="text/css">\
  387. <link rel="stylesheet" href="'+(mybrowser=="chrome"?"data:text/css;base64,":"")+GM_getResourceURL("global.min.css")+'" type="text/css">\
  388. <style>body { margin:0px; padding:0px; background:white; }</style>\
  389. <script>\
  390. function on_load() {\
  391. ('+functions[mybrowser].frame.toString()+')();\
  392. }\
  393. </script>\
  394. </head>\
  395. <body onload="on_load();">\
  396. <div style="border:0px solid; display:block; position:relative; border-radius:0px; padding:0px; margin:0px; box-shadow:none;" class="hover_div" id="hover_div">\
  397. <div class="hover_content">'+fixMetacriticURLs(html)+'</div>\
  398. </div>\
  399. </body>\
  400. </html>');
  401.  
  402. var frame = $("<iframe></iframe>").appendTo(div);
  403. frame.attr("id","mciframe123");
  404. frame.attr("src",framesrc);
  405. frame.attr("scrolling","auto");
  406. frame.css({
  407. width: 300,
  408. height: 170,
  409. border: "none"
  410. });
  411. functions[mybrowser].parent();
  412. var sub = $("<div></div>").appendTo(div);
  413. $('<time style="color:#b6b6b6; font-size: 11px;" datetime="'+time+'" title="'+time.toLocaleFormat()+'">'+minutesSince(time)+'</time>').appendTo(sub);
  414. $('<a style="color:#b6b6b6; font-size: 11px;" target="_blank" href="'+url+'" title="Open Metacritic">'+decodeURI(url.replace("http://www.","@"))+'</a>').appendTo(sub);
  415. $('<span title="Hide me" style="cursor:pointer; float:right; color:#b6b6b6; font-size: 11px;">&#128128;</span>').appendTo(sub).click(function() {
  416. document.body.removeChild(this.parentNode.parentNode);
  417. });
  418. $('<span title="This is the correct entry" style="cursor:pointer; float:right; color:green; font-size: 11px;">&check;</span>').data("url", url).appendTo(sub).click(function() {
  419. var docurl = document.location.href;
  420. var metaurl = $(this).data("url");
  421. addToMap(docurl,metaurl);
  422. alert("Saved to correct list!\n\n"+docurl+"\n"+metaurl);
  423. });
  424. $('<span title="This is NOT the correct entry" style="cursor:pointer; float:right; color:crimson; font-size: 11px;">&cross;</span>').data("url", url).appendTo(sub).click(function() {
  425. var docurl = document.location.href;
  426. var metaurl = $(this).data("url");
  427. addToBlacklist(docurl,metaurl);
  428. alert("Saved to blacklist!\n\n"+docurl+"\n"+metaurl);
  429. // Open search
  430. metacritic_searchcontainer(null, current.searchTerm);
  431. metacritic_search(null, current.searchTerm);
  432. });
  433.  
  434. },
  435. // On error i.e. no result on metacritic.com
  436. function(html, time) {
  437. // Make search available
  438. metacritic_waitForHotkeys();
  439. var handleresponse = function(response) {
  440. var data;
  441. try {
  442. data = JSON.parse(response.responseText);
  443. } catch(e) {
  444. console.log("Error in JSON: search_term="+current.searchTerm);
  445. console.log(e);
  446. }
  447. if(data && data.autoComplete && data.autoComplete.length) {
  448. // Remove data with wrong type
  449. var newdata = [];
  450. data.autoComplete.forEach(function(result) {
  451. if(metacritic2searchType(result.refType) == current.type) {
  452. newdata.push(result);
  453. }
  454. });
  455. data.autoComplete = newdata;
  456. if(data.autoComplete.length == 0) {
  457. // No results
  458. console.log("No results (after filtering by type) for search_term="+current.searchTerm);
  459. } else if(data.autoComplete.length == 1) {
  460. // One result, let's show it
  461. if(!isBlacklisted(baseURL + data.autoComplete[0].url)) {
  462. metacritic_showHoverInfo(baseURL + data.autoComplete[0].url);
  463. return;
  464. }
  465. } else {
  466. // More than one result
  467. console.log("Multiple results for search_term="+current.searchTerm);
  468. var exactMatches = [];
  469. data.autoComplete.forEach(function(result,i) { // Try to find the correct result by matching the search term to exactly one movie title
  470. if(current.searchTerm == result.name) {
  471. exactMatches.push(result);
  472. }
  473. });
  474. if(exactMatches.length == 1) {
  475. // Only one exact match, let's show it
  476. if(!isBlacklisted(baseURL + exactMatches[0].url)) {
  477. metacritic_showHoverInfo(baseURL + exactMatches[0].url);
  478. return;
  479. }
  480. }
  481. }
  482. }
  483. // HERE: multiple results or no result. The user may type "meta" now
  484. };
  485. var cache = JSON.parse(GM_getValue("autosearchcache","{}"));
  486. for(var prop in cache) {
  487. // Delete cached values, that are older than 2 hours
  488. if((new Date()).getTime() - (new Date(cache[prop].time)).getTime() > 2*60*60*1000) {
  489. delete cache[prop];
  490. }
  491. }
  492. current.searchTerm = current.data.join(" ");
  493. if(current.searchTerm in cache) {
  494. handleresponse(cache[current.searchTerm], true);
  495. } else {
  496. GM_xmlhttpRequest({
  497. method: "POST",
  498. url: baseURL_autosearch,
  499. data: "search_term="+encodeURIComponent(current.searchTerm),
  500. headers: {
  501. "Referer" : url,
  502. "Content-Type" : "application/x-www-form-urlencoded; charset=UTF-8",
  503. "Host" : "www.metacritic.com",
  504. "User-Agent" : "MetacriticUserscript Mozilla/5.0 (Android 4.4; Mobile; rv:41.0) Gecko/41.0 Firefox/41.0",
  505. "X-Requested-With" : "XMLHttpRequest"
  506. },
  507. onload: function(response) {
  508. response = {
  509. time : (new Date()).toJSON(),
  510. responseText : response.responseText,
  511. };
  512. cache[current.searchTerm] = response;
  513. GM_setValue("autosearchcache",JSON.stringify(cache));
  514. handleresponse(response, false);
  515. }
  516. });
  517. }
  518. });
  519. }
  520.  
  521. function metacritic_waitForHotkeys() {
  522. listenForHotkeys("meta",metacritic_searchcontainer);
  523. }
  524.  
  525. function metacritic_searchcontainer(ev, query) {
  526. if(!query) {
  527. query = current.data.join(" ");
  528. }
  529. $("#mcdiv123").remove();
  530. var div = $('<div id="mcdiv123"></div>').appendTo(document.body);
  531. div.css({
  532. position:"fixed",
  533. bottom :0,
  534. left: 0,
  535. minWidth: 300,
  536. maxHeight: "80%",
  537. maxWidth: 640,
  538. overflow:"auto",
  539. backgroundColor: "#fff",
  540. border: "2px solid #bbb",
  541. borderRadius:" 6px",
  542. boxShadow: "0 0 3px 3px rgba(100, 100, 100, 0.2)",
  543. color: "#000",
  544. padding:" 3px",
  545. zIndex: "5010001",
  546. });
  547. var query = $('<input type="text" size="60" id="mcisearchquery">').appendTo(div).focus().val(query).on('keypress', function(e) {
  548. var code = e.keyCode || e.which;
  549. if(code == 13) { // Enter key
  550. metacritic_search.call(this,e);
  551. }
  552. });
  553. $('<button id="mcisearchbutton">').text("Search").appendTo(div).click(metacritic_search);
  554. }
  555.  
  556.  
  557. function metacritic_search(ev, query) {
  558. if(!query) { // Use values from search form
  559. query = $("#mcisearchquery").val();
  560. }
  561. var type = current.type;
  562.  
  563. var style = document.createElement('style');
  564. style.type = 'text/css';
  565. style.innerHTML = CSS;
  566. document.head.appendChild(style);
  567. var div = $("#mcdiv123");
  568. var loader = $('<div style="width:20px; height:20px;" class="grespinner"></div>').appendTo($("#mcisearchbutton"));
  569. var url = baseURL_search.replace("{type}",encodeURIComponent(type)).replace("{query}",encodeURIComponent(query));
  570. metacritic_searchResults(url,
  571. // On success
  572. function(results, time) {
  573. loader.remove();
  574. var accept = function(ev) {
  575. var a = $(this.parentNode).find("a[href*='metacritic.com']");
  576. var metaurl = a.attr("href");
  577. var docurl = document.location.href;
  578.  
  579. addToMap(docurl,metaurl);
  580. metacritic_showHoverInfo(metaurl);
  581. };
  582. var denyAll = function(ev) {
  583. var urls = [];
  584. var docurl = document.location.href;
  585. $("#mcdiv123searchresults").find("div.result a[href*='metacritic.com']").each(function() {
  586. addToBlacklist(docurl, this.href);
  587. });
  588. };
  589. var resultdiv = $("#mcdiv123searchresults").length?$("#mcdiv123searchresults").html(""):$('<div id="mcdiv123searchresults"></div>').css("max-width","95%").appendTo(div);
  590. results.forEach(function(html) {
  591. var singleresult = $('<div class="result"></div>').html(fixMetacriticURLs(html)+'<div style="clear:left"></div>').appendTo(resultdiv);
  592. $('<span title="This is the correct entry" style="cursor:pointer; color:green; font-size: 13px;">&check;</span>').prependTo(singleresult).click(accept);
  593. });
  594. var sub = $("<div></div>").appendTo(div);
  595. $('<time style="color:#b6b6b6; font-size: 11px;" datetime="'+time+'" title="'+time.toLocaleFormat()+'">'+minutesSince(time)+'</time>').appendTo(sub);
  596. $('<a style="color:#b6b6b6; font-size: 11px;" target="_blank" href="'+url+'" title="Open Metacritic">'+decodeURI(url.replace("http://www.","@"))+'</a>').appendTo(sub);
  597. $('<span title="Hide me" style="cursor:pointer; float:right; color:#b6b6b6; font-size: 11px;">&#128128;</span>').appendTo(sub).click(function() {
  598. document.body.removeChild(this.parentNode.parentNode);
  599. });
  600. $('<span title="None of the above is the correct item" style="cursor:pointer; float:right; color:crimson; font-size: 11px;">&cross;</span>').appendTo(sub).click(function() {if(confirm("None of the above is the correct item\nConfirm?")) denyAll()});
  601. },
  602. // On error i.e. no results
  603. function(results, time) {
  604. loader.remove();
  605. var resultdiv = $("#mcdiv123searchresults").length?$("#mcdiv123searchresults").html(""):$('<div id="mcdiv123searchresults"></div>').appendTo(div);
  606. resultdiv.html("No search results.");
  607. var sub = $("<div></div>").appendTo(div);
  608. $('<time style="color:#b6b6b6; font-size: 11px;" datetime="'+time+'" title="'+time.toLocaleFormat()+'">'+minutesSince(time)+'</time>').appendTo(sub);
  609. $('<a style="color:#b6b6b6; font-size: 11px;" target="_blank" href="'+url+'" title="Open Metacritic">'+decodeURI(url.replace("http://www.","@"))+'</a>').appendTo(sub);
  610. $('<span title="Hide me" style="cursor:pointer; float:right; color:#b6b6b6; font-size: 11px;">&#128128;</span>').appendTo(sub).click(function() {
  611. document.body.removeChild(this.parentNode.parentNode);
  612. });
  613. }
  614. );
  615. }
  616.  
  617. var current = {
  618. url : null,
  619. type : null,
  620. data : null, // Array of raw search keys
  621. searchTerm : null
  622. };
  623.  
  624.  
  625. function showURL(url) {
  626. if(!isBlacklisted(url)) {
  627. metacritic_showHoverInfo(url);
  628. } else {
  629. console.log(url +" is blacklisted!");
  630. }
  631. }
  632.  
  633.  
  634. var metacritic = {
  635. "mapped" : function metacritic_mapped(url, type) {
  636. // url was in the map/whitelist
  637. current.data = [url]
  638. current.url = url;
  639. current.type = type;
  640. current.searchTerm = url;
  641. showURL(url);
  642. },
  643. "music" : function metacritic_music(artistname, albumname) {
  644. current.data = [albumname.trim(),artistname.trim()]
  645. artistname = name2metacritic(artistname);
  646. albumname = name2metacritic(albumname);
  647. var url = baseURL_music + albumname + "/" + artistname;
  648. current.url = url;
  649. current.type = "music";
  650. current.searchTerm = albumname + "/" + artistname;
  651. showURL(url);
  652. },
  653. "movie" : function metacritic_movie(moviename) {
  654. current.data = [moviename.trim()]
  655. moviename = name2metacritic(moviename);
  656. var url = baseURL_movie + moviename;
  657. current.url = url;
  658. current.type = "movie";
  659. current.searchTerm = moviename;
  660. showURL(url);
  661. },
  662. "tv" : function metacritic_tv(seriesname) {
  663. current.data = [seriesname.trim()]
  664. seriesname = name2metacritic(seriesname);
  665. var url = baseURL_tv + seriesname;
  666. current.url = url;
  667. current.type = "tv";
  668. current.searchTerm = seriesname;
  669. showURL(url);
  670. },
  671. "pcgame" : function metacritic_pcgame(gamename) {
  672. current.data = [gamename.trim()]
  673. gamename = name2metacritic(gamename);
  674. var url = baseURL_pcgame + gamename;
  675. current.url = url;
  676. current.type = "pcgame";
  677. current.searchTerm = gamename;
  678. showURL(url);
  679. },
  680. "ps4game" : function metacritic_ps4game(gamename) {
  681. current.data = [gamename.trim()]
  682. gamename = name2metacritic(gamename);
  683. var url = baseURL_ps4 + gamename;
  684. current.url = url;
  685. current.type = "ps4game";
  686. current.searchTerm = gamename;
  687. showURL(url);
  688. },
  689. "xonegame" : function metacritic_xonegame(gamename) {
  690. current.data = [gamename.trim()]
  691. gamename = name2metacritic(gamename);
  692. var url = baseURL_xone + gamename;
  693. current.url = url;
  694. current.type = "xonegame";
  695. current.searchTerm = gamename;
  696. showURL(url);
  697. }
  698. };
  699.  
  700.  
  701. var Always = () => true;
  702. var sites = {
  703. 'bandcamp' : {
  704. host : ["bandcamp.com"],
  705. condition : function() {
  706. return unsafeWindow.TralbumData
  707. },
  708. products : [{
  709. condition : Always,
  710. type : "music",
  711. data : () => [unsafeWindow.TralbumData.artist, unsafeWindow.TralbumData.current.title]
  712. }]
  713. },
  714. 'itunes' : {
  715. host : ["itunes.apple.com"],
  716. condition : Always,
  717. products : [{
  718. condition : () => ~document.location.href.indexOf("/album/") ,
  719. type : "music",
  720. data : () => [document.querySelector("*[itemprop=byArtist]").textContent, document.querySelector("*[itemprop=name]").textContent]
  721. }]
  722. },
  723. 'googleplay' : {
  724. host : ["play.google.com"],
  725. condition : Always,
  726. products : [
  727. {
  728. condition : () => ~document.location.href.indexOf("/album/"),
  729. type : "music",
  730. data : () => [document.querySelector("*[itemprop=byArtist] a").textContent, document.querySelector("*[itemprop=name]").textContent]
  731. },
  732. {
  733. condition : () => ~document.location.href.indexOf("/movies/details/"),
  734. type : "movie",
  735. data : () => document.querySelector("*[itemprop=name]").textContent
  736. }
  737. ]
  738. },
  739. 'imdb' : {
  740. host : ["imdb.com"],
  741. condition : Always,
  742. products : [
  743. {
  744. condition : function() {
  745. var e = document.querySelector("meta[property='og:type']");
  746. if(e) {
  747. return e.content == "video.movie"
  748. }
  749. return false;
  750. },
  751. type : "movie",
  752. data : function() {
  753. if(document.querySelector(".title-extra[itemprop=name]")) {
  754. return [document.querySelector(".title-extra[itemprop=name]").firstChild.textContent.replace(/\"/g,"")];
  755. } else {
  756. return document.querySelector("*[itemprop=name]").textContent;
  757. }
  758. }
  759. },
  760. {
  761. condition : function() {
  762. var e = document.querySelector("meta[property='og:type']");
  763. if(e) {
  764. return e.content == "video.tv_show"
  765. }
  766. return false;
  767. },
  768. type : "tv",
  769. data : () => document.querySelector("*[itemprop=name]").textContent
  770. }
  771. ]
  772. },
  773. 'steam' : {
  774. host : ["store.steampowered.com"],
  775. condition : () => document.querySelector("*[itemprop=name]"),
  776. products : [{
  777. condition : Always,
  778. type : "pcgame",
  779. data : () => document.querySelector("*[itemprop=name]").textContent
  780. }]
  781. },
  782. 'tv.com' : {
  783. host : ["www.tv.com"],
  784. condition : () => document.querySelector("h1[itemprop=name]"),
  785. products : [{
  786. condition : Always,
  787. type : "tv",
  788. data : () => document.querySelector("h1[itemprop=name]").textContent
  789. }]
  790. },
  791. 'rottentomatoes' : {
  792. host : ["www.rottentomatoes.com"],
  793. condition : Always,
  794. products : [{
  795. condition : () => document.location.pathname.startsWith("/m/"),
  796. type : "movie",
  797. data : () => document.querySelector("h1[itemprop=name]").firstChild.textContent
  798. },
  799. {
  800. condition : () => document.location.pathname.startsWith("/tv/") ,
  801. type : "tv",
  802. data : () => document.querySelector("*[itemprop=partOfSeries] *[itemprop=name]").textContent
  803. }
  804. ]
  805. },
  806. 'serienjunkies' : {
  807. host : ["www.serienjunkies.de"],
  808. condition : Always,
  809. products : [{
  810. condition : () => Always,
  811. type : "tv",
  812. data : function() {
  813. if(document.querySelector("h1[itemprop=name]")) {
  814. return document.querySelector("h1[itemprop=name]").textContent;
  815. } else {
  816. var n = $("a:contains(Details zur)");
  817. if(n) {
  818. var name = n.text().match(/Details zur Produktion der Serie (.+)/)[1];
  819. return name;
  820. }
  821. }
  822. }
  823. }]
  824. },
  825. 'gamespot' : {
  826. host : ["gamespot.com"],
  827. condition : () => document.querySelector("[itemprop=device]"),
  828. products : [
  829. {
  830. condition : () => $("[itemprop=device]").text().contains("PC"),
  831. type : "pcgame",
  832. data : () => document.querySelector("h1[itemprop=name]").textContent
  833. },
  834. {
  835. condition : () => $("[itemprop=device]").text().contains("PS4"),
  836. type : "ps4game",
  837. data : () => document.querySelector("h1[itemprop=name]").textContent
  838. },
  839. {
  840. condition : () => $("[itemprop=device]").text().contains("XONE"),
  841. type : "xonegame",
  842. data : () => document.querySelector("h1[itemprop=name]").textContent
  843. }
  844. ]
  845. },
  846. 'amazon' : {
  847. host : ["amazon."],
  848. condition : Always,
  849. products : [
  850. {
  851. condition : function() {
  852. var music = ["Music","Musique","Musik","Música","Musica","音楽"];
  853. return music.some(function(s) {
  854. if(~document.title.indexOf(s)) {
  855. return true;
  856. } else {
  857. return false;
  858. }
  859. });
  860. },
  861. type : "music",
  862. data : function() {
  863. var artist = document.querySelector("#byline .author a").textContent;
  864. var title = document.getElementById("productTitle").textContent;
  865. title = title.replace(/\[([^\]]*)\]/g,""); // Remove [brackets] and their content
  866. return [artist, title];
  867. }
  868. },
  869. {
  870. condition : () => (document.getElementById("aiv-content-title") && document.getElementsByClassName("season-single-dark").length),
  871. type : "tv",
  872. data : () => document.getElementById("aiv-content-title").firstChild.data.trim()
  873. },
  874. {
  875. condition : () => document.getElementById("aiv-content-title"),
  876. type : "movie",
  877. data : () => document.getElementById("aiv-content-title").firstChild.data.trim()
  878. }
  879. ]
  880. },
  881. 'BoxOfficeMojo' : {
  882. host : ["boxofficemojo.com"],
  883. condition : () => ~document.location.search.indexOf("id="),
  884. products : [{
  885. condition : () => document.querySelector("#body table:nth-child(2) tr:first-child b"),
  886. type : "movie",
  887. data : () => document.querySelector("#body table:nth-child(2) tr:first-child b").firstChild.data
  888. }]
  889. },
  890. 'AllMovie' : {
  891. host : ["allmovie.com"],
  892. condition : () => document.querySelector("h2[itemprop=name].movie-title"),
  893. products : [{
  894. condition : () => document.querySelector("h2[itemprop=name].movie-title"),
  895. type : "movie",
  896. data : () => document.querySelector("h2[itemprop=name].movie-title").firstChild.data.trim()
  897. }]
  898. },
  899. 'en.wikipedia' : {
  900. host : ["en.wikipedia.org"],
  901. condition : Always,
  902. products : [{
  903. condition : function() {
  904. if(!document.querySelector(".infobox .summary")) {
  905. return false;
  906. }
  907. var r = /\d\d\d\d films/;
  908. return $("#catlinks a").filter((i,e) => e.firstChild.data.match(r)).length;
  909. },
  910. type : "movie",
  911. data : () => document.querySelector(".infobox .summary").firstChild.data
  912. },
  913. {
  914. condition : function() {
  915. if(!document.querySelector(".infobox .summary")) {
  916. return false;
  917. }
  918. var r = /television series/;
  919. return $("#catlinks a").filter((i,e) => e.firstChild.data.match(r)).length;
  920. },
  921. type : "tv",
  922. data : () => document.querySelector(".infobox .summary").firstChild.data
  923. }]
  924. },
  925. 'movies.com' : {
  926. host : ["movies.com"],
  927. condition : () => document.querySelector("meta[property='og:title']"),
  928. products : [{
  929. condition : () => Always,
  930. type : "movie",
  931. data : () => document.querySelector("meta[property='og:title']").content
  932. }]
  933. },
  934. 'themoviedb' : {
  935. host : ["themoviedb.org"],
  936. condition : () => document.querySelector("meta[property='og:type']"),
  937. products : [{
  938. condition : () => document.querySelector("meta[property='og:type']").content == "movie",
  939. type : "movie",
  940. data : () => document.querySelector("meta[property='og:title']").content
  941. },
  942. {
  943. condition : () => document.querySelector("meta[property='og:type']").content == "tv_series",
  944. type : "tv",
  945. data : () => document.querySelector("meta[property='og:title']").content
  946. }]
  947. },
  948. 'letterboxd' : {
  949. host : ["letterboxd.com"],
  950. condition : () => unsafeWindow.filmData && "name" in unsafeWindow.filmData,
  951. products : [{
  952. condition : () => Always,
  953. type : "movie",
  954. data : () => unsafeWindow.filmData.name
  955. }]
  956. }
  957.  
  958. };
  959.  
  960.  
  961. function main() {
  962.  
  963. var map = false;
  964.  
  965. for(var name in sites) {
  966. var site = sites[name];
  967. if(site.host.some(function(e) {return ~this.indexOf(e)}, document.location.hostname) && site.condition()) {
  968. for(var i = 0; i < site.products.length; i++) {
  969. if(site.products[i].condition()) {
  970. // Check map for a match
  971. if(map === false) {
  972. map = JSON.parse(GM_getValue("map","{}"));
  973. }
  974. var docurl = document.location.host.replace(/^www\./,"") + document.location.pathname + document.location.search;
  975. if(docurl in map) {
  976. // Found in map, show result
  977. var metaurl = map[docurl];
  978. metacritic["mapped"].apply(undefined, [baseURL + metaurl, site.products[i].type]);
  979. break;
  980. }
  981. // Try to retrieve item name from page
  982. var data;
  983. try {
  984. data = site.products[i].data();
  985. } catch(e) {
  986. data = false;
  987. console.log(e);
  988. }
  989. if(data !== false) {
  990. metacritic[site.products[i].type].apply(undefined, Array.isArray(data)?data:[data]);
  991. }
  992. break;
  993. }
  994. }
  995. break;
  996. }
  997. }
  998. }
  999.  
  1000.  
  1001.  
  1002. main();
  1003. var lastLoc = document.location.href;
  1004. window.setInterval(function() {
  1005. if(document.location.href != lastLoc) {
  1006. lastLoc = document.location.href;
  1007. $("#mcdiv123").remove();
  1008. window.setTimeout(main,500);
  1009. }
  1010. },500);
  1011.