Nyaa.si Batch downloader

Batch download torrents from nyaa.si

目前為 2018-01-30 提交的版本,檢視 最新版本

// ==UserScript==
// @name        Nyaa.si Batch downloader
// @namespace   Autodownload
// @author      Victorique
// @description Batch download torrents from nyaa.si
// @include     *://nyaa.si/user/*?q=*
// @include     *://nyaa.si/user/*?f=*&c=*&q=*
// @version     6.1.2
// @icon        https://i.imgur.com/nx5ejHb.png
// @license     MIT
// @run-at      document-idle
// @grant       none
// @require     https://gf.qytechs.cn/scripts/19117-jsutils/code/JsUtils.js
// @require     https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.3/FileSaver.min.js
// ==/UserScript==
/*
var e = document.createElement("script");

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

/* OBJECT CREATION START */
'use strict';
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
     * @param   {String} title        The title of the episode
     * @param   {Number} size          The size in MB of the episode
     * @returns {Object} the proto of itself
     */
    function Episode(res, downloadLink, seeds, leechers, uid, resides, title, size) {
        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';
        }
        if (typeof title !== 'string') {
            throw 'Title must be a string';
        }
        if (typeof size !== 'number') {
            throw 'size must be a number';
        }
        var _res = res;
        var _downloadLink = downloadLink;
        var _seeds = seeds;
        var _leechers = leechers;
        var _uid = uid;
        var _resides = resides;
        var _title = title;
        var _size = size;
        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;
        };
        this.getTitle = function () {
            return _title;
        };
        this.getSize = function () {
            return _size;
        };
        return this;
    }
    Episode.prototype = {
        constructor: Episode
    };
    return Episode;
}());
var Anime = (function () {
    var currentAnime = null;
    var currentSubber = null;
    var _AbstractEps = (function () {
        /**
         * Array of Episode Objects
         */
        var eps = [
    ];
        var abstractGetEps = function (skipSeedLimit) {
            if (typeof skipSeedLimit !== 'boolean') {
                throw 'skipOptions must be true or false';
            }
            var minSeeds = Options.Seeds.minSeeds;
            if (minSeeds > -1 && skipSeedLimit == false) {
                var arrayOfEps = [
        ];
                for (let i = 0, len = eps.length; i < len; i++) {
                    var currentEp = eps[i];
                    if (currentEp.getSeeds() < minSeeds) {
                        continue;
                    }
                    arrayOfEps.push(currentEp);
                }
                return arrayOfEps;
            } else {
                return eps;
            }
        };
        var addEp = function (ep) {
            if (!(ep instanceof Episode)) {
                throw 'addEp must take an Episode object';
            }
            if (_validRes(ep.getRes()) === false) {
                throw new TypeError('The Episode supplied does not have a valid resolution');
            }
            for (let i = 0, len = eps.length; i < len; i++) {
                var epi = eps[i];
                if (Utils.deepEquals(epi, ep)) {
                    console.warn('The episode supplied already exsists, this episode has been ignored');
                    return;
                }
            }
            eps.push(ep);
        };
        var removeEpisodeFromAnime = function (obj) {
            var arr = eps;
            let i = arr.length;
            while (i--) {
                if (arr[i] === obj) {
                    arr.splice(i, 1);
                }
            }
        };
        return {
            abstractGetEps: abstractGetEps,
            addEp: addEp,
            removeEpisodeFromAnime: removeEpisodeFromAnime
        };
    }());
    /**
     * Array of available resolutions on the page
     */
    var availableRes = [
  ];
    /**
     * Array of supported resolutions for this program
     */
    var supportedRes = [
        {
            'id': 1,
            'res': 1080,
            'fullRes': '1920x1080'
    },
        {
            'id': 2,
            'res': 720,
            'fullRes': '1280x720'
    },
        {
            'id': 3,
            'res': 480,
            'fullRes': '640x480'
    },
        {
            'id': 4,
            'res': 360,
            'fullRes': '640x360'
    }
  ];
    /**
     * Set the current Anime name
     * @param {String} anime The of the Anime
     */
    var setCurrentAnime = function (anime) {
        if (anime === '*') {
            anime = 'Everything';
        }
        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;
    };
    var getSupportedRes = function () {
        return supportedRes;
    };
    var addSupportedRes = function (ResObj) {
        if (typeof ResObj !== 'object') {
            throw 'ResObj must be a object';
        }
        supportedRes.push(ResObj);
    };
    var getAvailableResolutions = function () {
        return availableRes;
    };
    var addAvailableResolutions = function (res, fullRes) {
        if (typeof res !== 'number') {
            throw 'res must be of type number';
        }
        if (typeof fullRes !== 'string' && fullRes !== null) {
            throw 'Full res must be a string or null';
        }
        if (_resExists(res)) {
            return;
        }
        availableRes.push({
            'res': res,
            'fullRes': fullRes
        });
    };
    var removeAvailableResolutions = function (resToRemove) {
        if (typeof resToRemove !== 'number' && typeof resToRemove !== 'string') {
            throw 'the res to remove can only be a number or string';
        }
        for (let i = 0; i < availableRes.length; i++) {
            var currentRes = availableRes[i];
            for (var res in currentRes) {
                if (currentRes.hasOwnProperty(res)) {
                    var localRes = currentRes[res];
                    if (localRes === resToRemove) {
                        availableRes._remove_(currentRes);
                    }
                }
            }
        }
    };
    var _resExists = function (_res) {
        for (let i = 0; i < availableRes.length; i++) {
            var currentRes = availableRes[i];
            for (var res in currentRes) {
                if (currentRes.hasOwnProperty(res)) {
                    var localRes = currentRes[res];
                    if (localRes === _res) {
                        return true;
                    }
                }
            }
        }
        return false;
    };
    /**
     * 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, skipSeedLimit) {
        if (typeof res !== 'number') {
            throw 'res Must be an number';
        }
        if (typeof skipSeedLimit !== 'boolean') {
            throw 'skipSeedLimit Must be an boolean';
        }
        var seedCount = 0;
        if (getamountOfEpsFromRes(res, skipSeedLimit) === 0) {
            return 0;
        }
        var eps = _AbstractEps.abstractGetEps(skipSeedLimit);
        for (let i = 0, len = eps.length; i < len; i++) {
            var currentEp = eps[i];
            if (currentEp.getRes() === res) {
                seedCount += currentEp.getSeeds();
            }
        }
        return Math.round(seedCount = seedCount / getamountOfEpsFromRes(res, skipSeedLimit));
    };
    /**
     * 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, skipSeedLimit) {
        if (typeof res !== 'number') {
            throw 'res Must be an number';
        }
        if (typeof skipSeedLimit !== 'boolean') {
            throw 'skipSeedLimit Must be an boolean';
        }
        var leechCount = 0;
        if (getamountOfEpsFromRes(res, skipSeedLimit) === 0) {
            return 0;
        }
        var eps = _AbstractEps.abstractGetEps(skipSeedLimit);
        for (let i = 0, len = eps.length; i < len; i++) {
            var currentEp = eps[i];
            if (currentEp.getRes() === res) {
                leechCount += currentEp.getLeechers();
            }
        }
        return Math.round(leechCount = leechCount / getamountOfEpsFromRes(res, skipSeedLimit));
    };
    var getTotalSizeForRes = function (res, skipSeedLimit, decimals) {
        if (typeof res !== 'number') {
            throw 'res Must be an number';
        }
        if (typeof skipSeedLimit !== 'boolean') {
            throw 'skipSeedLimit Must be an boolean';
        }
        var eps = getEpsForRes(res, skipSeedLimit);
        return Utils.getHumanReadableSize(eps, decimals);
    };
    var getTotalSizeFromEps = function (eps, decimals) {
        if (!Array.isArray(eps) && !(eps instanceof Episode)) {
            throw 'eps Must be an array or a single Episode';
        }
        return Utils.getHumanReadableSize(eps, decimals);
    };
    /**
     * 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, skipSeedLimit) {
        if (typeof res !== 'number') {
            throw 'res must be of type \'number\'';
        }
        return getEpsForRes(res, skipSeedLimit).length;
    };
    /**
     * Add Episodes to the array
     * @param {Episode} ep The Anime object to add
     */
    var addEps = function (ep) {
        _AbstractEps.addEp(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 (let i = 0; i < episode.length; i++) {
            _AbstractEps.addEp(episode[i]);
        }
    };
    var _validRes = function (res) {
        return _resExists(res); // if the res exists, then it's valid
    };
    /**
     * 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, skipSeedLimit) {
        if (typeof res !== 'number') {
            throw 'res Must be an int';
        }
        var arrayOfEps = [
    ];
        var eps = _AbstractEps.abstractGetEps(skipSeedLimit);
        for (let i = 0, len = eps.length; i < len; 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 (!Utils.isjQueryObject(obj)) {
            throw 'Object must be of type \'Jquery\'';
        }
        if (obj.is('tr')) {
            var anchor = (function () {
                let currectTd = getNameTr(obj);
                var tableRows = currectTd.find('a:not(a.comments)');
                if (tableRows.length > 1) {
                    throw 'Object must be unique';
                }
                return _getUidFromAnchor(tableRows.get(0));
            }());
            return anchor;
        }
        return null;
    };

    function getNameTr(obj) {
        return obj.find('td:nth-child(2)');
    }

    /**
     * Get the Episode from a given anchor tag or url
     * @param   {Object}  anchor Jquery or pure JS anchor dom element or URL String
     * @returns {Episode} The eipside that matches the Anchor
     */
    var getEpisodeFromAnchor = function (anchor) {
        var link = (function () {
            if (Utils.isjQueryObject(anchor)) {
                return anchor.get(0);
            }
            return anchor;
        }());
        var uid = _getUidFromAnchor(link);
        return getEpisodeFromUid(uid, true);
    };
    /**
     * 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, skipSeedLimit) {
        if (typeof uid !== 'string') {
            throw 'uid must be of type String';
        }
        var eps = _AbstractEps.abstractGetEps(skipSeedLimit);
        for (let i = 0, len = eps.length; i < len; 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, skipSeedLimit) {
        if (typeof resides !== 'number') {
            throw 'resides must be a number';
        }
        var arrayOfEps = [
    ];
        var eps = _AbstractEps.abstractGetEps(skipSeedLimit);
        for (let i = 0, len = eps.length; i < len; 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) {
        if (typeof anchor === 'string') {
            if (anchor.indexOf(".torrent") > -1) {
                return anchor.replace(".torrent", "").split("/").pop();
            }
            return anchor.split("/").pop();
        }
        return anchor.href.split("/").pop();
    };

    var getTdFromTable = function getTdFromTable(table, index) {
        return table.find('td:nth-child(' + index + ')');
    };
    /**
     * Get an array of all the pages avalible in the Anime (tables)
     * @returns {Array} Array of URLS
     */
    var getPageUrls = function () {

        function range(start, end) {
            return Array(end - start + 1).fill().map((_, idx) => start + idx)
        }

        let pages = $(".center > ul.pagination a");
        if (pages.length === 0) {
            return [];
        }
        let firstPage = ObjectUtil.getElementFromJqueryArray(pages, 1);
        let lastPage = ObjectUtil.getElementFromJqueryArray(pages, pages.length - 2);

        let firstPageNumber = Number.parseInt(firstPage.text());
        let lastPageNumber = Number.parseInt(lastPage.text());
        let rangeBetween = range(firstPageNumber, lastPageNumber);
        let baseUrl = window.location.href;
        let urls = [];
        var currentPage = QueryString.p === undefined ? 1 : QueryString.p;
        for (let i = 0; i < rangeBetween.length; i++) {
            let num = rangeBetween[i];
            if (num == currentPage) { // skip current page
                continue;
            }
            let newUrl = UrlUtils.addParameter(baseUrl, "p", num.toString());
            urls.push(newUrl);
        }
        return urls;
    };
    /**
     * Remove an episode from the Episode array based on UI
     * @param {String} uid the UID
     */
    var removeEpisodesFromUid = function (uid) {
        var episode = getEpisodeFromUid(uid, true);
        _AbstractEps.removeEpisodeFromAnime(episode);
    };
    /**
     * 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 eps = getEpisodesFromResidence(resides, exclude, true);
        for (let i = 0, len = eps.length; i < len; i++) {
            var currentEp = eps[i];
            _AbstractEps.removeEpisodeFromAnime(currentEp);
        }
    };
    var getAmountOfEps = function () {
        return _AbstractEps.abstractGetEps(true).length;
    };
    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,
        getAmountOfEps: getAmountOfEps,
        addAvailableResolutions: addAvailableResolutions,
        getAvailableResolutions: getAvailableResolutions,
        removeAvailableResolutions: removeAvailableResolutions,
        getSupportedRes: getSupportedRes,
        addSupportedRes: addSupportedRes,
        getTotalSizeForRes: getTotalSizeForRes,
        getTotalSizeFromEps: getTotalSizeFromEps,
        getTdFromTable: getTdFromTable
    };
}());
/** Utility functions ***/
var Utils = (function () {
    var sortSelect = function (selElem) {
        var tmpAry = [];
        for (let i = 0, length = selElem.options.length; i < length; i++) {
            tmpAry[i] = [];
            tmpAry[i][0] = selElem.options[i].text;
            tmpAry[i][1] = selElem.options[i].dataset.url;
        }
        tmpAry.sort(function (a, b) {
            return a[0].toUpperCase().localeCompare(b[0].toUpperCase());
        });

        selElem.innerHTML = "";

        for (let i = 0, len = tmpAry.length; i < len; i++) {
            var op = new Option(tmpAry[i][0]);
            op.dataset.url = tmpAry[i][1];
            selElem.options[i] = op;
        }
    };

    let getTable = function getTable() {
        return $('table');
    };

    let getQueryFromUrl = function getQueryFromUrl(url) {
        var obj = url.split("&").reduce(function (prev, curr, i, arr) {
            var p = curr.split("=");
            prev[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
            return prev;
        }, {});
        return obj;
    };

    let isjQueryObject = function (obj) {
        return ObjectUtil.isjQuery(obj);
    };
    /**
     * 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) {
        $('#crossPage').prop('disabled', true);
        var type = $(event.target).data('type');
        var amountOfAnime;
        var collectionOfAnime;
        var download = false;
        var html = UI.builDownloadAlert(type);
        var urlsToDownload = [
    ];
        $('#alertUser').html(html).slideDown("slow").show();
        if (type === 'downloadSelected') {
            $.each($('.checkboxes:checked').prev('a'), function (k, v) {
                var ep = Anime.getEpisodeFromAnchor(this);
                urlsToDownload.push(ep.getDownloadLink());
            });
        } else if (type === 'downloadSelects') {
            $.each($('#animeSelection option:selected'), function (k, v) {
                var url = this.dataset.url;
                urlsToDownload.push(url);
            });
        } else {
            var eps = Anime.getEpsForRes(parseInt($('#downloadRes').val()), false);
            for (let i = 0, len = eps.length; i < len; i++) {
                urlsToDownload.push(eps[i].getDownloadLink());
            }
        }
        bindAlertControls();

        function bindAlertControls() {
            $('#alertButtonCancel').on('click', function () {
                $('#alertUser').slideUp('slow');
                $('#crossPage').prop('disabled', false);
            });
            $('#alertButton').on('click', function () {
                doIt(urlsToDownload);
            });
        }

        function doIt(urls) {
            for (let i = 0; i < urls.length; i++) {
                let currentUrl = urls[i];
                AjaxUtils.downloadViaJavaScript(currentUrl, undefined, function (saveFunc) {
                    saveFunc();
                }, undefined, undefined, "GET");
            }
            $('#alertUser').slideUp('slow');
            $('#crossPage').prop('disabled', false);
        }
    };
    /**
     * 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;
    };
    /**
     * Return the current page offset (what table page you are on)
     * @returns {Number} The offset
     */
    var getCurrentPageOffset = function () {
        return parseInt((typeof QueryString.p === 'undefined') ? 1 : QueryString.p);
    };
    /**
     * 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;
        }
    };
    var cleanAvailableResolutions = function () {
        var avRes = ArrayUtils.arrayCopy(Anime.getAvailableResolutions(), true);
        var resLength = avRes.length;
        for (let i = 0, len = avRes.length; i < len; i++) {
            var currentRes = avRes[i].res;
            if (Anime.getamountOfEpsFromRes(currentRes, true) === 0) {
                Anime.removeAvailableResolutions(currentRes);
            }
        }
    };
    var sortAllControls = function () {
        sortSelect(document.getElementById('animeSelection'));
        sortSelect(document.getElementById('downloadRes'));
        $('#info').sortTable(0);
    }
    var reBindSelectFilters = function () {
        $('input[name=\'filterSelect\']').offOn('change', handleSelect);
        $('#clearResOptions').offOn('click', handleSelect);

        $("#animeSelection").offOn("click", function () {
            UI.autoEnableAcceptSelect();
        });

        $("#selectAllFromControl").offOn("click", function () {
            let allSelected = $("#animeSelection option:selected").length === $("#animeSelection option").length;
            if (allSelected) {
                $(this).text("Select all");
                $("#animeSelection option").prop("selected", false);
            } else {
                $(this).text("deselect all");
                $("#animeSelection option").prop("selected", true);
            }
            UI.autoEnableAcceptSelect();
        });

        function handleSelect(event) {
            var resTOFilter = $(event.target).data('set');
            $('#selectAnime').html(UI.buildSelect(resTOFilter));
            Utils.sortAllControls();
            var searchApplied = UI.getAppliedSearch();
            if (searchApplied !== '') {
                UI.applySearch(searchApplied);
            }
            reBindSelectFilters();
        }
    };
    var equals = function (episode, toEqual) {
        if (!(episode instanceof Episode && toEqual instanceof Episode)) {
            throw 'both objects must be episodes';
        }
        return episode.getUid() === toEqual.getUid();
    };
    var deepEquals = function (episode, toEqual) {
        if (!(episode instanceof Episode && toEqual instanceof Episode)) {
            throw 'both objects must be episodes';
        }
        for (var methods in episode) {
            if (episode.hasOwnProperty(methods)) {
                if (typeof episode[methods] === 'function') {
                    var method = episode[methods];
                    var method2 = toEqual[methods];
                    if (method.call(this) !== method2.call(this)) {
                        return false;
                    }
                }
            }
        }
        return true;
    };
    var getHumanReadableSize = function (from, decimals) {
        var bits = 0;
        if (Array.isArray(from)) {
            for (let i = 0; i < from.length; i++) {
                var ep = from[i];
                bits += ep.getSize();
            }
        } else if (typeof from === 'number') {
            bits = from;
        } else {
            bits += from.getSize();
        }

        function formatBytes(bytes, decimals) {
            if (bytes == 0) {
                return '0 Byte';
            }
            var k = 1024;
            var dm = decimals + 1 || 3;
            var sizes = [
        'Bytes',
        'KB',
        'MB',
        'GB',
        'TB',
        'PB',
        'EB',
        'ZB',
        'YB'
      ];
            let i = Math.floor(Math.log(bytes) / Math.log(k));
            return (bytes / Math.pow(k, i)).toPrecision(dm) + ' ' + sizes[i];
        }
        return formatBytes(bits, decimals);
    };
    return {
        disableButton: disableButton,
        enableButton: enableButton,
        doDownloads: doDownloads,
        checkBoxValid: checkBoxValid,
        getCurrentPageOffset: getCurrentPageOffset,
        html5StoreSupport: html5StoreSupport,
        cleanAvailableResolutions: cleanAvailableResolutions,
        sortAllControls: sortAllControls,
        reBindSelectFilters: reBindSelectFilters,
        sortSelect: sortSelect,
        isjQueryObject: isjQueryObject,
        equals: equals,
        deepEquals: deepEquals,
        getHumanReadableSize: getHumanReadableSize,
        getQueryFromUrl: getQueryFromUrl,
        getTable: getTable
    };
}());

function loading() {}
var UI = (function () {
    var epsInSelect = [
  ];
    var searchApplied = '';
    /**
     * Build the download infomation table
     * @returns {String} The html of the built table
     */
    var buildTable = function () {
        var html = '';
        html += '<table class="table table-responsive" style=\'width: 100%\' id=\'info\'>';
        html += '<caption>Download infomation</caption>';
        html += '<thead>';
        html += '<tr>';
        html += '<th>resolution</th>';
        html += '<th>Episode count</th>';
        html += '<th>Average seeds</th>';
        html += '<th>Average leechers</th>';
        html += '<th>Total size</th>';
        html += '</tr>';
        html += '</thead>';
        html += '<tbody>';
        var allRes = Anime.getAvailableResolutions();
        for (let i = 0; i < allRes.length; i++) {
            var currRes = allRes[i];
            var localRes = currRes.res;
            html += '<tr>';
            html += '<td>' + (localRes === -1 ? 'Others' : localRes + 'p') + '</td>';
            html += '<td>' + Anime.getamountOfEpsFromRes(localRes, true) + '</td>';
            html += '<td>' + Anime.avgSeedsForRes(localRes, true) + '</td>';
            html += '<td>' + Anime.avgPeersForRes(localRes, true) + '</td>';
            html += '<td>' + Anime.getTotalSizeForRes(localRes, true) + ' (aprox)</td>';
            html += '</tr>';
        }
        html += '</tbody>';
        html += '</table>';
        return html;
    };

    var stateChangeAcceptSelect = function stateChangeAcceptSelect(state) {
        $("#acceptSelect").enableButton(state);
    };

    var autoEnableAcceptSelect = function autoEnableAcceptSelect() {
        let selection = $("#animeSelection option:selected");
        if (selection.length > 0) {
            UI.stateChangeAcceptSelect(true)
        } else {
            UI.stateChangeAcceptSelect(false);
        }
    };


    var buildDropdownSelections = function () {
        var html = '';
        html += '<select class="form-control" style="margin-right:5px;display: inline;width: auto;" id="downloadRes">';
        var allRes = Anime.getAvailableResolutions();
        for (let i = 0; i < allRes.length; i++) {
            var currRes = allRes[i];
            var localRes = currRes.res;
            html += '<option value=' + localRes + '>' + (localRes === -1 ? 'Others' : localRes + 'p') + '</option>';
        }
        html += '</select>';
        return html;
    };
    var builDownloadAlert = function (type) {
        if (typeof type !== 'string') {
            throw 'type must a string';
        }
        var amountOfAnime = 0;
        var selectedRes = parseInt($('#downloadRes').val());
        var res = null;
        var totalSize = null;
        if (type === 'downloadSelected') {
            amountOfAnime = $('.checkboxes:checked').length;
            res = 'custom';
        } else if (type === 'downloadSelects') {
            amountOfAnime = $('#animeSelection option:selected').length;
            totalSize = Utils.getHumanReadableSize((function () {
                var localSize = 0;
                $('#animeSelection option:selected').each(function (k, v) {
                    var url = this.dataset.url;
                    var epSize = Anime.getEpisodeFromAnchor(url).getSize();
                    localSize += epSize;
                });
                return localSize;
            }()));
            res = 'custom';
        } else {
            amountOfAnime = Anime.getamountOfEpsFromRes(selectedRes, false);
            res = selectedRes === -1 ? 'Others' : selectedRes + 'p';
            totalSize = Anime.getTotalSizeForRes(parseInt(res), false);
        }
        var seedLimit = Options.Seeds.minSeeds === -1 ? 'None' : Options.Seeds.minSeeds;
        var html = '';
        html += '<div class=\'alert alert-success\'>';
        html += '<div><strong>Download: ' + res + '</strong></div> <br />';
        html += '<div><strong>Seed Limit: ' + seedLimit + '</strong></div>';
        if (totalSize !== null) {
            html += '<br /><div><strong>Total size: ' + totalSize + ' (aprox)</strong></div>';
        }
        html += '<p>You are about to download ' + amountOfAnime + ' ep(s)</p>';
        html += '<p>This will cause ' + amountOfAnime + ' download pop-up(s) Are you sure you want to continue?</p>';
        html += '<p>If there are a lot of eps, your browser might stop responding for a while. This is normal. If you are on Google Chrome, it will ask you to allow multiple-downloads</p>';
        html += '<button type="button" class="btn btn-success" id=\'alertButton\'>Okay</button>';
        html += '<button type="button" class="btn btn-warning" id=\'alertButtonCancel\'>Cancel</button>';
        html += '</div>';
        return html;
    };
    var showAjaxErrorAlert = function (AjaxInfo) {
        if (!$('#parseErrors').is(':hidden')) {
            return;
        }
        $('#parseErrors').html('');
        var html = '';
        html += '<div class=\'alert alert-danger\'>';
        html += '<p>There was an error in getting the infomation from page: \'' + AjaxInfo.error.pageAtError + '\'</p>';
        html += '<p>The last successful page parsed was page number ' + (AjaxInfo.currentPage === null ? 1 : AjaxInfo.currentPage) + ' </p>';
        html += '<button id=\'errorClose\'> close </button>';
        html += '</div>';
        $('#parseErrors').html(html);
        $('#parseErrors').slideDown('slow');
        $('#errorClose').off('click').on('click', function () {
            $('#parseErrors').slideUp('slow', function () {
                $(this).html('');
            });
        });
    };
    var buildSelect = function (resTOFilter) {
        resTOFilter = typeof resTOFilter === 'undefined' ? 'none' : resTOFilter;
        var html = '';
        epsInSelect = [];
        html += '<div id=\'selectWrapper\'>';
        html += '<div id=\'selectContainer\'>';
        html += '<p>Or you can select episodes here:</p>';
        html += '<p>Seed limit: ' + (Options.Seeds.minSeeds === -1 ? 'None' : Options.Seeds.minSeeds) + '</p>'
        html += '<select class="form-control" id=\'animeSelection\' multiple size=\'20\'>';
        var allRes = Anime.getAvailableResolutions();
        for (let i = 0; i < allRes.length; i++) {
            var currRes = allRes[i];
            var localRes = currRes.res;
            var eps = Anime.getEpsForRes(localRes, false);
            for (var j = 0, len = eps.length; j < len; j++) {
                var currentEp = eps[j];
                if (resTOFilter == 'none' || currentEp.getRes() == resTOFilter) {
                    html += '<option data-url=\'' + currentEp.getDownloadLink() + '\'>';
                    html += currentEp.getTitle() + ' - Seeders: ' + currentEp.getSeeds();
                    epsInSelect.push(currentEp);
                    html += '</option>';
                } else {
                    break;
                }
            }
        }
        html += '</select>';
        html += '<span>Filter select control: </span>';
        var checked = false;
        for (let i = 0; i < allRes.length; i++) {
            if (resTOFilter == allRes[i].res) {
                checked = true;
            }
            html += '<input type=\'radio\' ' + (checked ? 'checked' : '') + ' data-set= \'' + allRes[i].res + '\' name=\'filterSelect\'/>' + '<label class="filterLabel">' + (allRes[i].res === -1 ? 'Others' : allRes[i].res + 'p') + '</label>';
            checked = false;
        }
        html += '<a id=\'clearResOptions\' data-set=\'none\' >clear resolution filter</a>';
        html += '<a id=\'selectAllFromControl\'>Select all</a>';
        html += '</div>';
        html += '</div>';
        $("#acceptSelect").enableButton(false);
        return html;
    };
    var applySearch = function (textToFilter) {
        var opts = epsInSelect;
        var rxp = new RegExp(textToFilter);
        var optlist = $('#animeSelection').empty();
        for (let i = 0, len = opts.length; i < len; i++) {
            var ep = opts[i];
            if (rxp.test(ep.getTitle())) {
                optlist.append('<option data-url=\'' + ep.getDownloadLink() + '\'>' + ep.getTitle() + ' - Seeders: ' + ep.getSeeds() + '</option>');
            }
        }
        searchApplied = textToFilter;

        Utils.sortSelect(document.getElementById("animeSelection"));

        UI.autoEnableAcceptSelect();
    };
    var getAppliedSearch = function () {
        return searchApplied;
    };
    var getEpsInSelect = function () {
        return epsInSelect;
    };
    return {
        buildDropdownSelections: buildDropdownSelections,
        buildTable: buildTable,
        builDownloadAlert: builDownloadAlert,
        showAjaxErrorAlert: showAjaxErrorAlert,
        buildSelect: buildSelect,
        getEpsInSelect: getEpsInSelect,
        applySearch: applySearch,
        getAppliedSearch: getAppliedSearch,
        stateChangeAcceptSelect: stateChangeAcceptSelect,
        autoEnableAcceptSelect: autoEnableAcceptSelect
    };
}());
var DataParser = (function () {
    var table = null;
    let isParsing = false;
    var setTable = function (_table) {
        table = _table;
    };
    /**
     * 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 (currentPage) {
        if (table === null) {
            throw 'no table to parse on, table is null';
        }
        var trRow = table.find('img[src*=\'/static/img/icons/nyaa/1_2.png\']').closest('tr');
        var eps = [];
        $.each($(trRow), function (k, v) {
            var Dres = parseRes(this);
            if (Dres === -1) {
                Anime.addAvailableResolutions(-1, null);
            } else {
                Anime.addAvailableResolutions(Dres.res, Dres.fullRes);
            }
            let info = getEpisodeInfo(this);
            eps.push(new Episode(typeof Dres.res === ('undefined') ? -1 : Dres.res, info.currentDownloadLink, info.seeds, info.leech, info.uid, currentPage, info.title, info.size));
        });
        return eps;

        function parseRes(eventContent) {
            var suppRes = Anime.getSupportedRes();

            for (let i = 0; i < Anime.getSupportedRes().length; i++) {
                var currRes = suppRes[i].res;
                var currFullRes = suppRes[i].fullRes;
                if ($(eventContent).children('td:nth-child(2)').text().indexOf(currRes + 'p') > -1 || $(eventContent).children('td:nth-child(2)').text().indexOf(currFullRes) > -1) {
                    return suppRes[i];
                }
            }
            return -1;
        }

        function getEpisodeInfo(eventContent) {
            eventContent = $(eventContent);
            let currentDownloadLink = Anime.getTdFromTable(eventContent, 3).find("a")[0].href;
            let seeds = (isNaN(parseInt(Anime.getTdFromTable(eventContent, 6).text()))) ? 0 : parseInt(Anime.getTdFromTable(eventContent, 6).text());
            let leech = (isNaN(parseInt(Anime.getTdFromTable(eventContent, 7).text()))) ? 0 : parseInt(Anime.getTdFromTable(eventContent, 7).text());
            let title = Anime.getTdFromTable(eventContent, 2).text().trim().substring(1).trim();
            let uid = Anime.getUidFromJqueryObject($(eventContent));

            return {
                'currentDownloadLink': currentDownloadLink,
                'seeds': seeds,
                'leech': leech,
                'title': title,
                'uid': uid,
                'size': (function () {
                    var sizeValue = Anime.getTdFromTable(eventContent, 4).text();
                    var sizeText = $.trim(sizeValue.split(' ').pop());
                    let intValue = parseInt(sizeValue);
                    switch (sizeText) {
                        case 'MiB':
                            return ((Math.pow(2, 20)) / 1) * intValue;
                            break;
                        case 'GiB':
                            return intValue * 1073741824;
                            break;
                        default:
                            return 0;
                            break;
                    }
                }())
            };
        }
    };
    return {
        parseTable: parseTable,
        setTable: setTable,
        isParsing: isParsing
    };
}());
var QueryString = function () {
    var query_string = {};
    var query = window.location.search.substring(1);
    var vars = query.split('&');
    for (let 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', {
        enumerable: true,
        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 : this._minSeeds;
        }
    });
    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 */
init(); // init the pannel and set up objects and listeners
AfterInit(); // set page laod items and settings after the object and ui is built
function init() {
    setAnimeObj();
    buildUi();
    bindListeners();

    function setAnimeObj() {
        // Set currentAnime
        if (QueryString.q !== '') {
            Anime.setCurrentAnime(decodeURIComponent(QueryString.q).split('+').join(' '));
        } else {
            Anime.setCurrentAnime('Unknown');
        }
        // set subber
        let paths = window.location.pathname.split("/");
        var currentSubber = paths[2];
        Anime.setCurrentSubber(currentSubber);
        // Set eps
        DataParser.setTable(Utils.getTable());
        // set the seed limit
        var eps = DataParser.parseTable(Utils.getCurrentPageOffset());
        if (Localstore.getMinSeedsFromStore() !== null) {
            var minSeeds = parseInt(Localstore.getMinSeedsFromStore());
            Options.Seeds.minSeeds = minSeeds;
        }
        Anime.addAllEps(eps);
    }

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

        function makeStyles() {
            var styles = '';
            // Panel
            //    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; margin-left: 8px; margin-right: 8px; width: auto; margin-top: 19px;}';
            //    styles += '.panel-success {border-color: #d6e9c6;}';
            styles += '.collapsem{cursor: pointer; position: absolute; right: 4px; top: 2px;}';
            //        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 {position: relative;}';
            //        styles += '.panel-title {color: inherit; margin-bottom: 0; margin-top: 0; padding: 6px; display: inline-block;}';
            //        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; z-index: 1;}';
            styles += '#topbar{z-index: 2 !important;}';
            // Infomation
            styles += '#info th, #info td {padding: 5px;text-align: left;}';
            styles += 'label[for=\'MinSeeds\']{ display: block; margin-top: 10px;}';
            styles += '.filterLabel{margin-right: 10px;}'
                //     styles += '#SaveMinSeeds{margin-left:5px;}';
                // Alerts
            styles += '.alert {position:relative;}';
            //          styles += '.alert-success {background-color: #dff0d8;border-color: #d6e9c6;color: #3c763d;}';
            //        styles += '.alert-danger {color: #A94442;background-color: #F2DEDE;border-color: #EBCCD1;}';
            styles += '#alertUser, #parseErrors{margin-top: 15px;}';
            styles += '#alertButton{position:absolute; bottom:5px; right:5px;}';
            styles += '#alertButtonCancel{position:absolute; bottom:5px; right: 66px;}';
            styles += '#errorClose{position:absolute; bottom:5px; right: 11px;}';
            // Anime Selects
            styles += '#animeSelection{width: 100%;}';
            styles += '#clearResOptions{margin-left: 10px; margin-right: 10px ;cursor: pointer;}';
            styles += '#selectAllFromControl{cursor: pointer;}';
            styles += '#downloadCustomButton{float:right;}';
            styles += '#findEp{float: right; position: relative; bottom: 20px; width: 180px;}'
            DomUtil.injectCss('https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css');
            DomUtil.injectCss(styles);
        }

        function buildPanel() {
            var html = '';
            html += '<div class="panel panel-success">';
            html += '<div class="panel-heading">';
            html += '<h3 id="panel-title" class="panel-title"></h3>';
            html += '<i class="fa fa-minus collapsem" id="collapseToggle" title="Hide"></i>';
            html += '</div>';
            html += '<div class="panel-body" id="pannelContent"></div>';
            html += '</div>';
            $('.container > .row > h3').after(html);
            buildPanelContent();

            function buildPanelContent() {
                var html = '';
                html += '<div>';
                $('#panel-title').html('<span> Download "' + Anime.getCurrentAnime() + ' (' + Anime.getCurrentSubber() + ')"</span>');
                if (Anime.getAmountOfEps() === 0) {
                    html += '<span> No translated anime found or error occured</span>';
                    html += '</div>';
                    $('#pannelContent').html(html);
                    return;
                }
                html += '<span>Pick a resolution: </span>';
                html += '<span id=\'selectDownload\'>';
                html += UI.buildDropdownSelections();
                html += '</span>';
                html += '<button class="btn btn-default" type="button" data-type=\'downloadAll\' id="downloadAll">Download all</button>';
                html += '<button class="btn btn-default" type=\'button\' id=\'downloadCustomButton\' data-type=\'downloadSelected\' >download your selected items</button>';
                html += '</div>';
                html += '<div id=\'options\'>';

                html += '<div class="checkbox">';

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


                html += '<div class="input-group">';
                html += '<input placeholder="Minimum seeders" class="form-control" type=\'number\' min=\'0\' id=\'MinSeeds\' title=\'Any episode that is below this limit will be excluded from the download.\'/>';
                html += '<span class="input-group-btn">';
                html += '<button class="btn btn-default" type=\'button\' id=\'SaveMinSeeds\'>Save</button>';
                html += "</span>";
                html += "</div>";

                html += '<div id=\'tableInfo\'>';
                html += UI.buildTable();
                html += '</div>';
                html += '<div id=\'alertUser\' class=\'hide\'></div>';
                html += '<div class=\'selectAnime\' id=\'selectAnime\'>';
                html += UI.buildSelect();
                html += '</div>';
                html += '<input class="form-control" type=\'text\' id=\'findEp\' placeholder=\'Search Select (or use regex)\' />';
                html += '<button class="btn btn-default" disabled id=\'acceptSelect\' data-type=\'downloadSelects\'>Select for download</button>';
                html += '<div id=\'parseErrors\' class =\'hide\'></div>';
                $('#pannelContent').html(html);
            }
        }

        function afterBuild() {
            makeCheckBoxes();
            sortLists();

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

            function sortLists() {
                Utils.sortAllControls();
            }
        }
    }

    function bindListeners() {
        Utils.reBindSelectFilters();
        $('#downloadAll').on('click', function (e) {
            Utils.doDownloads(e);
        });
        $('#downloadCustomButton').on('click', function (e) {
            Utils.doDownloads(e);
        });
        $('.checkboxes').on('click', function (e) {
            if (Utils.checkBoxValid($('.checkboxes'))) {
                Utils.enableButton($('#downloadCustomButton'));
            } else {
                Utils.disableButton($('#downloadCustomButton'));
            }
        });

        $('#crossPage').on('click', function (e) {
            preformParsing(Anime.getPageUrls());

            function preformParsing(urls) {
                if (urls.length === 0) {
                    return;
                }
                if (Utils.checkBoxValid($('#crossPage'))) {
                    $('#tableInfo').html('<p>Please wait while we parse each page...</p>');
                    $('#selectAnime').html('');
                    $('#acceptSelect').hide();
                    $('#crossPage, #downloadAll').prop('disabled', true);
                    $('#parseErrors').slideUp('fast', function () {
                        $(this).html('');
                    });
                    var AjaxInfo = {
                        error: {
                            pageAtError: null
                        },
                        currentPage: null
                    };
                    urls.reduce(function (prev, cur, index) {
                        return prev.then(function (data) {
                            return $.ajax(cur).then(function (data) {
                                DataParser.isParsing = true;
                                var currentPage = 0;
                                let queryObjet = Utils.getQueryFromUrl(cur);
                                if (queryObjet.p) {
                                    currentPage = queryObjet.p;
                                } else {
                                    currentPage = 1;
                                }
                                /*if (cur.indexOf('offset') > -1) {
                                    currentPage = cur.substring(cur.indexOf('offset')).split('&')[0].split('=').pop();
                                } else {
                                    currentPage = 1;
                                }*/
                                AjaxInfo.currentPage = currentPage;
                                var table = $(data).find("table");
                                DataParser.setTable(table);
                                Anime.addAllEps(DataParser.parseTable(parseInt(currentPage)));
                                $('#tableInfo').append('<div>Page ' + currentPage + ' Done </div>');
                            }, function () {
                                AjaxInfo.error.pageAtError = cur.split('=').pop();
                            });
                        });
                    }, $().promise()).always(function () {
                        if (AjaxInfo.error.pageAtError !== null) {
                            UI.showAjaxErrorAlert(AjaxInfo);
                        }
                        $('#tableInfo').html(UI.buildTable());
                        $('#downloadRes').html(UI.buildDropdownSelections());
                        $('#selectAnime').html(UI.buildSelect());
                        Utils.sortAllControls();
                        $('#acceptSelect').show();
                        Utils.reBindSelectFilters();
                        $('#crossPage, #downloadAll').prop('disabled', false);
                        DataParser.isParsing = 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);
                    Utils.cleanAvailableResolutions();
                    $('#downloadRes').html(UI.buildDropdownSelections());
                    $('#tableInfo').html(UI.buildTable());
                    $('#selectAnime').html(UI.buildSelect());
                    Utils.reBindSelectFilters();
                    Utils.sortAllControls();
                }
            }
        });
        $('#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);
            }
            $('#selectAnime').html(UI.buildSelect());
            Utils.sortAllControls();
            Utils.reBindSelectFilters();
        });
        $('#collapseToggle').on('click', function () {
            $('#pannelContent').stop(true, true).slideToggle('slow', function () {
                if ($(this).is(':hidden')) {
                    $('#collapseToggle').removeClass('fa-minus').addClass('fa-plus');
                    $('#collapseToggle').attr('title', 'Show');
                } else {
                    $('#collapseToggle').addClass('fa-minus').removeClass('fa-plus');
                    $('#collapseToggle').attr('title', 'Hide');
                }
            });
        });
        $('#acceptSelect').on('click', function (e) {
            Utils.doDownloads(e);
        });
        $('#findEp').on('keyup', function () {
            UI.applySearch($(this).val());
        });
    }
}

function AfterInit() {
    initButtonStates();
    if (Utils.html5StoreSupport()) {
        setOptionsFromLocalStore();
    }

    function initButtonStates() {
        if (Utils.checkBoxValid($('.checkboxes'))) {
            Utils.enableButton($('#downloadCustomButton'));
        } else {
            Utils.disableButton($('#downloadCustomButton'));
        }
    }

    function setOptionsFromLocalStore() {
        // Min seeds
        if (Localstore.getMinSeedsFromStore() !== null) {
            $('#MinSeeds').val(Options.Seeds.minSeeds);
        }
    }
}

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址