my_request.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  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 uuid = require('uuid');
  7. import path = require('path');
  8. import fs = require('fs-extra');
  9. import log = require('./log');
  10. import { RequestPromise } from 'request-promise';
  11. import { Response } from 'request';
  12. // tslint:disable-next-line:no-var-requires
  13. const cookieStore = require('tough-cookie-file-store');
  14. // tslint:disable-next-line:no-var-requires
  15. const cloudscraper = require('cloudscraper');
  16. let isAuthenticated = false;
  17. let isPremium = false;
  18. let j: request.CookieJar;
  19. const defaultHeaders: request.Headers =
  20. {
  21. 'User-Agent': 'Mozilla/5.0 (Windows NT 6.2; WOW64; x64; rv:58.0) Gecko/20100101 Firefox/58.0',
  22. 'Connection': 'keep-alive',
  23. 'Referer': 'https://www.crunchyroll.com/login',
  24. };
  25. function startSession(config: IConfig): Promise<string>
  26. {
  27. if (config.crDeviceId === undefined)
  28. {
  29. config.crDeviceId = uuid.v4();
  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. jar: j,
  65. })
  66. .then((response) =>
  67. {
  68. if (response.error) throw new Error('Login failed: ' + response.message);
  69. return response.data;
  70. });
  71. }
  72. // TODO: logout
  73. function loadCookies(config: IConfig)
  74. {
  75. const cookiePath = path.join(config.output || process.cwd(), '.cookies.json');
  76. if(!fs.existsSync(cookiePath))
  77. {
  78. fs.closeSync(fs.openSync(cookiePath, 'w'));
  79. }
  80. j = request.jar(new cookieStore(cookiePath));
  81. }
  82. /**
  83. * Performs a GET request for the resource.
  84. */
  85. export function get(config: IConfig, options: string|request.Options, done: (err: any, result?: string) => void)
  86. {
  87. if (j === undefined)
  88. {
  89. loadCookies(config);
  90. }
  91. authenticate(config, (err) =>
  92. {
  93. if (err)
  94. {
  95. return done(err);
  96. }
  97. cloudscraper.request(modify(options, 'GET'), (error: any, response: any, body: any) =>
  98. {
  99. if (error) return done(error);
  100. done(null, typeof body === 'string' ? body : String(body));
  101. });
  102. });
  103. }
  104. /**
  105. * Performs a POST request for the resource.
  106. */
  107. export function post(config: IConfig, options: request.Options, done: (err: Error, result?: string) => void)
  108. {
  109. if (j === undefined)
  110. {
  111. loadCookies(config);
  112. }
  113. authenticate(config, (err) =>
  114. {
  115. if (err)
  116. {
  117. return done(err);
  118. }
  119. cloudscraper.request(modify(options, 'POST'), (error: Error, response: any, body: any) =>
  120. {
  121. if (error)
  122. {
  123. return done(error);
  124. }
  125. done(null, typeof body === 'string' ? body : String(body));
  126. });
  127. });
  128. }
  129. /**
  130. * Authenticates using the configured pass and user.
  131. */
  132. function authenticate(config: IConfig, done: (err: Error) => void)
  133. {
  134. if (isAuthenticated)
  135. {
  136. return done(null);
  137. }
  138. if (!config.pass || !config.user)
  139. {
  140. log.error('You need to give login/password to use Crunchy');
  141. process.exit(-1);
  142. }
  143. startSession()
  144. .then((sessionId: string) =>
  145. {
  146. defaultHeaders.Cookie = `sess_id=${sessionId}; c_locale=enUS`;
  147. return login(sessionId, config.user, config.pass);
  148. })
  149. .then((userData) =>
  150. {
  151. /**
  152. * The page return with a meta based redirection, as we wan't to check that everything is fine, reload
  153. * the main page. A bit convoluted, but more sure.
  154. */
  155. const options =
  156. {
  157. headers: defaultHeaders,
  158. jar: j,
  159. url: 'http://www.crunchyroll.com/',
  160. method: 'GET',
  161. };
  162. cloudscraper.request(options, (err: Error, rep: string, body: string) =>
  163. {
  164. if (err)
  165. {
  166. return done(err);
  167. }
  168. const $ = cheerio.load(body);
  169. /* Check if auth worked */
  170. const regexps = /ga\('set', 'dimension[5-8]', '([^']*)'\);/g;
  171. const dims = regexps.exec($('script').text());
  172. for (let i = 1; i < 5; i++)
  173. {
  174. if ((dims[i] !== undefined) && (dims[i] !== '') && (dims[i] !== 'not-registered'))
  175. {
  176. isAuthenticated = true;
  177. }
  178. if ((dims[i] === 'premium') || (dims[i] === 'premiumplus'))
  179. {
  180. isPremium = true;
  181. }
  182. }
  183. if (isAuthenticated === false)
  184. {
  185. const error = $('ul.message, li.error').text();
  186. log.error('Authentication failed: ' + error);
  187. process.exit(-1);
  188. }
  189. if (isPremium === false)
  190. {
  191. log.warn('Do not use this app without a premium account.');
  192. }
  193. else
  194. {
  195. log.info('You have a premium account! Good!');
  196. }
  197. done(null);
  198. });
  199. })
  200. .catch(done);
  201. }
  202. /**
  203. * Modifies the options to use the authenticated cookie jar.
  204. */
  205. function modify(options: string|request.Options, reqMethod: string): request.Options
  206. {
  207. if (typeof options !== 'string')
  208. {
  209. options.jar = j;
  210. options.headers = defaultHeaders;
  211. options.method = reqMethod;
  212. return options;
  213. }
  214. return {
  215. jar: j,
  216. headers: defaultHeaders,
  217. url: options.toString(),
  218. method: reqMethod
  219. };
  220. }