Nyaa.se AutoDownloader

Automatically download anime torrents from Nyaa

当前为 2015-07-05 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        Nyaa.se AutoDownloader
// @namespace   Autodownload
// @author      Victorique
// @description Automatically download anime torrents from Nyaa
// @include     http://www.nyaa.se/?page=search&cats=*&filter=*&term=*&user=*
// @include     http://www.nyaa.se/?page=search&filter=*&term=*&user=*
// @include     http://www.nyaa.se/?page=search&term=*&user=*
// @version     4.1.0
// @grant       none
// @license     MIT
// @run-at      document-start
// @require     https://code.jquery.com/jquery-2.1.4.min.js
// ==/UserScript==

/*
var e = document.createElement("script");

e.src = 'http://localhost/userScripts/AutoDownloader.user.js';
e.type = "text/javascript";
document.getElementsByTagName("head")[0].appendChild(e);
*/

"use strict";

/* OBJECT CREATION START */
var Episode = (function () {
    /**
     * An Episode represents an table row in the current page
     * @param   {Number} res          The resolution used for this episode
     * @param   {String} downloadLink The download link for this episode
     * @param   {Number} seeds        The seed count for this episode
     * @param   {Number} leechers     The leech count for this episode
     * @param   {String} uid          The ID of this episode
     * @param   {Number} resides      The page that this Episode resides in
     * @returns {Object} the proto of itself
     */
    function Episode(res, downloadLink, seeds, leechers, uid, resides) {

        if (typeof res !== "number") {
            throw "res must be a number";
        }

        if (typeof downloadLink !== "string") {
            throw "downloadLink must be a string";
        }

        if (typeof seeds !== "number") {
            throw "seeds must be a number";
        }

        if (typeof leechers !== "number") {
            throw "leechers must be a number";
        }

        if (typeof uid !== "string") {
            throw "uid must be a string";
        }

        if (typeof resides !== "number") {
            throw "resides must be a number";
        }
        var _res = res;

        var _downloadLink = downloadLink;

        var _seeds = seeds;

        var _leechers = leechers;

        var _uid = uid;

        var _resides = resides;

        this.getRes = function () {
            return _res;
        };

        this.getDownloadLink = function () {
            return _downloadLink;
        };

        this.getSeeds = function () {
            return _seeds;
        };

        this.getLeechers = function () {
            return _leechers;
        };

        this.getUid = function () {
            return _uid;
        };

        this.getResides = function () {
            return _resides;
        };
        return this;
    }
    Episode.prototype = {
        constructor: Episode
    }
    return Episode;
}());

