您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Batch download torrents from nyaa.se
当前为
// ==UserScript== // @name Nyaa.se Batch downloader // @namespace Autodownload // @author Victorique // @description Batch download torrents from nyaa.se // @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 5.2.11 // @icon https://i.imgur.com/nx5ejHb.png // @license MIT // @run-at document-start // @grant none // @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, title) { 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"; } var _res = res; var _downloadLink = downloadLink; var _seeds = seeds; var _leechers = leechers; var _uid = uid; var _resides = resides; var _title = title; 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; }; 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; var arrayOfEps = []; for (var i = 0; i < eps.length; i++) { var currentEp = eps[i]; if (minSeeds > -1 && skipSeedLimit == false) { if (currentEp.getSeeds() < minSeeds) { continue; } } arrayOfEps.push(currentEp); } return arrayOfEps; }; 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"); } eps.push(ep); }; /** * Remove an object from an array * @param {Array} arra Array to search in * @param {Object} obj Object to remove */ var removeObjectFromArray = function (obj, arra) { // TODO: move this into Utils var arr = eps; if (typeof arra !== "undefined") { arr = arra; } var i = arr.length; while (i--) { if (arr[i] === obj) { arr.splice(i, 1); } } }; return { abstractGetEps: abstractGetEps, addEp: addEp, removeObjectFromArray: removeObjectFromArray }; }()); /** * 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": "640×360" }]; /** * 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; }; 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 (var 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) { _AbstractEps.removeObjectFromArray(currentRes, availableRes); } } } } }; var _resExists = function (_res) { for (var 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 int"; } var seedCount = 0; if (getamountOfEpsFromRes(res, skipSeedLimit) === 0) { return 0; } var eps = _AbstractEps.abstractGetEps(skipSeedLimit); 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, 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 int"; } var leechCount = 0; if (getamountOfEpsFromRes(res, skipSeedLimit) === 0) { return 0; } var eps = _AbstractEps.abstractGetEps(skipSeedLimit); 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, skipSeedLimit)); }; /** * 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; }; 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) { _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 (var 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 minSeeds = Options.Seeds.minSeeds; var arrayOfEps = []; var iterEps = _AbstractEps.abstractGetEps(skipSeedLimit); for (var i = 0; i < iterEps.length; i++) { var currentEp = iterEps[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)); }()); return anchor; } return null; }; /** * 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 (_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 iterEp = _AbstractEps.abstractGetEps(skipSeedLimit); for (var i = 0; i < iterEp.length; i++) { var currentEp = iterEp[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 iterEp = _AbstractEps.abstractGetEps(skipSeedLimit); for (var i = 0; i < iterEp.length; i++) { var currentEp = iterEp[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") { return anchor.split("=").pop(); } return anchor.href.split("=").pop(); }; /** * Get an array of all the pages avalible in the Anime (tables) * @returns {Array} Array of URLS */ var getPageUrls = function (asHref, page) { if (typeof page === "undefined") { page = $('div.pages'); } if (typeof asHref !== "boolean") { throw "asHref must be a boolean"; } if (asHref === false) { return $(page).filter(function (i) { return i === 0; }); } var urls = []; $.each(page.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, true); for (var i = 0; i < episodes.length; i++) { var currentEp = episodes[i]; _AbstractEps.removeObjectFromArray(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, true); for (var i = 0; i < episodes.length; i++) { var currentEp = episodes[i]; _AbstractEps.removeObjectFromArray(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 }; }()); /** Utility functions ***/ var Utils = (function () { (function () { $.fn.extend({ sortSelect: function () { if (!this.is("select")) { throw "SortSelect can only be used on a select node"; } var options = this.children("option"); options.sort(function (a, b) { return $(a).text().toUpperCase().localeCompare($(b).text().toUpperCase()); }); for (var i = 0; i < options.length; i++) { this.append(options[i]); } return this; } }) }()); /** * 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 epsToDownload = []; $("#alertUser").html(html).slideDown("slow"); if (type === "downloadSelected") { $.each($('.checkboxes:checked').prev('a'), function (k, v) { epsToDownload.push(Anime.getEpisodeFromAnchor(this)); }); } else if (type === "downloadSelects") { $.each($('#animeSelection option:selected'), function (k, v) { epsToDownload.push(Anime.getEpisodeFromAnchor($(this).data("url"))); }); } else { epsToDownload = Anime.getEpsForRes(parseInt($("#downloadRes").val()), false); } bindAlertControls(); function bindAlertControls() { $("#alertButtonCancel").on("click", function () { $("#alertUser").slideUp("slow"); $("#crossPage").prop("disabled", false); }); $("#alertButton").on("click", function () { doIt(epsToDownload); }); } function doIt(eps) { for (var i = 0; i < eps.length; i++) { var currentEp = eps[i]; var currentDownloadLink = currentEp.getDownloadLink(); var link = document.createElement("a"); link.download = currentEp.getTitle(); link.href = currentDownloadLink; link.click(); link.remove(); } $("#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.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; } }; var cleanAvailableResolutions = function () { var avRes = Anime.getAvailableResolutions(); for (var i = 0; i < Anime.getAvailableResolutions().length; i++) { var currentRes = avRes[i].res; if (Anime.getamountOfEpsFromRes(currentRes, true) === 0) { Anime.removeAvailableResolutions(currentRes); } } }; var injectCss = function (css) { if (_isUrl(css)) { $("<link>").prop({ "type": "text/css", "rel": "stylesheet" }).attr("href", css).appendTo("head"); } else { $("<style>").prop("type", "text/css").html(css).appendTo("head"); } }; var _isUrl = function (url) { var matcher = new RegExp(/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/); return matcher.test(url); }; var sortAllSelects = function () { $("#animeSelection").sortSelect(); $("#downloadRes").sortSelect(); } var reBind = function () { $("input[name='filterSelect']").off("change").on("change", handleSelect); $("#clearResOptions").off("click").on("click", handleSelect); function handleSelect(event) { var resTOFilter = $(event.target).data("set") $("#selectContainer").html(UI.buildSelect(resTOFilter)); Utils.sortAllSelects(); reBind(); } }; return { disableButton: disableButton, enableButton: enableButton, doDownloads: doDownloads, checkBoxValid: checkBoxValid, getCurrentPageOffset: getCurrentPageOffset, html5StoreSupport: html5StoreSupport, injectCss: injectCss, cleanAvailableResolutions: cleanAvailableResolutions, sortAllSelects: sortAllSelects, reBind: reBind }; }()); var UI = (function () { /** * 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 += "<thead>"; html += "<tr>"; html += "<th>resolution</th>"; html += "<th>Episode count</th>"; html += "<th>Average seeds</th>"; html += "<th>Average leechers</th>"; html += "</tr>"; html += "</thead>"; html += "<tbody>"; var allRes = Anime.getAvailableResolutions(); for (var 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 += "</tr>"; } html += "</tbody>"; html += "</table>"; return html; }; var buildDropdownSelections = function () { var html = ""; html += "<select style=\"margin-right:5px;\" id=\"downloadRes\">"; var allRes = Anime.getAvailableResolutions(); for (var 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; if (type === "downloadSelected") { amountOfAnime = $(".checkboxes:checked").length; res = "custom"; } else if (type === "downloadSelects") { amountOfAnime = $("#animeSelection option:selected").length; res = "custom"; } else { amountOfAnime = Anime.getamountOfEpsFromRes(selectedRes, false); res = selectedRes === -1 ? "Others" : selectedRes + "p"; } 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>"; 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 id='alertButton'>Okay</button>"; html += "<button 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 = ""; 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 id='animeSelection' multiple size='20'>"; var allRes = Anime.getAvailableResolutions(); for (var i = 0; i < allRes.length; i++) { var currRes = allRes[i]; var localRes = currRes.res; var eps = Anime.getEpsForRes(localRes, false); for (var j = 0; j < eps.length; j++) { var currentEp = eps[j]; if (resTOFilter == "none") { html += "<option data-url=" + currentEp.getDownloadLink() + ">"; html += currentEp.getTitle(); html += "</option>"; } else { if (currentEp.getRes() == resTOFilter) { html += "<option data-url=" + currentEp.getDownloadLink() + ">"; html += currentEp.getTitle(); html += "</option>"; } } } } html += "</select>"; html += "<span>Filter select control: </span>"; var checked = false; for (var 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>" + (allRes[i].res === -1 ? "Others" : allRes[i].res + "p") + "</label>"; checked = false; } html += "<a href='#' id='clearResOptions' data-set='none' >clear</a>"; html += "</div>"; return html; }; var buildCustomResOptons = function () { $("input[name='filterSelect']").on("change", function (e) { var resTOFilter = this.id; $("#selectContainer").html(UI.buildSelect(resTOFilter)); Utils.reBind(); }); }; return { buildDropdownSelections: buildDropdownSelections, buildTable: buildTable, builDownloadAlert: builDownloadAlert, showAjaxErrorAlert: showAjaxErrorAlert, buildSelect: buildSelect }; }()); var DataParser = (function () { var table = null; var 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='http://files.nyaa.se/www-37.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); } var 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)); }); return eps; function parseRes(eventContent) { var suppRes = Anime.getSupportedRes(); for (var 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) { return { "currentDownloadLink": $(eventContent).find('td:nth-child(3) >a').attr('href'), "seeds": (isNaN(parseInt($(eventContent).find("td.tlistsn").text()))) ? 0 : parseInt($(eventContent).find("td.tlistsn").text()), "leech": (isNaN(parseInt($(eventContent).find("td.tlistln").text()))) ? 0 : parseInt($(eventContent).find("td.tlistln").text()), "title": $(eventContent).children(".tlistname").text(), "uid": Anime.getUidFromJqueryObject($(eventContent)) }; } }; 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 (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 = {}; var Res = {}; 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; } }); Object.defineProperties(Res, { "getAll": { get: function () { return Anime.getSupportedRes(); } }, "add": { value: function (customRes) { if (typeof customRes !== "object") { throw "res must be an object"; } if (typeof customRes.fullRes !== "string") { throw new TypeError("Full res must be a string"); } if (typeof customRes.res !== "number") { throw new TypeError("res must be a number"); } if (typeof customRes.id !== "number") { throw new TypeError("id must be a number"); } try { Localstore.addCustomResToStore(customRes); } catch (e) { throw (e); } } }, "remove": { value: function (resToRemove) { if (resToRemove !== null && typeof resToRemove !== "number") { throw "resToRemove must be null or an number"; } Localstore.removeCustomResFromStore(resToRemove) } } }); return { Seeds: Seeds, Res: Res }; }()); //Local storeage object var Localstore = { getMinSeedsFromStore: function () { return localStorage.getItem("minSeeds"); }, setMinSeedsFromStore: function (seeds) { localStorage.setItem('minSeeds', seeds); }, removeMinSeedsFromStore: function () { localStorage.removeItem("minSeeds"); }, addCustomResToStore: function (resObject) { // get all current custom res var currentReses = JSON.parse(localStorage.getItem("customRes")); if (currentReses === null) { localStorage.setItem("customRes", JSON.stringify([])); return; } for (var i = 0; i < currentReses.length; i++) { var currResObj = currentReses[i]; if (resObject.res === currResObj.res || resObject.fullRes === currResObj.fullRes) { throw TypeError("ERROR: The supplied resolution already exists"); } } // add new res currentReses.push(resObject); // convert back into string var parsedRes = JSON.stringify(currentReses); localStorage.setItem("customRes", parsedRes); }, getCustomResFromStore: function () { return JSON.parse(localStorage.getItem("customRes")); }, removeCustomResFromStore: function (id) { if (id === null) { localStorage.removeItem("customRes"); return; } var allObj = this.getCustomResFromStore(); for (var i = 0; i < allObj.length; i++) { var currObjId = allObj[i].id; if (currObjId === id) { _removeObjectFromArray(allObj, allObj[i]); } } var parsedRes = JSON.stringify(allObj); localStorage.setItem("customRes", parsedRes); function _removeObjectFromArray(arr, obj) { var i = arr.length; while (i--) { if (arr[i] === obj) { arr.splice(i, 1); } } } } }; // 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 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.term !== "") { Anime.setCurrentAnime(decodeURIComponent(QueryString.term).split("+").join(" ")); } else { Anime.setCurrentAnime("Unknown"); } // set subber Anime.setCurrentSubber($(".notice > a > *").html()); // Set eps DataParser.setTable($("table.tlist")); 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: 9px; margin-right: 8px; width: auto; margin-top: 19px;}"; styles += ".panel-success {border-color: #d6e9c6;}"; styles += ".collapse{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 {background-color: #dff0d8; border-color: #d6e9c6; color: #3c763d; 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, #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;}"; // Alerts styles += ".alert {border: 1px solid transparent;border-radius: 4px;margin-bottom: 20px;padding: 15px; 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;}"; // Custom Resolutions styles += "#customResButtonDiv {margin-top:10px;}"; styles += "#downloadCustomButton{float:right;}"; Utils.injectCss(styles); Utils.injectCss("https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css"); } 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 collapse" id="collapseToggle" title="Hide"></i>'; html += "</div>"; html += '<div class="panel-body" id="pannelContent"></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.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 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.'/>"; html += "<button type='button' id='SaveMinSeeds'>Save</button>"; /* html += "<div id='customResButtonDiv'>"; html += "<button id='customRes'> Manage resolutions </button>"; html += "</div>"; */ 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 += "<button 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.sortAllSelects(); } } } function bindListeners() { $("#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(true)); function preformParsing(urls) { if (Utils.checkBoxValid($("#crossPage"))) { $("#tableInfo").html("<p>Please wait while we parse each page...</p>"); $("#selectContainer").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 = cur.split("=").pop(); if (cur.indexOf("offset") === -1) { currentPage = 1; } AjaxInfo.currentPage = currentPage; var table = $(data).find("table.tlist"); 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()); $("#selectContainer").html(UI.buildSelect()); Utils.sortAllSelects(); $("#acceptSelect").show(); Utils.reBind(); $("#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()); $("#selectContainer").html(UI.buildSelect()); Utils.reBind(); Utils.sortAllSelects(); } } }); $("#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); } $("#selectContainer").html(UI.buildSelect()); Utils.sortAllSelects(); Utils.reBind(); }); $("#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"); } }); }); Utils.reBind(); $("#acceptSelect").on("click", function (e) { Utils.doDownloads(e); }); } } 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); } //customRes /* In devlopment if (Options.Res.getAll === null) { Options.Res.add({}); } else { Anime.addSupportedRes(Options.Res.getAll); } */ } }