Greasy Fork

Greasy Fork is available in English.

Torrentz : The Bobcat add-on

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

目前为 2014-08-04 提交的版本,查看 最新版本

// ==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.3.7
// @grant none
// @include       *://torrentz.*
// @match         *://torrentz.com/*
// @match         *://torrentz.eu/*

// ==/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 = {

    PageCache_movieInfo: {},  //Store info about movies of the page
    PageCache_lk_id_info: {}, //Lookup table - as several torrentz can point at the same movie info

    start: function () {
        
        initCss();
        this.addBadgeAndButtons();

        $("div.cloud").hide();

        var loginStore = Enbalaba.GetLocalStore("moviesInfo"),
            loginData = loginStore.get(),
            that = this,
            results;

        //Calculate cache size and clear it if too big
        this.checkCacheSize(loginStore);

        //Get rid of this incredibly annoying & ridiculous advertising banner
        $("body>iframe:first").hide();

        //Start processing of the rows
        results = $(".results");

        
        results.find("h3:first").append("<span>|&nbsp</span><b title='IMDB Rating. Brought to you by the Torrentz Dominion Plugin'>Rating</b>");
        results.children("dl").each(function (index) {
            that.processRow($(this), loginData, false);
        });

        results.find("span.downloadLink").click(function () {
            $(this).replaceWith("<div class='fleft bobcatStamp' style='width:80px;height:25px;position:relative;top:-4px'></div>");
            downloadTorrent($(this).attr("data-torrentid"));
        });
        
        //Add events for when the row is clicked
        results.find("dt").click(function () {
            var dt = $(this),
                text,
                divDesc = dt.find(".movieDesc"),
                div, lk, aElement;

            if (divDesc.length == 0) { //First time the user clicks here
                aElement = dt.children("a:first");
                if (aElement.length == 0) return;
                var id = aElement.attr("href").substr(1).toUpperCase(), info = null;
                if (!id) return;

                //Retrieve info from cache
                if (that.PageCache_lk_id_info[id]) {
                    info = that.PageCache_movieInfo[that.PageCache_lk_id_info[id]];
                }

                if (info) {
                    // 
                    text = "<div class='plot'><b>Plot</b>: " + info.Plot + "</div><div class='actorsInfo'> <b>Actors</b>: " + info.Actors + "</div>";
                }
                else {
                    //No info > display the title
                    text = aElement.attr("title");
                }

                divDesc = $("<div class='movieDesc'>" + text + "</div>");

                div = $("<div class='divQuality'></div>");
                lk = $("<div class='hyperlink fleft'>See user comments</div>");
                lk.hover(function (e) {
                    if ($(this).data('processed')) return;
                    that.getQuality($(this.parentNode), aElement.attr("href")); e.stopPropagation(); $(this).hide();
                    $(this).data('processed', true);
                });

                div.append(lk).append($("<img class='spinner fleft'></div><div class='qualityComments'><div/>").hide()); //Add get Quality link and spinner image
                divDesc.append(div).hide();
                dt.append(divDesc);
            }
            if (divDesc.is(":hidden")) {
                divDesc.children().hide();
                divDesc.slideDown(200, function () { divDesc.children().show(); });
                //dt.children(".expandCollapse").removeClass("expand").addClass("collapse");
                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, loginData, isIFrameDownload) {
        if (!row) return;
        var tags = null,
            name,
            lk = row.find("dt>a");

        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");

            rightCol.css("width", "400px");
            row.find("dt").css("width", "540px");

            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

            //lk.after("<span class='moreLk'>more</span>");
            var 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 (!loginData) return;
                lk.css('color', '#3F14FF');

                var yearIndex = name.search(/\s[0-9]{4}\s/); //Year is mandatory
                if (yearIndex != -1) {
                    var year = name.substr(yearIndex + 1, 4);
                    name = name.substr(0, yearIndex);
                    //console.log(name + ":" + year);

                    info = loginData[(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($("<a class='rateBox' " + (info.ImdbID && info.ImdbID != "" ? "target='_blank' href='http://www.imdb.com/title/" + info.ImdbID + "'" : "") + " >" + info.imdbRating + "</a>"));
                    }
                    else {
                        this.searchIMDBinfo(name, year, lk, rightCol);
                    }
                }
            }

            else if (type == "tv") {
                lk.css('color', 'Black' /*'#47D4FF'*/);
            }
            else {
                lk.css('color', '#555');
            }
            //Add link
            //if (isIFrameDownload == true) {
            rightCol.prepend("<span class='downloadLink hyperlink' data-torrentid='" + id + "'>Download</span>");
            //rightCol.prepend("<div class='downloadIcon fleft' data-torrentid='" + id + "'></div>");

            lk.parent().prepend("<div class='expand fleft'></div>");
        }
        //this.attachRowEvent(row);

        //row.find("span.downloadLink").text("download");
    }

    , 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)
        );
    }

    /*
     *
     */
 , searchIMDBinfo: function (name, year, link, rightCol, isRetry) {
     //console.log("encodeURI('http://www.imdbapi.com/?t=" + name + "')");
     var url = encodeURI("http://www.imdbapi.com/?t=" + name), that = this;
     $("<span></span>").css("display", "none").load(url, function (data) {
         var obj = $.parseJSON(data);
         if (obj) {
             if (obj.imdbRating) {
                 var loginStore = Enbalaba.GetLocalStore("moviesInfo")
               , loginData = loginStore.get();
                 if (loginData) {
                     var refName = (name + year).toLowerCase();
                     that.PageCache_movieInfo[refName] = { imdbRating: obj.imdbRating, Plot: obj.Plot, Actors: obj.Actors, ImdbID: obj.imdbID };
                     that.PageCache_lk_id_info[link.attr("href").substr(1).toUpperCase()] = refName;

                     loginData[refName] = { imdbRating: obj.imdbRating, Plot: obj.Plot, Actors: obj.Actors, ImdbID: obj.imdbID };
                     loginStore.set(loginData);
                 }
                 rightCol.append($("<a class='rateBox' " + (obj.imdbID && obj.imdbID != "" ? "target='_blank' href='http://www.imdb.com/title/" + obj.imdbID + "'" : "") + " >" + obj.imdbRating + "</a>"));
                 //rightCol.prepend($("<input type='text' disabled='disabled' class='rateBox' value='" + obj.imdbRating + "'></input>")); //tbMark.val(obj.imdbRating);
                 //if (obj.Plot) link.attr("title", obj.Plot).data("MovieInfo", { imdbRating: obj.imdbRating, Plot: obj.Plot, Actors: obj.Actors });
             }
             else if (obj.Response == "False"/* && obj.Error.indexOf("Movie not found") != -1*/) {
                 if (isRetry != true) {  //Tries a second search
                     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, link, rightCol, true); return; }
                 }
                 console.info(name + ": " + obj.Error);
                 //rightCol.prepend($("<input type='text' disabled='disabled' class='rateBox' value='?'></input>"));
             }
         }
     });
     return;
 }

     , tempID: 0
     , getQuality: function ($qualityDiv, url) {

         url = "http://torrentz.eu" + url;
         //console.info(url);
         $qualityDiv.find(".spinner").show();
         var id = "divComment" + (this.tempID++);
         $("<div style='display:none' id='" + id + "'></div>").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("<b>User comment:</b><br/>" + qualityComments.join("<br/>"));
             $(id).empty(); //free memory of the temporary div
         });
     }