var Anime = (function () {

    var currentAnime = null;
    var currentSubber = null;
    /**
     * Array of Episode Objects
     */
    var eps = [];

    /**
     * Set the current Anime name
     * @param {String} anime The of the Anime
     */
    var setCurrentAnime = function (anime) {
        currentAnime = anime;
    };

    /**
     * Set the name of the current subber
     * @param {String} sub Name of the current subber
     */
    var setCurrentSubber = function (sub) {
        currentSubber = sub;
    };

    /**
     * Get the current Subber
     * @returns {String} The name of the Subber for this anime
     */
    var getCurrentSubber = function () {
        return currentSubber;
    };

    /**
     * Get the current anime name
     * @returns {String} Name of the anime
     */
    var getCurrentAnime = function () {
        return currentAnime;
    };


    /**
     * Get the avrage seeds for a specified res
     * @param   {Number} res The res to get the avg seeds for
     * @returns {Number} The avg seeds
     */
    var avgSeedsForRes = function (res) {
        if (typeof res !== "number") {
            throw "res Must be an int";
        }
        var seedCount = 0;
        if (getamountOfEpsFromRes(res) === 0) {
            return 0;
        }
        for (var i = 0; i < eps.length; i++) {
            var currentEp = eps[i];
            if (currentEp.getRes() === res) {
                seedCount += currentEp.getSeeds();
            }
        }
        return Math.round(seedCount = seedCount / getamountOfEpsFromRes(res));
    };

    /**
     * Get the avrage leechers for a specified res
     * @param   {Number} res The res to get the avg seeds for
     * @returns {Number} The avg leechers
     */
    var avgPeersForRes = function (res) {
        if (typeof res !== "number") {
            throw "res Must be an int";
        }
        var leechCount = 0;
        if (getamountOfEpsFromRes(res) === 0) {
            return 0;
        }
        for (var i = 0; i < eps.length; i++) {
            var currentEp = eps[i];
            if (currentEp.getRes() === res) {
                leechCount += currentEp.getLeechers();
            }
        }
        return Math.round(leechCount = leechCount / getamountOfEpsFromRes(res));
    };

    /**
     * Get the total amount of eps for a res
     * @param   {Number} res res
     * @returns {Number} The amount of eps for the res
     */
    var getamountOfEpsFromRes = function (res) {
        if (typeof res !== "number") {
            throw "res must be of type 'number'";
        }
        return getEpsForRes(res).length;
    };


    var _isjQueryObject = function (obj) {
        if (obj instanceof jQuery || 'jquery' in Object(obj)) {
            return true;
        } else {
            return false;
        }
    };

    /**
     * Add Episodes to the array
     * @param {Episode} ep The Anime object to add
     */
    var addEps = function (ep) {
        if (typeof ep !== "object" && !ep instanceof Episode) {
            throw "addEps must take an Episode object";
        }
        eps.push(ep);
    };


    /**
     * Add an array of Episode object to the Anime object
     * @param {Array} episode Array of Episode objects to add
     */
    var addAllEps = function (episode) {
        for (var i = 0; i < episode.length; i++) {
            var currEp = episode[i];
            if (typeof currEp !== "object" && !currEp instanceof Episode) {
                throw "addEps must take an Episode object";
            }
            eps.push(currEp);
        }
    };

    /**
     * Get the Anime objects for a specified res
     * @param   {Number}  res res to use
     * @returns {Episode} Array of Episodes that match the specified res
     */
    var getEpsForRes = function (res) {
        if (typeof res !== "number") {
            throw "res Must be an int";
        }
        var arrayOfEps = [];
        for (var i = 0; i < eps.length; i++) {
            var currentEp = eps[i];
            if (currentEp.getRes() === res) {
                arrayOfEps.push(currentEp);
            }
        }
        return arrayOfEps;
    };

    /**
     * Given a JQuery object that represents a "tr" of the table, this will return that Episode's UID
     * @param   {Object} obj The Jquery representation of a tr (table row)
     * @returns {String} The UID of that Episode
     */
    var getUidFromJqueryObject = function (obj) {
        if (!_isjQueryObject(obj)) {
            throw "Object must be of type 'Jquery'";
        }
        if (obj.is("tr")) {
            var anchor = (function () {
                var tableRows = obj.find("td.tlistdownload > a");
                if (tableRows.length > 1) {
                    throw "Object must be unique";
                }
                return _getUidFromAnchor(tableRows.get(0))
            }());
        } else {
            return null;
        }
        return anchor;
    };

    /**
     * Get the Episode from a given anchor tag
     * @param   {Object}  anchor Jquery or pure JS anchor dom element
     * @returns {Episode} The eipside that matches the Anchor
     */
    var getEpisodeFromAnchor = function (anchor) {
        var link = (function () {
            if (_isjQueryObject(anchor)) {
                return anchor.get(0);
            }
            return anchor;
        }());
        var uid = _getUidFromAnchor(link);
        return getEpisodeFromUid(uid);
    };

    /**
     * Get the Episode object given a UID
     * @param   {String}  uid The Episode UID
     * @returns {Episode} The Episode that matches the UID
     */
    var getEpisodeFromUid = function (uid) {
        if (typeof uid !== "string") {
            throw "uid must be of type String";
        }
        for (var i = 0; i < eps.length; i++) {
            var currentEp = eps[i];
            if (currentEp.getUid() === uid) {
                return currentEp;
            }
        }
        return null;
    };

    /**
     * Get an array of Episodes that from the page that it came from
     * @param   {Number}  resides The page where the Episode originated from
     * @param   {Boolean} exclude Match this page only, or exculde this page and return all other objects. if true, will return all Episodes that are not of the passed in page
     * @returns {Episode} Array of Episodes
     */
    var getEpisodesFromResidence = function (resides, exclude) {
        if (typeof resides !== "number") {
            throw "resides must be a number";
        }
        var arrayOfEps = [];
        for (var i = 0; i < eps.length; i++) {
            var currentEp = eps[i];
            if (exclude === true) {
                if (currentEp.getResides() !== resides) {
                    arrayOfEps.push(currentEp);
                }
            } else {
                if (currentEp.getResides() === resides) {
                    arrayOfEps.push(currentEp);
                }
            }
        }
        return arrayOfEps;
    };

    /**
     * Get the UID from an anchor tag
     * @param   {Object} anchor Dom element of an Anchor
     * @returns {String} The UID
     */
    var _getUidFromAnchor = function (anchor) {
        return anchor.href.split("=").pop();
    };

    /**
     * Remove an object from an array
     * @param {Array}  arr Array to search in
     * @param {Object} obj Object to remove
     */
    var _removeObjectFromArray = function (arr, obj) {
        var i = arr.length;
        while (i--) {
            if (arr[i] === obj) {
                arr.splice(i, 1);
            }
        }
    }

    /**
     * Get an array of all the pages avalible in the Anime (tables)
     * @returns {Array} Array of URLS
     */
    var getPageUrls = function () {
        var urls = [];
        $.each($('div.pages').filter(function (i) {
            return i === 0;
        }).find('a'), function (k, v) {
            urls.push(this.href);
        });
        return urls;
    };

    /**
     * Remove an episode from the Episode array based on UI
     * @param {String} uid the UID
     */
    var removeEpisodesFromUid = function (uid) {
        var episodes = getEpisodeFromUid(uid);
        for (var i = 0; i < episodes.length; i++) {
            var currentEp = episodes[i];
            _removeObjectFromArray(eps, currentEp);
        }
    };

    /**
     * Remove all episodes that match a page number
     * @param {Number}  resides The page number
     * @param {Boolean} exclude if true, this will remove all Episode objects that do not match the passed in page number. if false, removes all episodes that match the passed in page number
     */
    var removeEpisodesFromResidence = function (resides, exclude) {
        if (typeof exclude !== "boolean") {
            throw "excluse must be true or false";
        }
        var episodes = getEpisodesFromResidence(resides, exclude);
        for (var i = 0; i < episodes.length; i++) {
            var currentEp = episodes[i];
            _removeObjectFromArray(eps, currentEp);
        }
    };


    return {
        setCurrentAnime: setCurrentAnime,
        getCurrentAnime: getCurrentAnime,
        addEps: addEps,
        getEpsForRes: getEpsForRes,
        getamountOfEpsFromRes: getamountOfEpsFromRes,
        setCurrentSubber: setCurrentSubber,
        getCurrentSubber: getCurrentSubber,
        avgSeedsForRes: avgSeedsForRes,
        getUidFromJqueryObject: getUidFromJqueryObject,
        getEpisodeFromUid: getEpisodeFromUid,
        getEpisodeFromAnchor: getEpisodeFromAnchor,
        getPageUrls: getPageUrls,
        addAllEps: addAllEps,
        getEpisodesFromResidence: getEpisodesFromResidence,
        removeEpisodesFromUid: removeEpisodesFromUid,
        removeEpisodesFromResidence: removeEpisodesFromResidence,
        avgPeersForRes: avgPeersForRes
    };
}());


