// ==UserScript== // @name Torrentz : The Bobcat add-on // @namespace http://torrentzBobCat // @homepage http://www.youtube.com/watch?v=1QyuIDw0CIw&feature=youtu.be // @description Torrentz.eu: Add IMDB ratings, download links, movie plot/actors, and other goodies. Also features an light built-in serie tracker. Torrentz gets so much simpler and efficient! Demo video here: http://www.youtube.com/watch?v=1QyuIDw0CIw&feature=youtu.be // @author CoolMatt // @version 1.6.0 // @grant none // @include *://torrentz.* // @match *://torrentz.com/* // @match *://torrentz.eu/* // @match *://torcache.net/* // @downloadURL none // ==/UserScript== // @date 19 Jun 2013 // @license GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html //Define the namespace var Torrentz = Torrentz || {}; Torrentz.GM = {}; Torrentz.GM.BobCatTorrentz = {}; Torrentz.GM.BobCatTorrentz = { //Store info about movies of the page PageCache_movieInfo: {}, //Lookup table [torrentId -> movieKey] - as several torrents can point at the same movie info PageCache_lk_id_info: {}, start: function () { // Load and inject css initCss(); // Add the 'bobcat touch' this.addBadgeAndButtons(); //// Prevent scripts to bounce you to another page //window.onbeforeunload = function () { // return "Exit this page?"; //}; // Hide visual spam $("div.cloud").hide(); // Load store var moviesStore = Enbalaba.GetLocalStore("moviesInfo"), moviesData = moviesStore.get(), that = this, results; //Calculate cache size and clear it if too big this.checkCacheSize(moviesStore); //Get rid of this incredibly annoying & ridiculous advertising banner $("body>iframe:first").hide(); // Append download modal $("body").append("

Click a link below in order to download your torrent

