CSFD Movie Preview

Při najetí myší na odkaz na film se zobrazí náhled jeho profilu.

当前为 2017-12-07 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name CSFD Movie Preview
  3. // @namespace http://csfd.cz
  4. // @description Při najetí myší na odkaz na film se zobrazí náhled jeho profilu.
  5. // @match http://www.csfd.cz/*
  6. // @match https://www.csfd.cz/*
  7. // @exclude http://www.csfd.cz/uzivatel/*/profile-edit/
  8. // @exclude https://www.csfd.cz/uzivatel/*/profile-edit/
  9. // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
  10. // @require http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
  11. // @grant GM_registerMenuCommand
  12. // @grant GM.registerMenuCommand
  13. // @grant GM_xmlhttpRequest
  14. // @grant GM.xmlHttpRequest
  15. // @grant GM_getValue
  16. // @grant GM.getValue
  17. // @grant GM_setValue
  18. // @grant GM.setValue
  19. // @version 2.0
  20. // ==/UserScript==
  21.  
  22. // CHANGES
  23. // -------
  24. // 2.0 - GM_* funkce nahrazeny novými kvůli změně API v GreaseMonkey 4.0+
  25. // 1.3 - upravena hlavička skriptu kvůli přechodu ČSFD na https
  26. // 1.2 - doplněna podpora dynamicky přidávaných odkazů
  27. // 1.1 - výměna jQuery.ajax(), který ve Firefoxu přestal fungovat, za GM_xmlhttpRequest()
  28. // 1.0 - první verze
  29.  
  30. $ = this.jQuery = jQuery.noConflict(true);
  31.  
  32. $('<div id="movie-preview" style="display: none; z-index: 999; width: 400px; background-color: #efefef; padding: 6px; ' +
  33. 'border-radius: 4px; box-shadow: 0 0 10px 4px #777777"><table border="1"><tr><td id="movie-preview-poster" width="120" ' +
  34. 'style="text-align: center"></td><td id="movie-preview-content" style="vertical-align: top; padding-left: 7px"></td>' +
  35. '</tr></table></div>').appendTo('body');
  36.  
  37. var cacheExpires = 7; // days
  38. var doPrefetch = false;
  39.  
  40. var movieBox = $('div#movie-preview');
  41. var movieBoxPoster = movieBox.find('#movie-preview-poster');
  42. var movieBoxContent = movieBox.find('#movie-preview-content');
  43.  
  44. var movieLinkSelector = 'a[href*="/film/"], a[href*="/film.php"]';
  45.  
  46. var thisPageMovieId = parseMovieId(window.location.href);
  47. var currentMovieId = null;
  48. var movies = [];
  49.  
  50. var timerId = -1;
  51.  
  52. // Greasmonkey-only section start
  53.  
  54. if (typeof GM.registerMenuCommand == 'function' && isStorageSupported()) {
  55. doPrefetch = GM.getValue("doPrefetch", false);
  56.  
  57. GM.registerMenuCommand("Přepnout automatické nahrávání náhledů filmů", function() {
  58. doPrefetch = !GM.getValue("doPrefetch", false);
  59.  
  60. GM.setValue("doPrefetch", doPrefetch);
  61.  
  62. alert("Automatické nahrávání náhledů filmů " + (doPrefetch? "zapnuto": "vypnuto") + ".\nZměna nastavení se projeví po obnovení stránky.");
  63. });
  64. }
  65.  
  66. // Greasmonkey-only section end
  67.  
  68. function isStorageSupported() {
  69. return typeof(Storage) !== void(0);
  70. }
  71.  
  72. function parseMovieId(movieURL) {
  73. var match = movieURL.match(/\/film(?:\.php\?|\/)([\d]+)/);
  74. return match && match.length >= 2? 'm' + match[1]: null;
  75. }
  76.  
  77. function getDiffDays(date1, date2) {
  78. return Math.round(Math.abs(date1 - date2) / (1000 * 3600 * 24));
  79. }
  80.  
  81. var storage = isStorageSupported()?
  82. { // local storage
  83. getStoredItem: function(movieURL) {
  84. return localStorage[parseMovieId(movieURL)];
  85. },
  86. setStoredItem: function(movieURL, value) {
  87. try {
  88. localStorage[parseMovieId(movieURL)] = value;
  89. } catch (ex) {
  90. // "Persistent storage maximum size reached" -> remove 10 random items
  91. for (i=0; i < 10; i++) {
  92. var index = Math.floor(Math.random() * localStorage.length);
  93. var key = localStorage.key(index);
  94.  
  95. localStorage.removeItem(key);
  96. }
  97.  
  98. return this.setStoredItem(movieURL, value);
  99. }
  100. },
  101. cleanExpiredData: function() {
  102. var lastCleanup = localStorage["last-cleanup"]? Date.parse(localStorage["last-cleanup"]): new Date(0);
  103.  
  104. // run cleanup only once per day
  105. if (getDiffDays(new Date(), lastCleanup) < 1) return;
  106.  
  107. for(var key in localStorage) {
  108. if (key.match(/m\d+/)) {
  109. var cached = JSON.parse(localStorage[key]);
  110. if (getDiffDays(new Date(), Date.parse(cached.timestamp)) > cacheExpires) {
  111. localStorage.removeItem(key);
  112. }
  113. }
  114. }
  115.  
  116. localStorage["last-cleanup"] = new Date();
  117. }
  118. }:
  119. { // dummy storage
  120. getStoredItem: function(movieURL) {
  121. return null;
  122. },
  123. setStoredItem: function(movieURL, value) {
  124. // noop
  125. },
  126. cleanExpiredData: function() {
  127. // noop
  128. }
  129. };
  130.  
  131. function getMovieBoxPosition(event) {
  132. var boxWidth = movieBox.width() + 10;
  133. var tPosX = boxWidth - event.clientX + 30 > 0? event.pageX + 30: event.pageX - boxWidth - 30;
  134. var tPosY = event.pageY + event.clientY;
  135.  
  136. if (event.clientY > 30) {
  137. var winHeight = $(window).height();
  138. var boxHeight = movieBox.height() > winHeight? winHeight - 60: movieBox.height();
  139. var overflowY = event.clientY + boxHeight - winHeight;
  140. tPosY = overflowY > 0? event.pageY - overflowY - 50: event.pageY - 30;
  141. }
  142.  
  143. return { X: tPosX, Y: tPosY };
  144. }
  145. function showMovieBox(event, profile, rating) {
  146. var poster = profile.find("#poster img");
  147. var title = "<h1 style='text-transform: none'>" + profile.find(".info h1").text().trim() + "</h1>";
  148. var genre = profile.find(".genre");
  149. var origin = profile.find(".origin");
  150. var creators = profile.find(".creators");
  151.  
  152. movieBoxPoster.html('');
  153. movieBoxPoster.append(poster.css('width', 120));
  154. movieBoxPoster.append('<br><h1 style="font-size: 32px">' + rating + '</h1>');
  155.  
  156. movieBoxContent.html('');
  157. movieBoxContent.append(title);
  158. movieBoxContent.append(genre.css('font-weight', 'bold'));
  159. movieBoxContent.append(origin.css('font-weight', 'bold'));
  160. movieBoxContent.append('<br>');
  161. movieBoxContent.append(creators);
  162.  
  163. var pos = getMovieBoxPosition(event);
  164. movieBox.css({ 'position': 'absolute', 'top': pos.Y, 'left': pos.X }).show();
  165. }
  166.  
  167. function getCachedData(movieURL) {
  168. var cached = storage.getStoredItem(movieURL);
  169.  
  170. if (cached) {
  171. cached = JSON.parse(cached);
  172.  
  173. if (getDiffDays(new Date(), Date.parse(cached.timestamp)) <= cacheExpires)
  174. return { "profile": $(cached.profile), "rating": cached.rating };
  175. }
  176.  
  177. return null;
  178. }
  179. function loadMovieBox(movieURL, doneCallback, errorCallback, redirectMovieURL) {
  180. if (!redirectMovieURL) redirectMovieURL = movieURL;
  181.  
  182. console.log("[CSFD Movie Preview] Loading movie page: " + redirectMovieURL);
  183.  
  184. GM.xmlHttpRequest({
  185. method: "GET",
  186. url: redirectMovieURL,
  187. onload: function(response) {
  188. try {
  189. if (false /* TODO: handle redirect */) {
  190. loadMovieBox(movieURL, doneCallback, errorCallback, response.redirect);
  191. } else {
  192. response = $(response.responseText);
  193.  
  194. var profile = response.find("div#profile").html().replace(/[\t\n]+/mg, '');
  195. var rating = response.find("div#rating .average").text().trim();
  196. storage.setStoredItem(movieURL, JSON.stringify({ "profile": profile, "rating": rating, "timestamp": new Date() }));
  197. if (doneCallback) doneCallback($(profile), rating);
  198. }
  199. } catch(ex) {
  200. console.log("[CSFD Movie Preview] Error in AJAX handler: " + ex.message);
  201.  
  202. if (errorCallback) errorCallback();
  203. }
  204. },
  205. onerror: function(response) {
  206. if (errorCallback) errorCallback();
  207. }
  208. });
  209. }
  210.  
  211. function prefetchMovies() {
  212. if (!doPrefetch || !isStorageSupported()) return;
  213.  
  214. var movieURL;
  215.  
  216. if (movieURL = movies.shift()) {
  217. setTimeout(function() {
  218. if (!getCachedData(movieURL)) {
  219. loadMovieBox(movieURL, prefetchMovies, prefetchMovies);
  220. } else {
  221. prefetchMovies();
  222. }
  223. }, 300);
  224. }
  225. }
  226.  
  227. function addHoverHandler(element) {
  228. element.hover(function(event) {
  229. var movieURL = $(this).attr("href").trim();
  230. var movieId = parseMovieId(movieURL);
  231.  
  232. // prevent previews of the movie on its page
  233. if (thisPageMovieId == movieId) return;
  234.  
  235. currentMovieId = movieId;
  236.  
  237. var cached = getCachedData(movieURL);
  238. if (cached) {
  239. showMovieBox(event, cached.profile, cached.rating);
  240. } else {
  241. clearTimeout(timerId);
  242.  
  243. timerId = setTimeout(function() {
  244. loadMovieBox(movieURL, function(profile, rating) {
  245. if (currentMovieId == movieId) showMovieBox(event, profile, rating);
  246. });
  247. }, 30);
  248. }
  249. }, function() {
  250. clearTimeout(timerId);
  251. timerId = -1;
  252. currentMovieId = null;
  253.  
  254. movieBox.hide();
  255. });
  256. }
  257.  
  258. function setupMutationObserver() {
  259. var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
  260.  
  261. var observer = new MutationObserver(function(mutations) {
  262. mutations.forEach(function(mutation) {
  263. for (var i=0; i < mutation.addedNodes.length; i++) {
  264. $(mutation.addedNodes[i]).find("a").each(function() {
  265. if (this.href && this.href.match(/\/film/)) {
  266. addHoverHandler($(this));
  267.  
  268. var movieURL = this.href.trim();
  269. movies.push(movieURL);
  270. }
  271. });
  272. }
  273. });
  274.  
  275. prefetchMovies();
  276. });
  277.  
  278. observer.observe(document.querySelector("body"), {
  279. childList: true,
  280. subtree: true
  281. });
  282. }
  283.  
  284. // program start
  285.  
  286. storage.cleanExpiredData();
  287.  
  288. $(movieLinkSelector).each(function() {
  289. addHoverHandler($(this));
  290.  
  291. var movieURL = $(this).attr("href").trim();
  292. movies.push(movieURL);
  293. });
  294.  
  295. setupMutationObserver();
  296.  
  297. prefetchMovies();
  298.  
  299. // program end