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

目前為 2015-11-13 提交的版本,檢視 最新版本

  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.