/** Utility functions ***/
var Utils = (function () {
    /**
     * Display a message to the use (built in alert)
     * @param   {String}  message    Message to show
     * @param   {Boolean} confirmMsg If true, message will be a confirm alert (yes or no)
     * @returns {Boolean} Returns true if confirmMsg is false, else returns true or false depending on the user selection
     */
    var displayMessage = function (message, confirmMsg) {
        if (typeof confirmMsg === "boolean") {
            if (confirmMsg === true) {
                return confirm(message);
            }
        }
        alert(message);
        return true;
    };

    /**
     * Disable the given button
     * @param {Object} button Jquery object of the button
     */
    var disableButton = function (button) {
        button.prop('disabled', true);
    };

    /**
     * Enable the given button
     * @param {Object} button Jquery object of the button
     */
    var enableButton = function (button) {
        button.prop('disabled', false);
    };

    /**
     * Do the downloads
     * @param {Object} event The event to decide if it is a download all or a downlaod selection (to make this method more abstract)
     */
    var doDownloads = function (event) {
        var type = $(event.target).data("type");
        var amountOfAnime;
        var collectionOfAnime;
        var userSelect;
        var _minSeeds = Options.Seeds.minSeeds === -1 ? 0 : Options.Seeds.minSeeds;
        if (type === "downloadAll") {
            var selectedRes = parseInt($("#downloadRes").val());
            amountOfAnime = Anime.getamountOfEpsFromRes(selectedRes);
            collectionOfAnime = Anime.getEpsForRes(selectedRes);
            userSelect = displayMessage("You are about to download " + amountOfAnime + " eps.\n\
This will cause " + amountOfAnime + " download pop-ups\nAre you sure you want to continue?\nIf there are a lot of eps, your browser might stop responding for a while.\
This is normal\nIf you are on Google Chrome, it will ask you to allow multiple-downloads\n\nYour min seeds restriction is: " + _minSeeds + "\
 This will not be reflected in this message download count, but all episodes with this restriction will be skipped", true);
        } else if (type === "downloadSelected") {
            amountOfAnime = $(".checkboxes:checked").length;
            collectionOfAnime = [];
            $.each($('.checkboxes:checked').prev('a'), function (k, v) {
                var episode = Anime.getEpisodeFromAnchor(this);
                collectionOfAnime.push(episode);
            });
            userSelect = displayMessage("You are about to download " + amountOfAnime + " items. if there are a lot of itmes, it might make your browser stop responding for a while This is normal\n\
            Chrome will ask you to accept multiple downloads ", true);
        } else {
            throw "Unexpected Error";
        }
        if (userSelect === false) {
            return;
        }
        for (var i = 0; i < collectionOfAnime.length; i++) {
            var currentEp = collectionOfAnime[i];
            if (_minSeedsSet() && type !== "downloadSelected") {
                if (currentEp.getSeeds() < minSeeds) {
                    continue;
                }
            }
            var currentDownloadLink = currentEp.getDownloadLink();
            var link = document.createElement("a");
            link.download = "";
            link.href = currentDownloadLink;
            link.click();
        }
    };

    /**
     * Returns if the checkbox is checked
     * @param   {Object}  checkbox The checkbox
     * @returns {Boolean} If ehcked or not
     */
    var checkBoxValid = function (checkbox) {
        return checkbox.is(":checked");
    };

    var _minSeedsSet = function () {
        return Options.Seeds.minSeeds !== -1 ? true : false;
        //(typeof Options.Seeds.minSeeds !== "undefined") ? 0: Options.Seeds.minSeeds;
    }

    /**
     * Build the download infomation table
     * @returns {String} The html of the built table
     */
    var buildTable = function () {
        var html = "";
        html += "<table style='width: 100%' id='info'>";

        html += "<caption>Download infomation</caption>";

        html += "<tr>";
        html += "<th>resolution</th>";
        html += "<th>Episode count</th>";
        html += "<th>Average seeds</th>";
        html += "<th>Average leechers</th>";
        html += "</tr>";

        html += "<tr>";
        html += "<td>1080p</td>";
        html += "<td>" + Anime.getamountOfEpsFromRes(1080) + "</td>";
        html += "<td>" + Anime.avgSeedsForRes(1080) + "</td>";
        html += "<td>" + Anime.avgPeersForRes(1080) + "</td>";
        html += "</tr>";

        html += "<tr>";
        html += "<td>720p</td>";
        html += "<td>" + Anime.getamountOfEpsFromRes(720) + "</td>";
        html += "<td>" + Anime.avgSeedsForRes(720) + "</td>";
        html += "<td>" + Anime.avgPeersForRes(720) + "</td>";
        html += "</tr>";


        html += "<tr>";
        html += "<td>480p</td>";
        html += "<td>" + Anime.getamountOfEpsFromRes(480) + "</td>";
        html += "<td>" + Anime.avgSeedsForRes(480) + "</td>";
        html += "<td>" + Anime.avgPeersForRes(480) + "</td>";
        html += "</tr>";

        html += "</table>";


        return html;
    };

    /**
     * Return the current page offset (what table page you are on)
     * @returns {Number} The offset
     */
    var getCurrentPageOffset = function () {
        return parseInt((typeof QueryString.offset === "undefined") ? 1 : QueryString.offset);
    };

    /**
     * Returns true of false if you can support HTML5 storeag
     * @returns {Boolean}
     */
    var html5StoreSupport = function () {
        try {
            return 'localStorage' in window && window['localStorage'] !== null;
        } catch (e) {
            return false;
        }
    };

    return {
        displayMessage: displayMessage,
        disableButton: disableButton,
        enableButton: enableButton,
        doDownloads: doDownloads,
        checkBoxValid: checkBoxValid,
        buildTable: buildTable,
        getCurrentPageOffset: getCurrentPageOffset,
        html5StoreSupport: html5StoreSupport
    };
}());

