// ==UserScript==
// @name CSFD Movie Preview
// @namespace http://csfd.cz
// @description Při najetí myší na odkaz na film se zobrazí náhled jeho profilu.
// @match http://www.csfd.cz/*
// @match https://www.csfd.cz/*
// @exclude http://www.csfd.cz/uzivatel/*/profile-edit/
// @exclude https://www.csfd.cz/uzivatel/*/profile-edit/
// @require http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
// @grant GM_registerMenuCommand
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// @version 1.3
// ==/UserScript==
// CHANGES
// -------
// 1.3 - upravena hlavička skriptu kvůli přechodu ČSFD na https
// 1.2 - doplněna podpora dynamicky přidávaných odkazů
// 1.1 - výměna jQuery.ajax(), který ve Firefoxu přestal fungovat, za GM_xmlhttpRequest()
// 1.0 - první verze
$ = this.jQuery = jQuery.noConflict(true);
$('<div id="movie-preview" style="display: none; z-index: 999; width: 400px; background-color: #efefef; padding: 6px; ' +
'border-radius: 4px; box-shadow: 0 0 10px 4px #777777"><table border="1"><tr><td id="movie-preview-poster" width="120" ' +
'style="text-align: center"></td><td id="movie-preview-content" style="vertical-align: top; padding-left: 7px"></td>' +
'</tr></table></div>').appendTo('body');
var cacheExpires = 7; // days
var doPrefetch = false;
var movieBox = $('div#movie-preview');
var movieBoxPoster = movieBox.find('#movie-preview-poster');
var movieBoxContent = movieBox.find('#movie-preview-content');
var movieLinkSelector = 'a[href*="/film/"], a[href*="/film.php"]';
var thisPageMovieId = parseMovieId(window.location.href);
var currentMovieId = null;
var movies = [];
var timerId = -1;
// Greasmonkey-only section start
if (typeof GM_registerMenuCommand == 'function' && isStorageSupported()) {
doPrefetch = GM_getValue("doPrefetch", false);
GM_registerMenuCommand("Přepnout automatické nahrávání náhledů filmů", function() {
doPrefetch = !GM_getValue("doPrefetch", false);
GM_setValue("doPrefetch", doPrefetch);
alert("Automatické nahrávání náhledů filmů " + (doPrefetch? "zapnuto": "vypnuto") + ".\nZměna nastavení se projeví po obnovení stránky.");
});
}
// Greasmonkey-only section end
function isStorageSupported() {
return typeof(Storage) !== void(0);
}
function parseMovieId(movieURL) {
var match = movieURL.match(/\/film(?:\.php\?|\/)([\d]+)/);
return match && match.length >= 2? 'm' + match[1]: null;
}
function getDiffDays(date1, date2) {
return Math.round(Math.abs(date1 - date2) / (1000 * 3600 * 24));
}
var storage = isStorageSupported()?
{ // local storage
getStoredItem: function(movieURL) {
return localStorage[parseMovieId(movieURL)];
},
setStoredItem: function(movieURL, value) {
try {
localStorage[parseMovieId(movieURL)] = value;
} catch (ex) {
// "Persistent storage maximum size reached" -> remove 10 random items
for (i=0; i < 10; i++) {
var index = Math.floor(Math.random() * localStorage.length);
var key = localStorage.key(index);
localStorage.removeItem(key);
}
return this.setStoredItem(movieURL, value);
}
},
cleanExpiredData: function() {
var lastCleanup = localStorage["last-cleanup"]? Date.parse(localStorage["last-cleanup"]): new Date(0);
// run cleanup only once per day
if (getDiffDays(new Date(), lastCleanup) < 1) return;
for(var key in localStorage) {
if (key.match(/m\d+/)) {
var cached = JSON.parse(localStorage[key]);
if (getDiffDays(new Date(), Date.parse(cached.timestamp)) > cacheExpires) {
localStorage.removeItem(key);
}
}
}
localStorage["last-cleanup"] = new Date();
}
}:
{ // dummy storage
getStoredItem: function(movieURL) {
return null;
},
setStoredItem: function(movieURL, value) {
// noop
},
cleanExpiredData: function() {
// noop
}
};
function getMovieBoxPosition(event) {
var boxWidth = movieBox.width() + 10;
var tPosX = boxWidth - event.clientX + 30 > 0? event.pageX + 30: event.pageX - boxWidth - 30;
var tPosY = event.pageY + event.clientY;
if (event.clientY > 30) {
var winHeight = $(window).height();
var boxHeight = movieBox.height() > winHeight? winHeight - 60: movieBox.height();
var overflowY = event.clientY + boxHeight - winHeight;
tPosY = overflowY > 0? event.pageY - overflowY - 50: event.pageY - 30;
}
return { X: tPosX, Y: tPosY };
}
function showMovieBox(event, profile, rating) {
var poster = profile.find("#poster img");
var title = "<h1 style='text-transform: none'>" + profile.find(".info h1").text().trim() + "</h1>";
var genre = profile.find(".genre");
var origin = profile.find(".origin");
var creators = profile.find(".creators");
movieBoxPoster.html('');
movieBoxPoster.append(poster.css('width', 120));
movieBoxPoster.append('<br><h1 style="font-size: 32px">' + rating + '</h1>');
movieBoxContent.html('');
movieBoxContent.append(title);
movieBoxContent.append(genre.css('font-weight', 'bold'));
movieBoxContent.append(origin.css('font-weight', 'bold'));
movieBoxContent.append('<br>');
movieBoxContent.append(creators);
var pos = getMovieBoxPosition(event);
movieBox.css({ 'position': 'absolute', 'top': pos.Y, 'left': pos.X }).show();
}
function getCachedData(movieURL) {
var cached = storage.getStoredItem(movieURL);
if (cached) {
cached = JSON.parse(cached);
if (getDiffDays(new Date(), Date.parse(cached.timestamp)) <= cacheExpires)
return { "profile": $(cached.profile), "rating": cached.rating };
}
return null;
}
function loadMovieBox(movieURL, doneCallback, errorCallback, redirectMovieURL) {
if (!redirectMovieURL) redirectMovieURL = movieURL;
console.log("[CSFD Movie Preview] Loading movie page: " + redirectMovieURL);
GM_xmlhttpRequest({
method: "GET",
url: redirectMovieURL,
onload: function(response) {
try {
if (false /* TODO: handle redirect */) {
loadMovieBox(movieURL, doneCallback, errorCallback, response.redirect);
} else {
response = $(response.responseText);
var profile = response.find("div#profile").html().replace(/[\t\n]+/mg, '');
var rating = response.find("div#rating .average").text().trim();
storage.setStoredItem(movieURL, JSON.stringify({ "profile": profile, "rating": rating, "timestamp": new Date() }));
if (doneCallback) doneCallback($(profile), rating);
}
} catch(ex) {
console.log("[CSFD Movie Preview] Error in AJAX handler: " + ex.message);
if (errorCallback) errorCallback();
}
},
onerror: function(response) {
if (errorCallback) errorCallback();
}
});
}
function prefetchMovies() {
if (!doPrefetch || !isStorageSupported()) return;
var movieURL;
if (movieURL = movies.shift()) {
setTimeout(function() {
if (!getCachedData(movieURL)) {
loadMovieBox(movieURL, prefetchMovies, prefetchMovies);
} else {
prefetchMovies();
}
}, 300);
}
}
function addHoverHandler(element) {
element.hover(function(event) {
var movieURL = $(this).attr("href").trim();
var movieId = parseMovieId(movieURL);
// prevent previews of the movie on its page
if (thisPageMovieId == movieId) return;
currentMovieId = movieId;
var cached = getCachedData(movieURL);
if (cached) {
showMovieBox(event, cached.profile, cached.rating);
} else {
clearTimeout(timerId);
timerId = setTimeout(function() {
loadMovieBox(movieURL, function(profile, rating) {
if (currentMovieId == movieId) showMovieBox(event, profile, rating);
});
}, 30);
}
}, function() {
clearTimeout(timerId);
timerId = -1;
currentMovieId = null;
movieBox.hide();
});
}
function setupMutationObserver() {
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
for (var i=0; i < mutation.addedNodes.length; i++) {
$(mutation.addedNodes[i]).find("a").each(function() {
if (this.href && this.href.match(/\/film/)) {
addHoverHandler($(this));
var movieURL = this.href.trim();
movies.push(movieURL);
}
});
}
});
prefetchMovies();
});
observer.observe(document.querySelector("body"), {
childList: true,
subtree: true
});
}
// program start
storage.cleanExpiredData();
$(movieLinkSelector).each(function() {
addHoverHandler($(this));
var movieURL = $(this).attr("href").trim();
movies.push(movieURL);
});
setupMutationObserver();
prefetchMovies();
// program end