series.ts 3.1 KB

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