/*
 * Calculate cache size and clear it if too big
 */
 , checkCacheSize: function (loginStore) {
     var cacheSize,
         k,
         that = this,
         loginData = loginStore.get();
     
     try {
         //Works in all recent browsers
         cacheSize = Object.keys(loginData).length; 
     }
     catch (err) {
         
         cacheSize = 0;
         for (k in loginData) {
             if (loginData.hasOwnProperty(k)) cacheSize++;
         }
     }
     console.log("Bobcat - Cache size:" + cacheSize);
     if (cacheSize > 150) {
         //Clear the cache from time to time
         loginStore.set({});
         console.info("Bobcat - Movie cache cleared");
     }
 }

 /*
  * Add badges and buttons
  */
  , addBadgeAndButtons: function () {
      //Add bobcat badge in the top banner
      $("div.top").append("<div id='bobcatLogoContainer' class='bobcatLogo'><a href='http://www.youtube.com/watch?v=1QyuIDw0CIw&feature=youtu.be'>with the Bobcat add-on</a></div>");

      //Add serie tracker button
      var btST = $("<button type='button' id='btSerieTracker' class='bcButton bobcatStamp'>Serie Tracker</button>"),
          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("<div class='results' id='serieContainer'><dl></dl><div id='addSerieContainer' style='float:left;position:relative;'> <h2>Track a new Serie</h2>"
              + "<div class='row'><div class='col1'><label>Name</label></div><div  class='col2'><input type='text' id='st_tbNameNew' class='bcTextbox'/></div> <div class='col3'><span id='st_lblSuggestion'></span></div></div>"
              + "<div class='row'><div class='col1'><label>Season</label></div><div  class='col2'><select id='st_ddSeasonNew' class='bcSelect'><option></option></select></div></div>"
              + "<div class='row'><div class='col1'><label>Episode</label></div><div  class='col2'><select id='st_ddEpisodeNew' class='bcSelect'><option></option></select></div></div>"
              + "<button type='button' id='btAddSerie' class='bcButton'>Add This Serie</button><span id='st_lblOutput' style='color:red'></span><br/>"
              + "<input type='checkbox' id='cbIsFinishedSeason'/><label for='cbIsFinishedSeason'>I know this season is finished and has </label><input type='input' id='tbSeasonNbEpisodes' style='width:20px' maxlength=2 value='20'/> episodes</div>"
              //+ "<div id='st_lblNotes' style='position:absolute;top:30px;left:30px' >Bobcat-Torrentz will check once a day for new episodes of tracked series<div/>"
              + "</div>"
              + "<div style='clear:both;cursor:pointer' id='st_btDeleteAll' >Delete All Tracked Series<div/>"
              + "<div style='width:500px;border-radius:6px; border-size:1px;margin-top:25px'>The Bobcat addon will check once a day your tracked series for new episodes.<br/><img src='http://i.imgur.com/n7tvk8I.png'/>: New episode(s)<br/><img src='http://i.imgur.com/tDWKswF.png'/>: No new episodes</div>");
              //$("div.note").css("width", "400px").html("The Bobcat addon will check once a day your tracked series for new episodes.<br/><img src='http://i.imgur.com/n7tvk8I.png'/>:New episode(s)<br/><img src='http://i.imgur.com/tDWKswF.png'/>: No new episodes");

              /*Populate dropdowns*/
              var i, htmlddSeasons = "", htmlddEpisodes = "";
              for (i = 1; i < 16; i++) htmlddSeasons += "<option value='" + i + "'>" + i + "</option>";
              for (i = 1; i < 31; i++) htmlddEpisodes += "<option value='" + i + "'>" + i + "</option>";
              $("#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");
                      //$("#st_tbNameNew").css("border-color", "red");
                  }
                  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());
              });
              $("#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("http://torrentz.eu/suggestions.php?q=" + $.trim(txt)/*.replace(' ', '+')*/);
                      $("<span></span>").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 {
          this.SerieTrackerMode = false;
          $("div.results h3,div.recent").nextAll().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 = "<div class='trackedSerieContainer'  data-id='" + serie.id + "'>"
        + "<div class='trackedSerieHeader'>"
        + "<div class='st_name st-col1'>"
        + "<div class='deleteIcon fleft' data-id='" + serie.id + "' title='delete' style='margin-right:2px'></div>"
        + serie.Name + "</div>"
        + "<div class='st-col2'><b>Season " + serie.Season + "</b></div>"
        + "<div class='episode st-col3'><b>" + (serie.History.length > 0 ? "Episode " + serie.History[0].e : " - ") + "</b></div>"
        + "<div class='st-col4'>" + (serie.isFinished ? "" : "Tracking: On") + "</div>"
        + "</div>"
        + "<div class='trackerSerieBody' style='display:none'>";

      //History

      if (serie.History.length == 0) {
          html += "<div style='margin-left:50px'>No results found</div>";
      }
      else {
          for (var j = 0, h, d, l = serie.History.length; j < l; j++) {
              h = serie.History[j];
              d = (h.d ? new Date(new Date() - getDateFromDateString(h.d)) : null);

              if (d) {
                  if (d.getMonth() > 0) {
                      dif = d.getMonth() + " month" + (d.getMonth() == 1 ? "" : "s") + " ago";
                  }
                  else {
                      time = d.getDate() - 1;
                      if (time == 0) {
                          time = "today";
                          hasNew = true;
                      }
                      else time += " days ago";
                  }
              }
              html += "<div class='st-row' data-serieData='" + (serie.id + "_" + h.e) + "'><div class='st-col1'>&nbsp</div> <div class='st-col2'>Episode " + h.e + "</div><div class='st-col3'>"
              + (h.f ? "<span class='st-btShowLk hyperlink'>Show Links<span>" : "<span>Not found</span>") + "</div>"
              + "<div class='st-col4'>" + time + "</div>"
              + "</div> ";
          }
      }
      html += "</div></div>";
      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());

      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("http://torrentz.eu/search?f=" + search)
      , that = this;
       //console.info(url);
       search = search.toLowerCase();
       $("<span></span>").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 < 1) return;
      $("#btSerieTracker").css("color", "Blue"); //.text("Serie Tracker ( New Episodes! )");

      var serieTrakerLastCheckedStore = Enbalaba.GetLocalStore("serieTrackerLastCheck")
        , serieTrakerLastChecked = serieTrakerLastCheckedStore.get();

      serieTrakerLastCheckedStore.set({ FoundNewEpisodes: true, LastChecked: encodeDate(new Date()) });
  }



  , onclick_serieHeader: function (headerEl) {

      headerEl = $(headerEl);
      var body = headerEl.parent().children(".trackerSerieBody");
      if (body.is(":visible")) {
          //console.info("up");
          body.slideUp(200, function () { });
      }
      else body.slideDown(200, function () { });

      //$(this).parent().data("id"));
  }

  , 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();
          //row.parent().remove(existingBox);
      }
      else {
          if (data) {
              dataParts = data.split('_'); //"serieId _episodeNumber"
              var serie = this.getSerieFromStore(dataParts[0]);

              this.lookForEpisode(serie, dataParts[1], function (s, e, results) {


                  var html = "<div class='st-link-container' data-seriedata='" + data + "'>"
                  , max = (results.length < 3 ? results.length : 3);
                  for (var i = 0; i < max; i++) { //show only 3 first
                      r = results[i];
                      that.processRow(r, null, true);
                      html += "<div class='st-row'><div class='st-link-col1'>" + r.find("dt").html() + "</div>";
                      html += "<div class='st-link-col2'>" + r.find("dd").html() + "</div> ";
                      html += "</div>";
                  }
                  var linkContainer = $(html);
                  linkContainer.find("span.downloadLink").click(function () {
                      downloadTorrent($(this).attr("data-torrentid"));
                  });
                  row.after(linkContainer);

              });
          }
      }
  }
   , onclick_deleteTrackedSerie: function (element) {
       //serie deletion

       var name = $(element).parent().text(), id = $(element).data('id');
       if (confirm("Are you sure you want to delete the entry for '" + name + "'")) {

           var store = Enbalaba.GetLocalStore("trackedSeriesInfo"), serieInfo = store.get();
           serieInfo.Ids = $.grep(serieInfo.Ids, function (value) { return value != id; });
           store.set(serieInfo);

           store = Enbalaba.GetLocalStore("ts_" + id); //.get();
           store.set({}); //TODO : real deletion
           this.displayTrackedSeries();
           //console.info("deleted");
       }
       //else console.info("Not deleted");

   }

  , getSerieFromStore: function (id) {
      var serieStore = Enbalaba.GetLocalStore("ts_" + id), serie = serieStore.get();
      return serie;
  }

}

