diff --git a/package-lock.json b/package-lock.json index 8cd9e70..e5546e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,8 @@ "@types/bluebird": { "version": "3.5.20", "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.20.tgz", - "integrity": "sha512-Wk41MVdF+cHBfVXj/ufUHJeO3BlIQr1McbHZANErMykaCWeDSZbH5erGjNBw2/3UlRdSxZbLfSuQTzFmPOYFsA==" + "integrity": "sha512-Wk41MVdF+cHBfVXj/ufUHJeO3BlIQr1McbHZANErMykaCWeDSZbH5erGjNBw2/3UlRdSxZbLfSuQTzFmPOYFsA==", + "dev": true }, "@types/caseless": { "version": "0.12.1", @@ -57,6 +58,7 @@ "version": "4.1.41", "resolved": "https://registry.npmjs.org/@types/request-promise/-/request-promise-4.1.41.tgz", "integrity": "sha512-qlx6COxSTdSFHY9oX9v2zL1I05hgz5lwqYiXa2SFL2nDxAiG5KK8rnllLBH7k6OqzS3Ck0bWbxlGVdrZhS6oNw==", + "dev": true, "requires": { "@types/bluebird": "3.5.20", "@types/request": "2.47.0" diff --git a/package.json b/package.json index 44186b0..fca6eec 100644 --- a/package.json +++ b/package.json @@ -34,10 +34,10 @@ }, "devDependencies": { "@types/bluebird": "^3.5.20", - "@types/request-promise": "^4.1.41", "@types/cheerio": "^0.22.7", "@types/mkdirp": "^0.5.2", "@types/request": "^2.47.0", + "@types/request-promise": "^4.1.41", "@types/xml2js": "^0.4.2", "tsconfig-lint": "^0.12.0", "tslint": "^5.9.1", diff --git a/src/batch.ts b/src/batch.ts index 7dfa962..7976b6c 100644 --- a/src/batch.ts +++ b/src/batch.ts @@ -6,7 +6,8 @@ import log = require('./log'); import series from './series'; /* correspondances between resolution and value CR excpect */ -const resol_table: { [id: string]: IResolData; } = { +const resol_table: { [id: string]: IResolData; } = +{ 360: {quality: '60', format: '106'}, 480: {quality: '61', format: '106'}, 720: {quality: '62', format: '106'}, @@ -28,7 +29,9 @@ export default function(args: string[], done: (err?: Error) => void) { config.video_format = resol_table[config.resolution].format; config.video_quality = resol_table[config.resolution].quality; - } catch (e) { + } + catch (e) + { log.warn(`Invalid resolution ${config.resolution}p. Setting to 1080p`); config.video_format = resol_table['1080'].format; config.video_quality = resol_table['1080'].quality; @@ -178,5 +181,6 @@ function parse(args: string[]): IConfigLine .option('-n, --filename ', 'The name override.') .option('-t, --tag ', 'The subgroup. (Default: CrunchyRoll)') .option('-r, --resolution ', 'The video resolution. (Default: 1080 (360, 480, 720, 1080))') + .option('-g, --rebuildcrp', 'Rebuild the crpersistant file.') .parse(args); } diff --git a/src/episode.ts b/src/episode.ts index f2789f4..cf82ff7 100644 --- a/src/episode.ts +++ b/src/episode.ts @@ -57,7 +57,8 @@ function fileExist(path: string) { fs.statSync(path); return true; - } catch (e) + } + catch (e) { return false; } @@ -82,6 +83,13 @@ function download(config: IConfig, page: IEpisodePage, player: IEpisodePlayer, d if (fileExist(filePath + '.mkv')) { let count = 0; + + if (config.rebuildcrp) + { + log.warn('Adding \'' + fileName + '\' to the DB...'); + return done(null, false); + } + log.warn('File \'' + fileName + '\' already exist...'); do @@ -94,6 +102,11 @@ function download(config: IConfig, page: IEpisodePage, player: IEpisodePlayer, d log.warn('Renaming to \'' + fileName + '\'...'); } + if (config.rebuildcrp) + { + return done(null, true); + } + mkdirp(path.dirname(filePath), (errM: Error) => { if (errM) diff --git a/src/interface/IConfig.d.ts b/src/interface/IConfig.d.ts index b2ec101..4a3945f 100644 --- a/src/interface/IConfig.d.ts +++ b/src/interface/IConfig.d.ts @@ -17,4 +17,5 @@ interface IConfig { resolution?: string; video_format?: string; video_quality?: string; + rebuildcrp?: boolean; } diff --git a/src/my_request.ts b/src/my_request.ts index 86fff6c..66f0b72 100644 --- a/src/my_request.ts +++ b/src/my_request.ts @@ -13,28 +13,34 @@ const cloudscraper = require('cloudscraper'); let isAuthenticated = false; let isPremium = false; -const defaultHeaders: request.Headers = { +const defaultHeaders: request.Headers = +{ 'User-Agent': 'Mozilla/5.0 (Windows NT 6.2; WOW64; x64; rv:58.0) Gecko/20100101 Firefox/58.0', 'Connection': 'keep-alive', 'Referer': 'https://www.crunchyroll.com/login', }; -function generateDeviceId(): string { - let id = ''; - const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; +function generateDeviceId(): string +{ + let id = ''; + const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for (let i = 0; i < 32; i++) { - id += possible.charAt(Math.floor(Math.random() * possible.length)); - } + for (let i = 0; i < 32; i++) + { + id += possible.charAt(Math.floor(Math.random() * possible.length)); + } - return id; + return id; } -function startSession(): Promise { - return rp({ +function startSession(): Promise +{ + return rp( + { method: 'GET', url: 'CR_SESSION_URL', - qs: { + qs: + { device_id: generateDeviceId(), device_type: 'CR_DEVICE_TYPE', access_token: 'CR_SESSION_KEY', @@ -43,16 +49,20 @@ function startSession(): Promise { }, json: true, }) - .then((response: any) => { + .then((response: any) => + { return response.data.session_id; }); } -function login(sessionId: string, user: string, pass: string): Promise { - return rp({ +function login(sessionId: string, user: string, pass: string): Promise +{ + return rp( + { method: 'POST', url: 'CR_LOGIN_URL', - form: { + form: + { account: user, password: pass, session_id: sessionId, @@ -60,7 +70,8 @@ function login(sessionId: string, user: string, pass: string): Promise { }, json: true, }) - .then((response) => { + .then((response) => + { if (response.error) throw new Error('Login failed: ' + response.message); return response.data; }); @@ -71,13 +82,17 @@ function login(sessionId: string, user: string, pass: string): Promise { /** * Performs a GET request for the resource. */ -export function get(config: IConfig, options: string|request.Options, done: (err: Error, result?: string) => void) { - authenticate(config, (err) => { - if (err) { +export function get(config: IConfig, options: string|request.Options, done: (err: Error, result?: string) => void) +{ + authenticate(config, (err) => + { + if (err) + { return done(err); } - cloudscraper.request(modify(options, 'GET'), (error: Error, response: any, body: any) => { + cloudscraper.request(modify(options, 'GET'), (error: Error, response: any, body: any) => + { if (error) return done(error); done(null, typeof body === 'string' ? body : String(body)); }); @@ -87,14 +102,21 @@ export function get(config: IConfig, options: string|request.Options, done: (err /** * Performs a POST request for the resource. */ -export function post(config: IConfig, options: request.Options, done: (err: Error, result?: string) => void) { - authenticate(config, (err) => { - if (err) { +export function post(config: IConfig, options: request.Options, done: (err: Error, result?: string) => void) +{ + authenticate(config, (err) => + { + if (err) + { return done(err); } - cloudscraper.request(modify(options, 'POST'), (error: Error, response: any, body: any) => { - if (error) return done(error); + cloudscraper.request(modify(options, 'POST'), (error: Error, response: any, body: any) => + { + if (error) + { + return done(error); + } done(null, typeof body === 'string' ? body : String(body)); }); }); @@ -103,30 +125,39 @@ export function post(config: IConfig, options: request.Options, done: (err: Erro /** * Authenticates using the configured pass and user. */ -function authenticate(config: IConfig, done: (err: Error) => void) { - if (isAuthenticated || !config.pass || !config.user) { +function authenticate(config: IConfig, done: (err: Error) => void) +{ + if (isAuthenticated || !config.pass || !config.user) + { return done(null); } startSession() - .then((sessionId: string) => { + .then((sessionId: string) => + { defaultHeaders.Cookie = `sess_id=${sessionId}; c_locale=enUS`; return login(sessionId, config.user, config.pass); }) - .then((userData) => { + .then((userData) => + { /** * The page return with a meta based redirection, as we wan't to check that everything is fine, reload * the main page. A bit convoluted, but more sure. */ - const options = { + const options = + { headers: defaultHeaders, jar: true, url: 'http://www.crunchyroll.com/', method: 'GET', }; - cloudscraper.request(options, (err: Error, rep: string, body: string) => { - if (err) return done(err); + cloudscraper.request(options, (err: Error, rep: string, body: string) => + { + if (err) + { + return done(err); + } const $ = cheerio.load(body); @@ -134,24 +165,31 @@ function authenticate(config: IConfig, done: (err: Error) => void) { const regexps = /ga\('set', 'dimension[5-8]', '([^']*)'\);/g; const dims = regexps.exec($('script').text()); - for (let i = 1; i < 5; i++) { - if ((dims[i] !== undefined) && (dims[i] !== '') && (dims[i] !== 'not-registered')) { + for (let i = 1; i < 5; i++) + { + if ((dims[i] !== undefined) && (dims[i] !== '') && (dims[i] !== 'not-registered')) + { isAuthenticated = true; } - if ((dims[i] === 'premium') || (dims[i] === 'premiumplus')) { + if ((dims[i] === 'premium') || (dims[i] === 'premiumplus')) + { isPremium = true; } } - if (isAuthenticated === false) { + if (isAuthenticated === false) + { const error = $('ul.message, li.error').text(); return done(new Error('Authentication failed: ' + error)); } - if (isPremium === false) { + if (isPremium === false) + { log.warn('Do not use this app without a premium account.'); - } else { + } + else + { log.info('You have a premium account! Good!'); } done(null); @@ -165,11 +203,17 @@ function authenticate(config: IConfig, done: (err: Error) => void) { */ function modify(options: string|request.Options, reqMethod: string): request.Options { - if (typeof options !== 'string') { + if (typeof options !== 'string') + { options.jar = true; options.headers = defaultHeaders; options.method = reqMethod; return options; } - return { jar: true, headers: defaultHeaders, url: options.toString(), method: reqMethod }; + return { + jar: true, + headers: defaultHeaders, + url: options.toString(), + method: reqMethod + }; } \ No newline at end of file