diff --git a/README.md b/README.md index a2ae60f..8c255f6 100644 --- a/README.md +++ b/README.md @@ -40,12 +40,20 @@ prior to using this application. * Add batch-mode to queue a bunch of series. * Add CLI interface with all the options. * Support scheduled merging; if it fails now, the video is probably being watched. +* Add authentication to the entire stack to support premium content. ### Pending Implementation -* Add authentication to the entire stack to support premium content. +* Binary runner for `npm` +* Windows examples with a .bat for ease of use. +* Documentation. +* Publish to `npm` with a fixed package.json. * Enjoy beautiful anime series from disk when internet is down. +## Configuration + +Set defaults in https://www.crunchyroll.com/acct/?action=video. We'll use that. + ## Work In Progress Open an issue or e-mail me directly. I'd be happy to answer your questions. diff --git a/src/batch.js b/src/batch.js index 5591173..6fabb75 100644 --- a/src/batch.js +++ b/src/batch.js @@ -91,6 +91,9 @@ function _parse(args) { .option('-m, --merge', 'Disables merging subtitles and videos.') // Filters .option('-e, --episode ', 'The episode filter.') + // Authentication + .option('-p, --pass ', 'The password.') + .option('-u, --user ', 'The e-mail address or username.') // Settings .option('-f, --format ', 'The subtitle format. (Default: ass)') .option('-o, --output ', 'The output path.') diff --git a/src/episode.js b/src/episode.js index a430079..0413d32 100644 --- a/src/episode.js +++ b/src/episode.js @@ -2,7 +2,7 @@ var cheerio = require('cheerio'); var fs = require('fs'); var mkdirp = require('mkdirp'); -var request = require('request'); +var request = require('./request'); var path = require('path'); var subtitle = require('./subtitle'); var video = require('./video'); @@ -15,9 +15,9 @@ var xml2js = require('xml2js'); * @param {function(Error)} done */ module.exports = function (config, address, done) { - _page(address, function(err, page) { + _page(config, address, function(err, page) { if (err) return done(err); - _player(address, page.id, function(err, player) { + _player(config, address, page.id, function(err, player) { if (err) return done(err); _download(config, page, player, done); }); @@ -87,13 +87,14 @@ function _name(config, page, series, tag) { /** * Requests the page data and scrapes the id, episode, series and swf. * @private + * @param {Object} config * @param {string} address * @param {function(Error, Object=)} done */ -function _page(address, done) { +function _page(config, address, done) { var id = parseInt((address.match(/[0-9]+$/) || [0])[0], 10); if (!id) return done(new Error('Invalid address.')); - request.get(address, function(err, res, body) { + request.get(config, address, function(err, res, body) { if (err) return done(err); var $ = cheerio.load(body); var swf = /^([^?]+)/.exec($('link[rel=video_src]').attr('href')); @@ -124,14 +125,15 @@ function _prefix(value, length) { /** * Requests the player data and scrapes the subtitle and video data. * @private + * @param {Object} config * @param {string} address * @param {number} id * @param {function(Error, Object=)} done */ -function _player(address, id, done) { +function _player(config, address, id, done) { var url = address.match(/^(https?:\/\/[^\/]+)/); if (!url) return done(new Error('Invalid address.')); - request.post({ + request.post(config, { form: {current_page: address}, url: url[1] + '/xml/?req=RpcApiVideoPlayer_GetStandardConfig&media_id=' + id }, function(err, res, xml) { diff --git a/src/request.js b/src/request.js new file mode 100644 index 0000000..53af976 --- /dev/null +++ b/src/request.js @@ -0,0 +1,63 @@ +'use strict'; +var isAuthenticated = false; +var request = require('request'); + +/** + * Performs a GET request for the resource. + * @param {Object} config + * @param {(string|Object)} options + * @param {function(Error, Object, string)} done + */ +module.exports.get = function(config, options, done) { + _authenticate(config, function(err) { + if (err) return done(err); + request.get(_modify(options), done); + }); +}; + +/** +* Performs a POST request for the resource. +* @param {Object} config +* @param {(string|Object)} options +* @param {function(Error, Object, string)} done +*/ +module.exports.post = function(config, options, done) { + _authenticate(config, function(err) { + if (err) return done(err); + request.post(_modify(options), done); + }); +}; + +/** + * Authenticates using the configured pass and user. + * @param {Object} config + * @param {function(Error)} done + */ +function _authenticate(config, done) { + if (isAuthenticated || !config.pass || !config.user) return done(); + request.post({ + form: { + formname: 'RpcApiUser_Login', + fail_url: 'https://www.crunchyroll.com/login', + name: config.user, + password: config.pass + }, + jar: true, + url: 'https://www.crunchyroll.com/?a=formhandler' + }, function(err) { + if (err) return done(err); + isAuthenticated = true; + done(); + }); +} + +/** + * Modifies the options to use the authenticated cookie jar. + * @param {(string|Object)} options + * @returns {Object} + */ +function _modify(options) { + if (typeof options === 'string') options = {url: options}; + options.jar = true; + return options; +} diff --git a/src/series.js b/src/series.js index b6ef546..e743b63 100644 --- a/src/series.js +++ b/src/series.js @@ -3,7 +3,7 @@ var cheerio = require('cheerio'); var episode = require('./episode'); var persistent = '.crpersistent'; var fs = require('fs'); -var request = require('request'); +var request = require('./request'); var path = require('path'); var url = require('url'); @@ -13,11 +13,11 @@ var url = require('url'); * @param {string} address * @param {function(Error)} done */ -module.exports = function (config, address, done) { +module.exports = function(config, address, done) { var persistentPath = path.join(config.output || process.cwd(), persistent); fs.readFile(persistentPath, 'utf8', function(err, data) { var cache = config.cache ? {} : JSON.parse(data || '{}'); - _page(address, function(err, page) { + _page(config, address, function(err, page) { if (err) return done(err); var i = 0; (function next() { @@ -62,11 +62,12 @@ function _download(cache, config, baseAddress, data, done) { /** * Requests the page data and scrapes the episodes and series. * @private + * @param {Object} config * @param {string} address * @param {function(Error, Object=)} done */ -function _page(address, done) { - request.get(address, function(err, res, body) { +function _page(config, address, done) { + request.get(config, address, function(err, res, body) { if (err) return done(err); var $ = cheerio.load(body); var title = $('.season-dropdown').text() || $('span[itemprop=name]').text();