//Basic Class to deal with the localstorage
Enbalaba = {};

//Add CSS
function initCss() {
    var css = [
        " .rateBox{ margin-left:10px;position:relative;bottom:3px; cursor: pointer; padding:1px; background-color:#EEE; border:#AAA solid 1px; border-radius:4px}"
        , ".bobcatLogo{background:transparent url(http://i.imgur.com/MlVVyzX.png) no-repeat scroll 0 0}"
        , "#bobcatLogoContainer a{color:White}"
        , "#bobcatLogoContainer a:hover{color:White}"
        , ".bobcatStamp{background:transparent url(http://i.imgur.com/tDWKswF.png) no-repeat scroll 0 0}"
        , ".bobcatStamp2{background:transparent url(http://i.imgur.com/n7tvk8I.png) no-repeat scroll 0 0}"
        , "#bobcatLogoContainer{color:White;height:30px;width:150px;float:left;padding-left:50px;padding-top:5px; margin-top:10px}"
        , ".downloadLink{;margin-right:20px}"
        , ".moreLk{padding-left:30px;cursor:pointer}"
        , ".movieDesc{width:530px;margin:10px 0px 40px 0px;color:Black;white-space:normal}"
        , ".fleft{ float:left}"
        , "dt:hover{ background-color:#EEE}"
        , ".qualityComments{float:clear}"
        , ".spinner{ background:url(http://www.andrewdavidson.com/articles/spinning-wait-icons/wait16trans.gif) no-repeat left center;width: 16px;height: 16px}"
        , ".actorsInfo,.qualityComments,.divQuality,.plot{ margin-top:11px;margin-bottom:5px; font-size:12px;font-family:Verdana,Tahoma,sans-serif}"
        , "#pluginZoneContainer{ position:absolute; left: 210px; top:10px; width: 200px; height: 200px;background-color:Gray}"
        , ".expand{  background:transparent url(http://i.imgur.com/mIIop2R.png) no-repeat scroll 0 0; width:15px; height: 9px; position: relative; top:3px}" //arrow1.png
        , ".deleteIcon{  background:transparent url(http://i.imgur.com/4RjuUFU.png) no-repeat scroll 0 0; width:15px; height: 13px; position: relative; top:3px}"
        , ".downloadIcon{  background:transparent url(http://i.imgur.com/7Jkx1N9.png) no-repeat scroll 0 0; width:17px; height: 18px; position: relative; top:3px}"
        , ".deleteIcon:hover{ background-color:#CCC}"
        , ".collapse{  background-image:url(http://i.imgur.com/apcKFJ5.png)}"//arrow2.png


        , ".btSerieTrackerHighlight{color:Yellow !important}"
        , "#btSerieTracker{ padding-left:40px;margin-left:20px; background-color:White;background-position:3px 3px}"
        , "#addSerieContainer{ width:50%;   border: 1px solid #B5B8C8; margin: 30px 0px; padding:20px; border-radius: 15px}"
        , "#st_tbNameNew{ width : 150px}"
        , "#btAddSerie{ margin: 10px 0px}"
        , "#cbIsFinishedSeason{margin-right:7px}"
        , "#st_lblSuggestion{ color:Grey; font-size:11px;cursor:pointer}"
        , ".trackedSerieHeader{ margin: 15px 0px}"
        , ".trackedSerieHeader,.st-row{clear:both;width:100%;font-size:12px;height:15px}"
        , ".trackedSerieHeader>div,.st-row>div{ margin-right:30px;float:left}"
        , ".st-col1{ width:200px}"
        , ".st-col2,.st-col3{ width:100px}"
        , ".st-link-col1{width:500px}"
        , ".st_name{font-weight:bold}"
        , " .st-link-container{margin:10px; border:1px solid #AAA;padding:20px}"
        , ".st-link-col2 span{ margin-right:10px}"
        , ".st-link-col2 .u{ font-weight:bold}"
        , "div.trackedSerieHeader{cursor:pointer}"
        , "div.trackedSerieHeader:hover{ background-color:#EEE}"
        , ".hasNew{color:Blue}"

    //Generic
        , ".hyperlink{color:#0066EE;text-decoration:none;cursor:pointer;text-decoration:underline}"
        , ".bcButton{color:#6B3F2E; border-radius: 6px; border: 1px solid #6B3F2E; height:25px; padding-bottom:1px; min-width:80px; font-weight:bold;cursor:pointer}"
        , ".bcButton:hover{color:#AA3F2E; }"
        , ".bcTextbox{background-color:#FFF;border: 1px solid #B5B8C8; font-size: 14px; height: 16px;  line-height: 14px; padding: 2px; vertical-align: middle;border-radius: 5px; color:color:#6B3F2E}"
        , ".bcSelect{ background-color:#FFFFFF;height:26px;line-height:26px;border:1px solid #CCCCCC;color:Black;font-size:16px;    padding:4px;border-radius:5px}"
        , " .col1{ float:left; width:100px; }"
        , ".col2{ float:left; width:200px}"
        , ".col3{ float:left; width:200px}"
        , ".row{ clear:both; width : 500px; margin:10px 0px; padding-bottom:20px}"
    ];
    css = css.join("\n");
    if (typeof GM_addStyle != "undefined") GM_addStyle(css);
    else if (typeof PRO_addStyle != "undefined") PRO_addStyle(css);
    else if (typeof addStyle != "undefined") addStyle(css);
    else {
        var heads = document.getElementsByTagName("head");
        if (heads.length > 0) {
            var node = document.createElement("style");
            node.type = "text/css";
            node.appendChild(document.createTextNode(css));
            heads[0].appendChild(node);
        }
    }
}