var DataParser = (function () {
    /**l
     * Parses a table and returns an array of Episodes from it
     * @param   {Object}  table Jquery representation of the anime table
     * @returns {Episode} Array of Episodes
     */
    var parseTable = function (table, currentPage) {
        var trRow = table.find("img[src='http://files.nyaa.se/www-37.png']").closest("tr");
        var p1080Only = $(trRow).filter(function (e) {
            return ($(this).children("td:nth-child(2)").text().indexOf("1080p") > -1 || $(this).children("td:nth-child(2)").text().indexOf("1920x1080") > -1);
        });

        var p720nly = $(trRow).filter(function (e) {
            return ($(this).children("td:nth-child(2)").text().indexOf("720p") > -1 || $(this).children("td:nth-child(2)").text().indexOf("1280x720")) > -1;
        });

        var p480nly = $(trRow).filter(function (e) {
            return ($(this).children("td:nth-child(2)").text().indexOf("480p") > -1 || $(this).children("td:nth-child(2)").text().indexOf("640x480") > -1);
        });
        var eps = [];
        $.each($(p1080Only), function (k, v) {
            var currentDownloadLink = $(this).find('td:nth-child(3) >a').attr('href');
            var seeds = (isNaN(parseInt($(this).find("td.tlistsn").text()))) ? 0 : parseInt($(this).find("td.tlistsn").text());
            var leech = (isNaN(parseInt($(this).find("td.tlistln").text()))) ? 0 : parseInt($(this).find("td.tlistln").text());
            var uid = Anime.getUidFromJqueryObject($(this));
            eps.push(new Episode(1080, currentDownloadLink, seeds, leech, uid, currentPage));
        });

        $.each($(p720nly), function (k, v) {
            var currentDownloadLink = $(this).find('td:nth-child(3) >a').attr('href');
            var seeds = (isNaN(parseInt($(this).find("td.tlistsn").text()))) ? 0 : parseInt($(this).find("td.tlistsn").text());
            var leech = (isNaN(parseInt($(this).find("td.tlistln").text()))) ? 0 : parseInt($(this).find("td.tlistln").text());
            var uid = Anime.getUidFromJqueryObject($(this));
            eps.push(new Episode(720, currentDownloadLink, seeds, leech, uid, currentPage));
        });


        $.each($(p480nly), function (k, v) {
            var currentDownloadLink = $(this).find('td:nth-child(3) >a').attr('href');
            var seeds = (isNaN(parseInt($(this).find("td.tlistsn").text()))) ? 0 : parseInt($(this).find("td.tlistsn").text());
            var leech = (isNaN(parseInt($(this).find("td.tlistln").text()))) ? 0 : parseInt($(this).find("td.tlistln").text());
            var uid = Anime.getUidFromJqueryObject($(this));
            eps.push(new Episode(480, currentDownloadLink, seeds, leech, uid, currentPage));
        });

        return eps;
    };
    return {
        parseTable: parseTable,
    };
}());

