my_request.ts 5.0 KB

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