crunchy
Version:
Crunchy is a fork of Crunchyroll.js, capable of downloading anime episodes from the popular CrunchyRoll streaming service.
295 lines • 10.3 kB
JavaScript
;
exports.__esModule = true;
var cheerio = require("cheerio");
var request = require("request");
var rp = require("request-promise");
var uuid = require("uuid");
var path = require("path");
var fs = require("fs-extra");
var languages = require("./languages");
var log = require("./log");
// tslint:disable-next-line:no-var-requires
var cookieStore = require('tough-cookie-file-store');
var CR_COOKIE_DOMAIN = 'http://crunchyroll.com';
var isAuthenticated = false;
var isPremium = false;
var j;
// tslint:disable-next-line:no-var-requires
var cloudscraper = require("cloudscraper");
var currentOptions;
var optionsSet = false;
function AuthError(msg) {
return { name: 'AuthError', message: msg, authError: true };
}
function startSession(config) {
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(function (response) {
if ((response.data === undefined) || (response.data.session_id === undefined)) {
throw new Error('Getting session failed: ' + JSON.stringify(response));
}
return response.data.session_id;
});
}
function APIlogin(config, sessionId, user, pass) {
return rp({
method: 'POST',
url: config.crLoginUrl,
form: {
account: user,
password: pass,
session_id: sessionId,
version: config.crAPIVersion
},
json: true,
jar: j
})
.then(function (response) {
if (response.error)
throw new Error('Login failed: ' + response.message);
return response.data;
});
}
function checkIfUserIsAuth(config, done) {
/**
* The main page give us some information about the user
*/
var url = 'http://www.crunchyroll.com/';
cloudscraper.get(url, getOptions(config, null), function (err, rep, body) {
if (err) {
return done(err);
}
var $ = cheerio.load(body);
/* As we are here, try to detect which locale CR tell us */
var localeRE = /LOCALE = "([a-zA-Z]+)",/g;
var locale = localeRE.exec($('script').text())[1];
var 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 */
var regexps = /ga\('set', 'dimension[5-8]', '([^']*)'\);/g;
var dims = regexps.exec($('script').text());
for (var 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) {
var error = $('ul.message, li.error').text();
log.warn('Authentication failed: ' + error);
log.dumpToDebug('not auth rep', rep);
log.dumpToDebug('not auth body', body);
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) {
var 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));
}
function eatCookies(config) {
var cookiePath = path.join(config.output || process.cwd(), '.cookies.json');
if (fs.existsSync(cookiePath)) {
fs.removeSync(cookiePath);
}
j = undefined;
}
exports.eatCookies = eatCookies;
function getUserAgent() {
return currentOptions.headers['User-Agent'];
}
exports.getUserAgent = getUserAgent;
/**
* Performs a GET request for the resource.
*/
function get(config, url, done) {
authenticate(config, function (err) {
if (err) {
return done(err);
}
cloudscraper.get(url, getOptions(config, null), function (error, response, body) {
if (error)
return done(error);
done(null, typeof body === 'string' ? body : String(body));
});
});
}
exports.get = get;
/**
* Performs a POST request for the resource.
*/
function post(config, url, form, done) {
authenticate(config, function (err) {
if (err) {
return done(err);
}
cloudscraper.post(url, getOptions(config, form), function (error, response, body) {
if (error) {
return done(error);
}
done(null, typeof body === 'string' ? body : String(body));
});
});
}
exports.post = post;
function authUsingCookies(config, done) {
j.setCookie(request.cookie('session_id=' + config.crSessionId + '; Domain=crunchyroll.com; HttpOnly; hostOnly=false;'), CR_COOKIE_DOMAIN);
checkIfUserIsAuth(config, function (errCheckAuth2) {
if (isAuthenticated) {
return done(null);
}
else {
return done(errCheckAuth2);
}
});
}
function authUsingApi(config, done) {
if (!config.pass || !config.user) {
log.error('You need to give login/password to use Crunchy');
process.exit(-1);
}
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(function (sessionId) {
// defaultHeaders['Cookie'] = `sess_id=${sessionId}; c_locale=enUS`;
return APIlogin(config, sessionId, config.user, config.pass);
})
.then(function (userData) {
checkIfUserIsAuth(config, function (errCheckAuth2) {
if (isAuthenticated) {
return done(null);
}
else {
return done(errCheckAuth2);
}
});
})["catch"](function (errInChk) {
return done(AuthError(errInChk.message));
});
}
function authUsingForm(config, done) {
/* 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);
}
/* First get https://www.crunchyroll.com/login to get the login token */
cloudscraper.get('https://www.crunchyroll.com/login', getOptions(config, null), function (err, rep, body) {
if (err)
return done(err);
var $ = cheerio.load(body);
/* Get the token from the login page */
var 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 */
var paramForm = {
'login_form[name]': config.user,
'login_form[password]': config.pass,
'login_form[redirect_url]': '/',
'login_form[_token]': token
};
cloudscraper.post('https://www.crunchyroll.com/login', getOptions(config, paramForm), function (err, rep, body) {
if (err) {
return done(err);
}
/* Now let's check if we are authentificated */
checkIfUserIsAuth(config, function (errCheckAuth2) {
if (isAuthenticated) {
return done(null);
}
else {
return done(errCheckAuth2);
}
});
});
});
}
/**
* Authenticates using the configured pass and user.
*/
function authenticate(config, done) {
if (isAuthenticated) {
return done(null);
}
/* First of all, check if the user is not already logged via the cookies */
checkIfUserIsAuth(config, function (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, form) {
if (!optionsSet) {
currentOptions = {};
currentOptions.headers = {};
currentOptions.headers['Cache-Control'] = 'private';
currentOptions.headers.Accept = 'application/xml,application/xhtml+xml,text/html;q=0.9, text/plain;q=0.8,image/png,*/*;q=0.5';
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;
}
//# sourceMappingURL=my_request.js.map