var QueryString = function () {
    var query_string = {};
    var query = window.location.search.substring(1);
    var vars = query.split('&');
    for (var i = 0; i < vars.length; i++) {
        var pair = vars[i].split('=');
        if (typeof query_string[pair[0]] === 'undefined') {
            query_string[pair[0]] = pair[1];
        } else if (typeof query_string[pair[0]] === 'string') {
            var arr = [
        query_string[pair[0]],
        pair[1]
      ];
            query_string[pair[0]] = arr;
        } else {
            query_string[pair[0]].push(pair[1]);
        }
    }
    return query_string;
}();

var Options = (function () {
    var Seeds = {};
    Object.defineProperty(Seeds, "minSeeds", {
        set: function (seeds) {
            if (typeof seeds !== "number") {
                throw "seeds must be a number";
            }
            this._minSeeds = seeds;
            if (Utils.html5StoreSupport() === true) { // also set it on the local DB
                if (this._minSeeds === -1) {
                    Localstore.removeMinSeedsFromStore();
                } else {
                    Localstore.setMinSeedsFromStore(this._minSeeds);
                }
            }
        },
        get: function () {
            return typeof this._minSeeds === "undefined" ? -1 : 0;
        }

    });

    return {
        Seeds: Seeds
    }
}());

//Local storeage object
var Localstore = {
    getMinSeedsFromStore: function () {
        return localStorage.getItem("minSeeds");
    },
    setMinSeedsFromStore: function (seeds) {
        localStorage.setItem('minSeeds', seeds);
    },
    removeMinSeedsFromStore: function () {
        localStorage.removeItem("minSeeds");
    }
};

