123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433 |
- 'use strict';
- import cheerio = require('cheerio');
- import request = require('request');
- import rp = require('request-promise');
- import Promise = require('bluebird');
- import uuid = require('uuid');
- import path = require('path');
- import fs = require('fs-extra');
- import languages = require('./languages');
- import log = require('./log');
- import { RequestPromise } from 'request-promise';
- import { Response } from 'request';
- // tslint:disable-next-line:no-var-requires
- const cookieStore = require('tough-cookie-file-store');
- // tslint:disable-next-line:no-var-requires
- const cloudscraper = require('cloudscraper');
- const CR_COOKIE_DOMAIN = 'http://crunchyroll.com';
- let isAuthenticated = false;
- let isPremium = false;
- let j: request.CookieJar;
- const defaultHeaders: request.Headers =
- {
- 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36',
- 'Connection': 'keep-alive',
- 'Referer': 'https://www.crunchyroll.com/login',
- };
- function AuthError(msg: string): IAuthError
- {
- return { name: 'AuthError', message: msg, authError: true };
- }
- function startSession(config: IConfig): Promise<any>
- {
- return rp(
- {
- method: 'GET',
- url: config.crSessionUrl,
- qs:
- {
- device_id: config.crDeviceId,
- device_type: config.crDeviceType,
- access_token: config.crSessionKey,
- version: config.crAPIVersion,
- locale: config.crLocale,
- },
- json: true,
- })
- .then((response: any) =>
- {
- if ((response.data === undefined) || (response.data.session_id === undefined))
- {
- throw new Error('Getting session failed: ' + JSON.stringify(response));
- }
- return response.data.session_id;
- });
- }
- function login(config: IConfig, sessionId: string, user: string, pass: string): Promise<any>
- {
- return rp(
- {
- method: 'POST',
- url: config.crLoginUrl,
- form:
- {
- account: user,
- password: pass,
- session_id: sessionId,
- version: config.crAPIVersion,
- },
- json: true,
- jar: j,
- })
- .then((response) =>
- {
- if (response.error) throw new Error('Login failed: ' + response.message);
- return response.data;
- });
- }
- function checkIfUserIsAuth(config: IConfig, done: (err: Error) => void): void
- {
- if (j === undefined)
- {
- loadCookies(config);
- }
- /**
- * The main page give us some information about the user
- */
- const options =
- {
- headers: defaultHeaders,
- jar: j,
- url: 'http://www.crunchyroll.com/',
- method: 'GET',
- };
- cloudscraper.request(options, (err: Error, rep: string, body: string) =>
- {
- if (err)
- {
- return done(err);
- }
- const $ = cheerio.load(body);
- /* As we are here, try to detect which locale CR tell us */
- const localeRE = /LOCALE = "([a-zA-Z]+)",/g;
- const locale = localeRE.exec($('script').text())[1];
- const countryCode = languages.localeToCC(locale);
- if (config.crlang === undefined)
- {
- log.info('No locale set. Setting to the one reported by CR: "' + countryCode + '"');
- config.crlang = countryCode;
- }
- else if (config.crlang !== countryCode)
- {
- log.warn('Crunchy is configured for locale "' + config.crlang + '" but CR report "' + countryCode + '" (LOCALE = ' + locale + ')');
- log.warn('Check if it is correct or rerun (once) with "-l ' + countryCode + '" to correct.');
- }
- /* Check if auth worked */
- 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'))
- {
- isAuthenticated = true;
- }
- if ((dims[i] === 'premium') || (dims[i] === 'premiumplus'))
- {
- isPremium = true;
- }
- }
- if (isAuthenticated === false)
- {
- const error = $('ul.message, li.error').text();
- log.warn('Authentication failed: ' + error);
- return done(AuthError('Authentication failed: ' + error));
- }
- else
- {
- if (isPremium === false)
- {
- log.warn('Do not use this app without a premium account.');
- }
- else
- {
- log.info('You have a premium account! Good!');
- }
- }
- done(null);
- });
- }
- function loadCookies(config: IConfig)
- {
- const cookiePath = path.join(config.output || process.cwd(), '.cookies.json');
- if (!fs.existsSync(cookiePath))
- {
- fs.closeSync(fs.openSync(cookiePath, 'w'));
- }
- j = request.jar(new cookieStore(cookiePath));
- }
- export function eatCookies(config: IConfig)
- {
- const cookiePath = path.join(config.output || process.cwd(), '.cookies.json');
- if (fs.existsSync(cookiePath))
- {
- fs.removeSync(cookiePath);
- }
- j = undefined;
- }
- export function getUserAgent(): string
- {
- return defaultHeaders['User-Agent'];
- }
- /**
- * Performs a GET request for the resource.
- */
- export function get(config: IConfig, options: string|request.Options, done: (err: any, result?: string) => void)
- {
- if (j === undefined)
- {
- loadCookies(config);
- }
- if (config.userAgent)
- {
- defaultHeaders['User-Agent'] = config.userAgent;
- }
- authenticate(config, (err) =>
- {
- if (err)
- {
- return done(err);
- }
- cloudscraper.request(modify(options, 'GET'), (error: any, response: any, body: any) =>
- {
- if (error) return done(error);
- done(null, typeof body === 'string' ? body : String(body));
- });
- });
- }
- /**
- * Performs a POST request for the resource.
- */
- export function post(config: IConfig, options: request.Options, done: (err: Error, result?: string) => void)
- {
- if (j === undefined)
- {
- loadCookies(config);
- }
- if (config.userAgent)
- {
- defaultHeaders['User-Agent'] = config.userAgent;
- }
- authenticate(config, (err) =>
- {
- if (err)
- {
- return done(err);
- }
- cloudscraper.request(modify(options, 'POST'), (error: Error, response: any, body: any) =>
- {
- if (error)
- {
- return done(error);
- }
- done(null, typeof body === 'string' ? body : String(body));
- });
- });
- }
- /**
- * Authenticates using the configured pass and user.
- */
- function authenticate(config: IConfig, done: (err: Error) => void)
- {
- if (isAuthenticated)
- {
- return done(null);
- }
- /* First of all, check if the user is not already logged via the cookies */
- checkIfUserIsAuth(config, (errCheckAuth) =>
- {
- if (isAuthenticated)
- {
- return done(null);
- }
- /* 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);
- }
- log.info('Seems we are not currently logged. Let\'s login!');
- if (config.logUsingApi)
- {
- 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.'));
- }
- startSession(config)
- .then((sessionId: string) =>
- {
- defaultHeaders.Cookie = `sess_id=${sessionId}; c_locale=enUS`;
- return login(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)
- {
- 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)
- {
- return done(null);
- }
- else
- {
- return done(errCheckAuth2);
- }
- });
- }
- else
- {
- /* First get https://www.crunchyroll.com/login to get the login token */
- const options =
- {
- headers: defaultHeaders,
- jar: j,
- gzip: false,
- method: 'GET',
- url: 'https://www.crunchyroll.com/login'
- };
- cloudscraper.request(options, (err: Error, rep: string, body: string) =>
- {
- if (err) return done(err);
- const $ = cheerio.load(body);
- /* 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 options =
- {
- headers: defaultHeaders,
- form:
- {
- 'login_form[name]': config.user,
- 'login_form[password]': config.pass,
- 'login_form[redirect_url]': '/',
- 'login_form[_token]': token
- },
- jar: j,
- gzip: false,
- method: 'POST',
- url: 'https://www.crunchyroll.com/login'
- };
- cloudscraper.request(options, (err: Error, rep: string, body: string) =>
- {
- 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);
- }
- });
- });
- });
- }
- });
- }
- /**
- * Modifies the options to use the authenticated cookie jar.
- */
- function modify(options: string|request.Options, reqMethod: string): request.Options
- {
- if (typeof options !== 'string')
- {
- options.jar = j;
- options.headers = defaultHeaders;
- options.method = reqMethod;
- options.followAllRedirects = true;
- return options;
- }
- return {
- jar: j,
- headers: defaultHeaders,
- url: options.toString(),
- method: reqMethod
- };
- }
|