Rating"); results.children("dl").each(function (index) { that.processRow($(this), moviesData, false); }); //Add events for when the row is clicked results.find("dt").click(function () { var dt = $(this), divDesc = dt.find(".movieDesc"), info = null, text, id, divQuality, lk, aElement; if (divDesc.length == 0) { // First time the user clicks here // Init the description container aElement = dt.children("a:first"); if (aElement.length == 0) return; id = aElement.attr("href").substr(1).toUpperCase(); if (!id) return; //Query cache to retrieve movie info if (that.PageCache_lk_id_info[id]) { info = that.PageCache_movieInfo[that.PageCache_lk_id_info[id]]; } if (info) { // Info found in cache text = "
Plot: " + info.Plot + "
Actors: " + info.Actors + "
"; } else { //No info > display the title text = aElement.attr("title"); } divDesc = $("
" + text + "
"); divQuality = $("
"); lk = $(""); lk.hover(function (e) { if ($(this).data('processed')) return; that.getQuality($(this.parentNode), aElement.attr("href")); e.stopPropagation(); $(this).hide(); $(this).data('processed', true); }); divQuality.append(lk).append($("
").hide()); divDesc.append(divQuality).hide(); dt.append(divDesc); } // Animate the pane if (divDesc.is(":hidden")) { divDesc.children().hide(); divDesc.slideDown(200, function () { divDesc.children().show(); }); dt.children(".expand").addClass("collapse"); } else { divDesc.slideUp(); dt.children(".expand").removeClass("collapse"); } }); // Once a day, the serie tracker check if there are new episodes available this.checkForNewEpisodes(); } , processRow: function (row, moviesData, isIFrameDownload) { if (!row) return; var tags = null, lk = row.find("dt>a"), name, torrentId; if (lk.length > 0) { var id = lk.attr("href").substr(1).toUpperCase(), //Get id from href info = lk.parent().text(), index = info.indexOf('\u00BB'), //Look for the utf-8 character >> in the row rightCol = row.find("dd"), torrentId = lk.attr("href").substr(1).toUpperCase(), type, year; rightCol.addClass("actionAndStatsColumns"); row.find("dt").css("width", "600px"); if (index > -1) { tags = info.substr(index + 1); name = info.substr(0, index); } lk.attr("title", tags ? "Tags: " + tags : info).parent().html(lk); //Remove info to make some room type = this.getType(name, tags); if (this.isTVSerie(name)) { type = "tv"; //extra verification as sometimes a tv serie is not tagged as such } if (type == "movie") { if (!moviesData) return; lk.css('color', '#3F14FF'); var yearIndex = name.search(/\s[0-9]{4}\s/); //Year is mandatory if (yearIndex != -1) { year = name.substr(yearIndex + 1, 4); name = name.substr(0, yearIndex); //console.log(name + ":" + year); info = moviesData[(name + year).toLowerCase()]; //Search in cache if (info) { this.PageCache_movieInfo[(name + year).toLowerCase()] = info; this.PageCache_lk_id_info[id] = (name + year).toLowerCase(); lk.text(name + " " + year); rightCol.append($("" + info.ImdbRating + "")); } else { this.searchIMDBinfo( name, year, torrentId, function (movieData) { // Add rating link in the DOM rightCol.append($("" + movieData.ImdbRating + "")); }); } } } else if (type == "tv") { lk.css('color', 'black' /*'#47D4FF'*/); } else { lk.css('color', '#555'); } //Add download link rightCol.prepend('Download'); // lk.parent().prepend("
"); } } , getType: function (name, tags) { if (tags.indexOf("movies") > -1) return "movie"; else if (tags.indexOf("tv") > -1) return "tv"; else if (tags.indexOf("games") > -1) return "game"; } /*As sometimes the tv serie is not tagged as such. This test will help catch those ones*/ , isTVSerie: function (fullName) { return ( new RegExp(/[sS][0-9]+[eE][0-9]+/).test(fullName) || new RegExp(/[0-9]+[x][0-9]+/).test(fullName) || new RegExp(/season[\s]?[0-9]{1,2}[\s]/i).test(fullName) ); } /* Make a query to the IMDB database to get data for the specified movie*/ , searchIMDBinfo: function (name, year, torrentId, successCallback, isRetry) { var url = encodeURI("http://www.omdbapi.com/?t=" + name + "&y=" + year + "&plot=full&r=json"), that = this; // Cross the same origin policy boundaries: Torrentz (https) to IMDB (http) GM_xmlhttpRequest({ method: "GET", url: url, onload: function (response) { var obj = $.parseJSON(response.responseText), moviesStore, moviesData, movieData; if (obj) { if (obj.imdbRating) { // That's a legit object moviesStore = Enbalaba.GetLocalStore("moviesInfo"); moviesData = moviesStore.get(); if (moviesData) { refName = (name + "&y=" + year).toLowerCase(); movieData = { ImdbRating: obj.imdbRating, Plot: obj.Plot, Actors: obj.Actors, ImdbID: obj.imdbID, Poster: obj.Poster, Genre: obj.Genre, Runtime: obj.Runtime, Metascore: obj.Metascore }; // Store in cache that.PageCache_movieInfo[refName] = movieData; // Link the torrent id to a movie data key in the movie cache (several torrent can point at the same key) that.PageCache_lk_id_info[torrentId] = refName; // Save in store moviesData[refName] = movieData; moviesStore.set(moviesData); } if (typeof successCallback === "function") { successCallback(movieData); } } else if (obj.Response == "False") { if (isRetry != true) { //Tries a second search, if applicable var name2 = name.replace(/thats/gi, "that's") .replace(/it's/gi, "its") .replace(/spiderman/i, "spider man") .replace(/extended$/i, ""); if (name2 != name) { that.searchIMDBinfo(name2, year, torrentId, successCallback, true); return; } } // Display error in console console.info(name + ": " + obj.Error); } } } }); return; } , tempID: 0 , getQuality: function ($qualityDiv, url) { url = "https://torrentz.eu" + url; //console.info(url); $qualityDiv.find(".spinner").show(); var id = "divComment" + (this.tempID++); $("").load(url, function (data) { var comments = $(data).find("div.comment .com"), qualityComments = []; for (var i = 0, comment; i < comments.length; i++) { comment = $(comments[i]).text(); if (comment.length > 400) comment = comment.substr(0, 400) + " (...)"; qualityComments.push(comment); } $qualityDiv.find(".spinner").hide(); $qualityDiv.find(".qualityComments").show().html("User comment:
" + qualityComments.join("
")); $(id).empty(); //free memory of the temporary div }); } /* * Calculate cache size and clear it if too big */ , checkCacheSize: function (moviesStore) { var cacheSize, k, that = this, moviesData = moviesStore.get(); try { //Works in all recent browsers cacheSize = Object.keys(moviesData).length; } catch (err) { cacheSize = 0; for (k in moviesData) { if (moviesData.hasOwnProperty(k)) cacheSize++; } } console.log("Bobcat - Cache size:" + cacheSize); if (cacheSize > 150) { //Clear the cache from time to time moviesStore.set({}); console.info("Bobcat - Movie cache cleared"); } } /* * Add badges and buttons */ , addBadgeAndButtons: function () { //Add bobcat badge in the top banner $("div.top").append(""); //Add serie tracker button var btST = $(""), that = this; btST.click(function () { that.onclick_btSerieTracker(); }); $("div.results h2").append(btST); //this.onclick_btSerieTracker(); //~~ Uncomment when developping serie tracker } //--------------------- //-----SERIE TRACKER--- //--------------------- , _ddSeasonHTML: "" , _ddEpisodeHTML: "" , onclick_btSerieTracker: function () { if (!this.SerieTrackerMode) { $("div.results h3").nextAll().hide(); $("div.recent").hide(); $("#serieContainer").show(); $("#btSerieTracker").text("Return to List"); if (this.SerieTrackerMode == null) { //init serie tracker var serieTrakerLastCheckedStore = Enbalaba.GetLocalStore("serieTrackerLastCheck") , serieTrakerLastChecked = serieTrakerLastCheckedStore.get(); serieTrakerLastCheckedStore.set({ FoundNewEpisodes: false, LastChecked: encodeDate(new Date()) }); delete serieTrakerLastCheckedStore; var serieStore = Enbalaba.GetLocalStore("trackedSeriesInfo"), serieInfo = serieStore.get(); if (!serieInfo.Ids) serieStore.set({ Ids: [], CurrentId: 1 }); $("div.results").after("

