7 Commits

Author SHA1 Message Date
Godzil
141bdccf02 1.3.7 2018-07-30 22:47:38 +01:00
Godzil
4990effa1c Try to fix #81 and probably some other issues when the URL is not valid to properly display that the URL is not valid. Also change a bit on how error are handled 2018-07-30 22:47:38 +01:00
Godzil
2459f342c5 Force debug file to be written synchronously 2018-07-30 22:47:38 +01:00
Godzil
d68a2b7bce Update dependencies 2018-07-30 22:47:38 +01:00
Godzil
69d5ceac36 Remove useless ignore in .gitignore 2018-07-30 22:47:38 +01:00
Manoël Trapier
cf7039400c Update readme with more usefull examples 2018-07-30 22:47:38 +01:00
Godzil
02a9d763cd Add the episode title in the default file name template. 2018-07-30 22:47:38 +01:00
8 changed files with 2155 additions and 265 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,2 @@
dist/ dist/
node_modules/ node_modules/
typings/

View File

@@ -92,37 +92,47 @@ When no sequence of series addresses is provided, the batch-mode source file wil
Download in batch-mode: Download in batch-mode:
crunchy You will need to create the batch file (default name is `CrunchyRoll.txt`):
Download *Fairy Tail* to the current work directory: http://www.cr.com/tail-fairy
http://www.cr.com/gin-mama
http://www.cr.com/two-parts
// Just download episodes 3 to 42
http://www.cr.com/defense-of-dwarfs -e 3-42
crunchy http://www.crunchyroll.com/fairy-tail Then launch crunchy:
Download *Fairy Tail* to `C:\Anime`: crunchy -u login -p password http://www.cr.com/tail-fairy
crunchy --output C:\Anime http://www.crunchyroll.com/fairy-tail Download *Tail Fairy* to the current work directory:
Download episode 42 of *Fairy Tail* to `C:\Anime`: crunchy -u login -p password http://www.cr.com/tail-fairy
crunchy --output C:\Anime @http://www.crunchyroll.com/fairy-tail/episode-46-the-silver-labyrinth-662721 Download *Tail Fairy* to `C:\Anime`:
crunchy -u login -p password --output C:\Anime http://www.cr.com/tail-fairy
Download episode 42 of *Tail Fairy* to `C:\Anime`:
crunchy -u login -p password --output C:\Anime @http://www.cr.com/tail-fairy/episode-42-the-episode-which-dont-exist-665544
*Notice the '@' in front of the URL, it is there to tell Crunchy that the URL is an episode URL and not a series URL.* *Notice the '@' in front of the URL, it is there to tell Crunchy that the URL is an episode URL and not a series URL.*
or or
crunchy --output C:\Anime http://www.crunchyroll.com/fairy-tail -e 42 crunchy -u login -p password --output C:\Anime http://www.cr.com/tail-fairy -e 42
Download episode 10 to 42 (both included) of *Fairy Tail*: Download episode 10 to 42 (both included) of *Tail Fairy*:
crunchy http://www.crunchyroll.com/fairy-tail -e 10-42 crunchy -u login -p password http://www.cr.com/tail-fairy -e 10-42
Download episode up to 42 (included) of *Fairy Tail*: Download episode up to 42 (included) of *Tail Fairy*:
crunchy http://www.crunchyroll.com/fairy-tail -e -42 crunchy -u login -p password http://www.cr.com/tail-fairy -e -42
Download episodes starting from 42 to the last available of *Fairy Tail*: Download episodes starting from 42 to the last available of *Tail Fairy*:
crunchy http://www.crunchyroll.com/fairy-tail -e 42- crunchy -u login -p password http://www.cr.com/tail-fairy -e 42-

