Nyaa.se Batch downloader

Batch download torrents from nyaa.se

当前为 2016-05-04 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Nyaa.se Batch downloader
  3. // @namespace Autodownload
  4. // @author Victorique
  5. // @description Batch download torrents from nyaa.se
  6. // @include http://www.nyaa.se/?page=search&cats=*&filter=*&term=*&user=*
  7. // @include http://www.nyaa.se/?page=search&filter=*&term=*&user=*
  8. // @include http://www.nyaa.se/?page=search&term=*&user=*
  9. // @include *://www.nyaa.eu/?page=search&cats=*&filter=*&term=*&user=*
  10. // @include *://www.nyaa.eu/?page=search&filter=*&term=*&user=*
  11. // @include *://www.nyaa.eu/?page=search&term=*&user=*
  12. // @version 5.4.5
  13. // @icon https://i.imgur.com/nx5ejHb.png
  14. // @license MIT
  15. // @run-at document-idle
  16. // @grant none
  17. // @require https://code.jquery.com/jquery-2.2.3.min.js
  18. // @require https://greasyfork.org/scripts/19117-jsutils/code/JsUtils.js
  19. // ==/UserScript==
  20. /*
  21. var e = document.createElement("script");
  22.  
  23. e.src = 'http://localhost/userScripts/AutoDownloader.user.js';
  24. e.type = "application/javascript;version=1.7";
  25. document.getElementsByTagName("head")[0].appendChild(e);
  26. */
  27. 'use strict';
  28. /* OBJECT CREATION START */
  29. var Episode = (function () {
  30. /**
  31. * An Episode represents an table row in the current page
  32. * @param {Number} res The resolution used for this episode
  33. * @param {String} downloadLink The download link for this episode
  34. * @param {Number} seeds The seed count for this episode
  35. * @param {Number} leechers The leech count for this episode
  36. * @param {String} uid The ID of this episode
  37. * @param {Number} resides The page that this Episode resides in
  38. * @param {String} title The title of the episode
  39. * @param {Number} size The size in MB of the episode
  40. * @returns {Object} the proto of itself
  41. */
  42. function Episode(res, downloadLink, seeds, leechers, uid, resides, title, size) {
  43. if (typeof res !== 'number') {
  44. throw 'res must be a number';
  45. }
  46. if (typeof downloadLink !== 'string') {
  47. throw 'downloadLink must be a string';
  48. }
  49. if (typeof seeds !== 'number') {
  50. throw 'seeds must be a number';
  51. }
  52. if (typeof leechers !== 'number') {
  53. throw 'leechers must be a number';
  54. }
  55. if (typeof uid !== 'string') {
  56. throw 'uid must be a string';
  57. }
  58. if (typeof resides !== 'number') {
  59. throw 'resides must be a number';
  60. }
  61. if (typeof title !== 'string') {
  62. throw 'Title must be a string';
  63. }
  64. if (typeof size !== 'number') {
  65. throw 'size must be a number';
  66. }
  67. var _res = res;
  68. var _downloadLink = downloadLink;
  69. var _seeds = seeds;
  70. var _leechers = leechers;
  71. var _uid = uid;
  72. var _resides = resides;
  73. var _title = title;
  74. var _size = size;
  75. this.getRes = function () {
  76. return _res;
  77. };
  78. this.getDownloadLink = function () {
  79. return _downloadLink;
  80. };
  81. this.getSeeds = function () {
  82. return _seeds;
  83. };
  84. this.getLeechers = function () {
  85. return _leechers;
  86. };
  87. this.getUid = function () {
  88. return _uid;
  89. };
  90. this.getResides = function () {
  91. return _resides;
  92. };
  93. this.getTitle = function () {
  94. return _title;
  95. };
  96. this.getSize = function () {
  97. return _size;
  98. };
  99. return this;
  100. }
  101. Episode.prototype = {
  102. constructor: Episode
  103. };
  104. return Episode;
  105. }());
  106. var Anime = (function () {
  107. var currentAnime = null;
  108. var currentSubber = null;
  109. var _AbstractEps = (function () {
  110. /**
  111. * Array of Episode Objects
  112. */
  113. var eps = [
  114. ];
  115. var abstractGetEps = function (skipSeedLimit) {
  116. if (typeof skipSeedLimit !== 'boolean') {
  117. throw 'skipOptions must be true or false';
  118. }
  119. var minSeeds = Options.Seeds.minSeeds;
  120. if (minSeeds > -1 && skipSeedLimit == false) {
  121. var arrayOfEps = [
  122. ];
  123. for (let i = 0, len = eps.length; i < len; i++) {
  124. var currentEp = eps[i];
  125. if (currentEp.getSeeds() < minSeeds) {
  126. continue;
  127. }
  128. arrayOfEps.push(currentEp);
  129. }
  130. return arrayOfEps;
  131. } else {
  132. return eps;
  133. }
  134. };
  135. var addEp = function (ep) {
  136. if (!(ep instanceof Episode)) {
  137. throw 'addEp must take an Episode object';
  138. }
  139. if (_validRes(ep.getRes()) === false) {
  140. throw new TypeError('The Episode supplied does not have a valid resolution');
  141. }
  142. for (let i = 0, len = eps.length; i < len; i++) {
  143. var epi = eps[i];
  144. if (Utils.deepEquals(epi, ep)) {
  145. console.warn('The episode supplied already exsists, this episode has been ignored');
  146. return;
  147. }
  148. }
  149. eps.push(ep);
  150. };
  151. var removeEpisodeFromAnime = function (obj) {
  152. var arr = eps;
  153. let i = arr.length;
  154. while (i--) {
  155. if (arr[i] === obj) {
  156. arr.splice(i, 1);
  157. }
  158. }
  159. };
  160. return {
  161. abstractGetEps: abstractGetEps,
  162. addEp: addEp,
  163. removeEpisodeFromAnime: removeEpisodeFromAnime
  164. };
  165. }());
  166. /**
  167. * Array of available resolutions on the page
  168. */
  169. var availableRes = [
  170. ];
  171. /**
  172. * Array of supported resolutions for this program
  173. */
  174. var supportedRes = [
  175. {
  176. 'id': 1,
  177. 'res': 1080,
  178. 'fullRes': '1920x1080'
  179. },
  180. {
  181. 'id': 2,
  182. 'res': 720,
  183. 'fullRes': '1280x720'
  184. },
  185. {
  186. 'id': 3,
  187. 'res': 480,
  188. 'fullRes': '640x480'
  189. },
  190. {
  191. 'id': 4,
  192. 'res': 360,
  193. 'fullRes': '640x360'
  194. }
  195. ];
  196. /**
  197. * Set the current Anime name
  198. * @param {String} anime The of the Anime
  199. */
  200. var setCurrentAnime = function (anime) {
  201. if (anime === '*') {
  202. anime = 'Everything';
  203. }
  204. currentAnime = anime;
  205. };
  206. /**
  207. * Set the name of the current subber
  208. * @param {String} sub Name of the current subber
  209. */
  210. var setCurrentSubber = function (sub) {
  211. currentSubber = sub;
  212. };
  213. /**
  214. * Get the current Subber
  215. * @returns {String} The name of the Subber for this anime
  216. */
  217. var getCurrentSubber = function () {
  218. return currentSubber;
  219. };
  220. /**
  221. * Get the current anime name
  222. * @returns {String} Name of the anime
  223. */
  224. var getCurrentAnime = function () {
  225. return currentAnime;
  226. };
  227. var getSupportedRes = function () {
  228. return supportedRes;
  229. };
  230. var addSupportedRes = function (ResObj) {
  231. if (typeof ResObj !== 'object') {
  232. throw 'ResObj must be a object';
  233. }
  234. supportedRes.push(ResObj);
  235. };
  236. var getAvailableResolutions = function () {
  237. return availableRes;
  238. };
  239. var addAvailableResolutions = function (res, fullRes) {
  240. if (typeof res !== 'number') {
  241. throw 'res must be of type number';
  242. }
  243. if (typeof fullRes !== 'string' && fullRes !== null) {
  244. throw 'Full res must be a string or null';
  245. }
  246. if (_resExists(res)) {
  247. return;
  248. }
  249. availableRes.push({
  250. 'res': res,
  251. 'fullRes': fullRes
  252. });
  253. };
  254. var removeAvailableResolutions = function (resToRemove) {
  255. if (typeof resToRemove !== 'number' && typeof resToRemove !== 'string') {
  256. throw 'the res to remove can only be a number or string';
  257. }
  258. for (let i = 0; i < availableRes.length; i++) {
  259. var currentRes = availableRes[i];
  260. for (var res in currentRes) {
  261. if (currentRes.hasOwnProperty(res)) {
  262. var localRes = currentRes[res];
  263. if (localRes === resToRemove) {
  264. availableRes._remove_(currentRes);
  265. }
  266. }
  267. }
  268. }
  269. };
  270. var _resExists = function (_res) {
  271. for (let i = 0; i < availableRes.length; i++) {
  272. var currentRes = availableRes[i];
  273. for (var res in currentRes) {
  274. if (currentRes.hasOwnProperty(res)) {
  275. var localRes = currentRes[res];
  276. if (localRes === _res) {
  277. return true;
  278. }
  279. }
  280. }
  281. }
  282. return false;
  283. };
  284. /**
  285. * Get the avrage seeds for a specified res
  286. * @param {Number} res The res to get the avg seeds for
  287. * @returns {Number} The avg seeds
  288. */
  289. var avgSeedsForRes = function (res, skipSeedLimit) {
  290. if (typeof res !== 'number') {
  291. throw 'res Must be an number';
  292. }
  293. if (typeof skipSeedLimit !== 'boolean') {
  294. throw 'skipSeedLimit Must be an boolean';
  295. }
  296. var seedCount = 0;
  297. if (getamountOfEpsFromRes(res, skipSeedLimit) === 0) {
  298. return 0;
  299. }
  300. var eps = _AbstractEps.abstractGetEps(skipSeedLimit);
  301. for (let i = 0, len = eps.length; i < len; i++) {
  302. var currentEp = eps[i];
  303. if (currentEp.getRes() === res) {
  304. seedCount += currentEp.getSeeds();
  305. }
  306. }
  307. return Math.round(seedCount = seedCount / getamountOfEpsFromRes(res, skipSeedLimit));
  308. };
  309. /**
  310. * Get the avrage leechers for a specified res
  311. * @param {Number} res The res to get the avg seeds for
  312. * @returns {Number} The avg leechers
  313. */
  314. var avgPeersForRes = function (res, skipSeedLimit) {
  315. if (typeof res !== 'number') {
  316. throw 'res Must be an number';
  317. }
  318. if (typeof skipSeedLimit !== 'boolean') {
  319. throw 'skipSeedLimit Must be an boolean';
  320. }
  321. var leechCount = 0;
  322. if (getamountOfEpsFromRes(res, skipSeedLimit) === 0) {
  323. return 0;
  324. }
  325. var eps = _AbstractEps.abstractGetEps(skipSeedLimit);
  326. for (let i = 0, len = eps.length; i < len; i++) {
  327. var currentEp = eps[i];
  328. if (currentEp.getRes() === res) {
  329. leechCount += currentEp.getLeechers();
  330. }
  331. }
  332. return Math.round(leechCount = leechCount / getamountOfEpsFromRes(res, skipSeedLimit));
  333. };
  334. var getTotalSizeForRes = function (res, skipSeedLimit, decimals) {
  335. if (typeof res !== 'number') {
  336. throw 'res Must be an number';
  337. }
  338. if (typeof skipSeedLimit !== 'boolean') {
  339. throw 'skipSeedLimit Must be an boolean';
  340. }
  341. var eps = getEpsForRes(res, skipSeedLimit);
  342. return Utils.getHumanReadableSize(eps, decimals);
  343. };
  344. var getTotalSizeFromEps = function (eps, decimals) {
  345. if (!Array.isArray(eps) && !(eps instanceof Episode)) {
  346. throw 'eps Must be an array or a single Episode';
  347. }
  348. return Utils.getHumanReadableSize(eps, decimals);
  349. };
  350. /**
  351. * Get the total amount of eps for a res
  352. * @param {Number} res res
  353. * @returns {Number} The amount of eps for the res
  354. */
  355. var getamountOfEpsFromRes = function (res, skipSeedLimit) {
  356. if (typeof res !== 'number') {
  357. throw 'res must be of type \'number\'';
  358. }
  359. return getEpsForRes(res, skipSeedLimit).length;
  360. };
  361. /**
  362. * Add Episodes to the array
  363. * @param {Episode} ep The Anime object to add
  364. */
  365. var addEps = function (ep) {
  366. _AbstractEps.addEp(ep);
  367. };
  368. /**
  369. * Add an array of Episode object to the Anime object
  370. * @param {Array} episode Array of Episode objects to add
  371. */
  372. var addAllEps = function (episode) {
  373. for (let i = 0; i < episode.length; i++) {
  374. _AbstractEps.addEp(episode[i]);
  375. }
  376. };
  377. var _validRes = function (res) {
  378. return _resExists(res); // if the res exists, then it's valid
  379. };
  380. /**
  381. * Get the Anime objects for a specified res
  382. * @param {Number} res res to use
  383. * @returns {Episode} Array of Episodes that match the specified res
  384. */
  385. var getEpsForRes = function (res, skipSeedLimit) {
  386. if (typeof res !== 'number') {
  387. throw 'res Must be an int';
  388. }
  389. var arrayOfEps = [
  390. ];
  391. var eps = _AbstractEps.abstractGetEps(skipSeedLimit);
  392. for (let i = 0, len = eps.length; i < len; i++) {
  393. var currentEp = eps[i];
  394. if (currentEp.getRes() === res) {
  395. arrayOfEps.push(currentEp);
  396. }
  397. }
  398. return arrayOfEps;
  399. };
  400. /**
  401. * Given a JQuery object that represents a "tr" of the table, this will return that Episode's UID
  402. * @param {Object} obj The Jquery representation of a tr (table row)
  403. * @returns {String} The UID of that Episode
  404. */
  405. var getUidFromJqueryObject = function (obj) {
  406. if (!Utils.isjQueryObject(obj)) {
  407. throw 'Object must be of type \'Jquery\'';
  408. }
  409. if (obj.is('tr')) {
  410. var anchor = (function () {
  411. var tableRows = obj.find('td.tlistdownload > a');
  412. if (tableRows.length > 1) {
  413. throw 'Object must be unique';
  414. }
  415. return _getUidFromAnchor(tableRows.get(0));
  416. }());
  417. return anchor;
  418. }
  419. return null;
  420. };
  421. /**
  422. * Get the Episode from a given anchor tag or url
  423. * @param {Object} anchor Jquery or pure JS anchor dom element or URL String
  424. * @returns {Episode} The eipside that matches the Anchor
  425. */
  426. var getEpisodeFromAnchor = function (anchor) {
  427. var link = (function () {
  428. if (Utils.isjQueryObject(anchor)) {
  429. return anchor.get(0);
  430. }
  431. return anchor;
  432. }());
  433. var uid = _getUidFromAnchor(link);
  434. return getEpisodeFromUid(uid, true);
  435. };
  436. /**
  437. * Get the Episode object given a UID
  438. * @param {String} uid The Episode UID
  439. * @returns {Episode} The Episode that matches the UID
  440. */
  441. var getEpisodeFromUid = function (uid, skipSeedLimit) {
  442. if (typeof uid !== 'string') {
  443. throw 'uid must be of type String';
  444. }
  445. var eps = _AbstractEps.abstractGetEps(skipSeedLimit);
  446. for (let i = 0, len = eps.length; i < len; i++) {
  447. var currentEp = eps[i];
  448. if (currentEp.getUid() === uid) {
  449. return currentEp;
  450. }
  451. }
  452. return null;
  453. };
  454. /**
  455. * Get an array of Episodes that from the page that it came from
  456. * @param {Number} resides The page where the Episode originated from
  457. * @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
  458. * @returns {Episode} Array of Episodes
  459. */
  460. var getEpisodesFromResidence = function (resides, exclude, skipSeedLimit) {
  461. if (typeof resides !== 'number') {
  462. throw 'resides must be a number';
  463. }
  464. var arrayOfEps = [
  465. ];
  466. var eps = _AbstractEps.abstractGetEps(skipSeedLimit);
  467. for (let i = 0, len = eps.length; i < len; i++) {
  468. var currentEp = eps[i];
  469. if (exclude === true) {
  470. if (currentEp.getResides() !== resides) {
  471. arrayOfEps.push(currentEp);
  472. }
  473. } else {
  474. if (currentEp.getResides() === resides) {
  475. arrayOfEps.push(currentEp);
  476. }
  477. }
  478. }
  479. return arrayOfEps;
  480. };
  481. /**
  482. * Get the UID from an anchor tag
  483. * @param {Object} anchor Dom element of an Anchor
  484. * @returns {String} The UID
  485. */
  486. var _getUidFromAnchor = function (anchor) {
  487. if (typeof anchor === 'string') {
  488. return anchor.split('=').pop();
  489. }
  490. return anchor.href.split('=').pop();
  491. };
  492. /**
  493. * Get an array of all the pages avalible in the Anime (tables)
  494. * @returns {Array} Array of URLS
  495. */
  496. var getPageUrls = function (asHref, page) {
  497. if (typeof page === 'undefined') {
  498. page = $('div.pages');
  499. }
  500. if (typeof asHref !== 'boolean') {
  501. throw 'asHref must be a boolean';
  502. }
  503. if (asHref === false) {
  504. return $(page).filter(function (i) {
  505. return i === 0;
  506. });
  507. }
  508. var urls = [
  509. ];
  510. $.each(page.filter(function (i) {
  511. return i === 0;
  512. }).find('a'), function (k, v) {
  513. urls.push(this.href);
  514. });
  515. return urls;
  516. };
  517. /**
  518. * Remove an episode from the Episode array based on UI
  519. * @param {String} uid the UID
  520. */
  521. var removeEpisodesFromUid = function (uid) {
  522. var episode = getEpisodeFromUid(uid, true);
  523. _AbstractEps.removeEpisodeFromAnime(episode);
  524. };
  525. /**
  526. * Remove all episodes that match a page number
  527. * @param {Number} resides The page number
  528. * @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
  529. */
  530. var removeEpisodesFromResidence = function (resides, exclude) {
  531. if (typeof exclude !== 'boolean') {
  532. throw 'excluse must be true or false';
  533. }
  534. var eps = getEpisodesFromResidence(resides, exclude, true);
  535. for (let i = 0, len = eps.length; i < len; i++) {
  536. var currentEp = eps[i];
  537. _AbstractEps.removeEpisodeFromAnime(currentEp);
  538. }
  539. };
  540. var getAmountOfEps = function () {
  541. return _AbstractEps.abstractGetEps(true).length;
  542. };
  543. return {
  544. setCurrentAnime: setCurrentAnime,
  545. getCurrentAnime: getCurrentAnime,
  546. addEps: addEps,
  547. getEpsForRes: getEpsForRes,
  548. getamountOfEpsFromRes: getamountOfEpsFromRes,
  549. setCurrentSubber: setCurrentSubber,
  550. getCurrentSubber: getCurrentSubber,
  551. avgSeedsForRes: avgSeedsForRes,
  552. getUidFromJqueryObject: getUidFromJqueryObject,
  553. getEpisodeFromUid: getEpisodeFromUid,
  554. getEpisodeFromAnchor: getEpisodeFromAnchor,
  555. getPageUrls: getPageUrls,
  556. addAllEps: addAllEps,
  557. getEpisodesFromResidence: getEpisodesFromResidence,
  558. removeEpisodesFromUid: removeEpisodesFromUid,
  559. removeEpisodesFromResidence: removeEpisodesFromResidence,
  560. avgPeersForRes: avgPeersForRes,
  561. getAmountOfEps: getAmountOfEps,
  562. addAvailableResolutions: addAvailableResolutions,
  563. getAvailableResolutions: getAvailableResolutions,
  564. removeAvailableResolutions: removeAvailableResolutions,
  565. getSupportedRes: getSupportedRes,
  566. addSupportedRes: addSupportedRes,
  567. getTotalSizeForRes: getTotalSizeForRes,
  568. getTotalSizeFromEps: getTotalSizeFromEps
  569. };
  570. }());
  571. /** Utility functions ***/
  572. var Utils = (function () {
  573. var sortSelect = function (selElem) {
  574. var tmpAry = [];
  575. for (let i = 0, length = selElem.options.length; i < length; i++) {
  576. tmpAry[i] = [];
  577. tmpAry[i][0] = selElem.options[i].text;
  578. tmpAry[i][1] = selElem.options[i].dataset.url;
  579. }
  580. tmpAry.sort(function (a, b) {
  581. return a[0].toUpperCase().localeCompare(b[0].toUpperCase());
  582. });
  583.  
  584. selElem.innerHTML = "";
  585.  
  586. for (let i = 0, len = tmpAry.length; i < len; i++) {
  587. var op = new Option(tmpAry[i][0]);
  588. op.dataset.url = tmpAry[i][1];
  589. selElem.options[i] = op;
  590. }
  591. };
  592. let isjQueryObject = function (obj) {
  593. return ObjectUtil.isjQuery(obj);
  594. };
  595. /**
  596. * Disable the given button
  597. * @param {Object} button Jquery object of the button
  598. */
  599. var disableButton = function (button) {
  600. button.prop('disabled', true);
  601. };
  602. /**
  603. * Enable the given button
  604. * @param {Object} button Jquery object of the button
  605. */
  606. var enableButton = function (button) {
  607. button.prop('disabled', false);
  608. };
  609. /**
  610. * Do the downloads
  611. * @param {Object} event The event to decide if it is a download all or a downlaod selection (to make this method more abstract)
  612. */
  613. var doDownloads = function (event) {
  614. $('#crossPage').prop('disabled', true);
  615. var type = $(event.target).data('type');
  616. var amountOfAnime;
  617. var collectionOfAnime;
  618. var download = false;
  619. var html = UI.builDownloadAlert(type);
  620. var urlsToDownload = [
  621. ];
  622. $('#alertUser').html(html).slideDown('slow');
  623. if (type === 'downloadSelected') {
  624. $.each($('.checkboxes:checked').prev('a'), function (k, v) {
  625. var ep = Anime.getEpisodeFromAnchor(this);
  626. urlsToDownload.push(ep.getDownloadLink());
  627. });
  628. } else if (type === 'downloadSelects') {
  629. $.each($('#animeSelection option:selected'), function (k, v) {
  630. var url = this.dataset.url;
  631. urlsToDownload.push(url);
  632. });
  633. } else {
  634. var eps = Anime.getEpsForRes(parseInt($('#downloadRes').val()), false);
  635. for (let i = 0, len = eps.length; i < len; i++) {
  636. urlsToDownload.push(eps[i].getDownloadLink());
  637. }
  638. }
  639. bindAlertControls();
  640.  
  641. function bindAlertControls() {
  642. $('#alertButtonCancel').on('click', function () {
  643. $('#alertUser').slideUp('slow');
  644. $('#crossPage').prop('disabled', false);
  645. });
  646. $('#alertButton').on('click', function () {
  647. doIt(urlsToDownload);
  648. });
  649. }
  650.  
  651. function doIt(urls) {
  652. for (let i = 0; i < urls.length; i++) {
  653. var currentUrl = urls[i];
  654. var link = document.createElement('a');
  655. link.href = currentUrl;
  656. link.download = '';
  657. link.click();
  658. link.remove();
  659. }
  660. $('#alertUser').slideUp('slow');
  661. $('#crossPage').prop('disabled', false);
  662. }
  663. };
  664. /**
  665. * Returns if the checkbox is checked
  666. * @param {Object} checkbox The checkbox
  667. * @returns {Boolean} If ehcked or not
  668. */
  669. var checkBoxValid = function (checkbox) {
  670. return checkbox.is(':checked');
  671. };
  672. var _minSeedsSet = function () {
  673. return Options.Seeds.minSeeds !== -1;
  674. };
  675. /**
  676. * Return the current page offset (what table page you are on)
  677. * @returns {Number} The offset
  678. */
  679. var getCurrentPageOffset = function () {
  680. return parseInt((typeof QueryString.offset === 'undefined') ? 1 : QueryString.offset);
  681. };
  682. /**
  683. * Returns true of false if you can support HTML5 storeag
  684. * @returns {Boolean}
  685. */
  686. var html5StoreSupport = function () {
  687. try {
  688. return 'localStorage' in window && window['localStorage'] !== null;
  689. } catch (e) {
  690. return false;
  691. }
  692. };
  693. var cleanAvailableResolutions = function () {
  694. var avRes = ArrayUtils.arrayCopy(Anime.getAvailableResolutions(), true);
  695. var resLength = avRes.length;
  696. for (let i = 0, len = avRes.length; i < len; i++) {
  697. var currentRes = avRes[i].res;
  698. if (Anime.getamountOfEpsFromRes(currentRes, true) === 0) {
  699. Anime.removeAvailableResolutions(currentRes);
  700. }
  701. }
  702. };
  703. var sortAllControls = function () {
  704. sortSelect(document.getElementById('animeSelection'));
  705. sortSelect(document.getElementById('downloadRes'));
  706. $('#info').sortTable(0);
  707. }
  708. var reBindSelectFilters = function () {
  709. $('input[name=\'filterSelect\']').off('change').on('change', handleSelect);
  710. $('#clearResOptions').off('click').on('click', handleSelect);
  711.  
  712. function handleSelect(event) {
  713. var resTOFilter = $(event.target).data('set')
  714. $('#selectAnime').html(UI.buildSelect(resTOFilter));
  715. Utils.sortAllControls();
  716. var searchApplied = UI.getAppliedSearch();
  717. if (searchApplied !== '') {
  718. UI.applySearch(searchApplied);
  719. }
  720. reBindSelectFilters();
  721. }
  722. };
  723. var equals = function (episode, toEqual) {
  724. if (!(episode instanceof Episode && toEqual instanceof Episode)) {
  725. throw 'both objects must be episodes';
  726. }
  727. return episode.getUid() === toEqual.getUid();
  728. };
  729. var deepEquals = function (episode, toEqual) {
  730. if (!(episode instanceof Episode && toEqual instanceof Episode)) {
  731. throw 'both objects must be episodes';
  732. }
  733. for (var methods in episode) {
  734. if (episode.hasOwnProperty(methods)) {
  735. if (typeof episode[methods] === 'function') {
  736. var method = episode[methods];
  737. var method2 = toEqual[methods];
  738. if (method.call(this) !== method2.call(this)) {
  739. return false;
  740. }
  741. }
  742. }
  743. }
  744. return true;
  745. };
  746. var getHumanReadableSize = function (from, decimals) {
  747. var bits = 0;
  748. if (Array.isArray(from)) {
  749. for (let i = 0; i < from.length; i++) {
  750. var ep = from[i];
  751. bits += ep.getSize();
  752. }
  753. } else if (typeof from === 'number') {
  754. bits = from;
  755. } else {
  756. bits += from.getSize();
  757. }
  758.  
  759. function formatBytes(bytes, decimals) {
  760. if (bytes == 0) {
  761. return '0 Byte';
  762. }
  763. var k = 1024;
  764. var dm = decimals + 1 || 3;
  765. var sizes = [
  766. 'Bytes',
  767. 'KB',
  768. 'MB',
  769. 'GB',
  770. 'TB',
  771. 'PB',
  772. 'EB',
  773. 'ZB',
  774. 'YB'
  775. ];
  776. let i = Math.floor(Math.log(bytes) / Math.log(k));
  777. return (bytes / Math.pow(k, i)).toPrecision(dm) + ' ' + sizes[i];
  778. }
  779. return formatBytes(bits, decimals);
  780. };
  781. return {
  782. disableButton: disableButton,
  783. enableButton: enableButton,
  784. doDownloads: doDownloads,
  785. checkBoxValid: checkBoxValid,
  786. getCurrentPageOffset: getCurrentPageOffset,
  787. html5StoreSupport: html5StoreSupport,
  788. cleanAvailableResolutions: cleanAvailableResolutions,
  789. sortAllControls: sortAllControls,
  790. reBindSelectFilters: reBindSelectFilters,
  791. sortSelect: sortSelect,
  792. isjQueryObject: isjQueryObject,
  793. equals: equals,
  794. deepEquals: deepEquals,
  795. getHumanReadableSize: getHumanReadableSize
  796. };
  797. }());
  798. var UI = (function () {
  799. var epsInSelect = [
  800. ];
  801. var searchApplied = '';
  802. /**
  803. * Build the download infomation table
  804. * @returns {String} The html of the built table
  805. */
  806. var buildTable = function () {
  807. var html = '';
  808. html += '<table style=\'width: 100%\' id=\'info\'>';
  809. html += '<caption>Download infomation</caption>';
  810. html += '<thead>';
  811. html += '<tr>';
  812. html += '<th>resolution</th>';
  813. html += '<th>Episode count</th>';
  814. html += '<th>Average seeds</th>';
  815. html += '<th>Average leechers</th>';
  816. html += '<th>Total size</th>';
  817. html += '</tr>';
  818. html += '</thead>';
  819. html += '<tbody>';
  820. var allRes = Anime.getAvailableResolutions();
  821. for (let i = 0; i < allRes.length; i++) {
  822. var currRes = allRes[i];
  823. var localRes = currRes.res;
  824. html += '<tr>';
  825. html += '<td>' + (localRes === -1 ? 'Others' : localRes + 'p') + '</td>';
  826. html += '<td>' + Anime.getamountOfEpsFromRes(localRes, true) + '</td>';
  827. html += '<td>' + Anime.avgSeedsForRes(localRes, true) + '</td>';
  828. html += '<td>' + Anime.avgPeersForRes(localRes, true) + '</td>';
  829. html += '<td>' + Anime.getTotalSizeForRes(localRes, true) + ' (aprox)</td>';
  830. html += '</tr>';
  831. }
  832. html += '</tbody>';
  833. html += '</table>';
  834. return html;
  835. };
  836. var buildDropdownSelections = function () {
  837. var html = '';
  838. html += '<select style="margin-right:5px;" id="downloadRes">';
  839. var allRes = Anime.getAvailableResolutions();
  840. for (let i = 0; i < allRes.length; i++) {
  841. var currRes = allRes[i];
  842. var localRes = currRes.res;
  843. html += '<option value=' + localRes + '>' + (localRes === -1 ? 'Others' : localRes + 'p') + '</option>';
  844. }
  845. html += '</select>';
  846. return html;
  847. };
  848. var builDownloadAlert = function (type) {
  849. if (typeof type !== 'string') {
  850. throw 'type must a string';
  851. }
  852. var amountOfAnime = 0;
  853. var selectedRes = parseInt($('#downloadRes').val());
  854. var res = null;
  855. var totalSize = null;
  856. if (type === 'downloadSelected') {
  857. amountOfAnime = $('.checkboxes:checked').length;
  858. res = 'custom';
  859. } else if (type === 'downloadSelects') {
  860. amountOfAnime = $('#animeSelection option:selected').length;
  861. totalSize = Utils.getHumanReadableSize((function () {
  862. var localSize = 0;
  863. $('#animeSelection option:selected').each(function (k, v) {
  864. var url = this.dataset.url;
  865. var epSize = Anime.getEpisodeFromAnchor(url).getSize();
  866. localSize += epSize;
  867. });
  868. return localSize;
  869. }()));
  870. res = 'custom';
  871. } else {
  872. amountOfAnime = Anime.getamountOfEpsFromRes(selectedRes, false);
  873. res = selectedRes === -1 ? 'Others' : selectedRes + 'p';
  874. totalSize = Anime.getTotalSizeForRes(parseInt(res), false);
  875. }
  876. var seedLimit = Options.Seeds.minSeeds === -1 ? 'None' : Options.Seeds.minSeeds;
  877. var html = '';
  878. html += '<div class=\'alert alert-success\'>';
  879. html += '<div><strong>Download: ' + res + '</strong></div> <br />';
  880. html += '<div><strong>Seed Limit: ' + seedLimit + '</strong></div>';
  881. if (totalSize !== null) {
  882. html += '<br /><div><strong>Total size: ' + totalSize + ' (aprox)</strong></div>';
  883. }
  884. html += '<p>You are about to download ' + amountOfAnime + ' ep(s)</p>';
  885. html += '<p>This will cause ' + amountOfAnime + ' download pop-up(s) Are you sure you want to continue?</p>';
  886. 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>';
  887. html += '<button id=\'alertButton\'>Okay</button>';
  888. html += '<button id=\'alertButtonCancel\'>Cancel</button>';
  889. html += '</div>';
  890. return html;
  891. };
  892. var showAjaxErrorAlert = function (AjaxInfo) {
  893. if (!$('#parseErrors').is(':hidden')) {
  894. return;
  895. }
  896. $('#parseErrors').html('');
  897. var html = '';
  898. html += '<div class=\'alert alert-danger\'>';
  899. html += '<p>There was an error in getting the infomation from page: \'' + AjaxInfo.error.pageAtError + '\'</p>';
  900. html += '<p>The last successful page parsed was page number ' + (AjaxInfo.currentPage === null ? 1 : AjaxInfo.currentPage) + ' </p>';
  901. html += '<button id=\'errorClose\'> close </button>';
  902. html += '</div>';
  903. $('#parseErrors').html(html);
  904. $('#parseErrors').slideDown('slow');
  905. $('#errorClose').off('click').on('click', function () {
  906. $('#parseErrors').slideUp('slow', function () {
  907. $(this).html('');
  908. });
  909. });
  910. };
  911. var buildSelect = function (resTOFilter) {
  912. resTOFilter = typeof resTOFilter === 'undefined' ? 'none' : resTOFilter;
  913. var html = '';
  914. epsInSelect = [
  915. ];
  916. html += '<div id=\'selectWrapper\'>';
  917. html += '<div id=\'selectContainer\'>';
  918. html += '<p>Or you can select episodes here:</p>';
  919. html += '<p>Seed limit: ' + (Options.Seeds.minSeeds === -1 ? 'None' : Options.Seeds.minSeeds) + '</p>'
  920. html += '<select id=\'animeSelection\' multiple size=\'20\'>';
  921. var allRes = Anime.getAvailableResolutions();
  922. for (let i = 0; i < allRes.length; i++) {
  923. var currRes = allRes[i];
  924. var localRes = currRes.res;
  925. var eps = Anime.getEpsForRes(localRes, false);
  926. for (var j = 0, len = eps.length; j < len; j++) {
  927. var currentEp = eps[j];
  928. if (resTOFilter == 'none' || currentEp.getRes() == resTOFilter) {
  929. html += '<option data-url=\'' + currentEp.getDownloadLink() + '\'>';
  930. html += currentEp.getTitle() + ' - Seeders: ' + currentEp.getSeeds();
  931. epsInSelect.push(currentEp);
  932. html += '</option>';
  933. } else {
  934. break;
  935. }
  936. }
  937. }
  938. html += '</select>';
  939. html += '<span>Filter select control: </span>';
  940. var checked = false;
  941. for (let i = 0; i < allRes.length; i++) {
  942. if (resTOFilter == allRes[i].res) {
  943. checked = true;
  944. }
  945. html += '<input type=\'radio\' ' + (checked ? 'checked' : '') + ' data-set= \'' + allRes[i].res + '\' name=\'filterSelect\'/>' + '<label>' + (allRes[i].res === -1 ? 'Others' : allRes[i].res + 'p') + '</label>';
  946. checked = false;
  947. }
  948. html += '<a id=\'clearResOptions\' data-set=\'none\' >clear</a>';
  949. html += '</div>';
  950. html += '</div>';
  951. return html;
  952. };
  953. var applySearch = function (textToFilter) {
  954. var opts = epsInSelect;
  955. var rxp = new RegExp(textToFilter);
  956. var optlist = $('#animeSelection').empty();
  957. for (let i = 0, len = opts.length; i < len; i++) {
  958. var ep = opts[i];
  959. if (rxp.test(ep.getTitle())) {
  960. optlist.append('<option data-url=\'' + ep.getDownloadLink() + '\'>' + ep.getTitle() + ' - Seeders: ' + ep.getSeeds() + '</option>');
  961. }
  962. }
  963. searchApplied = textToFilter;
  964.  
  965. Utils.sortSelect(document.getElementById("animeSelection"));
  966. };
  967. var getAppliedSearch = function () {
  968. return searchApplied;
  969. };
  970. var getEpsInSelect = function () {
  971. return epsInSelect;
  972. };
  973. return {
  974. buildDropdownSelections: buildDropdownSelections,
  975. buildTable: buildTable,
  976. builDownloadAlert: builDownloadAlert,
  977. showAjaxErrorAlert: showAjaxErrorAlert,
  978. buildSelect: buildSelect,
  979. getEpsInSelect: getEpsInSelect,
  980. applySearch: applySearch,
  981. getAppliedSearch: getAppliedSearch
  982. };
  983. }());
  984. var DataParser = (function () {
  985. var table = null;
  986. let isParsing = false;
  987. var setTable = function (_table) {
  988. table = _table;
  989. };
  990. /**
  991. * Parses a table and returns an array of Episodes from it
  992. * @param {Object} table Jquery representation of the anime table
  993. * @returns {Episode} Array of Episodes
  994. */
  995. var parseTable = function (currentPage) {
  996. if (table === null) {
  997. throw 'no table to parse on, table is null';
  998. }
  999. var trRow = table.find('img[src*=\'www-37.png\']').closest('tr');
  1000. var eps = [
  1001. ];
  1002. $.each($(trRow), function (k, v) {
  1003. var Dres = parseRes(this);
  1004. if (Dres === -1) {
  1005. Anime.addAvailableResolutions(-1, null);
  1006. } else {
  1007. Anime.addAvailableResolutions(Dres.res, Dres.fullRes);
  1008. }
  1009. let info = getEpisodeInfo(this);
  1010. eps.push(new Episode(typeof Dres.res === ('undefined') ? -1 : Dres.res, info.currentDownloadLink, info.seeds, info.leech, info.uid, currentPage, info.title, info.size));
  1011. });
  1012. return eps;
  1013.  
  1014. function parseRes(eventContent) {
  1015. var suppRes = Anime.getSupportedRes();
  1016.  
  1017. for (let i = 0; i < Anime.getSupportedRes().length; i++) {
  1018. var currRes = suppRes[i].res;
  1019. var currFullRes = suppRes[i].fullRes;
  1020. if ($(eventContent).children('td:nth-child(2)').text().indexOf(currRes + 'p') > -1 || $(eventContent).children('td:nth-child(2)').text().indexOf(currFullRes) > -1) {
  1021. return suppRes[i];
  1022. }
  1023. }
  1024. return -1;
  1025. }
  1026.  
  1027. function getEpisodeInfo(eventContent) {
  1028. return {
  1029. 'currentDownloadLink': $(eventContent).find('td:nth-child(3) >a').attr('href'),
  1030. 'seeds': (isNaN(parseInt($(eventContent).find('td.tlistsn').text()))) ? 0 : parseInt($(eventContent).find('td.tlistsn').text()),
  1031. 'leech': (isNaN(parseInt($(eventContent).find('td.tlistln').text()))) ? 0 : parseInt($(eventContent).find('td.tlistln').text()),
  1032. 'title': $(eventContent).children('.tlistname').text(),
  1033. 'uid': Anime.getUidFromJqueryObject($(eventContent)),
  1034. 'size': (function () {
  1035. var sizeValue = $(eventContent).find('td.tlistsize').text();
  1036. var sizeText = $.trim(sizeValue.split(' ').pop());
  1037. let intValue = parseInt(sizeValue);
  1038. switch (sizeText) {
  1039. case 'MiB':
  1040. return ((Math.pow(2, 20)) / 1) * intValue;
  1041. break;
  1042. case 'GiB':
  1043. return intValue * 1073741824;
  1044. break;
  1045. default:
  1046. return 0;
  1047. break;
  1048. }
  1049. }())
  1050. };
  1051. }
  1052. };
  1053. return {
  1054. parseTable: parseTable,
  1055. setTable: setTable,
  1056. isParsing: isParsing
  1057. };
  1058. }());
  1059. var QueryString = function () {
  1060. var query_string = {};
  1061. var query = window.location.search.substring(1);
  1062. var vars = query.split('&');
  1063. for (let i = 0; i < vars.length; i++) {
  1064. var pair = vars[i].split('=');
  1065. if (typeof query_string[pair[0]] === 'undefined') {
  1066. query_string[pair[0]] = pair[1];
  1067. } else if (typeof query_string[pair[0]] === 'string') {
  1068. var arr = [
  1069. query_string[pair[0]],
  1070. pair[1]
  1071. ];
  1072. query_string[pair[0]] = arr;
  1073. } else {
  1074. query_string[pair[0]].push(pair[1]);
  1075. }
  1076. }
  1077. return query_string;
  1078. }();
  1079. var Options = (function () {
  1080. var Seeds = {};
  1081. Object.defineProperty(Seeds, 'minSeeds', {
  1082. enumerable: true,
  1083. set: function (seeds) {
  1084. if (typeof seeds !== 'number') {
  1085. throw 'seeds must be a number';
  1086. }
  1087. this._minSeeds = seeds;
  1088. if (Utils.html5StoreSupport() === true) { // also set it on the local DB
  1089. if (this._minSeeds === -1) {
  1090. Localstore.removeMinSeedsFromStore();
  1091. } else {
  1092. Localstore.setMinSeedsFromStore(this._minSeeds);
  1093. }
  1094. }
  1095. },
  1096. get: function () {
  1097. return typeof this._minSeeds === 'undefined' ? -1 : this._minSeeds;
  1098. }
  1099. });
  1100. return {
  1101. Seeds: Seeds
  1102. };
  1103. }());
  1104. //Local storeage object
  1105. var Localstore = {
  1106. getMinSeedsFromStore: function () {
  1107. return localStorage.getItem('minSeeds');
  1108. },
  1109. setMinSeedsFromStore: function (seeds) {
  1110. localStorage.setItem('minSeeds', seeds);
  1111. },
  1112. removeMinSeedsFromStore: function () {
  1113. localStorage.removeItem('minSeeds');
  1114. }
  1115. };
  1116. // Download fix for firefox
  1117. HTMLElement.prototype.click = function () {
  1118. var evt = this.ownerDocument.createEvent('MouseEvents');
  1119. evt.initMouseEvent('click', true, true, this.ownerDocument.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
  1120. this.dispatchEvent(evt);
  1121. };
  1122. /* OBJECT CREATION END */
  1123. init(); // init the pannel and set up objects and listeners
  1124. AfterInit(); // set page laod items and settings after the object and ui is built
  1125. function init() {
  1126. setAnimeObj();
  1127. buildUi();
  1128. bindListeners();
  1129.  
  1130. function setAnimeObj() {
  1131. // Set currentAnime
  1132. if (QueryString.term !== '') {
  1133. Anime.setCurrentAnime(decodeURIComponent(QueryString.term).split('+').join(' '));
  1134. } else {
  1135. Anime.setCurrentAnime('Unknown');
  1136. }
  1137. // set subber
  1138.  
  1139. Anime.setCurrentSubber($('.notice > a > *').html());
  1140. // Set eps
  1141. DataParser.setTable($('table.tlist'));
  1142. // set the seed limit
  1143. var eps = DataParser.parseTable(Utils.getCurrentPageOffset());
  1144. if (Localstore.getMinSeedsFromStore() !== null) {
  1145. var minSeeds = parseInt(Localstore.getMinSeedsFromStore());
  1146. Options.Seeds.minSeeds = minSeeds;
  1147. }
  1148. Anime.addAllEps(eps);
  1149. }
  1150.  
  1151. function buildUi() {
  1152. makeStyles();
  1153. buildPanel();
  1154. afterBuild();
  1155.  
  1156. function makeStyles() {
  1157. var styles = '';
  1158. // Panel
  1159. 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;}';
  1160. styles += '.panel-success {border-color: #d6e9c6;}';
  1161. styles += '.collapse{cursor: pointer; position: absolute; right: 4px; top: 2px;}';
  1162. styles += '.panel-heading{ border-bottom: 1px solid transparent; border-top-left-radius: 3px; border-top-right-radius: 3px; padding: 4px 15px; text-align:center}';
  1163. styles += '.panel-success > .panel-heading {background-color: #dff0d8; border-color: #d6e9c6; color: #3c763d; position: relative;}';
  1164. styles += '.panel-title {color: inherit; margin-bottom: 0; margin-top: 0; padding: 6px; display: inline-block;}';
  1165. styles += '.panel-body {padding: 15px;}';
  1166. styles += '.avgSeeds{floar:left; padding-right:10px; color:#3c763d;}';
  1167. styles += '.checkboxes{left:1px; margin:0; padding:0; position: relative; top: 1px; z-index: 1;}';
  1168. styles += '#topbar{z-index: 2 !important;}';
  1169. // Infomation
  1170. styles += '#info, #info th, #info td {border: 1px solid black;border-collapse: collapse;}';
  1171. styles += '#info th, #info td {padding: 5px;text-align: left;}';
  1172. styles += 'label[for=\'MinSeeds\']{ display: block; margin-top: 10px;}';
  1173. styles += '#SaveMinSeeds{margin-left:5px;}';
  1174. // Alerts
  1175. styles += '.alert {border: 1px solid transparent;border-radius: 4px;margin-bottom: 20px;padding: 15px; position:relative;}';
  1176. styles += '.alert-success {background-color: #dff0d8;border-color: #d6e9c6;color: #3c763d;}';
  1177. styles += '.alert-danger {color: #A94442;background-color: #F2DEDE;border-color: #EBCCD1;}';
  1178. styles += '#alertUser, #parseErrors{margin-top: 15px;}';
  1179. styles += '#alertButton{position:absolute; bottom:5px; right:5px;}';
  1180. styles += '#alertButtonCancel{position:absolute; bottom:5px; right: 66px;}';
  1181. styles += '#errorClose{position:absolute; bottom:5px; right: 11px;}';
  1182. // Anime Selects
  1183. styles += '#animeSelection{width: 100%;}';
  1184. styles += '#clearResOptions{margin-left: 10px; cursor: pointer;}';
  1185. styles += '#downloadCustomButton{float:right;}';
  1186. styles += '#findEp{float: right; position: relative; bottom: 20px; width: 180px;}'
  1187. DomUtil.injectCss('https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css');
  1188. DomUtil.injectCss(styles);
  1189. }
  1190.  
  1191. function buildPanel() {
  1192. var html = '';
  1193. html += '<div class="panel panel-success">';
  1194. html += '<div class="panel-heading">';
  1195. html += '<h3 id="panel-title" class="panel-title"></h3>';
  1196. html += '<i class="fa fa-minus collapse" id="collapseToggle" title="Hide"></i>';
  1197. html += '</div>';
  1198. html += '<div class="panel-body" id="pannelContent"></div>';
  1199. html += '</div>';
  1200. $('.content > .notice').after(html);
  1201. buildPanelContent();
  1202.  
  1203. function buildPanelContent() {
  1204. var html = '';
  1205. html += '<div>';
  1206. $('#panel-title').html('<span> Download "' + Anime.getCurrentAnime() + ' (' + Anime.getCurrentSubber() + ')"</span>');
  1207. if (Anime.getAmountOfEps() === 0) {
  1208. html += '<span> No translated anime found or error occured</span>';
  1209. html += '</div>';
  1210. $('#pannelContent').html(html);
  1211. return;
  1212. }
  1213. html += '<span>Pick a resolution: </span>';
  1214. html += '<span id=\'selectDownload\'>';
  1215. html += UI.buildDropdownSelections();
  1216. html += '</span>';
  1217. html += '<button type="button" data-type=\'downloadAll\' id="downloadAll">Download all</button>';
  1218. html += '<button type=\'button\' id=\'downloadCustomButton\' data-type=\'downloadSelected\' >download your selected items</button>';
  1219. html += '</div>';
  1220. html += '<div id=\'options\'>';
  1221. html += '<label for=\'crossPage\'> include Cross pages</label>';
  1222. html += '<input type=\'checkbox\' id=\'crossPage\' /> ';
  1223. html += '<label for=\'MinSeeds\'>Minimum seeders:</label>';
  1224. html += '<input type=\'number\' min=\'0\' id=\'MinSeeds\' title=\'Any episode that is below this limit will be excluded from the download.\'/>';
  1225. html += '<button type=\'button\' id=\'SaveMinSeeds\'>Save</button>';
  1226. html += '<div id=\'tableInfo\'>';
  1227. html += UI.buildTable();
  1228. html += '</div>';
  1229. html += '<div id=\'alertUser\' class=\'hide\'></div>';
  1230. html += '<div class=\'selectAnime\' id=\'selectAnime\'>';
  1231. html += UI.buildSelect();
  1232. html += '</div>';
  1233. html += '<input type=\'text\' id=\'findEp\' placeholder=\'Search Select (or use regex)\' />';
  1234. html += '<button id=\'acceptSelect\' data-type=\'downloadSelects\'>Select for download</button>';
  1235. html += '<div id=\'parseErrors\' class =\'hide\'></div>';
  1236. $('#pannelContent').html(html);
  1237. }
  1238. }
  1239.  
  1240. function afterBuild() {
  1241. makeCheckBoxes();
  1242. sortLists();
  1243.  
  1244. function makeCheckBoxes() {
  1245. $('.tlistdownload > a').after('<input class=\'checkboxes\' type=\'checkbox\'/>');
  1246. }
  1247.  
  1248. function sortLists() {
  1249. Utils.sortAllControls();
  1250. }
  1251. }
  1252. }
  1253.  
  1254. function bindListeners() {
  1255. Utils.reBindSelectFilters();
  1256. $('#downloadAll').on('click', function (e) {
  1257. Utils.doDownloads(e);
  1258. });
  1259. $('#downloadCustomButton').on('click', function (e) {
  1260. Utils.doDownloads(e);
  1261. });
  1262. $('.checkboxes').on('click', function (e) {
  1263. if (Utils.checkBoxValid($('.checkboxes'))) {
  1264. Utils.enableButton($('#downloadCustomButton'));
  1265. } else {
  1266. Utils.disableButton($('#downloadCustomButton'));
  1267. }
  1268. });
  1269. $('#crossPage').on('click', function (e) {
  1270. preformParsing(Anime.getPageUrls(true));
  1271.  
  1272. function preformParsing(urls) {
  1273. if (urls.length === 0) {
  1274. return;
  1275. }
  1276. if (Utils.checkBoxValid($('#crossPage'))) {
  1277. $('#tableInfo').html('<p>Please wait while we parse each page...</p>');
  1278. $('#selectAnime').html('');
  1279. $('#acceptSelect').hide();
  1280. $('#crossPage, #downloadAll').prop('disabled', true);
  1281. $('#parseErrors').slideUp('fast', function () {
  1282. $(this).html('');
  1283. });
  1284. var AjaxInfo = {
  1285. error: {
  1286. pageAtError: null
  1287. },
  1288. currentPage: null
  1289. };
  1290. urls.reduce(function (prev, cur, index) {
  1291. return prev.then(function (data) {
  1292. return $.ajax(cur).then(function (data) {
  1293. DataParser.isParsing = true;
  1294. var currentPage = 0;
  1295. if (cur.indexOf('offset') > -1) {
  1296. currentPage = cur.substring(cur.indexOf('offset')).split('&')[0].split('=').pop();
  1297. } else {
  1298. currentPage = 1;
  1299. }
  1300. AjaxInfo.currentPage = currentPage;
  1301. var table = $(data).find('table.tlist');
  1302. DataParser.setTable(table);
  1303. Anime.addAllEps(DataParser.parseTable(parseInt(currentPage)));
  1304. $('#tableInfo').append('<div>Page ' + currentPage + ' Done </div>');
  1305. }, function () {
  1306. AjaxInfo.error.pageAtError = cur.split('=').pop();
  1307. });
  1308. });
  1309. }, $().promise()).always(function () {
  1310. if (AjaxInfo.error.pageAtError !== null) {
  1311. UI.showAjaxErrorAlert(AjaxInfo);
  1312. }
  1313. $('#tableInfo').html(UI.buildTable());
  1314. $('#downloadRes').html(UI.buildDropdownSelections());
  1315. $('#selectAnime').html(UI.buildSelect());
  1316. Utils.sortAllControls();
  1317. $('#acceptSelect').show();
  1318. Utils.reBindSelectFilters();
  1319. $('#crossPage, #downloadAll').prop('disabled', false);
  1320. DataParser.isParsing = false;
  1321. });
  1322. } else { // when un-chekced, clear the Episodes of all eps that are not of the current page
  1323. $('#tableInfo').html('<p>Please wait while we re-calculate the Episodes</p>');
  1324. var currentPage = Utils.getCurrentPageOffset();
  1325. Anime.removeEpisodesFromResidence(currentPage, true);
  1326. Utils.cleanAvailableResolutions();
  1327. $('#downloadRes').html(UI.buildDropdownSelections());
  1328. $('#tableInfo').html(UI.buildTable());
  1329. $('#selectAnime').html(UI.buildSelect());
  1330. Utils.reBindSelectFilters();
  1331. Utils.sortAllControls();
  1332. }
  1333. }
  1334. });
  1335. $('#SaveMinSeeds').on('click', function (e) {
  1336. if (parseInt($('#MinSeeds').val()) < 0) {
  1337. alert('number cannot be negative');
  1338. return;
  1339. }
  1340. var value = parseInt($('#MinSeeds').val() === '' ? -1 : $('#MinSeeds').val());
  1341. Options.Seeds.minSeeds = value;
  1342. if (value === -1) {
  1343. alert('Minimum seeds have been cleared');
  1344. } else {
  1345. alert('Minimum seeds now set to: ' + value);
  1346. }
  1347. $('#selectAnime').html(UI.buildSelect());
  1348. Utils.sortAllControls();
  1349. Utils.reBindSelectFilters();
  1350. });
  1351. $('#collapseToggle').on('click', function () {
  1352. $('#pannelContent').stop(true, true).slideToggle('slow', function () {
  1353. if ($(this).is(':hidden')) {
  1354. $('#collapseToggle').removeClass('fa-minus').addClass('fa-plus');
  1355. $('#collapseToggle').attr('title', 'Show');
  1356. } else {
  1357. $('#collapseToggle').addClass('fa-minus').removeClass('fa-plus');
  1358. $('#collapseToggle').attr('title', 'Hide');
  1359. }
  1360. });
  1361. });
  1362. $('#acceptSelect').on('click', function (e) {
  1363. Utils.doDownloads(e);
  1364. });
  1365. $('#findEp').on('keyup', function () {
  1366. UI.applySearch($(this).val());
  1367. });
  1368. }
  1369. }
  1370.  
  1371. function AfterInit() {
  1372. initButtonStates();
  1373. if (Utils.html5StoreSupport()) {
  1374. setOptionsFromLocalStore();
  1375. }
  1376.  
  1377. function initButtonStates() {
  1378. if (Utils.checkBoxValid($('.checkboxes'))) {
  1379. Utils.enableButton($('#downloadCustomButton'));
  1380. } else {
  1381. Utils.disableButton($('#downloadCustomButton'));
  1382. }
  1383. }
  1384.  
  1385. function setOptionsFromLocalStore() {
  1386. // Min seeds
  1387. if (Localstore.getMinSeedsFromStore() !== null) {
  1388. $('#MinSeeds').val(Options.Seeds.minSeeds);
  1389. }
  1390. }
  1391. }