series.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. 'use strict';
  2. var cheerio = require('cheerio');
  3. var episode = require('./episode');
  4. var persistent = '.crpersistent';
  5. var fs = require('fs');
  6. var request = require('./request');
  7. var path = require('path');
  8. var url = require('url');
  9. /**
  10. * Streams the series to disk.
  11. * @param {Object} config
  12. * @param {string} address
  13. * @param {function(Error)} done
  14. */
  15. module.exports = function(config, address, done) {
  16. var persistentPath = path.join(config.output || process.cwd(), persistent);
  17. fs.readFile(persistentPath, 'utf8', function(err, contents) {
  18. var cache = config.cache ? {} : JSON.parse(contents || '{}');
  19. _page(config, address, function(err, page) {
  20. if (err) return done(err);
  21. var i = 0;
  22. (function next() {
  23. if (i >= page.episodes.length) return done();
  24. _download(cache, config, address, page.episodes[i], function(err) {
  25. if (err) return done(err);
  26. var newCache = JSON.stringify(cache, null, ' ');
  27. fs.writeFile(persistentPath, newCache, function(err) {
  28. if (err) return done(err);
  29. i += 1;
  30. next();
  31. });
  32. });
  33. })();
  34. });
  35. });
  36. };
  37. /**
  38. * Downloads the episode.
  39. * @private
  40. * @param {Object.<string, string>} cache
  41. * @param {Object} config
  42. * @param {string} baseAddress
  43. * @param {Object} item
  44. * @param {function(Error)} done
  45. */
  46. function _download(cache, config, baseAddress, item, done) {
  47. if (!_filter(config, item)) return done();
  48. var address = url.resolve(baseAddress, item.address);
  49. if (cache[address]) return done();
  50. episode(config, address, function(err) {
  51. if (err) return done(err);
  52. cache[address] = Date.now();
  53. done();
  54. });
  55. }
  56. /**
  57. * Filters the item based on the configuration.
  58. * @param {Object} config
  59. * @param {Object} item
  60. * @returns {boolean}
  61. */
  62. function _filter(config, item) {
  63. // Filter on chapter.
  64. var episodeFilter = parseInt(config.episode, 10);
  65. if (episodeFilter > 0 && item.episode <= episodeFilter) return false;
  66. if (episodeFilter < 0 && item.episode >= -episodeFilter) return false;
  67. // Filter on volume.
  68. var volumeFilter = parseInt(config.volume, 10);
  69. if (volumeFilter > 0 && item.volume <= volumeFilter) return false;
  70. if (volumeFilter < 0 && item.volume >= -volumeFilter) return false;
  71. return true;
  72. }
  73. /**
  74. * Requests the page and scrapes the episodes and series.
  75. * @private
  76. * @param {Object} config
  77. * @param {string} address
  78. * @param {function(Error, Object=)} done
  79. */
  80. function _page(config, address, done) {
  81. request.get(config, address, function(err, res, body) {
  82. if (err) return done(err);
  83. var $ = cheerio.load(body);
  84. var title = $('span[itemprop=name]').text();
  85. if (!title) return done(new Error('Invalid page.'));
  86. var episodes = [];
  87. $('.episode').each(function(i, el) {
  88. if ($(el).children('img[src*=coming_soon]').length) return;
  89. var volume = /([0-9]+)\s*$/.exec($(el).closest('ul').prev('a').text());
  90. var regexp = /Episode\s+([0-9]+)\s*$/i;
  91. var episode = regexp.exec($(el).children('.series-title').text());
  92. var address = $(el).attr('href');
  93. if (!address || !episode) return;
  94. episodes.push({
  95. address: address,
  96. episode: parseInt(episode[0], 10),
  97. volume: volume ? parseInt(volume[0], 10) : 1
  98. });
  99. });
  100. done(undefined, {episodes: episodes.reverse(), series: title});
  101. });
  102. }