|
@@ -22,26 +22,10 @@ let isPremium = false;
|
|
|
|
|
|
let j: request.CookieJar;
|
|
|
|
|
|
-const defaultHeaders =
|
|
|
-{
|
|
|
- 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36',
|
|
|
- 'Connection': 'keep-alive',
|
|
|
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
|
|
|
- 'Referer': 'https://www.crunchyroll.com/login',
|
|
|
- 'Cache-Control': 'private',
|
|
|
- 'Accept-Language': 'en-US,en;q=0.9'
|
|
|
-};
|
|
|
-
|
|
|
-const defaultOptions =
|
|
|
-{
|
|
|
- followAllRedirects: true,
|
|
|
- decodeEmails: true,
|
|
|
- challengesToSolve: 3,
|
|
|
- gzip: true,
|
|
|
-};
|
|
|
-
|
|
|
// tslint:disable-next-line:no-var-requires
|
|
|
-const cloudscraper = require('cloudscraper').defaults(defaultOptions);
|
|
|
+import cloudscraper = require('cloudscraper');
|
|
|
+let currentOptions: any;
|
|
|
+let optionsSet = false;
|
|
|
|
|
|
function AuthError(msg: string): IAuthError
|
|
|
{
|
|
@@ -98,19 +82,14 @@ function APIlogin(config: IConfig, sessionId: string, user: string, pass: string
|
|
|
});
|
|
|
}
|
|
|
|
|
|
-function checkIfUserIsAuth(config: IConfig, done: (err: Error) => void): void
|
|
|
+function checkIfUserIsAuth(config: IConfig, done: (err: any) => void): void
|
|
|
{
|
|
|
- if (j === undefined)
|
|
|
- {
|
|
|
- loadCookies(config);
|
|
|
- }
|
|
|
-
|
|
|
/**
|
|
|
* The main page give us some information about the user
|
|
|
*/
|
|
|
const url = 'http://www.crunchyroll.com/';
|
|
|
|
|
|
- cloudscraper.get({gzip: true, uri: url}, (err: Error, rep: string, body: string) =>
|
|
|
+ cloudscraper.get(url, getOptions(config, null), (err: any, rep: Response, body: string) =>
|
|
|
{
|
|
|
if (err)
|
|
|
{
|
|
@@ -204,24 +183,14 @@ export function eatCookies(config: IConfig)
|
|
|
|
|
|
export function getUserAgent(): string
|
|
|
{
|
|
|
- return defaultHeaders['User-Agent'];
|
|
|
+ return currentOptions.headers['User-Agent'];
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Performs a GET request for the resource.
|
|
|
*/
|
|
|
-export function get(config: IConfig, options: string|any, done: (err: any, result?: string) => void)
|
|
|
+export function get(config: IConfig, url: string, done: (err: any, result?: string) => void)
|
|
|
{
|
|
|
- if (j === undefined)
|
|
|
- {
|
|
|
- loadCookies(config);
|
|
|
- }
|
|
|
-
|
|
|
- if (config.userAgent)
|
|
|
- {
|
|
|
- defaultHeaders['User-Agent'] = config.userAgent;
|
|
|
- }
|
|
|
-
|
|
|
authenticate(config, (err) =>
|
|
|
{
|
|
|
if (err)
|
|
@@ -229,7 +198,7 @@ export function get(config: IConfig, options: string|any, done: (err: any, resul
|
|
|
return done(err);
|
|
|
}
|
|
|
|
|
|
- cloudscraper.get(modify(options), (error: any, response: any, body: any) =>
|
|
|
+ cloudscraper.get(url, getOptions(config, null), (error: any, response: any, body: any) =>
|
|
|
{
|
|
|
if (error) return done(error);
|
|
|
|
|
@@ -241,18 +210,8 @@ export function get(config: IConfig, options: string|any, done: (err: any, resul
|
|
|
/**
|
|
|
* Performs a POST request for the resource.
|
|
|
*/
|
|
|
-export function post(config: IConfig, options: request.Options, done: (err: Error, result?: string) => void)
|
|
|
+export function post(config: IConfig, url: string, form: any, done: (err: any, result?: string) => void)
|
|
|
{
|
|
|
- if (j === undefined)
|
|
|
- {
|
|
|
- loadCookies(config);
|
|
|
- }
|
|
|
-
|
|
|
- if (config.userAgent)
|
|
|
- {
|
|
|
- defaultHeaders['User-Agent'] = config.userAgent;
|
|
|
- }
|
|
|
-
|
|
|
authenticate(config, (err) =>
|
|
|
{
|
|
|
if (err)
|
|
@@ -260,7 +219,7 @@ export function post(config: IConfig, options: request.Options, done: (err: Erro
|
|
|
return done(err);
|
|
|
}
|
|
|
|
|
|
- cloudscraper.post(modify(options), (error: Error, response: any, body: any) =>
|
|
|
+ cloudscraper.post(url, getOptions(config, form), (error: Error, response: any, body: any) =>
|
|
|
{
|
|
|
if (error)
|
|
|
{
|
|
@@ -271,78 +230,53 @@ 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)
|
|
|
+function authUsingCookies(config: IConfig, done: (err: any) => void)
|
|
|
{
|
|
|
- if (isAuthenticated)
|
|
|
- {
|
|
|
- return done(null);
|
|
|
- }
|
|
|
+ j.setCookie(request.cookie('c_userid=' + config.crUserId + '; Domain=crunchyroll.com; HttpOnly; hostOnly=false;'),
|
|
|
+ CR_COOKIE_DOMAIN);
|
|
|
+ j.setCookie(request.cookie('c_userkey=' + config.crUserKey + '; Domain=crunchyroll.com; HttpOnly; hostOnly=false;'),
|
|
|
+ CR_COOKIE_DOMAIN);
|
|
|
|
|
|
- /* First of all, check if the user is not already logged via the cookies */
|
|
|
- checkIfUserIsAuth(config, (errCheckAuth) =>
|
|
|
+ checkIfUserIsAuth(config, (errCheckAuth2) =>
|
|
|
{
|
|
|
if (isAuthenticated)
|
|
|
{
|
|
|
return done(null);
|
|
|
}
|
|
|
-
|
|
|
- /* So if we are here now, that mean we are not authenticated so do as usual */
|
|
|
- if ((!config.logUsingApi && !config.logUsingCookie) && (!config.pass || !config.user))
|
|
|
+ else
|
|
|
{
|
|
|
- log.error('You need to give login/password to use Crunchy');
|
|
|
- process.exit(-1);
|
|
|
+ return done(errCheckAuth2);
|
|
|
}
|
|
|
+ });
|
|
|
+}
|
|
|
|
|
|
- log.info('Seems we are not currently logged. Let\'s login!');
|
|
|
+function authUsingApi(config: IConfig, done: (err: any) => void)
|
|
|
+{
|
|
|
+ if (!config.pass || !config.user)
|
|
|
+ {
|
|
|
+ log.error('You need to give login/password to use Crunchy');
|
|
|
+ process.exit(-1);
|
|
|
+ }
|
|
|
|
|
|
- if (config.logUsingApi)
|
|
|
- {
|
|
|
- if (config.crDeviceId === undefined)
|
|
|
- {
|
|
|
- config.crDeviceId = uuid.v4();
|
|
|
- }
|
|
|
+ if (config.crDeviceId === undefined)
|
|
|
+ {
|
|
|
+ config.crDeviceId = uuid.v4();
|
|
|
+ }
|
|
|
|
|
|
- if (!config.crSessionUrl || !config.crDeviceType || !config.crAPIVersion ||
|
|
|
- !config.crLocale || !config.crLoginUrl)
|
|
|
- {
|
|
|
- return done(AuthError('Invalid API configuration, please check your config file.'));
|
|
|
- }
|
|
|
+ if (!config.crSessionUrl || !config.crDeviceType || !config.crAPIVersion ||
|
|
|
+ !config.crLocale || !config.crLoginUrl)
|
|
|
+ {
|
|
|
+ return done(AuthError('Invalid API configuration, please check your config file.'));
|
|
|
+ }
|
|
|
|
|
|
- startSession(config)
|
|
|
- .then((sessionId: string) =>
|
|
|
- {
|
|
|
- // defaultHeaders['Cookie'] = `sess_id=${sessionId}; c_locale=enUS`;
|
|
|
- return APIlogin(config, sessionId, config.user, config.pass);
|
|
|
- })
|
|
|
- .then((userData) =>
|
|
|
- {
|
|
|
- checkIfUserIsAuth(config, (errCheckAuth2) =>
|
|
|
- {
|
|
|
- if (isAuthenticated)
|
|
|
- {
|
|
|
- return done(null);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- return done(errCheckAuth2);
|
|
|
- }
|
|
|
- });
|
|
|
- })
|
|
|
- .catch((errInChk) =>
|
|
|
- {
|
|
|
- return done(AuthError(errInChk.message));
|
|
|
- });
|
|
|
- }
|
|
|
- else if (config.logUsingCookie)
|
|
|
+ startSession(config)
|
|
|
+ .then((sessionId: string) =>
|
|
|
+ {
|
|
|
+ // defaultHeaders['Cookie'] = `sess_id=${sessionId}; c_locale=enUS`;
|
|
|
+ return APIlogin(config, sessionId, config.user, config.pass);
|
|
|
+ })
|
|
|
+ .then((userData) =>
|
|
|
{
|
|
|
- j.setCookie(request.cookie('c_userid=' + config.crUserId + '; Domain=crunchyroll.com; HttpOnly; hostOnly=false;'),
|
|
|
- CR_COOKIE_DOMAIN);
|
|
|
- j.setCookie(request.cookie('c_userkey=' + config.crUserKey + '; Domain=crunchyroll.com; HttpOnly; hostOnly=false;'),
|
|
|
- CR_COOKIE_DOMAIN);
|
|
|
-
|
|
|
checkIfUserIsAuth(config, (errCheckAuth2) =>
|
|
|
{
|
|
|
if (isAuthenticated)
|
|
@@ -354,80 +288,137 @@ function authenticate(config: IConfig, done: (err: Error) => void)
|
|
|
return done(errCheckAuth2);
|
|
|
}
|
|
|
});
|
|
|
- }
|
|
|
- else
|
|
|
+ })
|
|
|
+ .catch((errInChk) =>
|
|
|
{
|
|
|
- /* First get https://www.crunchyroll.com/login to get the login token */
|
|
|
- const options =
|
|
|
- {
|
|
|
- // jar: j,
|
|
|
- uri: 'https://www.crunchyroll.com/login'
|
|
|
- };
|
|
|
+ return done(AuthError(errInChk.message));
|
|
|
+ });
|
|
|
+}
|
|
|
|
|
|
- cloudscraper.get(options, (err: Error, rep: string, body: string) =>
|
|
|
- {
|
|
|
- if (err) return done(err);
|
|
|
+function authUsingForm(config: IConfig, done: (err: any) => void)
|
|
|
+{
|
|
|
+ /* So if we are here now, that mean we are not authenticated so do as usual */
|
|
|
+ if (!config.pass || !config.user)
|
|
|
+ {
|
|
|
+ log.error('You need to give login/password to use Crunchy');
|
|
|
+ process.exit(-1);
|
|
|
+ }
|
|
|
|
|
|
- const $ = cheerio.load(body);
|
|
|
+ /* First get https://www.crunchyroll.com/login to get the login token */
|
|
|
+ cloudscraper.get('https://www.crunchyroll.com/login', getOptions(config, null), (err: any, rep: Response, body: string) =>
|
|
|
+ {
|
|
|
+ if (err) return done(err);
|
|
|
|
|
|
- /* Get the token from the login page */
|
|
|
- const token = $('input[name="login_form[_token]"]').attr('value');
|
|
|
- if (token === '')
|
|
|
- {
|
|
|
- return done(AuthError('Can\'t find token!'));
|
|
|
- }
|
|
|
+ const $ = cheerio.load(body);
|
|
|
|
|
|
- /* Now call the page again with the token and credentials */
|
|
|
- const options =
|
|
|
- {
|
|
|
- form:
|
|
|
- {
|
|
|
+ /* Get the token from the login page */
|
|
|
+ const token = $('input[name="login_form[_token]"]').attr('value');
|
|
|
+ if (token === '')
|
|
|
+ {
|
|
|
+ return done(AuthError('Can\'t find token!'));
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Now call the page again with the token and credentials */
|
|
|
+ const paramForm =
|
|
|
+ {
|
|
|
'login_form[name]': config.user,
|
|
|
'login_form[password]': config.pass,
|
|
|
'login_form[redirect_url]': '/',
|
|
|
'login_form[_token]': token
|
|
|
- },
|
|
|
- // jar: j,
|
|
|
- url: 'https://www.crunchyroll.com/login'
|
|
|
- };
|
|
|
+ };
|
|
|
+
|
|
|
+ cloudscraper.post('https://www.crunchyroll.com/login', getOptions(config, paramForm), (err: any, rep: Response, body: string) =>
|
|
|
+ {
|
|
|
+ if (err)
|
|
|
+ {
|
|
|
+ return done(err);
|
|
|
+ }
|
|
|
|
|
|
- cloudscraper.post(options, (err: Error, rep: string, body: string) =>
|
|
|
+ /* Now let's check if we are authentificated */
|
|
|
+ checkIfUserIsAuth(config, (errCheckAuth2) =>
|
|
|
+ {
|
|
|
+ if (isAuthenticated)
|
|
|
+ {
|
|
|
+ return done(null);
|
|
|
+ }
|
|
|
+ else
|
|
|
{
|
|
|
- if (err)
|
|
|
- {
|
|
|
- return done(err);
|
|
|
- }
|
|
|
-
|
|
|
- /* Now let's check if we are authentificated */
|
|
|
- checkIfUserIsAuth(config, (errCheckAuth2) =>
|
|
|
- {
|
|
|
- if (isAuthenticated)
|
|
|
- {
|
|
|
- return done(null);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- return done(errCheckAuth2);
|
|
|
- }
|
|
|
- });
|
|
|
- });
|
|
|
+ return done(errCheckAuth2);
|
|
|
+ }
|
|
|
});
|
|
|
- }
|
|
|
+ });
|
|
|
});
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Modifies the options to use the authenticated cookie jar.
|
|
|
+ * Authenticates using the configured pass and user.
|
|
|
*/
|
|
|
-function modify(options: string|any): any
|
|
|
+function authenticate(config: IConfig, done: (err: any) => void)
|
|
|
{
|
|
|
- if (typeof options !== 'string')
|
|
|
+ if (isAuthenticated)
|
|
|
{
|
|
|
- options.jar = j;
|
|
|
- return options;
|
|
|
+ return done(null);
|
|
|
}
|
|
|
- return {
|
|
|
- jar: j,
|
|
|
- url: options.toString(),
|
|
|
- };
|
|
|
+
|
|
|
+ /* First of all, check if the user is not already logged via the cookies */
|
|
|
+ checkIfUserIsAuth(config, (errCheckAuth) =>
|
|
|
+ {
|
|
|
+ if (isAuthenticated)
|
|
|
+ {
|
|
|
+ return done(null);
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info('Seems we are not currently logged. Let\'s login!');
|
|
|
+
|
|
|
+ if (config.logUsingApi)
|
|
|
+ {
|
|
|
+ return authUsingApi(config, done);
|
|
|
+ }
|
|
|
+ else if (config.logUsingCookie)
|
|
|
+ {
|
|
|
+ return authUsingCookies(config, done);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return authUsingForm(config, done);
|
|
|
+ }
|
|
|
+ });
|
|
|
}
|
|
|
+
|
|
|
+function getOptions(config: IConfig, form: any)
|
|
|
+{
|
|
|
+ if (!optionsSet)
|
|
|
+ {
|
|
|
+ currentOptions = {};
|
|
|
+ currentOptions.headers = {};
|
|
|
+
|
|
|
+ if (config.userAgent)
|
|
|
+ {
|
|
|
+ currentOptions.headers['User-Agent'] = config.userAgent;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ currentOptions.headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0';
|
|
|
+ }
|
|
|
+
|
|
|
+ if (j === undefined)
|
|
|
+ {
|
|
|
+ loadCookies(config);
|
|
|
+ }
|
|
|
+
|
|
|
+ currentOptions.decodeEmails = true;
|
|
|
+ currentOptions.jar = j;
|
|
|
+
|
|
|
+ optionsSet = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ currentOptions.form = {};
|
|
|
+
|
|
|
+ if (form !== null)
|
|
|
+ {
|
|
|
+ currentOptions.form = form;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ return currentOptions;
|
|
|
+}
|