Greasy Fork 还支持 简体中文。

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-21 提交的版本,檢視 最新版本

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