A bit of code reformating and add an option to regenerate the .crpersistant file in case it become corrupted and Crunchy try to redownload everything.

This commit is contained in:
Godzil 2018-06-01 20:37:10 +01:00
parent 6ad4cbed0a
commit 849c7612aa
6 changed files with 109 additions and 45 deletions

4
package-lock.json generated
View File

@ -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"

View File

@ -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",

View File

@ -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 <s>', 'The name override.')
.option('-t, --tag <s>', 'The subgroup. (Default: CrunchyRoll)')
.option('-r, --resolution <s>', 'The video resolution. (Default: 1080 (360, 480, 720, 1080))')
.option('-g, --rebuildcrp', 'Rebuild the crpersistant file.')
.parse(args);
}

View File

@ -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)

View File

@ -17,4 +17,5 @@ interface IConfig {
resolution?: string;
video_format?: string;
video_quality?: string;
rebuildcrp?: boolean;
}

View File

@ -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<string> {
return rp({
function startSession(): Promise<string>
{
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<string> {
},
json: true,
})
.then((response: any) => {
.then((response: any) =>
{
return response.data.session_id;
});
}
function login(sessionId: string, user: string, pass: string): Promise<any> {
return rp({
function login(sessionId: string, user: string, pass: string): Promise<any>
{
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<any> {
},
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<any> {
/**
* 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
};
}