my_request.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. 'use strict';
  2. import request = require('request');
  3. import cheerio = require('cheerio');
  4. import log = require('./log');
  5. const cloudscraper = require('cloudscraper');
  6. let isAuthenticated = false;
  7. let isPremium = false;
  8. const defaultHeaders: request.Headers =
  9. {
  10. 'User-Agent': 'Mozilla/5.0 (Windows NT 6.2; WOW64; x64; rv:58.0) Gecko/20100101 Firefox/58.0',
  11. // Mozilla/5.0 (Windows NT 6.2; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0',
  12. 'Connection': 'keep-alive',
  13. 'Referer': 'https://www.crunchyroll.com/login'
  14. };
  15. /**
  16. * Performs a GET request for the resource.
  17. */
  18. export function get(config: IConfig, options: string|request.Options, done: (err: Error, result?: string) => void)
  19. {
  20. authenticate(config, err =>
  21. {
  22. if (err)
  23. {
  24. return done(err);
  25. }
  26. cloudscraper.request(modify(options, 'GET'), (err: Error, response: any, body: any) =>
  27. {
  28. if (err)
  29. {
  30. return done(err);
  31. }
  32. done(null, typeof body === 'string' ? body : String(body));
  33. });
  34. });
  35. }
  36. /**
  37. * Performs a POST request for the resource.
  38. */
  39. export function post(config: IConfig, options: request.Options, done: (err: Error, result?: string) => void)
  40. {
  41. authenticate(config, err =>
  42. {
  43. if (err)
  44. {
  45. return done(err);
  46. }
  47. cloudscraper.request(modify(options, 'POST'), (err: Error, response: any, body: any) =>
  48. {
  49. if (err)
  50. {
  51. return done(err);
  52. }
  53. done(null, typeof body === 'string' ? body : String(body));
  54. });
  55. });
  56. }
  57. /**
  58. * Authenticates using the configured pass and user.
  59. */
  60. function authenticate(config: IConfig, done: (err: Error) => void)
  61. {
  62. if (isAuthenticated || !config.pass || !config.user)
  63. {
  64. return done(null);
  65. }
  66. /* Bypass the login page and send a login request directly */
  67. let options =
  68. {
  69. headers: defaultHeaders,
  70. jar: true,
  71. gzip: false,
  72. method: 'GET',
  73. url: 'https://www.crunchyroll.com/login'
  74. };
  75. cloudscraper.request(options, (err: Error, rep: string, body: string) =>
  76. {
  77. if (err) return done(err);
  78. const $ = cheerio.load(body);
  79. /* Get the token from the login page */
  80. const token = $('input[name="login_form[_token]"]').attr('value');
  81. if (token === '')
  82. {
  83. return done(new Error('Can`t find token!'));
  84. }
  85. let options =
  86. {
  87. headers: defaultHeaders,
  88. form:
  89. {
  90. 'login_form[name]': config.user,
  91. 'login_form[password]': config.pass,
  92. 'login_form[redirect_url]': '/',
  93. 'login_form[_token]': token
  94. },
  95. jar: true,
  96. gzip: false,
  97. method: 'POST',
  98. url: 'https://www.crunchyroll.com/login'
  99. };
  100. cloudscraper.request(options, (err: Error, rep: string, body: string) =>
  101. {
  102. if (err)
  103. {
  104. return done(err);
  105. }
  106. /* The page return with a meta based redirection, as we wan't to check that everything is fine, reload
  107. * the main page. A bit convoluted, but more sure.
  108. */
  109. let options =
  110. {
  111. headers: defaultHeaders,
  112. jar: true,
  113. url: 'http://www.crunchyroll.com/',
  114. method: 'GET'
  115. };
  116. cloudscraper.request(options, (err: Error, rep: string, body: string) =>
  117. {
  118. if (err)
  119. {
  120. return done(err);
  121. }
  122. let $ = cheerio.load(body);
  123. /* Check if auth worked */
  124. const regexps = /ga\('set', 'dimension[5-8]', '([^']*)'\);/g;
  125. const dims = regexps.exec($('script').text());
  126. for (let i = 1; i < 5; i++)
  127. {
  128. if ((dims[i] !== undefined) && (dims[i] !== '') && (dims[i] !== 'not-registered'))
  129. {
  130. isAuthenticated = true;
  131. }
  132. if ((dims[i] === 'premium') || (dims[i] === 'premiumplus'))
  133. {
  134. isPremium = true;
  135. }
  136. }
  137. if (isAuthenticated === false)
  138. {
  139. const error = $('ul.message, li.error').text();
  140. return done(new Error('Authentication failed: ' + error));
  141. }
  142. if (isPremium === false)
  143. {
  144. log.warn('Do not use this app without a premium account.');
  145. }
  146. else
  147. {
  148. log.info('You have a premium account! Good!');
  149. }
  150. done(null);
  151. });
  152. });
  153. });
  154. }
  155. /**
  156. * Modifies the options to use the authenticated cookie jar.
  157. */
  158. function modify(options: string|request.Options, reqMethod: string): request.Options
  159. {
  160. if (typeof options !== 'string')
  161. {
  162. options.jar = true;
  163. options.headers = defaultHeaders;
  164. options.method = reqMethod;
  165. return options;
  166. }
  167. return { jar: true, headers: defaultHeaders, url: options.toString(), method: reqMethod };
  168. }