2257
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -15,19 +15,18 @@
"engines": { "engines": {
"node": ">=5.0" "node": ">=5.0"
}, },
"version": "1.3.6", "version": "1.3.7",
"bin": { "bin": {
"crunchy": "./bin/crunchy", "crunchy": "./bin/crunchy",
"crunchy.sh": "./bin/crunchy.sh" "crunchy.sh": "./bin/crunchy.sh"
}, },
"dependencies": { "dependencies": {
"@types/node": "^10.3.3", "big-integer": "^1.6.32",
"big-integer": "^1.6.31",
"bluebird": "^3.5.1", "bluebird": "^3.5.1",
"cheerio": "^0.22.0", "cheerio": "^0.22.0",
"cloudscraper": "^1.5.0", "cloudscraper": "^1.5.0",
"commander": "^2.15.1", "commander": "^2.16.0",
"fs-extra": "^6.0.1", "fs-extra": "^7.0.0",
"mkdirp": "^0.5.0", "mkdirp": "^0.5.0",
"pjson": "^1.0.9", "pjson": "^1.0.9",
"request": "^2.87.0", "request": "^2.87.0",
@@ -35,15 +34,17 @@
"xml2js": "^0.4.5" "xml2js": "^0.4.5"
}, },
"devDependencies": { "devDependencies": {
"@types/bluebird": "^3.5.20", "@types/bluebird": "^3.5.23",
"@types/cheerio": "^0.22.7", "@types/cheerio": "^0.22.8",
"@types/fs-extra": "^5.0.3", "@types/fs-extra": "^5.0.4",
"@types/mkdirp": "^0.5.2", "@types/mkdirp": "^0.5.2",
"@types/node": "^10.5.3",
"@types/request": "^2.47.1", "@types/request": "^2.47.1",
"@types/request-promise": "^4.1.41", "@types/request-promise": "^4.1.42",
"@types/xml2js": "^0.4.3", "@types/xml2js": "^0.4.3",
"npm-check": "^5.7.1",
"tsconfig-lint": "^0.12.0", "tsconfig-lint": "^0.12.0",
"tslint": "^5.10.0", "tslint": "^5.11.0",
"typescript": "^2.9.2" "typescript": "^2.9.2"
}, },
"scripts": { "scripts": {

View File

@@ -62,6 +62,11 @@ export default function(args: string[], done: (err?: Error) => void)
return done(err); return done(err);
} }
if (tasksArr[0].address === '')
{
return done();
}
let i = 0; let i = 0;
(function next() (function next()
@@ -80,12 +85,18 @@ export default function(args: string[], done: (err?: Error) => void)
{ {
if (errin) if (errin)
{ {
if (errin.error)
{
/* Error from the request, so ignore it */
tasksArr[i].retry = 0;
}
if (tasksArr[i].retry <= 0) if (tasksArr[i].retry <= 0)
{ {
log.error(errin.stack || errin); log.error(JSON.stringify(errin));
if (config.debug) if (config.debug)
{ {
log.dumpToDebug('BatchGiveUp', errin.stack || errin); log.dumpToDebug('BatchGiveUp', JSON.stringify(errin));
} }
log.error('Cannot get episodes from "' + tasksArr[i].address + '", please rerun later'); log.error('Cannot get episodes from "' + tasksArr[i].address + '", please rerun later');
/* Go to the next on the list */ /* Go to the next on the list */
@@ -95,11 +106,11 @@ export default function(args: string[], done: (err?: Error) => void)
{ {
if (config.verbose) if (config.verbose)
{ {
log.error(errin); log.error(JSON.stringify(errin));
} }
if (config.debug) if (config.debug)
{ {
log.dumpToDebug('BatchRetry', errin.stack || errin); log.dumpToDebug('BatchRetry', JSON.stringify(errin));
} }
log.warn('Retrying to fetch episodes list from' + tasksArr[i].retry + ' / ' + config.retry); log.warn('Retrying to fetch episodes list from' + tasksArr[i].retry + ' / ' + config.retry);
tasksArr[i].retry -= 1; tasksArr[i].retry -= 1;
@@ -195,6 +206,27 @@ function get_max_filter(filter: string): number
return +Infinity; return +Infinity;
} }
/**
* Check that URL start with http:// or https://
* As for some reason request just return an error but a useless one when that happen so check it
* soon enough.
*/
function checkURL(address: string): boolean
{
if (address.startsWith('http:\/\/'))
{
return true;
}
if (address.startsWith('http:\/\/'))
{
return true;
}
log.error('URL ' + address + ' miss \'http:\/\/\' or \'https:\/\/\' => will be ignored');
return false;
}
/** /**
* Parses the configuration or reads the batch-mode file for tasks. * Parses the configuration or reads the batch-mode file for tasks.
*/ */
@@ -204,8 +236,13 @@ function tasks(config: IConfigLine, batchPath: string, done: (err: Error, tasks?
{ {
return done(null, config.args.map((addressIn) => return done(null, config.args.map((addressIn) =>
{ {
return {address: addressIn, retry: config.retry, if (checkURL(addressIn))
episode_min: get_min_filter(config.episodes), episode_max: get_max_filter(config.episodes)}; {
return {address: addressIn, retry: config.retry,
episode_min: get_min_filter(config.episodes), episode_max: get_max_filter(config.episodes)};
}
return {address: '', retry: 0, episode_min: 0, episode_max: 0};
})); }));
} }
@@ -241,8 +278,11 @@ function tasks(config: IConfigLine, batchPath: string, done: (err: Error, tasks?
return; return;
} }
map.push({address: addressIn, retry: lineConfig.retry, if (checkURL(addressIn))
episode_min: get_min_filter(lineConfig.episodes), episode_max: get_max_filter(lineConfig.episodes)}); {
map.push({address: addressIn, retry: lineConfig.retry,
episode_min: get_min_filter(lineConfig.episodes), episode_max: get_max_filter(lineConfig.episodes)});
}
}); });
}); });
done(null, map); done(null, map);
@@ -268,7 +308,7 @@ function parse(args: string[]): IConfigLine
.option('-f, --format <s>', 'The subtitle format.', 'ass') .option('-f, --format <s>', 'The subtitle format.', 'ass')
.option('-o, --output <s>', 'The output path.') .option('-o, --output <s>', 'The output path.')
.option('-s, --series <s>', 'The series name override.') .option('-s, --series <s>', 'The series name override.')
.option('-n, --nametmpl <s>', 'Output name template', '{SERIES_TITLE} - s{SEASON_NUMBER}e{EPISODE_NUMBER} - [{TAG}]') .option('-n, --nametmpl <s>', 'Output name template', '{SERIES_TITLE} - s{SEASON_NUMBER}e{EPISODE_NUMBER} - {EPISODE_TITLE} - [{TAG}]')
.option('-t, --tag <s>', 'The subgroup.', 'CrunchyRoll') .option('-t, --tag <s>', 'The subgroup.', 'CrunchyRoll')
.option('-r, --resolution <s>', 'The video resolution. (valid: 360, 480, 720, 1080)', '1080') .option('-r, --resolution <s>', 'The video resolution. (valid: 360, 480, 720, 1080)', '1080')
.option('-b, --batch <s>', 'Batch file', 'CrunchyRoll.txt') .option('-b, --batch <s>', 'Batch file', 'CrunchyRoll.txt')

View File

@@ -41,15 +41,9 @@ export function dumpToDebug(what: string, data: any, create = false)
{ {
if (create) if (create)
{ {
fs.writeFile('debug.txt', '>>>>>>>> ' + what + ':\n' + data + '\n<<<<<<<<\n', (err) => fs.writeFileSync('debug.txt', '>>>>>>>> ' + what + ':\n' + data + '\n<<<<<<<<\n');
{
if (err) throw err;
});
return; return;
} }
fs.appendFile('debug.txt', '>>>>>>>> ' + what + ':\n' + data + '\n<<<<<<<<\n', (err) => fs.appendFileSync('debug.txt', '>>>>>>>> ' + what + ':\n' + data + '\n<<<<<<<<\n');
{
if (err) throw err;
});
} }

View File

@@ -82,7 +82,7 @@ function login(sessionId: string, user: string, pass: string): Promise<any>
/** /**
* Performs a GET request for the resource. * Performs a GET request for the resource.
*/ */
export function get(config: IConfig, options: string|request.Options, done: (err: Error, result?: string) => void) export function get(config: IConfig, options: string|request.Options, done: (err: any, result?: string) => void)
{ {
authenticate(config, (err) => authenticate(config, (err) =>
{ {
@@ -91,7 +91,7 @@ export function get(config: IConfig, options: string|request.Options, done: (err
return done(err); return done(err);
} }
cloudscraper.request(modify(options, 'GET'), (error: Error, response: any, body: any) => cloudscraper.request(modify(options, 'GET'), (error: any, response: any, body: any) =>
{ {
if (error) return done(error); if (error) return done(error);
done(null, typeof body === 'string' ? body : String(body)); done(null, typeof body === 'string' ? body : String(body));

View File

@@ -27,7 +27,7 @@ function fileExist(path: string)
/** /**
* Streams the series to disk. * Streams the series to disk.
*/ */
export default function(config: IConfig, task: IConfigTask, done: (err: Error) => void) export default function(config: IConfig, task: IConfigTask, done: (err: any) => void)
{ {
const persistentPath = path.join(config.output || process.cwd(), persistent); const persistentPath = path.join(config.output || process.cwd(), persistent);
@@ -45,6 +45,12 @@ export default function(config: IConfig, task: IConfigTask, done: (err: Error) =
{ {
if (errP) if (errP)
{ {
const reqErr = errP.error;
if ((reqErr.syscall === 'getaddrinfo') && (reqErr.errno === 'ENOTFOUND'))
{
log.error('The URL \'' + task.address + '\' is invalid, please check => I\'m ignoring it.');
}
return done(errP); return done(errP);
} }
@@ -61,9 +67,17 @@ export default function(config: IConfig, task: IConfigTask, done: (err: Error) =
{ {
if (errD) if (errD)
{ {
/* Check if domain is valid */
const reqErr = errD.error;
if ((reqErr.syscall === 'getaddrinfo') && (reqErr.errno === 'ENOTFOUND'))
{
page.episodes[i].retry = 0;
log.error('The URL \'' + task.address + '\' is invalid, please check => I\'m ignoring it.');
}
if (page.episodes[i].retry <= 0) if (page.episodes[i].retry <= 0)
{ {
log.error(errD); log.error(JSON.stringify(errD));
log.error('Cannot fetch episode "s' + page.episodes[i].volume + 'e' + page.episodes[i].episode + log.error('Cannot fetch episode "s' + page.episodes[i].volume + 'e' + page.episodes[i].episode +
'", please rerun later'); '", please rerun later');
/* Go to the next on the list */ /* Go to the next on the list */
@@ -76,7 +90,7 @@ export default function(config: IConfig, task: IConfigTask, done: (err: Error) =
if (config.debug) if (config.debug)
{ {
log.dumpToDebug('series address', task.address); log.dumpToDebug('series address', task.address);
log.dumpToDebug('series error', errD.stack || errD); log.dumpToDebug('series error', JSON.stringify(errD));
log.dumpToDebug('series data', JSON.stringify(page)); log.dumpToDebug('series data', JSON.stringify(page));
} }
log.error(errD); log.error(errD);
@@ -120,7 +134,7 @@ export default function(config: IConfig, task: IConfigTask, done: (err: Error) =
*/ */
function download(cache: {[address: string]: number}, config: IConfig, function download(cache: {[address: string]: number}, config: IConfig,
task: IConfigTask, item: ISeriesEpisode, task: IConfigTask, item: ISeriesEpisode,
done: (err: Error, ign: boolean) => void) done: (err: any, ign: boolean) => void)
{ {
const episodeNumber = parseInt(item.episode, 10); const episodeNumber = parseInt(item.episode, 10);
@@ -152,7 +166,7 @@ function download(cache: {[address: string]: number}, config: IConfig,
/** /**
* Requests the page and scrapes the episodes and series. * Requests the page and scrapes the episodes and series.
*/ */
function pageScrape(config: IConfig, task: IConfigTask, done: (err: Error, result?: ISeries) => void) function pageScrape(config: IConfig, task: IConfigTask, done: (err: any, result?: ISeries) => void)
{ {
if (task.address[0] === '@') if (task.address[0] === '@')
{ {
@@ -170,7 +184,8 @@ function pageScrape(config: IConfig, task: IConfigTask, done: (err: Error, resul
{ {
let episodeCount = 0; let episodeCount = 0;
my_request.get(config, task.address, (err, result) => { my_request.get(config, task.address, (err, result) => {
if (err) { if (err)
{
return done(err); return done(err);
} }