Track a new Serie

" + "
" + "
" + "
" + "
" + " episodes
" //+ "
Bobcat-Torrentz will check once a day for new episodes of tracked series
" + "
" + "
Delete All Tracked Series
" + "
The Bobcat addon will check once a day your tracked series for new episodes.
: New episode(s)
: No new episodes
"); //$("div.note").css("width", "400px").html("The Bobcat addon will check once a day your tracked series for new episodes.
:New episode(s)
: No new episodes"); /*Populate dropdowns*/ var i, htmlddSeasons = "", htmlddEpisodes = ""; for (i = 1; i < 16; i++) htmlddSeasons += ""; for (i = 1; i < 31; i++) htmlddEpisodes += ""; $("#st_ddSeasonNew").html(htmlddSeasons); $("#st_ddEpisodeNew").html(htmlddEpisodes); htmlddSeasons = ""; //blank variable because of closures htmlddEpisodes = ""; /*Display Series*/ this.displayTrackedSeries(); /*Search for new*/ this.searchForNewEpisodes(this.episodeFoundCallback); /*Add Serie event*/ var that = this; $("#btAddSerie").click(function () { var name = $("#st_tbNameNew").val(); if ($.trim(name) == "") { $("#st_lblOutput").text("Enter a Name"); } else { //---ADDITION var store = Enbalaba.GetLocalStore("trackedSeriesInfo"), serieInfo = store.get() , id = serieInfo.CurrentId; serieInfo.CurrentId = id + 1; serieInfo.Ids.push(id); store.set(serieInfo); var store = Enbalaba.GetLocalStore("ts_" + id) , isFinished = $("#cbIsFinishedSeason").is(":checked") , serie = { Name: name, Season: parseInt($("#st_ddSeasonNew").val(), 10), Current: { e: parseInt($("#st_ddEpisodeNew").val(), 10) }, History: [], id: id }; if (isFinished) { serie.isFinished = true; serie.NbTotEpisodes = parseInt($("#tbSeasonNbEpisodes").val(), 10); if (isNaN(serie.NbTotEpisodes)) { alert("Enter a valid number of episodes"); return; } } store.set(serie); that.displayTrackedSeries(); that.searchForNewEpisodes(that.episodeFoundCallback); //that.displayTrackedSeries(serieStore); $("#st_lblOutput").text(""); } }); $("#st_lblSuggestion").click(function () { $("#st_tbNameNew").val($(this).text()); }); // Logic serie name live suggestions $("#st_tbNameNew").keypress(function (e) { if (e.keyCode >= 20 && e.keyCode <= 40 && e.keyCode != 32) return true; //arrows, shift, and other keys that don't change the input. 32 is 'space' var txt = $(this).val(); if (txt.length >= 3) { var url = encodeURI("https://torrentz.eu/suggestions.php?q=" + $.trim(txt)); $("").css("display", "none").load(url, function (data) { var res = $.parseJSON(data); if (res && res.length == 2 && res[1] != null && res[1].length > 0) { console.log(res[1][0]); $("#st_lblSuggestion").text(res[1][0]); } else $("#st_lblSuggestion").val("-"); }); } }); $("#st_btDeleteAll").click(function () { if (confirm("Do you want to delete all the currently tracked series ?")) { Enbalaba.GetLocalStore("trackedSeriesInfo").set({ Ids: [], CurrentId: 1 }); that.displayTrackedSeries(); } }); } this.SerieTrackerMode = true; } else { // Back to the list of torrents this.SerieTrackerMode = false; $("div.results h3,div.recent").nextAll().not("#downloadModal,#downloadModalOverlay").show(); $("#serieContainer").hide(); $("#btSerieTracker").text("Serie Tracker"); } } , displayTrackedSeries: function () { var serieIds = Enbalaba.GetLocalStore("trackedSeriesInfo").get().Ids; var dl = $("#serieContainer dl"); dl.empty(); for (var i = 0, serie, id; i < serieIds.length; i++) { this.displaySerie(serieIds[i]); } } , displaySerie: function (serieId) { var serie = Enbalaba.GetLocalStore("ts_" + serieId).get(), time , hasNew = false; if (!serie.History) serie.History = []; var html = "
" + "
" + "
" + "
" + serie.Name + "
" + "
Season " + serie.Season + "
" + "
" + (serie.History.length > 0 ? "Episode " + serie.History[0].e : " - ") + "
" + "
" + (serie.isFinished ? "" : "Tracking: On") + "
" + "
" + "
"; var el = $("#serieContainer .trackedSerieContainer").filter(function () { return $(this).data("id") == serieId; }), newEl = $(html); delete html; //for the closure if (el.length > 0) el.empty().replaceWith(newEl); else $("#serieContainer dl").append(newEl); var that = this; newEl.find("div.deleteIcon").click(function (e) { e.stopImmediatePropagation(); that.onclick_deleteTrackedSerie(this); }); newEl.find("span.st-btShowLk").click(function (e) { that.onclick_showLinks(this); }); newEl.find("div.trackedSerieHeader").addClass(hasNew ? "hasNew" : "").click(function (e) { that.onclick_serieHeader(this); }); } // Once a day, the serie tracker check if there are new episodes available. , checkForNewEpisodes: function () { var serieTrakerLastCheckedStore = Enbalaba.GetLocalStore("serieTrackerLastCheck"), serieTrakerLastChecked = serieTrakerLastCheckedStore.get(), today = encodeDate(new Date()); if (!serieTrakerLastChecked || serieTrakerLastChecked.LastChecked != today) { //Start daily search serieTrakerLastCheckedStore.set({ FoundNewEpisodes: false, LastChecked: today }); this.searchForNewEpisodes(this.episodeFoundCallback_2); } else if (serieTrakerLastChecked.FoundNewEpisodes) { //A search was previously done, but the user didn't go to the serie tracker. Add a specific icon to signal new episode available. $("#btSerieTracker").css("color", "Blue").removeClass("bobcatStamp").addClass("bobcatStamp2"); } } , searchForNewEpisodes: function (callback) { var serieInfo = Enbalaba.GetLocalStore("trackedSeriesInfo").get() , that = this , today = encodeDate(new Date()); if (serieInfo && serieInfo.Ids) { for (var i = 0, store, serie, search, ids = serieInfo.Ids; i < ids.length; i++) { //serie = series[i]; store = Enbalaba.GetLocalStore("ts_" + ids[i]); serie = store.get(); if (!serie.isFinished || serie.History.length == 0) { this.lookForEpisode(serie, serie.Current.e, callback, store); } } } } , lookForEpisode: function (serie, episode, callback, store) { var search = serie.Name + " S" + (serie.Season < 10 ? "0" : "") + serie.Season + "E" + (episode < 10 ? "0" : "") + episode , url = encodeURI("https://torrentz.eu/search?f=" + search) , that = this; //console.info(url); search = search.toLowerCase(); $("").css("display", "none").load(url, function (data) { var rows = $(this).find("div.results dl"), results = []; //console.info(rows.length + " results"); for (var i = 0, $row, txt; i < rows.length; i++) { $row = $(rows[i]); txt = $row.find("dt").text().toLowerCase(); if (txt.indexOf(search) > -1) { //we need to be sure the results returned are related to the search results.push($row); } } if (callback) callback(serie, episode, results, store, that); $(this).empty(); //free memory of the temporary div }); } , episodeFoundCallback: function (s, e, results, store, context) { if (results.length < 1) { //NO EPISODE FOUND if (s.isFinished && e < s.NbTotEpisodes) { s.History.splice(0, 0, { e: e, f: false, d: encodeDate(new Date()) }); //insert new entry in history - at the begining s.Current.e = parseInt(s.Current.e, 10) + 1; store.set(s); context.displaySerie(s.id); //redisplay the result for the serie context.lookForEpisode(s, e + 1, context.episodeFoundCallback, store); //rec } } else { //EPISODE FOUND //console.info("New episode Found for " + s.Name); //New episode found s.History.splice(0, 0, { e: e, f: true, d: encodeDate(new Date()) }); //insert new entry in history - at the begining s.Current.e = parseInt(s.Current.e, 10) + 1; store.set(s); context.displaySerie(s.id); //redisplay the result for the serie var el = $("#serieContainer .trackedSerieContainer").filter(function () { return $(this).data("id") == s.id; }); el.find(".trackedSerieHeader").css("color", "Blue"); context.lookForEpisode(s, s.Current.e, context.episodeFoundCallback, store); //rec } } // Function called the first time the user visit torrentz in the day, // even if he hasn't entered the Section Tracker section , episodeFoundCallback_2: function (s, e, results) { if (results.length > 0) { var serieTrakerLastCheckedStore = Enbalaba.GetLocalStore("serieTrackerLastCheck") , serieTrakerLastChecked = serieTrakerLastCheckedStore.get(); $("#btSerieTracker").css("color", "Blue"); //.text("Serie Tracker ( New Episodes! )"); serieTrakerLastCheckedStore.set({ FoundNewEpisodes: true, LastChecked: encodeDate(new Date()) }); } } // Handler for when a followed-serie header is clicked , onclick_serieHeader: function (headerEl) { headerEl = $(headerEl); var body = headerEl.parent().children(".trackerSerieBody"); if (body.is(":visible")) { body.slideUp(200, function () { }); } else body.slideDown(200, function () { }); } // Handler for when the user clicks on "Show Links" , onclick_showLinks: function (lk) { lk = $(lk); var row = lk.parent().parent() , that = this , data = row.data("seriedata"), dataParts; //row store some data : serieId_episodeNumber var existingBox = row.parent().children(".st-link-container").filter(function () { return $(this).data("seriedata") == data; }); if (existingBox.length != 0) { existingBox.remove(); } else { if (data) { dataParts = data.split('_'); //format: "serieId _episodeNumber" var serie = this.getSerieFromStore(dataParts[0]); this.lookForEpisode(serie, dataParts[1], function (s, e, results) { var html = "