my_request.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. 'use strict';
  2. import cheerio = require('cheerio');
  3. import request = require('request');
  4. import rp = require('request-promise');
  5. import Promise = require('bluebird');
  6. import log = require('./log');
  7. import { RequestPromise } from 'request-promise';
  8. import { Response } from 'request';
  9. // tslint:disable-next-line:no-var-requires
  10. const cloudscraper = require('cloudscraper');
  11. let isAuthenticated = false;
  12. let isPremium = false;
  13. const defaultHeaders: request.Headers = {
  14. 'User-Agent': 'Mozilla/5.0 (Windows NT 6.2; WOW64; x64; rv:58.0) Gecko/20100101 Firefox/58.0',
  15. 'Connection': 'keep-alive',
  16. 'Referer': 'https://www.crunchyroll.com/login',
  17. };
  18. function generateDeviceId(): string {
  19. let id = '';
  20. const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  21. for (let i = 0; i < 32; i++) {
  22. id += possible.charAt(Math.floor(Math.random() * possible.length));
  23. }
  24. return id;
  25. }
  26. function startSession(): Promise<string> {
  27. return rp({
  28. method: 'GET',
  29. url: 'CR_SESSION_URL',
  30. qs: {
  31. device_id: generateDeviceId(),
  32. device_type: 'CR_DEVICE_TYPE',
  33. access_token: 'CR_SESSION_KEY',
  34. version: 'CR_API_VERSION',
  35. locale: 'CR_LOCALE',
  36. },
  37. json: true,
  38. })
  39. .then((response: any) => {
  40. return response.data.session_id;
  41. });
  42. }
  43. function login(sessionId: string, user: string, pass: string): Promise<any> {
  44. return rp({
  45. method: 'POST',
  46. url: 'CR_LOGIN_URL',
  47. form: {
  48. account: user,
  49. password: pass,
  50. session_id: sessionId,
  51. version: 'CR_API_VERSION',
  52. },
  53. json: true,
  54. })
  55. .then((response) => {
  56. if (response.error) throw new Error('Login failed: ' + response.message);
  57. return response.data;
  58. });
  59. }
  60. // TODO: logout
  61. /**
  62. * Performs a GET request for the resource.
  63. */
  64. export function get(config: IConfig, options: string|request.Options, done: (err: Error, result?: string) => void) {
  65. authenticate(config, (err) => {
  66. if (err) {
  67. return done(err);
  68. }
  69. cloudscraper.request(modify(options, 'GET'), (error: Error, response: any, body: any) => {
  70. if (error) return done(error);
  71. done(null, typeof body === 'string' ? body : String(body));
  72. });
  73. });
  74. }
  75. /**
  76. * Performs a POST request for the resource.
  77. */
  78. export function post(config: IConfig, options: request.Options, done: (err: Error, result?: string) => void) {
  79. authenticate(config, (err) => {
  80. if (err) {
  81. return done(err);
  82. }
  83. cloudscraper.request(modify(options, 'POST'), (error: Error, response: any, body: any) => {
  84. if (error) return done(error);
  85. done(null, typeof body === 'string' ? body : String(body));
  86. });
  87. });
  88. }
  89. /**
  90. * Authenticates using the configured pass and user.
  91. */
  92. function authenticate(config: IConfig, done: (err: Error) => void) {
  93. if (isAuthenticated || !config.pass || !config.user) {
  94. return done(null);
  95. }
  96. startSession()
  97. .then((sessionId: string) => {
  98. defaultHeaders.Cookie = `sess_id=${sessionId}; c_locale=enUS`;
  99. return login(sessionId, config.user, config.pass);
  100. })
  101. .then((userData) => {
  102. /**
  103. * The page return with a meta based redirection, as we wan't to check that everything is fine, reload
  104. * the main page. A bit convoluted, but more sure.
  105. */
  106. const options = {
  107. headers: defaultHeaders,
  108. jar: true,
  109. url: 'http://www.crunchyroll.com/',
  110. method: 'GET',
  111. };
  112. cloudscraper.request(options, (err: Error, rep: string, body: string) => {
  113. if (err) return done(err);
  114. const $ = cheerio.load(body);
  115. /* Check if auth worked */
  116. const regexps = /ga\('set', 'dimension[5-8]', '([^']*)'\);/g;
  117. const dims = regexps.exec($('script').text());
  118. for (let i = 1; i < 5; i++) {
  119. if ((dims[i] !== undefined) && (dims[i] !== '') && (dims[i] !== 'not-registered')) {
  120. isAuthenticated = true;
  121. }
  122. if ((dims[i] === 'premium') || (dims[i] === 'premiumplus')) {
  123. isPremium = true;
  124. }
  125. }
  126. if (isAuthenticated === false) {
  127. const error = $('ul.message, li.error').text();
  128. return done(new Error('Authentication failed: ' + error));
  129. }
  130. if (isPremium === false) {
  131. log.warn('Do not use this app without a premium account.');
  132. } else {
  133. log.info('You have a premium account! Good!');
  134. }
  135. done(null);
  136. });
  137. })
  138. .catch(done);
  139. }
  140. /**
  141. * Modifies the options to use the authenticated cookie jar.
  142. */
  143. function modify(options: string|request.Options, reqMethod: string): request.Options
  144. {
  145. if (typeof options !== 'string') {
  146. options.jar = true;
  147. options.headers = defaultHeaders;
  148. options.method = reqMethod;
  149. return options;
  150. }
  151. return { jar: true, headers: defaultHeaders, url: options.toString(), method: reqMethod };
  152. }