// Download fix for firefox
HTMLElement.prototype.click = function () {
    var evt = this.ownerDocument.createEvent('MouseEvents');
    evt.initMouseEvent('click', true, true, this.ownerDocument.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
    this.dispatchEvent(evt);
};

/* OBJECT CREATION END */

$(document).ready(function () {
    init(); // init the pannel and set up objects and listeners
    if (Utils.checkBoxValid($(".checkboxes"))) { // TODO: Refactor this statement!
        Utils.enableButton($("#downloadCustomButton"));
    } else {
        Utils.disableButton($("#downloadCustomButton"));
    }
});

function init() {
    var doDownload = true;
    setAnimeObj();
    buildUi();
    bindListeners();
    if (Utils.html5StoreSupport()) {
        setOptionsFromLocalStor();
    }

    function setAnimeObj() {
        // Set currentAnime
        if (QueryString.term !== "") {
            Anime.setCurrentAnime(decodeURIComponent(QueryString.term).split("+").join(" "));
        } else {
            Anime.setCurrentAnime("Unknown");
        }

        // set subber
        Anime.setCurrentSubber($(".notice > a > span").html());

        // Set eps

        var eps = DataParser.parseTable($("table.tlist"), Utils.getCurrentPageOffset());

        Anime.addAllEps(eps);

    }

    function buildUi() {
        makeStyles();
        buildPanel();
        makeCheckBoxes();

        function makeStyles() {
            var styles = "";
            styles += ".panel{background-color: #fff; border: 1px solid transparent; border-radius: 4px; box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); margin-bottom: 20px;}";
            styles += ".panel-success {border-color: #d6e9c6;}";
            styles += ".panel-heading{   border-bottom: 1px solid transparent; border-top-left-radius: 3px; border-top-right-radius: 3px; padding: 4px 15px; text-align:center}";
            styles += ".panel-success > .panel-heading {background-color: #dff0d8; border-color: #d6e9c6; color: #3c763d;}";
            styles += ".panel-success > .panel-footer + .panel-collapse > .panel-body {border-bottom-color: #d6e9c6;}";
            styles += ".panel-footer {background-color: #f5f5f5; border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; border-top: 1px solid #ddd; padding: 10px 15px;}";
            styles += ".panel-title {color: inherit; margin-bottom: 0; margin-top: 0; padding: 6px;}";
            styles += ".panel-body {padding: 15px;}";
            styles += ".avgSeeds{floar:left; padding-right:10px; color:#3c763d;}";
            styles += ".checkboxes{left:1px; margin:0; padding:0; position: relative; top: 1px;}";

            styles += "#info, #info th, #info td {border: 1px solid black;border-collapse: collapse;}";
            styles += "#info th, #info td {padding: 5px;text-align: left;}";
            styles += "label[for='MinSeeds']{ display: block; margin-top: 10px;}";
            styles += "#SaveMinSeeds{margin-left:5px;}"

            styles += "#downloadCustomButton{float:right;}";
            $("<style>").prop("type", "text/css").html(styles).appendTo("head");
        }

        function buildPanel() {
            var html = "";
            html += '<div class="panel panel-success" style="margin-left: 9px; margin-top: 19px; width: 851px;">';

            html += '<div class="panel-heading">';
            html += '<h3 id="panel-title" class="panel-title"></h3>';
            html += "</div>";

            html += '<div class="panel-body" id="pannelContent"></div>';
            html += '<div class="panel-footer" id="panelFooter">';

            html += '</div>';

            html += '</div>';
            $(".content > .notice").after(html);
            buildPanelContent();

            function buildPanelContent() {
                var html = "";
                html += "<div>";
                $("#panel-title").html("<span> Download \"" + Anime.getCurrentAnime() + " (" + Anime.getCurrentSubber() + ")\"</span>");
                if (Anime.getamountOfEpsFromRes(480) === 0 && Anime.getamountOfEpsFromRes(720) === 0 && Anime.getamountOfEpsFromRes(1080) === 0) {
                    html += "<span> No translated anime found or error occured</span>";
                    html += "</div>";
                    $("#pannelContent").html(html);
                    doDownload = false;
                    return;
                }
                html += "<span>Pick a resolution: </span>";
                html += "<select style=\"margin-right:5px;\" id=\"downloadRes\">";
                if (Anime.getamountOfEpsFromRes(1080) >= 1) {
                    html += "<option value=\"1080\">1080p</option>";
                }
                if (Anime.getamountOfEpsFromRes(720) >= 1) {
                    html += "<option value=\"720\">720p</option>";
                }
                if (Anime.getamountOfEpsFromRes(480) >= 1) {
                    html += "<option value=\"480\">480p</option>";
                }
                html += "</select>";
                html += "<button type=\"button\" data-type='downloadAll' id=\"downloadAll\">Download all</button>";

                html += "<button type='button' id='downloadCustomButton' data-type='downloadSelected' >download your selected items</button>";

                html += "</div>";

                html += "<div id='options'>";


                html += "<label for='crossPage'> include Cross pages</label>";
                html += "<input type='checkbox' id='crossPage' /> ";

                html += "<label for='MinSeeds'>Minimum seeders:</label>";
                html += "<input type='number' min='0' id='MinSeeds' title='Any episode that is below this limit will be excluded from the download but not the infomation table (coming soon).'/>";
                html += "<button type='button' id='SaveMinSeeds'>Save</button>";

                html += "</div>";

                html += "<div id='tableInfo'>";
                html += Utils.buildTable();
                html += "</div>";

                $("#pannelContent").html(html);
            }
        }

        function makeCheckBoxes() {
            $(".tlistdownload > a").after("<input class='checkboxes' type='checkbox'/>");
        }
    }

    function bindListeners() {
        $("#downloadAll").on("click", function (e) {
            if (doDownload === true) {
                Utils.doDownloads(e);
            }
        });

        $("#downloadCustomButton").on("click", function (e) {
            if (doDownload === true) {
                Utils.doDownloads(e);
            }
        });

        $(".checkboxes").on("click", function (e) {
            if (Utils.checkBoxValid($(".checkboxes"))) {
                Utils.enableButton($("#downloadCustomButton"));
            } else {
                Utils.disableButton($("#downloadCustomButton"));
            }
        });

        $("#crossPage").on("click", function (e) {
            if (Utils.checkBoxValid($("#crossPage"))) {
                $("#tableInfo").html("<p>Please wait while we parse each page...</p>");
                $("#crossPage").prop("disabled", true);
                Anime.getPageUrls().reduce(function (prev, cur, index) {
                    return prev.then(function (data) {
                        return $.ajax(cur).then(function (data) {
                            var currentPage = cur.split("=").pop();
                            if (cur.indexOf("offset") === -1) {
                                currentPage = 1;
                            }
                            var table = $(data).find("table.tlist");

                            Anime.addAllEps(DataParser.parseTable(table, parseInt(currentPage)));
                            $("#tableInfo").append("<div>Page " + currentPage + " Done </div>")
                        });
                    })
                }, $().promise()).done(function () {
                    $("#tableInfo").html(Utils.buildTable());
                    $("#crossPage").prop("disabled", false);
                });
            } else { // when un-chekced, clear the Episodes of all eps that are not of the current page
                $("#tableInfo").html("<p>Please wait while we re-calculate the Episodes</p>");
                var currentPage = Utils.getCurrentPageOffset();
                Anime.removeEpisodesFromResidence(currentPage, true);
                $("#tableInfo").html(Utils.buildTable());
            }
        });

        $("#SaveMinSeeds").on("click", function (e) {
            if (parseInt($("#MinSeeds").val()) < 0) {
                alert("number cannot be negative");
                return;
            }
            var value = parseInt($("#MinSeeds").val() === "" ? -1 : $("#MinSeeds").val());
            Options.Seeds.minSeeds = value;
            if (value === -1) {
                alert("Minimum seeds have been cleared");
            } else {
                alert("Minimum seeds now set to: " + value);
            }
        });
    }

    function setOptionsFromLocalStor() {
        // Min seeds
        if (Localstore.getMinSeedsFromStore() !== null) {
            var minSeeds = parseInt(Localstore.getMinSeedsFromStore());
            Options.Seeds.minSeeds = minSeeds;
            $("#MinSeeds").val(minSeeds);
        }
    }
}