/*Parse a string with a basic format (yyyyMMdd HHmmss) to a date object */
function getDateFromDateString(dateString, isUTCDate) {
    try {
        //This is for Javascript to understand format 'yymmdd hhmmmss'
        var year = dateString.substring(0, 4),
                month = dateString.substring(4, 6),
                day = dateString.substring(6, 8),
                hours = dateString.substring(9, 11),
                minutes = dateString.substring(11, 13),
                seconds = dateString.substring(13, 15);
        var date = new Date(year, month - 1, day, hours, minutes, seconds, "00"); // months are 0-based
        if (isUTCDate == true) { //Must convert the date from UTC/GMT to local time
            var n = date.getTimezoneOffset();
            date.setMinutes(date.getMinutes() - n);
        }
        return date;
    }
    catch (err) {
        return new Date(dateString);
    }
}

/** Encode a date : "yyyyMMdd"
*/
function encodeDate(d) {
    //debugger;
    var twoDigit = function (val) { if (val < 10) return "0" + val; else return val; };
    if (d && d.getMonth) return d.getFullYear().toString() + twoDigit((d.getMonth() + 1)) + twoDigit(d.getDate()); // + " " + twoDigit(d.getHours()) + twoDigit(d.getMinutes()) + twoDigit(d.getSeconds());
    else return null;
}


