series.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. 'use strict';
  2. import cheerio = require('cheerio');
  3. import episode from './episode';
  4. import fs = require('fs');
  5. const fse = require('fs-extra');
  6. import request = require('./request');
  7. import path = require('path');
  8. import url = require('url');
  9. import log = require('./log');
  10. const persistent = '.crpersistent';
  11. /**
  12. * Streams the series to disk.
  13. */
  14. export default function(config: IConfig, address: string, done: (err: Error) => void)
  15. {
  16. const persistentPath = path.join(config.output || process.cwd(), persistent);
  17. /* Make a backup of the persistent file in case of */
  18. fse.copySync(persistentPath, persistentPath + '.backup');
  19. fs.readFile(persistentPath, 'utf8', (err: Error, contents: string) =>
  20. {
  21. const cache = config.cache ? {} : JSON.parse(contents || '{}');
  22. page(config, address, (errP, page) =>
  23. {
  24. if (errP)
  25. {
  26. return done(errP);
  27. }
  28. let i = 0;
  29. (function next()
  30. {
  31. if (i >= page.episodes.length) return done(null);
  32. download(cache, config, address, page.episodes[i], (errD, ignored) =>
  33. {
  34. if (errD)
  35. {
  36. return done(errD);
  37. }
  38. if ((ignored === false) || (ignored === undefined))
  39. {
  40. const newCache = JSON.stringify(cache, null, ' ');
  41. fs.writeFile(persistentPath, newCache, (errW: Error) =>
  42. {
  43. if (errW)
  44. {
  45. return done(errW);
  46. }
  47. i += 1;
  48. next();
  49. });
  50. }
  51. else
  52. {
  53. i += 1;
  54. next();
  55. }
  56. });
  57. })();
  58. });
  59. });
  60. }
  61. /**
  62. * Downloads the episode.
  63. */
  64. function download(cache: {[address: string]: number}, config: IConfig,
  65. baseAddress: string, item: ISeriesEpisode,
  66. done: (err: Error, ign: boolean) => void)
  67. {
  68. if (!filter(config, item))
  69. {
  70. return done(null, false);
  71. }
  72. const address = url.resolve(baseAddress, item.address);
  73. if (cache[address])
  74. {
  75. return done(null, false);
  76. }
  77. episode(config, address, (err, ignored) =>
  78. {
  79. if (err)
  80. {
  81. return done(err, false);
  82. }
  83. cache[address] = Date.now();
  84. done(null, ignored);
  85. });
  86. }
  87. /**
  88. * Filters the item based on the configuration.
  89. */
  90. function filter(config: IConfig, item: ISeriesEpisode)
  91. {
  92. // Filter on chapter.
  93. const episodeFilter = config.episode;
  94. // Filter on volume.
  95. const volumeFilter = config.volume;
  96. const currentEpisode = parseInt(item.episode, 10);
  97. const currentVolume = item.volume;
  98. if ( ( (episodeFilter > 0) && (currentEpisode <= episodeFilter) ) ||
  99. ( (episodeFilter < 0) && (currentEpisode >= -episodeFilter) ) ||
  100. ( (volumeFilter > 0) && (currentVolume <= volumeFilter ) ) ||
  101. ( (volumeFilter < 0) && (currentVolume >= -volumeFilter ) ) )
  102. {
  103. return false;
  104. }
  105. return true;
  106. }
  107. /**
  108. * Requests the page and scrapes the episodes and series.
  109. */
  110. function page(config: IConfig, address: string, done: (err: Error, result?: ISeries) => void)
  111. {
  112. if (address[0] === '@')
  113. {
  114. log.info('Trying to fetch from ' + address.substr(1));
  115. const episodes: ISeriesEpisode[] = [];
  116. episodes.push({
  117. address: address.substr(1),
  118. episode: '',
  119. volume: 0,
  120. });
  121. done(null, {episodes: episodes.reverse(), series: ""});
  122. }
  123. else
  124. {
  125. let episodeCount = 0;
  126. request.get(config, address, (err, result) => {
  127. if (err) {
  128. return done(err);
  129. }
  130. const $ = cheerio.load(result);
  131. const title = $('span[itemprop=name]').text();
  132. if (!title) {
  133. return done(new Error('Invalid page.(' + address + ')'));
  134. }
  135. log.info('Checking availability for ' + title);
  136. const episodes: ISeriesEpisode[] = [];
  137. $('.episode').each((i, el) => {
  138. if ($(el).children('img[src*=coming_soon]').length) {
  139. return;
  140. }
  141. const volume = /([0-9]+)\s*$/.exec($(el).closest('ul').prev('a').text());
  142. const regexp = /Episode\s+((PV )?[S0-9][\-P0-9.]*[a-fA-F]?)\s*$/i;
  143. const episode = regexp.exec($(el).children('.series-title').text());
  144. const url = $(el).attr('href');
  145. if ((!url) || (!episode)) {
  146. return;
  147. }
  148. episodeCount += 1;
  149. episodes.push({
  150. address: url,
  151. episode: episode[1],
  152. volume: volume ? parseInt(volume[0], 10) : 1,
  153. });
  154. });
  155. if (episodeCount === 0)
  156. {
  157. log.warn("No episodes found for " + title + ". Could it be a movie?");
  158. }
  159. done(null, {episodes: episodes.reverse(), series: title});
  160. });
  161. }
  162. }