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, TVmaze, TVGuide, followshows.com, TheTVDB.com, ConsequenceOfSound, Pitchfork, Last.fm, TVnfo, rateyourmusic.com

目前為 2019-05-15 提交的版本,檢視 最新版本

  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, TVmaze, TVGuide, followshows.com, TheTVDB.com, ConsequenceOfSound, Pitchfork, Last.fm, TVnfo, rateyourmusic.com
  4. // @namespace cuzi
  5. // @grant GM_xmlhttpRequest
  6. // @grant GM_setValue
  7. // @grant GM_getValue
  8. // @grant unsafeWindow
  9. // @grant GM.xmlHttpRequest
  10. // @grant GM.setValue
  11. // @grant GM.getValue
  12. // @require http://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js
  13. // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
  14. // @license GPL-3.0-or-later; http://www.gnu.org/licenses/gpl-3.0.txt
  15. // @version 46
  16. // @connect metacritic.com
  17. // @connect php-cuzi.herokuapp.com
  18. // @include https://*.bandcamp.com/*
  19. // @include https://itunes.apple.com/*/album/*
  20. // @include https://play.google.com/store/music/album/*
  21. // @include https://play.google.com/store/movies/details/*
  22. // @include https://music.amazon.com/*
  23. // @include http://www.amazon.com/*
  24. // @include https://www.amazon.com/*
  25. // @include http://www.amazon.co.uk/*
  26. // @include https://www.amazon.co.uk/*
  27. // @include http://www.amazon.fr/*
  28. // @include https://www.amazon.fr/*
  29. // @include http://www.amazon.de/*
  30. // @include https://www.amazon.de/*
  31. // @include http://www.amazon.es/*
  32. // @include https://www.amazon.es/*
  33. // @include http://www.amazon.ca/*
  34. // @include https://www.amazon.ca/*
  35. // @include http://www.amazon.in/*
  36. // @include https://www.amazon.in/*
  37. // @include http://www.amazon.it/*
  38. // @include https://www.amazon.it/*
  39. // @include http://www.amazon.co.jp/*
  40. // @include https://www.amazon.co.jp/*
  41. // @include http://www.amazon.com.mx/*
  42. // @include https://www.amazon.com.mx/*
  43. // @include http://www.amazon.com.au/*
  44. // @include https://www.amazon.com.au/*
  45. // @include http://www.imdb.com/title/*
  46. // @include https://www.imdb.com/title/*
  47. // @include http://store.steampowered.com/app/*
  48. // @include https://store.steampowered.com/app/*
  49. // @include http://www.gamespot.com/*
  50. // @include https://www.gamespot.com/*
  51. // @include http://www.serienjunkies.de/*
  52. // @include https://www.serienjunkies.de/*
  53. // @include http://www.tv.com/shows/*
  54. // @include http://www.rottentomatoes.com/m/*
  55. // @include https://www.rottentomatoes.com/m/*
  56. // @include http://www.rottentomatoes.com/tv/*
  57. // @include https://www.rottentomatoes.com/tv/*
  58. // @include http://www.rottentomatoes.com/tv/*/s*/
  59. // @include https://www.rottentomatoes.com/tv/*/s*/
  60. // @include http://www.boxofficemojo.com/movies/*
  61. // @include https://www.boxofficemojo.com/movies/*
  62. // @include http://www.allmovie.com/movie/*
  63. // @include https://www.allmovie.com/movie/*
  64. // @include https://en.wikipedia.org/*
  65. // @include http://www.movies.com/*/m*
  66. // @include https://www.themoviedb.org/movie/*
  67. // @include https://www.themoviedb.org/tv/*
  68. // @include http://letterboxd.com/film/*
  69. // @include https://letterboxd.com/film/*
  70. // @include http://www.tvmaze.com/shows/*
  71. // @include https://www.tvmaze.com/shows/*
  72. // @include http://www.tvguide.com/tvshows/*
  73. // @include https://www.tvguide.com/tvshows/*
  74. // @include http://followshows.com/show/*
  75. // @include https://followshows.com/show/*
  76. // @include http://thetvdb.com/*tab=series*
  77. // @include https://thetvdb.com/*tab=series*
  78. // @include http://www.thetvdb.com/*tab=series*
  79. // @include https://www.thetvdb.com/*tab=series*
  80. // @include https://www.thetvdb.com/series/*
  81. // @include http://consequenceofsound.net/*
  82. // @include https://consequenceofsound.net/*
  83. // @include http://pitchfork.com/*
  84. // @include https://pitchfork.com/*
  85. // @include http://www.last.fm/*
  86. // @include https://www.last.fm/*
  87. // @include http://tvnfo.com/s/*
  88. // @include https://tvnfo.com/s/*
  89. // @include http://rateyourmusic.com/release/album/*
  90. // @include https://rateyourmusic.com/release/album/*
  91. // @include https://open.spotify.com/*
  92. // @include https://play.spotify.com/album/*
  93. // @include https://www.nme.com/reviews/*
  94. // @include https://www.albumoftheyear.org/album/*
  95. // @include https://itunes.apple.com/*/movie/*
  96. // @include https://itunes.apple.com/*/album/*
  97. // @include https://itunes.apple.com/*/tv-season/*
  98. // @include http://epguides.com/*
  99. // @include http://www.epguides.com/*
  100. // @include https://sharetv.com/shows/*
  101. // ==/UserScript==
  102.  
  103.  
  104. var baseURL = "https://www.metacritic.com/";
  105.  
  106. var baseURL_music = "https://www.metacritic.com/music/";
  107. var baseURL_movie = "https://www.metacritic.com/movie/";
  108. var baseURL_pcgame = "https://www.metacritic.com/game/pc/";
  109. var baseURL_ps4 = "https://www.metacritic.com/game/playstation-4/";
  110. var baseURL_xone = "https://www.metacritic.com/game/xbox-one/";
  111. var baseURL_tv = "https://www.metacritic.com/tv/";
  112.  
  113. var baseURL_search = "https://www.metacritic.com/search/{type}/{query}/results";
  114. var baseURL_autosearch = "https://www.metacritic.com/autosearch";
  115.  
  116. var baseURL_database = "https://php-cuzi.herokuapp.com/r.php";
  117. var baseURL_whitelist = "https://php-cuzi.herokuapp.com/whitelist.php";
  118. var baseURL_blacklist = "https://php-cuzi.herokuapp.com/blacklist.php";
  119.  
  120. // http://www.designcouch.com/home/why/2013/05/23/dead-simple-pure-css-loading-spinner/
  121. 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%}";
  122.  
  123. async function versionUpdate() {
  124. let version = parseInt(await GM.getValue("version", 0));
  125. if(version <= 45) {
  126. // Reset database
  127. await GM.setValue("map", "{}");
  128. await GM.setValue("black", "{}");
  129. await GM.setValue("hovercache", "{}");
  130. await GM.setValue("searchcache", "{}");
  131. await GM.setValue("autosearchcache", "{}");
  132. }
  133. await GM.setValue("version", 46);
  134. }
  135.  
  136. function getHostname(url) {
  137. with(document.createElement("a")) {
  138. href = url;
  139. return hostname;
  140. }
  141. }
  142. function absoluteMetaURL(url) {
  143. if(url.startsWith("https://")) {
  144. return url;
  145. }
  146. if(url.startsWith("http://")) {
  147. return "https" + url.substr(4);
  148. }
  149. if(url.startsWith("//")) {
  150. return baseURL + url.substr(2);
  151. }
  152. if(url.startsWith("/")) {
  153. return baseURL + url.substr(1);
  154. }
  155. return baseURL + url;
  156. }
  157.  
  158. function parseLDJSON(keys, condition) {
  159. if(document.querySelector('script[type="application/ld+json"]')) {
  160. var data = [];
  161. var scripts = document.querySelectorAll('script[type="application/ld+json"]');
  162. for(let i = 0; i < scripts.length; i++) {
  163. try {
  164. var jsonld = JSON.parse(scripts[i].innerText);
  165. } catch(e) {
  166. continue;
  167. }
  168. if(jsonld) {
  169. if(Array.isArray(jsonld)) {
  170. data.push(...jsonld)
  171. } else {
  172. data.push(jsonld);
  173. }
  174. }
  175. }
  176. for(let i = 0; i < data.length; i++) {
  177. try {
  178. if(data[i] && data[i] && (typeof condition != 'function' || condition(data[i]))) {
  179. if(Array.isArray(keys)) {
  180. let r = [];
  181. for(let j = 0; j < keys.length; j++) {
  182. r.push(data[i][keys[j]]);
  183. }
  184. return r;
  185. } else if(keys) {
  186. return data[i][keys];
  187. } else if(typeof condition === 'function') {
  188. return data[i]; // Return whole object
  189. }
  190. }
  191. } catch(e) {
  192. continue;
  193. }
  194. }
  195. return data;
  196. }
  197. return null;
  198. }
  199.  
  200.  
  201. function name2metacritic(s) {
  202. return s.normalize('NFKD').replace(/\//g,"").replace(/[\u0300-\u036F]/g, '').replace(/&/g,"and").replace(/\W+/g, " ").toLowerCase().trim().replace(/\W+/g,"-");
  203. }
  204. function minutesSince(time) {
  205. var seconds = ((new Date()).getTime() - time.getTime()) / 1000;
  206. return seconds>60?parseInt(seconds/60)+" min ago":"now";
  207. }
  208. function randomStringId() {
  209. var id10 = () => Math.floor((1 + Math.random()) * 0x10000000000).toString(16).substring(1);
  210. return id10()+id10()+id10()+id10()+id10()+id10();
  211. }
  212. function fixMetacriticURLs(html) {
  213. return html.replace(/<a /g,'<a target="_blank" ').replace(/href="\//g,'href="'+baseURL).replace(/src="\//g,'src="'+baseURL);
  214. }
  215. function searchType2metacritic(type) {
  216. return ({
  217. 'movie' : 'movie',
  218. 'pcgame' : 'game',
  219. 'xonegame' : 'game',
  220. 'ps4game' : 'game',
  221. 'music' : 'album',
  222. 'tv' : 'tv'
  223. })[type];
  224. }
  225. function metacritic2searchType(type) {
  226. return ({
  227. "Album" : "music",
  228. "TV" : "tv",
  229. "Movie" : "movie",
  230. "PC Game" : "pcgame",
  231. "PS4 Game" : "ps4game",
  232. "XONE Game" : "onegame",
  233. "WIIU Game" : "xxxxx",
  234. "3DS Game" : "xxxx"
  235. })[type];
  236. }
  237.  
  238.  
  239. function metaScore(score, word) {
  240. var fg,bg,t;
  241. if(score == null) {
  242. fg = "black";
  243. bg = "#ccc";
  244. t = "tbd";
  245. } else if(score >= 75) {
  246. fg = "white";
  247. bg = "#6c3";
  248. t = parseInt(score);
  249. } else if(score < 40) {
  250. fg = "white";
  251. bg = "#f00";
  252. t = parseInt(score);
  253. } else {
  254. fg = "white";
  255. bg = "#fc3";
  256. t = parseInt(score);
  257. }
  258.  
  259. 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>';
  260. }
  261.  
  262. function balloonAlert(message, timeout, title, css, click) {
  263. var header;
  264. if(title) {
  265. header = '<div style="background:rgb(220,230,150); padding: 2px 12px;">' + title +"</div>";
  266. } else if(title === false) {
  267. header = '';
  268. } else {
  269. header = '<div style="background:rgb(220,230,150); padding: 2px 12px;">Userscript alert</div>';
  270. }
  271. var div = $("<div>" + header + '<div style="padding:5px">' + message.split("\n").join("<br>") + "</div></div>");
  272. div.css({
  273. position : "fixed",
  274. top : 10,
  275. left : 10,
  276. maxWidth: 200,
  277. zIndex : "5010002",
  278. background : "rgb(240,240,240)",
  279. border : "2px solid yellow",
  280. borderRadius :"6px",
  281. boxShadow: "0 0 3px 3px rgba(100, 100, 100, 0.2)",
  282. fontFamily: "sans-serif",
  283. color: "black"
  284. });
  285. if(css) {
  286. div.css(css);
  287. }
  288. div.appendTo(document.body);
  289.  
  290. if(click) {
  291. div.click(function(ev) {
  292. $(this).hide(500);
  293. click.call(this,ev);
  294. });
  295. }
  296.  
  297. if(!click) {
  298. var close = $('<div title="Close" style="cursor:pointer; position:absolute; top:0px; right:3px;">&#10062;</div>').appendTo(div);
  299. close.click(function(){
  300. $(this.parentNode).hide(1000);
  301. });
  302. }
  303.  
  304. if(timeout && timeout > 0) {
  305. window.setTimeout(function() {
  306. div.hide(3000);
  307. }, timeout);
  308. }
  309. return div;
  310. }
  311.  
  312.  
  313. function filterUniversalUrl(url) {
  314. try {
  315. url = url.match(/http.+/)[0];
  316. } catch(e) { }
  317.  
  318. try {
  319. url = url.replace(/https?:\/\/(www.)?/,"");
  320. } catch(e) { }
  321.  
  322. if(url.startsWith("imdb.com/") && url.match(/(imdb\.com\/\w+\/\w+\/)/)) {
  323. // Remove movie subpage from imdb url
  324. return url.match(/(imdb\.com\/\w+\/\w+\/)/)[1];
  325. } else if(url.startsWith("thetvdb.com/")) {
  326. // Do nothing with thetvdb.com urls
  327. return url;
  328. } else if(url.startsWith("boxofficemojo.com/")) {
  329. // Keep the important id= on
  330. try {
  331. var parts = url.split("?");
  332. var page = url[0] + "?";
  333. var idparam = url[1].match(/(id=.+?)(\.|&)/)[1];
  334. return page+idparam;
  335. } catch(e) {
  336. return url;
  337. }
  338. } else {
  339. // Default: Remove parameters
  340. return url.split("?")[0].split("&")[0];
  341. }
  342. }
  343.  
  344. async function addToMap(url, metaurl) {
  345. var data = JSON.parse(await GM.getValue("map","{}"));
  346.  
  347. var url = filterUniversalUrl(url);
  348. var metaurl = metaurl.replace(/^https?:\/\/(www.)?metacritic\.com\//,"");
  349.  
  350. data[url] = metaurl;
  351.  
  352. await GM.setValue("map", JSON.stringify(data));
  353.  
  354. (new Image()).src = baseURL_whitelist + "?docurl="+encodeURIComponent(url)+"&metaurl="+encodeURIComponent(metaurl)+"&ref="+encodeURIComponent(randomStringId());
  355. return [url, metaurl];
  356. }
  357.  
  358. async function addToBlacklist(url, metaurl) {
  359. var data = JSON.parse(await GM.getValue("black","[]"));
  360.  
  361. var url = filterUniversalUrl(url);
  362. var metaurl = metaurl.replace(/^https?:\/\/(www.)?metacritic\.com\//,"");
  363.  
  364. data.push([url,metaurl]);
  365.  
  366. await GM.setValue("black", JSON.stringify(data));
  367.  
  368. (new Image()).src = baseURL_blacklist + "?docurl="+encodeURIComponent(url)+"&metaurl="+encodeURIComponent(metaurl)+"&ref="+encodeURIComponent(randomStringId());
  369. return [url, metaurl];
  370. }
  371.  
  372.  
  373.  
  374.  
  375. async function isBlacklistedUrl(docurl, metaurl) {
  376.  
  377. docurl = filterUniversalUrl(docurl);
  378. docurl = docurl.replace(/https?:\/\/(www.)?/,"");
  379.  
  380. metaurl = metaurl.replace(/^https?:\/\/(www.)?metacritic\.com\//,"");
  381. metaurl = metaurl.replace(/\/\//g,"/").replace(/\/\//g,"/");; // remove double slash
  382. metaurl = metaurl.replace(/^\/+/,""); // remove starting slash
  383.  
  384. var data = JSON.parse(await GM.getValue("black","[]")); // [ [docurl0, metaurl0] , [docurl1, metaurl1] , ... ]
  385. for(var i = 0; i < data.length; i++) {
  386. if(data[i][0] == docurl && data[i][1] == metaurl) {
  387. return true;
  388. }
  389. }
  390. return false;
  391. }
  392.  
  393. async function isBlacklisted(metaurl) {
  394. return await isBlacklistedUrl("" + document.location.host.replace(/^www\./,"") + document.location.pathname + document.location.search, metaurl);
  395. }
  396.  
  397.  
  398.  
  399. function listenForHotkeys(code, cb) {
  400. // Call cb() as soon as the code sequence was typed
  401. var i = 0;
  402. $(document).bind("keydown.listenForHotkeys",function(ev) {
  403. if(document.activeElement == document.body) {
  404. if(ev.key != code[i]) {
  405. i = 0;
  406. } else {
  407. i++;
  408. if(i == code.length) {
  409. ev.preventDefault();
  410. $(document).unbind("keydown.listenForHotkeys");
  411. cb();
  412. }
  413. }
  414. }
  415. });
  416. }
  417.  
  418.  
  419. async function metacritic_hoverInfo(url, docurl, cb, errorcb, nocache) {
  420.  
  421. // Get the metacritic hover info. Requests are cached.
  422. var handleresponse = function(response, cached) {
  423. if(response.status == 200 && response.responseText && cb) {
  424. if(~response.responseText.indexOf('"jsonRedirect"')) { // {"viewer":{},"mixpanelToken":"6e219fd....","mixpanelDistinctId":"255.255.255.255","omnitureDebug":0,"jsonRedirect":"\/movie\/national-lampoons-vacation"}
  425. var blacklistedredirect = false;
  426. var j = JSON.parse(response.responseText);
  427. current.url = absoluteMetaURL(j["jsonRedirect"]);
  428. delete cache[url]; // Delete original url from cache. The redirect URL will then be saved in metacritic_hoverInfo(...)
  429.  
  430. // Blacklist items from database received?
  431. if("blacklist" in j && j.blacklist && j.blacklist.length) {
  432. // Save new blacklist items
  433. GM.getValue("black","[]").then(function(json_data) {
  434. var data = JSON.parse(json_data);
  435. for(var i = 0; i < j.blacklist.length; i++) {
  436. var save_docurl = j.blacklist[i].docurl;
  437. var save_metaurl = j.blacklist[i].metaurl;
  438.  
  439. data.push([save_docurl,save_metaurl]);
  440. if(j["jsonRedirect"] == "/"+save_metaurl) {
  441. // Redirect is blacklisted!
  442. blacklistedredirect = true;
  443. }
  444. }
  445. GM.setValue("black", JSON.stringify(data));
  446. });
  447.  
  448. }
  449. if(blacklistedredirect && errorcb) {
  450. // Redirect was blacklisted, show nothing
  451. errorcb(response.responseText, new Date(response.time));
  452. } else {
  453. // Load redirect
  454. metacritic_hoverInfo(absoluteMetaURL(j["jsonRedirect"]), false, cb, errorcb, true);
  455. }
  456. } else {
  457. // Show
  458. cb(response.responseText, new Date(response.time));
  459. }
  460. } else if(response.status != 200 && errorcb) {
  461. errorcb(response.responseText, new Date(response.time));
  462. if(!cached)
  463. console.log("Show metacritic ratings: Error 01:"+response.status+"\n"+url);
  464. } else if(!response.responseText) {
  465. errorcb("", new Date(response.time));
  466. if(!cached)
  467. console.log("Show metacritic ratings: Error 02: response empty. Status:"+response.status+"\n"+url);
  468. }
  469. };
  470.  
  471.  
  472.  
  473. var cache = JSON.parse(await GM.getValue("hovercache","{}"));
  474. for(var prop in cache) {
  475. // Delete cached values, that are older than 2 hours
  476. if((new Date()).getTime() - (new Date(cache[prop].time)).getTime() > 2*60*60*1000) {
  477. delete cache[prop];
  478. }
  479. }
  480.  
  481. if(!nocache && url in cache) {
  482. handleresponse(cache[url], true);
  483. } else {
  484. var requestURL = url;
  485. var requestParams = "hoverinfo=1";
  486.  
  487. if(docurl && docurl.indexOf("metacritic.com") == -1 && docurl.indexOf(baseURL_database) == -1) {
  488. // Ask database for correct metacritic entry:
  489. requestURL = baseURL_database;
  490. requestParams = "m=" + encodeURIComponent(docurl) + "&a=" + encodeURIComponent(url);
  491. }
  492.  
  493.  
  494. GM.xmlHttpRequest({
  495. method: "POST",
  496. url: requestURL,
  497. data: requestParams,
  498. headers: {
  499. "Referer" : url,
  500. "Content-Type" : "application/x-www-form-urlencoded; charset=UTF-8",
  501. "Host" : getHostname(requestURL), // This is important, otherwise Metacritic refuses to answer!
  502. //"User-Agent" : "MetacriticUserscript "+navigator.userAgent,
  503. "User-Agent" : navigator.userAgent,
  504. "X-Requested-With" : "XMLHttpRequest"
  505. },
  506. onload: async function(response) {
  507. response.time = (new Date()).toJSON();
  508.  
  509.  
  510. if(!response.responseText) {
  511. // Temporary fix:
  512. // Hover info seems to be available only for movies.
  513. requestURL = url;
  514. if(requestURL.indexOf('/critic-reviews') !== -1) {
  515. requestURL = url.split('/critic-reviews')[0]
  516. }
  517. GM.xmlHttpRequest({
  518. method: "GET",
  519. url: requestURL,
  520. headers: {
  521. "Referer" : url,
  522. "Host" : getHostname(requestURL), // This is important, otherwise Metacritic refuses to answer!
  523. //"User-Agent" : "MetacriticUserscript "+navigator.userAgent,
  524. "User-Agent" : navigator.userAgent
  525. },
  526. onload: async function(response) {
  527. response.time = (new Date()).toJSON();
  528.  
  529.  
  530. let parts = response.responseText.split('class="score_details')
  531. let text_part = '<div class="' + parts[1].split('</div>')[0] + '</div>'
  532.  
  533.  
  534.  
  535.  
  536. let title_text = '<div class="product_page_title' + response.responseText.split('class="product_page_title')[1].split('</div>')[0]
  537. title_text = title_text.split('<h1>').join('<h1 style="padding:0px; margin:2px">') + '</div>'
  538.  
  539. parts = response.responseText.split('id="nav_to_metascore"')
  540. let meta_score_part = '<div ' + parts[1].split('<div class="subsection_title"')[0] + '</div></div>'
  541.  
  542.  
  543. meta_score_part = meta_score_part.split('href="">').join('href="' + requestURL + '">')
  544. meta_score_part = meta_score_part.split('section_title bold">').join('section_title bold">' + title_text)
  545.  
  546. let html = meta_score_part.split('<div class="distribution">').join(text_part + '<div class="distribution">')
  547.  
  548. // Chrome fix: Otherwise JSON.stringify(cache) omits responseText
  549. var newobj = {};
  550. for(var key in response) {
  551. newobj[key] = response[key];
  552. }
  553. newobj.responseText = html;
  554.  
  555. //alert(requestURL+'\n'+requestParams+'\nResult:\n'+response.responseText)
  556.  
  557. cache[url] = newobj;
  558.  
  559. await GM.setValue("hovercache",JSON.stringify(cache));
  560.  
  561. handleresponse(newobj, false);
  562. },
  563. onerror: function(response) {
  564. console.log("Show metacritic ratings: Hover info error 03: "+response.status+"\nURL: "+requestURL+"\nResponse:\n"+response.responseText);
  565. },
  566. });
  567.  
  568. } else {
  569.  
  570. // Chrome fix: Otherwise JSON.stringify(cache) omits responseText
  571. var newobj = {};
  572. for(var key in response) {
  573. newobj[key] = response[key];
  574. }
  575. newobj.responseText = response.responseText;
  576.  
  577.  
  578.  
  579. cache[url] = newobj;
  580.  
  581. await GM.setValue("hovercache",JSON.stringify(cache));
  582.  
  583. handleresponse(response, false);
  584. }
  585. },
  586. onerror: function(response) {
  587. console.log("Show metacritic ratings: Hover info error 03: "+response.status+"\nURL: "+requestURL+"\nResponse:\n"+response.responseText);
  588. },
  589. });
  590. }
  591. }
  592. async function metacritic_searchResults(url, cb, errorcb) {
  593. // Get metacritic search results. Requests are cached.
  594. var handleresponse = function(response, cached) {
  595. if(response.results.length && cb) {
  596. cb(response.results, new Date(response.time));
  597. } else if(response.results.length == 0 && errorcb) {
  598. errorcb(response.results, new Date(response.time));
  599. }
  600. };
  601.  
  602. var cache = JSON.parse(await GM.getValue("searchcache","{}"));
  603. for(var prop in cache) {
  604. // Delete cached values, that are older than 2 hours
  605. if((new Date()).getTime() - (new Date(cache[prop].time)).getTime() > 2*60*60*1000) {
  606. delete cache[prop];
  607. }
  608. }
  609.  
  610. if(url in cache) {
  611. handleresponse(cache[url], true);
  612. } else {
  613. GM.xmlHttpRequest({
  614. method: "GET",
  615. url: url,
  616. headers: {
  617. "Referer" : url,
  618. "Content-Type" : "application/x-www-form-urlencoded; charset=UTF-8",
  619. "Host" : "www.metacritic.com",
  620. "User-Agent" : "MetacriticUserscript "+navigator.userAgent,
  621. },
  622. onload: async function(response) {
  623. var results = [];
  624. if(!~response.responseText.indexOf("No search results found.")) {
  625. var d = $('<html>').html(response.responseText);
  626. d.find("ul.search_results.module .result").each(function() {
  627. results.push(this.innerHTML);
  628. });
  629. }
  630.  
  631. response = {
  632. time : (new Date()).toJSON(),
  633. results : results,
  634. };
  635. cache[url] = response;
  636. await GM.setValue("searchcache",JSON.stringify(cache));
  637. handleresponse(response, false);
  638. },
  639. onerror: function(response) {
  640. console.log("Show metacritic ratings: Search error 04: "+response.status+"\n"+url);
  641. handleresponse({
  642. time : (new Date()).toJSON(),
  643. results : [],
  644. }, false);
  645. }
  646. });
  647. }
  648. }
  649.  
  650. function metacritic_showHoverInfo(url, docurl) {
  651. if(!url) {
  652. return;
  653. }
  654. metacritic_hoverInfo(url, docurl?docurl:false,
  655. // On Success
  656. function(html, time) {
  657. $("#mcdiv123").remove();
  658. var div = $('<div id="mcdiv123"></div>').appendTo(document.body);
  659. div.css({
  660. position:"fixed",
  661. bottom :0,
  662. left: 0,
  663. minWidth: 300,
  664. backgroundColor: "#fff",
  665. border: "2px solid #bbb",
  666. borderRadius:" 6px",
  667. boxShadow: "0 0 3px 3px rgba(100, 100, 100, 0.2)",
  668. color: "#000",
  669. padding:" 3px",
  670. zIndex: "5010001",
  671. });
  672.  
  673. // Functions for communication between page and iframe
  674. // Mozilla can access parent.document
  675. // Chrome can use postMessage()
  676. var frame_status = false; // if this remains false, loading the frame content failed. A reason could be "Content Security Policy"
  677. function loadExternalImage(url, myframe) {
  678. // Load external image, bypass CSP
  679. GM.xmlHttpRequest({
  680. method: "GET",
  681. url: url,
  682. responseType : "arraybuffer",
  683. onload: function(response) {
  684. myframe.contentWindow.postMessage({
  685. "mcimessage_imgLoaded" : true,
  686. "mcimessage_imgData" : response.response,
  687. "mcimessage_imgOrgSrc" : url
  688. },'*');
  689. }
  690. });
  691. }
  692. var functions = {
  693. "parent" : function() {
  694. var f = parent.document.getElementById('mciframe123');
  695. var lastdiff = -200000;
  696. window.addEventListener("message", function(e){
  697. if(typeof e.data != "object") {
  698. return;
  699. } else if("mcimessage0" in e.data) {
  700. frame_status = true; // Frame content was loaded successfully
  701. } else if("mcimessage1" in e.data) {
  702. f.style.width = parseInt(f.style.width)+10+"px";
  703. if(e.data.heightdiff == lastdiff) {
  704. f.style.height = parseInt(f.style.height)+5+"px";
  705. }
  706. lastdiff = e.data.heightdiff;
  707.  
  708. } else if("mcimessage2" in e.data) {
  709. f.style.height = parseInt(f.style.height)+15+"px";
  710. f.style.width = "400px";
  711. } else if("mcimessage_loadImg" in e.data) {
  712. loadExternalImage(e.data.mcimessage_imgUrl, f);
  713. } else {
  714. return;
  715. }
  716. f.contentWindow.postMessage({
  717. "mcimessage3" : true,
  718. "mciframe123_clientHeight" : f.clientHeight,
  719. "mciframe123_clientWidth" : f.clientWidth,
  720. },'*');
  721. });
  722. },
  723. "frame" : function() {
  724. parent.postMessage({"mcimessage0":true},'*'); // Loading frame content was successfull
  725.  
  726. var i = 0;
  727. window.addEventListener("message", function(e){
  728. if(typeof e.data == "object" && "mcimessage_imgLoaded" in e.data) {
  729. // Load external image
  730. var arrayBufferView = new Uint8Array( e.data["mcimessage_imgData"] );
  731. var blob = new Blob( [ arrayBufferView ], { type: "image/jpeg" } );
  732. var urlCreator = window.URL || window.webkitURL;
  733. var imageUrl = urlCreator.createObjectURL( blob );
  734. var img = failedImages[e.data["mcimessage_imgOrgSrc"]];
  735. img.src = imageUrl;
  736. }
  737.  
  738. if(!("mcimessage3" in e.data)) return;
  739.  
  740. if(e.data.mciframe123_clientHeight < document.body.scrollHeight && i < 100) {
  741. parent.postMessage({"mcimessage1":1, "heightdiff":document.body.scrollHeight - e.data.mciframe123_clientHeight},'*');
  742. i++;
  743. }
  744. if(i >= 100) {
  745. parent.postMessage({"mcimessage2":1},'*')
  746. i = 0;
  747. }
  748. });
  749. parent.postMessage({"mcimessage1":1,"heightdiff":-100000},'*');
  750. }
  751.  
  752. };
  753.  
  754.  
  755.  
  756. var css = "#hover_div .clr { clear: both}#hover_div { background-color: #fff; color: #666; font-family:Arial,Helvetica,sans-serif; font-size:12px; font-weight:400; font-style:normal;} #hover_div .hoverinfo .hover_left { float: left} #hover_div .hoverinfo .product_image_wrapper { color: #999; font-size: 6px; font-weight: normal; min-height: 98px; min-width: 98px;} #hover_div .hoverinfo .product_image_wrapper a { color: #999; font-size: 6px; font-weight: normal;} #hover_div a * { cursor: pointer} #hover_div a { color: #09f; font-weight: bold;} #hover_div a:link, #hover_div a:visited { text-decoration: none;} #hover_div a:hover { text-decoration: underline;} #hover_div .hoverinfo .hover_right { float: left; margin-left: 15px; max-width: 395px;} #hover_div .hoverinfo .product_title { color: #333; font-family: georgia,serif; font-size: 24px; line-height: 26px; margin-bottom: 10px;} #hover_div .hoverinfo .product_title a { color:#333; font-family: georgia,serif; font-size: 24px;} #hover_div .hoverinfo .summary_detail.publisher, .hoverinfo .summary_detail.release_data { float: left} #hover_div .hoverinfo .summary_detail { font-size: 11px; margin-bottom: 10px;} #hover_div .hoverinfo .summary_detail.product_credits a { color: #999; font-weight: normal; } #hover_div .hoverinfo .hr { background-color: #ccc; height: 2px; margin: 15px 0 10px;} #hover_div .hoverinfo .hover_scores { width: 100%; border-collapse: collapse; border-spacing: 0;} #hover_div .hoverinfo .hover_scores td.num { width: 39px} #hover_div .hoverinfo .hover_scores td { vertical-align: middle} #hover_div caption, #hover_div th, #hover_div td { font-weight: normal; text-align: left;} #hover_div .metascore_anchor, #hover_div a.metascore_w { text-decoration: none !important} #hover_div span.metascore_w, #hover_div a.metascore_w { display: inline-block; padding:0px;}.metascore_w { background-color: transparent; color: #fff !important; font-family: Arial,Helvetica,sans-serif; font-size: 17px; font-style: normal !important; font-weight: bold !important; height: 2em; line-height: 2em; text-align: center; vertical-align: middle; width: 2em;} #hover_div .metascore, #hover_div .metascore a, #hover_div .avguserscore, #hover_div .avguserscore a { color: #fff} #hover_div .critscore, #hover_div .critscore a, #hover_div .userscore, #hover_div .userscore a { color: #333}.score_tbd { background: #eaeaea; color: #333; font-size: 14px;} #hover_div .score_tbd a { color: #333}.negative, .score_terrible, .score_unfavorable, .carousel_set a.product_terrible:hover, .carousel_set a.product_unfavorable:hover { background-color: #f00}.mixed, .neutral, .score_mixed, .carousel_set a.product_mixed:hover { background-color: #fc3; color: #333;} #hover_div .score_mixed a { color: #333}.positive, .score_favorable, .score_outstanding, .carousel_set a.product_favorable:hover, .carousel_set a.product_outstanding:hover { background-color: #6c3}.critscore_terrible, .critscore_unfavorable { border-color: #f00}.critscore_mixed { border-color: #fc3}.critscore_favorable, .critscore_outstanding { border-color: #6c3}.metascore .score_total, .userscore .score_total { display: none; visibility: hidden;}.hoverinfo .metascore_label, .hoverinfo .userscore_label { font-size: 12px; font-weight: bold; line-height: 16px; margin-top: 2%;}.hoverinfo .metascore_review_count, .hoverinfo .userscore_review_count { font-size: 11px}.hoverinfo .hover_scores td { vertical-align: middle}.hoverinfo .hover_scores td.num { width: 39px}.hoverinfo .hover_scores td.usr.num { padding-left: 20px}.metascore_anchor, a.metascore_w { text-decoration: none !important} .metascore_w.album { padding-top:0px; !important} .metascore_w.user { border-radius: 55%; color: #fff;}.metascore_anchor, .metascore_w.album { padding: 0px;!important, padding-top: 0px;!important} a.metascore_w { text-decoration: none!important}.metascore_anchor:hover { text-decoration: none!important}.metascore_w:hover { text-decoration: none!important}span.metascore_w, a.metascore_w { display: inline-block}.metascore_w.xlarge, .metascore_w.xl { font-size: 42px}.metascore_w.large, .metascore_w.lrg { font-size: 25px}.m .metascore_w.medium, .m .metascore_w.med { font-size: 19px}.metascore_w.med_small { font-size: 14px}.metascore_w.small, .metascore_w.sm { font-size: 12px}.metascore_w.tiny { height: 1.9em; font-size: 11px; line-height: 1.9em;}.metascore_w.user { border-radius: 55%; color: #fff;}.metascore_w.user.small, .metascore_w.user.sm { font-size: 11px}.metascore_w.tbd, .metascore_w.score_tbd { color: #000!important; background-color: #ccc;}.metascore_w.tbd.hide_tbd, .metascore_w.score_tbd.hide_tbd { visibility: hidden}.metascore_w.tbd.no_tbd, .metascore_w.score_tbd.no_tbd { display: none}.metascore_w.noscore::before, .metascore_w.score_noscore::before { content: '\2022\2022\2022'}.metascore_w.noscore, .metascore_w.score_noscore { color: #fff!important; background-color: #ccc;}.metascore_w.rip, .metascore_w.score_rip { border-radius: 4px; color: #fff!important; background-color: #999;}.metascore_w.negative, .metascore_w.score_terrible, .metascore_w.score_unfavorable { background-color: #f00}.metascore_w.mixed, .metascore_w.forty, .metascore_w.game.fifty, .metascore_w.score_mixed { background-color: #fc3}.metascore_w.positive, .metascore_w.sixtyone, .metascore_w.game.seventyfive, .metascore_w.score_favorable, .metascore_w.score_outstanding { background-color: #6c3}.metascore_w.indiv { height: 1.9em; width: 1.9em; font-size: 15px; line-height: 1.9em;}.metascore_w.indiv.large, .metascore_w.indiv.lrg { font-size: 24px}.m .metascore_w.indiv.medium, .m .metascore_w.indiv.med { font-size: 16px}.metascore_w.indiv.small, .metascore_w.indiv.sm { font-size: 11px}.metascore_w.indiv.perfect { padding-right: 1px}.promo_amazon .esite_btn { margin: 3px 0 0 7px;}.esite_amazon { background-color: #fdc354; border: 1px solid #aaa;}.esite_label_wrapper { display:none;}.esite_btn { border-radius: 4px; color: #222; font-size: 12px; height: 40px; line-height: 40px; width: 120px;}";
  757.  
  758. var framesrc = 'data:text/html,';
  759. framesrc += encodeURIComponent('<!DOCTYPE html>\
  760. <html lang="en">\
  761. <head>\
  762. <meta charset="utf-8">\
  763. <title>Metacritic info</title>\
  764. <style>body { margin:0px; padding:0px; background:white; }' + css
  765. +'\
  766. </style>\
  767. <script>\
  768. var failedImages = {};\
  769. function detectCSP(img) {\
  770. if(img.complete && (!img.naturalWidth || !img.naturalHeight)) {\
  771. return true;\
  772. }\
  773. return false;\
  774. }\
  775. function findCSPerrors() {\
  776. var imgs = document.querySelectorAll("img");\
  777. for(var i = 0; i < imgs.length; i++) {\
  778. if(imgs[i].complete && detectCSP(imgs[i])) {\
  779. fixCSP(imgs[i]);\
  780. }\
  781. }\
  782. }\
  783. function fixCSP(img) {\
  784. console.log("Loading image failed. Bypassing CSP...");\
  785. failedImages[img.src] = img;\
  786. parent.postMessage({"mcimessage_loadImg":true, "mcimessage_imgUrl": img.src},"*"); \
  787. }\
  788. function on_load() {\
  789. ('+functions.frame.toString()+')();\
  790. window.setTimeout(findCSPerrors, 500);\
  791. \
  792. }\
  793. </script>\
  794. </head>\
  795. <body onload="on_load();">\
  796. <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">\
  797. <div class="hover_content">'+fixMetacriticURLs(html)+'</div>\
  798. </div>\
  799. </body>\
  800. </html>');
  801.  
  802. var frame = $("<iframe></iframe>").appendTo(div);
  803. frame.attr("id","mciframe123");
  804. frame.attr("src",framesrc);
  805. frame.attr("scrolling","auto");
  806. frame.css({
  807. width: 400,
  808. height: 170,
  809. border: "none"
  810. });
  811.  
  812. window.setTimeout(function() {
  813. if(!frame_status) { // Loading frame content failed.
  814. // Directly inject the html without an iframe (this may break the site or the metacritic)
  815. console.log("Loading iframe content failed. Injecting directly.");
  816. $("head").append("<style>"+css+"</style>");
  817. var noframe = $('<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">\
  818. <div class="hover_content">'+fixMetacriticURLs(html)+'</div>\
  819. </div>');
  820. frame.replaceWith(noframe);
  821. }
  822.  
  823. },2000);
  824.  
  825. functions.parent();
  826.  
  827. var sub = $("<div></div>").appendTo(div);
  828. $('<time style="color:#b6b6b6; font-size: 11px;" datetime="'+time+'" title="'+time.toLocaleTimeString()+" "+time.toLocaleDateString()+'">'+minutesSince(time)+'</time>').appendTo(sub);
  829. $('<a style="color:#b6b6b6; font-size: 11px;" target="_blank" href="'+url+'" title="Open Metacritic">'+decodeURI(url.replace("https://www.","@"))+'</a>').appendTo(sub);
  830. $('<span title="Hide me" style="cursor:pointer; float:right; color:#b6b6b6; font-size: 11px; padding-left:5px;">&#10062;</span>').appendTo(sub).click(function() {
  831. document.body.removeChild(this.parentNode.parentNode);
  832. });
  833.  
  834. $('<span title="Assist us: This is the correct entry!" style="cursor:pointer; float:right; color:green; font-size: 11px;">&check;</span>').data("url", url).appendTo(sub).click(function() {
  835. var docurl = document.location.href;
  836. var metaurl = $(this).data("url");
  837. addToMap(docurl, metaurl).then(function(r) {
  838. balloonAlert("Thanks for your submission!\n\nSaved as a correct entry.\n\n"+r[0]+"\n"+r[1], 6000, "Success");
  839. });
  840. });
  841. $('<span title="Assist us: 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() {
  842. if(!confirm("This is NOT the correct entry!\n\nAdd to blacklist?")) return;
  843. var docurl = document.location.href;
  844. var metaurl = $(this).data("url");
  845. addToBlacklist(docurl,metaurl).then(function(r) {
  846. balloonAlert("Thanks for your submission!\n\nSaved to blacklist.\n\n"+r[0]+"\n"+r[1], 6000, "Success");
  847. });
  848.  
  849.  
  850. // Open search
  851. metacritic_searchcontainer(null, current.searchTerm);
  852. metacritic_search(null, current.searchTerm);
  853. });
  854.  
  855.  
  856.  
  857. },
  858. // On error i.e. no result on metacritic.com
  859. async function(html, time) {
  860. // Make search available
  861. metacritic_waitForHotkeys();
  862.  
  863. var handleresponse = await async function(response, fromcache) {
  864. var data;
  865. var multiple = false;
  866. try {
  867. data = JSON.parse(response.responseText);
  868. } catch(e) {
  869. console.log("Error in JSON 05: search_term="+current.searchTerm);
  870. console.log(e);
  871. }
  872. if(data && data.autoComplete && data.autoComplete.results && data.autoComplete.results.length) {
  873. // Remove data with wrong type
  874. data.autoComplete = data.autoComplete.results;
  875.  
  876. var newdata = [];
  877. data.autoComplete.forEach(function(result) {
  878. if(metacritic2searchType(result.refType) == current.type) {
  879. newdata.push(result);
  880. }
  881. });
  882. data.autoComplete = newdata;
  883. if(data.autoComplete.length == 0) {
  884. // No results
  885. console.log("No results (after filtering by type) for search_term="+current.searchTerm);
  886. } else if(data.autoComplete.length == 1) {
  887. // One result, let's show it
  888. if(! await isBlacklisted(absoluteMetaURL(data.autoComplete[0].url))) {
  889. metacritic_showHoverInfo(absoluteMetaURL(data.autoComplete[0].url));
  890. return;
  891. }
  892. } else {
  893. // More than one result
  894. multiple = true;
  895. console.log("Multiple results for search_term="+current.searchTerm);
  896. var exactMatches = [];
  897. data.autoComplete.forEach(function(result,i) { // Try to find the correct result by matching the search term to exactly one movie title
  898. if(current.searchTerm == result.name) {
  899. exactMatches.push(result);
  900. }
  901. });
  902. if(exactMatches.length == 1) {
  903. // Only one exact match, let's show it
  904. console.log("Only one exact match for search_term="+current.searchTerm);
  905. if(! await isBlacklisted(absoluteMetaURL(exactMatches[0].url))) {
  906. if( url != absoluteMetaURL(exactMatches[0].url)) {
  907. metacritic_showHoverInfo(absoluteMetaURL(exactMatches[0].url));
  908. } else {
  909. console.log("Loop detected for: "+url);
  910. }
  911. return;
  912. }
  913. }
  914. }
  915. } else {
  916. console.log("No results (at all) for search_term="+current.searchTerm);
  917. }
  918. // HERE: multiple results or no result. The user may type "meta" now
  919. if(multiple) {
  920. balloonAlert("Multiple metacritic results. Type &#34;meta&#34; for manual search.", 10000, false, {bottom: 5, top:"auto", maxWidth: 400, paddingRight: 5}, metacritic_searchcontainer);
  921. }
  922. };
  923. var cache = JSON.parse(await GM.getValue("autosearchcache","{}"));
  924. for(var prop in cache) {
  925. // Delete cached values, that are older than 2 hours
  926. if((new Date()).getTime() - (new Date(cache[prop].time)).getTime() > 2*60*60*1000) {
  927. delete cache[prop];
  928. }
  929. }
  930.  
  931. if(current.type == "music") {
  932. current.searchTerm = current.data[0];
  933. } else {
  934. current.searchTerm = current.data.join(" ");
  935. }
  936. if(current.searchTerm in cache) {
  937. handleresponse(cache[current.searchTerm], true);
  938. } else {
  939. GM.xmlHttpRequest({
  940. method: "POST",
  941. url: baseURL_autosearch,
  942. data: "search_term="+encodeURIComponent(current.searchTerm)+"&image_size=98&search_each=1&sort_type=popular",
  943. headers: {
  944. "Referer" : url,
  945. "Content-Type" : "application/x-www-form-urlencoded; charset=UTF-8",
  946. "Host" : "www.metacritic.com",
  947. "User-Agent" : "MetacriticUserscript Mozilla/5.0 (Android 4.4; Mobile; rv:41.0) Gecko/41.0 Firefox/41.0",
  948. "X-Requested-With" : "XMLHttpRequest"
  949. },
  950. onload: async function(response) {
  951. response = {
  952. time : (new Date()).toJSON(),
  953. responseText : response.responseText,
  954. };
  955. cache[current.searchTerm] = response;
  956. await GM.setValue("autosearchcache",JSON.stringify(cache));
  957. handleresponse(response, false);
  958. }
  959. });
  960. }
  961. });
  962. }
  963.  
  964. function metacritic_waitForHotkeys() {
  965. listenForHotkeys("meta",metacritic_searchcontainer);
  966. }
  967.  
  968. function metacritic_searchcontainer(ev, query) {
  969. if(!query) {
  970. if(current.type == "music") {
  971. query = current.data[0];
  972. } else {
  973. query = current.data.join(" ");
  974. }
  975. }
  976. $("#mcdiv123").remove();
  977. var div = $('<div id="mcdiv123"></div>').appendTo(document.body);
  978. div.css({
  979. position:"fixed",
  980. bottom :0,
  981. left: 0,
  982. minWidth: 300,
  983. maxHeight: "80%",
  984. maxWidth: 640,
  985. overflow:"auto",
  986. backgroundColor: "#fff",
  987. border: "2px solid #bbb",
  988. borderRadius:" 6px",
  989. boxShadow: "0 0 3px 3px rgba(100, 100, 100, 0.2)",
  990. color: "#000",
  991. padding:" 3px",
  992. zIndex: "5010001",
  993. });
  994. var query = $('<input type="text" size="60" id="mcisearchquery" style="background:white;color:black;">').appendTo(div).focus().val(query).on('keypress', function(e) {
  995. var code = e.keyCode || e.which;
  996. if(code == 13) { // Enter key
  997. metacritic_search.call(this,e);
  998. }
  999. });
  1000. $('<button id="mcisearchbutton" style="background:silver;color:black;">').text("Search").appendTo(div).click(metacritic_search);
  1001. }
  1002.  
  1003.  
  1004. function metacritic_search(ev, query) {
  1005. if(!query) { // Use values from search form
  1006. query = $("#mcisearchquery").val();
  1007. }
  1008. var type = searchType2metacritic(current.type);
  1009.  
  1010. var style = document.createElement('style');
  1011. style.type = 'text/css';
  1012. style.innerHTML = CSS;
  1013. document.head.appendChild(style);
  1014.  
  1015. var div = $("#mcdiv123");
  1016. var loader = $('<div style="width:20px; height:20px;" class="grespinner"></div>').appendTo($("#mcisearchbutton"));
  1017.  
  1018. var url = baseURL_search.replace("{type}",encodeURIComponent(type)).replace("{query}",encodeURIComponent(query));
  1019.  
  1020. metacritic_searchResults(url,
  1021. // On success
  1022. function(results, time) {
  1023. loader.remove();
  1024.  
  1025. var accept = function(ev) {
  1026. var a = $(this.parentNode).find("a[href*='metacritic.com']");
  1027. var metaurl = a.attr("href");
  1028.  
  1029. var docurl = document.location.href;
  1030.  
  1031. addToMap(docurl,metaurl).then(function() {
  1032. metacritic_showHoverInfo(metaurl);
  1033. });
  1034. };
  1035. var denyAll = function(ev) {
  1036. var urls = [];
  1037. var docurl = document.location.href;
  1038. $("#mcdiv123searchresults").find("div.result a[href*='metacritic.com']").each(function() {
  1039. addToBlacklist(docurl, this.href);
  1040. });
  1041. };
  1042.  
  1043. var resultdiv = $("#mcdiv123searchresults").length?$("#mcdiv123searchresults").html(""):$('<div id="mcdiv123searchresults"></div>').css("max-width","95%").appendTo(div);
  1044. results.forEach(function(html) {
  1045. var singleresult = $('<div class="result"></div>').html(fixMetacriticURLs(html)+'<div style="clear:left"></div>').appendTo(resultdiv);
  1046. $('<span title="Assist us: This is the correct entry!" style="cursor:pointer; color:green; font-size: 13px;">&check;</span>').prependTo(singleresult).click(accept);
  1047. });
  1048. resultdiv.find(".metascore_w.album").removeClass("album"); // Remove some classes
  1049.  
  1050. var sub = $("<div></div>").appendTo(div);
  1051. $('<time style="color:#b6b6b6; font-size: 11px;" datetime="'+time+'" title="'+time.toLocaleDateString()+'">'+minutesSince(time)+'</time>').appendTo(sub);
  1052. $('<a style="color:#b6b6b6; font-size: 11px;" target="_blank" href="'+url+'" title="Open Metacritic">'+decodeURI(url.replace("https://www.","@"))+'</a>').appendTo(sub);
  1053. $('<span title="Hide me" style="cursor:pointer; float:right; color:#b6b6b6; font-size: 11px;">&#10062;</span>').appendTo(sub).click(function() {
  1054. document.body.removeChild(this.parentNode.parentNode);
  1055. });
  1056. $('<span title="Assist us: 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()});
  1057. },
  1058. // On error i.e. no results
  1059. function(results, time) {
  1060. loader.remove();
  1061. var resultdiv = $("#mcdiv123searchresults").length?$("#mcdiv123searchresults").html(""):$('<div id="mcdiv123searchresults"></div>').appendTo(div);
  1062. resultdiv.html("No search results.");
  1063.  
  1064. var sub = $("<div></div>").appendTo(div);
  1065. $('<time style="color:#b6b6b6; font-size: 11px;" datetime="'+time+'" title="'+time.toLocaleDateString()+'">'+minutesSince(time)+'</time>').appendTo(sub);
  1066. $('<a style="color:#b6b6b6; font-size: 11px;" target="_blank" href="'+url+'" title="Open Metacritic">'+decodeURI(url.replace("https://www.","@"))+'</a>').appendTo(sub);
  1067. $('<span title="Hide me" style="cursor:pointer; float:right; color:#b6b6b6; font-size: 11px;">&#10062;</span>').appendTo(sub).click(function() {
  1068. document.body.removeChild(this.parentNode.parentNode);
  1069. });
  1070.  
  1071. }
  1072. );
  1073. }
  1074.  
  1075. var current = {
  1076. url : null,
  1077. type : null,
  1078. data : null, // Array of raw search keys
  1079. searchTerm : null
  1080. };
  1081.  
  1082.  
  1083. async function showURL(url) {
  1084. if(! await isBlacklisted(url)) {
  1085. var docurl = document.location.host.replace(/^www\./,"") + document.location.pathname + document.location.search;
  1086. docurl = filterUniversalUrl(docurl);
  1087. metacritic_showHoverInfo(url, docurl);
  1088. } else {
  1089. console.log(url +" is blacklisted!");
  1090. }
  1091. }
  1092.  
  1093.  
  1094. var metacritic = {
  1095. "mapped" : function metacritic_mapped(url, type) {
  1096. // url was in the map/whitelist
  1097. current.data = [url]
  1098. current.url = url;
  1099. current.type = type;
  1100. current.searchTerm = url;
  1101. showURL(url);
  1102. },
  1103. "music" : function metacritic_music(artistname, albumname) {
  1104. current.data = [albumname.trim(),artistname.trim()]
  1105. artistname = name2metacritic(artistname);
  1106. albumname = albumname.replace("&"," ");
  1107. albumname = name2metacritic(albumname);
  1108. var url = baseURL_music + albumname + "/" + artistname;
  1109. current.url = url;
  1110. current.type = "music";
  1111. current.searchTerm = albumname + "/" + artistname;
  1112. showURL(url);
  1113. },
  1114. "movie" : function metacritic_movie(moviename) {
  1115. current.data = [moviename.trim()]
  1116. moviename = name2metacritic(moviename);
  1117. var url = baseURL_movie + moviename;
  1118. current.url = url;
  1119. current.type = "movie";
  1120. current.searchTerm = moviename;
  1121. showURL(url);
  1122. },
  1123. "tv" : function metacritic_tv(seriesname) {
  1124. current.data = [seriesname.trim()]
  1125. seriesname = name2metacritic(seriesname);
  1126. var url = baseURL_tv + seriesname;
  1127. current.url = url;
  1128. current.type = "tv";
  1129. current.searchTerm = seriesname;
  1130. showURL(url);
  1131. },
  1132. "pcgame" : function metacritic_pcgame(gamename) {
  1133. current.data = [gamename.trim()]
  1134. gamename = name2metacritic(gamename);
  1135. var url = baseURL_pcgame + gamename;
  1136. current.url = url;
  1137. current.type = "pcgame";
  1138. current.searchTerm = gamename;
  1139. showURL(url);
  1140. },
  1141. "ps4game" : function metacritic_ps4game(gamename) {
  1142. current.data = [gamename.trim()]
  1143. gamename = name2metacritic(gamename);
  1144. var url = baseURL_ps4 + gamename;
  1145. current.url = url;
  1146. current.type = "ps4game";
  1147. current.searchTerm = gamename;
  1148. showURL(url);
  1149. },
  1150. "xonegame" : function metacritic_xonegame(gamename) {
  1151. current.data = [gamename.trim()]
  1152. gamename = name2metacritic(gamename);
  1153. var url = baseURL_xone + gamename;
  1154. current.url = url;
  1155. current.type = "xonegame";
  1156. current.searchTerm = gamename;
  1157. showURL(url);
  1158. }
  1159. };
  1160.  
  1161.  
  1162. var Always = () => true;
  1163. var sites = {
  1164. 'bandcamp' : {
  1165. host : ["bandcamp.com"],
  1166. condition : function() {
  1167. return unsafeWindow.TralbumData
  1168. },
  1169. products : [{
  1170. condition : Always,
  1171. type : "music",
  1172. data : () => [unsafeWindow.TralbumData.artist, unsafeWindow.TralbumData.current.title]
  1173. }]
  1174. },
  1175. 'itunes' : {
  1176. host : ["itunes.apple.com"],
  1177. condition : Always,
  1178. products : [{
  1179. condition : () => ~document.location.href.indexOf("/album/"),
  1180. type : "music",
  1181. data : () => function() {
  1182. let [title, artistObj] = parseLDJSON(["name", "byArtist"], (j) => (j["@type"] == "MusicAlbum"))
  1183. return [artistObj["name"], title]
  1184. }
  1185. }]
  1186. },
  1187. 'googleplay' : {
  1188. host : ["play.google.com"],
  1189. condition : Always,
  1190. products : [
  1191. {
  1192. condition : () => ~document.location.href.indexOf("/album/"),
  1193. type : "music",
  1194. data : () => [document.querySelector('[itemprop="byArtist"] meta[itemprop="name"]').content, document.querySelector('[itemtype="https://schema.org/MusicAlbum"] meta[itemprop="name"]').content]
  1195. },
  1196. {
  1197. condition : () => ~document.location.href.indexOf("/movies/details/"),
  1198. type : "movie",
  1199. data : () => document.querySelector("*[itemprop=name]").textContent
  1200. }
  1201. ]
  1202. },
  1203. 'imdb' : {
  1204. host : ["imdb.com"],
  1205. condition : () => !~document.location.pathname.indexOf("/mediaviewer") && !~document.location.pathname.indexOf("/mediaindex") && !~document.location.pathname.indexOf("/videoplayer"),
  1206. products : [
  1207. {
  1208. condition : function() {
  1209. var e = document.querySelector("meta[property='og:type']");
  1210. if(e) {
  1211. return e.content == "video.movie"
  1212. }
  1213. return false;
  1214. },
  1215. type : "movie",
  1216. data : function() {
  1217. if(document.querySelector(".originalTitle") && document.querySelector(".title_wrapper h1")) { // Use English title 2018
  1218. return document.querySelector(".title_wrapper h1").firstChild.data.trim();
  1219. } else if(document.querySelector('script[type="application/ld+json"]')) { // Use original language title
  1220. return parseLDJSON("name");
  1221. } else if(document.querySelector("h1[itemprop=name]")) { // Movie homepage (New design 2015-12)
  1222. return document.querySelector("h1[itemprop=name]").firstChild.textContent.trim();
  1223. } else if(document.querySelector("*[itemprop=name] a") && document.querySelector("*[itemprop=name] a").firstChild.data) { // Subpage of a move
  1224. return document.querySelector("*[itemprop=name] a").firstChild.data.trim();
  1225. } else if(document.querySelector(".title-extra[itemprop=name]")) { // Movie homepage: sub-/alternative-/original title
  1226. return document.querySelector(".title-extra[itemprop=name]").firstChild.textContent.replace(/\"/g,"").trim();
  1227. } else { // Movie homepage (old design)
  1228. return document.querySelector("*[itemprop=name]").firstChild.textContent.trim();
  1229. }
  1230. }
  1231. },
  1232. {
  1233. condition : function() {
  1234. var e = document.querySelector("meta[property='og:type']");
  1235. if(e) {
  1236. return e.content == "video.tv_show"
  1237. }
  1238. return false;
  1239. },
  1240. type : "tv",
  1241. data : function() {
  1242. var year = null;
  1243. if(document.querySelector("*[itemprop=name]")) {
  1244. return document.querySelector("*[itemprop=name]").textContent;
  1245. } else {
  1246. var jsonld = JSON.parse(document.querySelector('script[type="application/ld+json"]').innerText);
  1247. return jsonld["name"];
  1248. }
  1249. }
  1250. }
  1251. ]
  1252. },
  1253. 'steam' : {
  1254. host : ["store.steampowered.com"],
  1255. condition : () => document.querySelector("*[itemprop=name]"),
  1256. products : [{
  1257. condition : Always,
  1258. type : "pcgame",
  1259. data : () => document.querySelector("*[itemprop=name]").textContent
  1260. }]
  1261. },
  1262. 'tv.com' : {
  1263. host : ["www.tv.com"],
  1264. condition : () => document.querySelector("meta[property='og:type']"),
  1265. products : [{
  1266. condition : () => document.querySelector("meta[property='og:type']").content == "tv_show" && document.querySelector("h1[data-name]"),
  1267. type : "tv",
  1268. data : () => document.querySelector("h1[data-name]").dataset.name
  1269. }]
  1270. },
  1271. 'rottentomatoes' : {
  1272. host : ["www.rottentomatoes.com"],
  1273. condition : Always,
  1274. products : [{
  1275. condition : () => document.location.pathname.startsWith("/m/"),
  1276. type : "movie",
  1277. data : () => document.querySelector("h1").firstChild.textContent
  1278. },
  1279. {
  1280. condition : () => document.location.pathname.startsWith("/tv/") ,
  1281. type : "tv",
  1282. data : () => unsafeWindow.BK.TvSeriesTitle
  1283. }
  1284. ]
  1285. },
  1286. 'serienjunkies' : {
  1287. host : ["www.serienjunkies.de"],
  1288. condition : Always,
  1289. products : [{
  1290. condition : () => Always,
  1291. type : "tv",
  1292. data : () => parseLDJSON("name", (j) => (j["@type"] == "TVSeries"))
  1293. }]
  1294. },
  1295. 'gamespot' : {
  1296. host : ["gamespot.com"],
  1297. condition : () => document.querySelector("[itemprop=device]"),
  1298. products : [
  1299. {
  1300. condition : () => ~$("[itemprop=device]").text().indexOf("PC"),
  1301. type : "pcgame",
  1302. data : () => parseLDJSON("name", (j) => (j["@type"] == "VideoGame"))
  1303. },
  1304. {
  1305. condition : () => ~$("[itemprop=device]").text().indexOf("PS4"),
  1306. type : "ps4game",
  1307. data : () => parseLDJSON("name", (j) => (j["@type"] == "VideoGame"))
  1308. },
  1309. {
  1310. condition : () => ~$("[itemprop=device]").text().indexOf("XONE"),
  1311. type : "xonegame",
  1312. data : () => parseLDJSON("name", (j) => (j["@type"] == "VideoGame"))
  1313. }
  1314. ]
  1315. },
  1316. 'amazon' : {
  1317. host : ["amazon."],
  1318. condition : Always,
  1319. products : [
  1320. {
  1321. condition : () => document.location.hostname == "music.amazon.com" && document.location.pathname.startsWith("/albums/") && document.querySelector(".viewTitle"), // "Amazon Music Unlimited" page
  1322. type : "music",
  1323. data : function() {
  1324. var artist = document.querySelector(".artistLink").textContent.trim();
  1325. var title = document.querySelector(".viewTitle").textContent.trim();
  1326. title = title.replace(/\[([^\]]*)\]/g,"").trim(); // Remove [brackets] and their content
  1327. if(artist && title) {
  1328. return [artist, title];
  1329. }
  1330. return false;
  1331. }
  1332. },
  1333. {
  1334. condition : function() { // "Normal amazon" page
  1335. try {
  1336. if(document.querySelector(".nav-categ-image").alt.toLowerCase().indexOf("musi") != -1) {
  1337. return true;
  1338. }
  1339. } catch(e) {}
  1340. var music = ["Music","Musique","Musik","Música","Musica","音楽"];
  1341. return music.some(function(s) {
  1342. if(~document.title.indexOf(s)) {
  1343. return true;
  1344. } else {
  1345. return false;
  1346. }
  1347. });
  1348. },
  1349. type : "music",
  1350. data : function() {
  1351. var artist = document.querySelector("#ProductInfoArtistLink").textContent.trim();
  1352. var title = document.querySelector("#dmusicProductTitle_feature_div").textContent.trim();
  1353. title = title.replace(/\[([^\]]*)\]/g,"").trim(); // Remove [brackets] and their content
  1354. return [artist, title];
  1355. }
  1356. },
  1357. {
  1358. condition : () => (document.querySelector('[data-automation-id=title]') && (document.getElementsByClassName("av-season-single").length || document.querySelector('[data-automation-id="num-of-seasons-badge"]'))),
  1359. type : "tv",
  1360. data : () => document.querySelector('[data-automation-id=title]').textContent.trim()
  1361. },
  1362. {
  1363. condition : () => document.querySelector('[data-automation-id=title]'),
  1364. type : "movie",
  1365. data : () => document.querySelector('[data-automation-id=title]').textContent.trim()
  1366. }
  1367. ]
  1368. },
  1369. 'BoxOfficeMojo' : {
  1370. host : ["boxofficemojo.com"],
  1371. condition : () => ~document.location.search.indexOf("id="),
  1372. products : [{
  1373. condition : () => document.querySelector("#body table:nth-child(2) tr:first-child b"),
  1374. type : "movie",
  1375. data : () => document.querySelector("#body table:nth-child(2) tr:first-child b").firstChild.data
  1376. }]
  1377. },
  1378. 'AllMovie' : {
  1379. host : ["allmovie.com"],
  1380. condition : () => document.querySelector("h2[itemprop=name].movie-title"),
  1381. products : [{
  1382. condition : () => document.querySelector("h2[itemprop=name].movie-title"),
  1383. type : "movie",
  1384. data : () => document.querySelector("h2[itemprop=name].movie-title").firstChild.data.trim()
  1385. }]
  1386. },
  1387. 'en.wikipedia' : {
  1388. host : ["en.wikipedia.org"],
  1389. condition : Always,
  1390. products : [{
  1391. condition : function() {
  1392. if(!document.querySelector(".infobox .summary")) {
  1393. return false;
  1394. }
  1395. var r = /\d\d\d\d films/;
  1396. return $("#catlinks a").filter((i,e) => e.firstChild.data.match(r)).length;
  1397. },
  1398. type : "movie",
  1399. data : () => document.querySelector(".infobox .summary").firstChild.data
  1400. },
  1401. {
  1402. condition : function() {
  1403. if(!document.querySelector(".infobox .summary")) {
  1404. return false;
  1405. }
  1406. var r = /television series/;
  1407. return $("#catlinks a").filter((i,e) => e.firstChild.data.match(r)).length;
  1408. },
  1409. type : "tv",
  1410. data : () => document.querySelector(".infobox .summary").firstChild.data
  1411. }]
  1412. },
  1413. 'movies.com' : {
  1414. host : ["movies.com"],
  1415. condition : () => document.querySelector("meta[property='og:title']"),
  1416. products : [{
  1417. condition : Always,
  1418. type : "movie",
  1419. data : () => document.querySelector("meta[property='og:title']").content
  1420. }]
  1421. },
  1422. 'themoviedb' : {
  1423. host : ["themoviedb.org"],
  1424. condition : () => document.querySelector("meta[property='og:type']"),
  1425. products : [{
  1426. condition : () => document.querySelector("meta[property='og:type']").content == "movie",
  1427. type : "movie",
  1428. data : () => document.querySelector("meta[property='og:title']").content
  1429. },
  1430. {
  1431. condition : () => document.querySelector("meta[property='og:type']").content == "tv" || document.querySelector("meta[property='og:type']").content == "tv_series",
  1432. type : "tv",
  1433. data : () => document.querySelector("meta[property='og:title']").content
  1434. }]
  1435. },
  1436. 'letterboxd' : {
  1437. host : ["letterboxd.com"],
  1438. condition : () => unsafeWindow.filmData && "name" in unsafeWindow.filmData,
  1439. products : [{
  1440. condition : Always,
  1441. type : "movie",
  1442. data : () => unsafeWindow.filmData.name
  1443. }]
  1444. },
  1445. 'TVmaze' : {
  1446. host : ["tvmaze.com"],
  1447. condition : () => document.querySelector("h1"),
  1448. products : [{
  1449. condition : Always,
  1450. type : "tv",
  1451. data : () => document.querySelector("h1").firstChild.data
  1452. }]
  1453. },
  1454. 'TVGuide' : {
  1455. host : ["tvguide.com"],
  1456. condition : Always,
  1457. products : [{
  1458. condition : () => document.location.pathname.startsWith("/tvshows/"),
  1459. type : "tv",
  1460. data : function() {
  1461. if(document.querySelector("meta[itemprop=name]")) {
  1462. return document.querySelector("meta[itemprop=name]").content;
  1463. } else {
  1464. return document.querySelector("meta[property='og:title']").content.split("|")[0];
  1465. }
  1466. }
  1467. }]
  1468. },
  1469. 'followshows' : {
  1470. host : ["followshows.com"],
  1471. condition : Always,
  1472. products : [{
  1473. condition : () => document.querySelector("meta[property='og:type']").content == "video.tv_show",
  1474. type : "tv",
  1475. data : () => document.querySelector("meta[property='og:title']").content
  1476. }]
  1477. },
  1478. 'TheTVDB' : {
  1479. host : ["thetvdb.com"],
  1480. condition : Always,
  1481. products : [{
  1482. condition : () => document.location.pathname.startsWith("/series/") || ~document.location.search.indexOf("tab=series"),
  1483. type : "tv",
  1484. data : () => document.getElementById("series_title").firstChild.data.trim()
  1485. }]
  1486. },
  1487. 'ConsequenceOfSound' : {
  1488. host : ["consequenceofsound.net"],
  1489. condition : () => document.querySelector("#main-content .review-summary"),
  1490. products : [{
  1491. condition : () => document.title.match(/(.+?)\s+\u2013\s+(.+?) \| Album Review/),
  1492. type : "music",
  1493. data : function() {
  1494. let m = document.title.match(/(.+?)\s+\u2013\s+(.+?) \| Album Review/)
  1495. return [m[1], m[2]]
  1496. }
  1497. }]
  1498. },
  1499. 'Pitchfork' : {
  1500. host : ["pitchfork.com"],
  1501. condition : () => ~document.location.href.indexOf("/reviews/albums/"),
  1502. products : [{
  1503. condition : () => document.querySelector(".single-album-tombstone"),
  1504. type : "music",
  1505. data : function() {
  1506. var artist, album;
  1507. if(document.querySelector(".single-album-tombstone .artists")) {
  1508. artist = document.querySelector(".single-album-tombstone .artists").innerText.trim();
  1509. } else if(document.querySelector(".single-album-tombstone .artist-list")) {
  1510. artist = document.querySelector(".single-album-tombstone .artist-list").innerText.trim();
  1511. }
  1512. if(document.querySelector(".single-album-tombstone h1.review-title")) {
  1513. album = document.querySelector(".single-album-tombstone h1.review-title").innerText.trim();
  1514. } else if(document.querySelector(".single-album-tombstone h1")) {
  1515. album = document.querySelector(".single-album-tombstone h1").innerText.trim();
  1516. }
  1517.  
  1518. return [artist, album];
  1519. }
  1520. }]
  1521. },
  1522. 'Last.fm' : {
  1523. host : ["last.fm"],
  1524. condition : () => document.querySelector("*[data-page-resource-type]") && document.querySelector("*[data-page-resource-type]").dataset.pageResourceType == "album",
  1525. products : [{
  1526. condition : () => document.querySelector("*[data-page-resource-type]").dataset.pageResourceName,
  1527. type : "music",
  1528. data : function() {
  1529. var artist = document.querySelector("*[data-page-resource-type]").dataset.pageResourceArtistName;
  1530. var album = document.querySelector("*[data-page-resource-type]").dataset.pageResourceName;
  1531. return [artist, album];
  1532. }
  1533. }]
  1534. },
  1535. 'TVNfo' : {
  1536. host : ["tvnfo.com"],
  1537. condition : () => document.querySelector("#tvsign"),
  1538. products : [{
  1539. condition : Always,
  1540. type : "tv",
  1541. data : () => document.querySelector(".heading h1").textContent.trim()
  1542. }]
  1543. },
  1544. 'rateyourmusic' : {
  1545. host : ["rateyourmusic.com"],
  1546. condition : () => document.querySelector("meta[property='og:type']"),
  1547. products : [{
  1548. condition : () => document.querySelector("meta[property='og:type']").content == "music.album",
  1549. type : "music",
  1550. data : function() {
  1551. var artist = document.querySelector(".section_main_info .artist").innerText.trim();
  1552. var album = document.querySelector(".section_main_info .album_title").innerText.trim();
  1553. return [artist, album];
  1554. }
  1555. }]
  1556. },
  1557. 'spotify_webplayer' : {
  1558. host : ["open.spotify.com"],
  1559. condition : Always,
  1560. products : [{
  1561. condition : () => document.querySelector("#main .main-view-container .content.album"),
  1562. type : "music",
  1563. data : function() {
  1564. var artist = document.querySelector("#main .media-bd div a[href*='artist']").textContent;
  1565. var album = document.querySelector("#main .media-bd h2").textContent;
  1566. return [artist, album];
  1567. }
  1568. },
  1569. {
  1570. condition : () => document.location.pathname.startsWith("/album/") && document.querySelector("meta[property='og:type']").content == "music.album",
  1571. type : "music",
  1572. data : function() {
  1573. var artist = "";
  1574. var album = document.querySelector("meta[property='og:title']").content;
  1575. return [artist, album];
  1576. }
  1577. }]
  1578. },
  1579. 'spotify' : {
  1580. host : ["play.spotify.com"],
  1581. condition : Always,
  1582. products : [{
  1583. condition : () => document.location.pathname.startsWith("/album/"),
  1584. type : "music",
  1585. data : function() {
  1586. var artist = document.querySelector(".context_landing p.secondary-title").textContent;
  1587. var album = document.querySelector(".context_landing p.primary-title").textContent;
  1588. return [artist, album];
  1589. }
  1590. }]
  1591. },
  1592. 'nme' : {
  1593. host : ["nme.com"],
  1594. condition : () => document.location.pathname.startsWith("/reviews/"),
  1595. products : [
  1596. {
  1597. condition : () => document.location.pathname.startsWith("/reviews/movie/"),
  1598. type : "movie",
  1599. data : function() {
  1600. try {
  1601. return document.querySelector(".title-primary").textContent.match(/‘(.+?)’/)[1];
  1602. } catch(e) {
  1603. return document.querySelector("h1").textContent.match(/:\s*(.+)/)[1].trim();
  1604. }
  1605. }
  1606. },
  1607. {
  1608. condition : () => document.location.pathname.startsWith("/reviews/album/"),
  1609. type : "music",
  1610. data : () => document.querySelector(".title-primary").textContent.match(/\s*(.+?)\s*.\s*‘(.+?)’/).slice(1)
  1611. }]
  1612. },
  1613. 'albumoftheyear' : {
  1614. host : ["albumoftheyear.org"],
  1615. condition : Always,
  1616. products : [{
  1617. condition : () => document.location.pathname.startsWith("/album/"),
  1618. type : "music",
  1619. data : function() {
  1620. var artist = document.querySelector("*[itemprop=byArtist] *[itemprop=name]").textContent;
  1621. var album = document.querySelector(".albumTitle *[itemprop=name]").textContent;
  1622. return [artist, album];
  1623. }
  1624. }]
  1625. },
  1626. 'itunes' : {
  1627. host : ["itunes.apple.com"],
  1628. condition : Always,
  1629. products : [{
  1630. condition : () => ~document.location.href.indexOf("/movie/"),
  1631. type : "movie",
  1632. data : () => parseLDJSON("name", (j) => (j["@type"] == "Movie"))
  1633. },
  1634. {
  1635. condition : () => ~document.location.href.indexOf("/tv-season/"),
  1636. type : "tv",
  1637. data : function() {
  1638. var name = parseLDJSON("name", (j) => (j["@type"] == "TVSeries"));
  1639. if(~name.indexOf(", Season")) {
  1640. name = name.split(", Season")[0];
  1641. }
  1642. return name;
  1643. }
  1644. },
  1645. {
  1646. condition : () => ~document.location.href.indexOf("/album/"),
  1647. type : "music",
  1648. data : function() {
  1649. var ld = parseLDJSON(["name","byArtist"], (j) => (j["@type"] == "MusicAlbum"));
  1650. var album = ld[0];
  1651. var artist = ld[1]["name"]
  1652. return [artist, album];
  1653. }
  1654. }]
  1655. },
  1656. 'epguides' : {
  1657. host : ['epguides.com'],
  1658. condition : () => document.getElementById('TVHeader'),
  1659. products : [{
  1660. condition : () => document.getElementById('TVHeader') && document.querySelector('body>div#header h1'),
  1661. type : 'tv',
  1662. data : () => document.querySelector('body>div#header h1').textContent.trim()
  1663. }]
  1664. },
  1665. 'ShareTV' : {
  1666. host : ['sharetv.com'],
  1667. condition : () => document.location.pathname.startsWith("/shows/"),
  1668. products : [{
  1669. condition : () => document.location.pathname.split("/").length === 3 && document.querySelector("meta[property='og:title']"),
  1670. type : 'tv',
  1671. data : () => document.querySelector("meta[property='og:title']").content
  1672. }]
  1673. },
  1674.  
  1675. };
  1676.  
  1677.  
  1678. async function main() {
  1679.  
  1680. var dataFound = false;
  1681.  
  1682. var map = false;
  1683.  
  1684. for(var name in sites) {
  1685. var site = sites[name];
  1686. if(site.host.some(function(e) {return ~this.indexOf(e)}, document.location.hostname) && site.condition()) {
  1687. for(var i = 0; i < site.products.length; i++) {
  1688. if(site.products[i].condition()) {
  1689. // Check map for a match
  1690. if(map === false) {
  1691. map = JSON.parse(await GM.getValue("map","{}"));
  1692. }
  1693. var docurl = document.location.host.replace(/^www\./,"") + document.location.pathname + document.location.search;
  1694. docurl = filterUniversalUrl(docurl);
  1695. if(docurl in map) {
  1696. // Found in map, show result
  1697. var metaurl = map[docurl];
  1698. metacritic["mapped"].apply(undefined, [absoluteMetaURL(metaurl), site.products[i].type]);
  1699. break;
  1700. }
  1701. // Try to retrieve item name from page
  1702. var data;
  1703. try {
  1704. data = site.products[i].data();
  1705. } catch(e) {
  1706. data = false;
  1707. console.log(e);
  1708. }
  1709. if(data) {
  1710. metacritic[site.products[i].type].apply(undefined, Array.isArray(data)?data:[data]);
  1711. dataFound = true;
  1712. }
  1713. break;
  1714. }
  1715. }
  1716. break;
  1717. }
  1718. }
  1719. return dataFound;
  1720. }
  1721.  
  1722.  
  1723.  
  1724.  
  1725. (async function() {
  1726. await versionUpdate();
  1727. await main();
  1728. var lastLoc = document.location.href;
  1729. var lastContent = document.body.innerText;
  1730. var lastCounter = 0;
  1731. async function newpage() {
  1732. if(lastContent == document.body.innerText && lastCounter < 15) {
  1733. window.setTimeout(newpage, 500);
  1734. lastCounter++;
  1735. } else {
  1736. lastCounter = 0;
  1737. var re = await main();
  1738. if(!re) { // No page matched or no data found
  1739. window.setTimeout(newpage, 1000);
  1740. }
  1741. }
  1742. }
  1743. window.setInterval(function() {
  1744. if(document.location.href != lastLoc) {
  1745. lastLoc = document.location.href;
  1746. $("#mcdiv123").remove();
  1747.  
  1748. window.setTimeout(newpage,1000);
  1749. }
  1750. },500);
  1751.  
  1752. })();