function downloadTorrent(id){
    if (!id) return;

    var hiddenIFrameID = 'hiddenDownloader',
        //url = "http://torcache.net/?download=" + id;
        url = "http://torrage.com/torrent/" + id;
        iframe = document.getElementById(hiddenIFrameID);
          if (iframe===null) {
              iframe = document.createElement('iframe');
              iframe.id = hiddenIFrameID;
              iframe.style.display = 'none';
              document.body.appendChild(iframe);
              console.log("SRC : "+url);
          }
          iframe.src = url;
  }


//--Application specific. Ensure Singleton, single location to set up the specific configs. Better to use that than using new Enbalaba.LocalStore()
Enbalaba.GetLocalStore = (function () {
    var _stores = []; //*Private*
    return function (name) {
        if (!_stores[name]) {
            var config = {};
            switch (name) {
                case "moviesInfo": config = { MaxProperties: 100 }; break;
                case "trackedSeries": config = { IsArray: true }; break;
            }
            _stores[name] = new Enbalaba.LocalStore(name, config);
        }
        return _stores[name];
    }
})();
//--------------
Enbalaba.LocalStore = function (name, config) {
    this.Name = name;
    var defaultConfig = { EmptyValue: {}, MaxTotalSize: 250000 };
    //Notes : 1 char = 2octets (Strings in JavaScript are UTF-16, so each character requires two bytes of memory)
    if (!config) config = {};
    else {
        if (config.IsArray == true) defaultConfig = { EmptyValue: [], MaxItems: 100, MaxTotalSize: 250000 }; //MaxTotalSize for arrays (usually used for MRU)== 500Ko        
    }
    this.Config = $.extend(defaultConfig, config);
};

Enbalaba.LocalStore.prototype = {

    _isSupported: !(typeof localStorage == 'undefined' || typeof JSON == 'undefined'),

    set: function (val) {
        if (this._isSupported) {
            if ($.isArray(val) && val.length > this.Config.MaxItems) {
                for (var i = 0, dif = val.length - this.Config.MaxItems; i < dif; i++) val.shift(); //remove X first elements
            }

            var s = JSON.stringify(val);

            if (s.length > this.Config.MaxTotalSize) return false; //todo: something more significant
            localStorage.setItem(this.Name, s);
            return true;
        }
    }

    /*Get the value associated with the store are. Can return null, except if Config.EmptyValue has been defined */
    , get: function () {
        if (this._isSupported) {
            var s = localStorage.getItem(this.Name);
            if (s != null && s != "") {
                return JSON.parse(s);
            }
            else if (this.Config.EmptyValue) return this.Config.EmptyValue;
        }
        if (this.Config.EmptyValue) return this.Config.EmptyValue;
        return null;
    }
};


Torrentz.GM.BobCatTorrentz.start();