123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 |
- '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 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.2; WOW64; x64; rv:58.0) Gecko/20100101 Firefox/58.0',
- '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);
- /* 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();
- 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;
- }
- /**
- * 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);
- }
- 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);
- }
- 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);
- }
- 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
- {
- log.error('This method of login is currently unsupported...\n');
- return done(AuthError('Unsupported login method'));
- }
- });
- }
- /**
- * 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;
- return options;
- }
- return {
- jar: j,
- headers: defaultHeaders,
- url: options.toString(),
- method: reqMethod
- };
- }
|