您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Batch download torrents from nyaa.si
当前为
// ==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.0 // @icon https://i.imgur.com/nx5ejHb.png // @license MIT // @run-at document-idle // @grant none // @require https://greasyfork.org/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, #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 += '.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 > 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); } } }