Advertisement
Guest User

Untitled

a guest
Apr 17th, 2018
1,493
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.59 KB | None | 0 0
  1. /* global fetch, chrome, decrypt */
  2. var browser = browser || chrome;
  3.  
  4. const API_BASE = 'http://api-manga.crunchyroll.com/cr_start_session?api_ver=1.0';
  5. const SERVERS = [
  6. { url: `${API_BASE}&device_type=com.crunchyroll.manga.android&access_token=FLpcfZH4CbW4muO`, sendUserId: false, generateDeviceId: true },
  7. { url: `${API_BASE}&device_type=com.crunchyroll.iphone&access_token=QWjz212GspMHH9h`, sendUserId: false, generateDeviceId: true },
  8. { url: `${API_BASE}&device_type=com.crunchyroll.windows.desktop&access_token=LNDJgOit5yaRIWN`, sendUserId: false, generateDeviceId: true },
  9. { url: 'https://api1.cr-unblocker.com/getsession.php?version=1.1', sendUserId: true },
  10. { url: 'https://api2.cr-unblocker.com/start_session?version=1.1', sendUserId: true }
  11. ];
  12.  
  13. /**
  14. * Main function fetching and setting the US based cookies
  15. * @param {String} extension extension of domain
  16. */
  17. function localizeToUs(extension) {
  18. console.log('Fetching session id...');
  19. SERVERS.shuffle();
  20. let settings = this.settings.get();
  21. browser.storage.local.get({ login: null, user: null }, (item) => {
  22. var auth = '';
  23. if (settings.saveLogin && item.login !== null) {
  24. console.log('Logging in using auth token...');
  25. auth = `&auth=${encodeURIComponent(item.login.auth)}`;
  26. }
  27. sequentialFetch(SERVERS, extension, auth, item.user);
  28. });
  29. }
  30.  
  31. /**
  32. * Fetch in order an array of servers URLs
  33. * @param {Array} servers Servers to fetch in order
  34. * @param {String} extension Extension of the current domain
  35. * @param {String} auth Auth token to login user
  36. * @param {Object} user User data for the user to log in
  37. */
  38. function sequentialFetch(servers, extension, auth, user) {
  39. console.log(`Fetching server ${servers[0].url}...`);
  40. fetchServer(servers[0], auth, user)
  41. .then(sessionData => updateCookies(extension, sessionData))
  42. .catch(e => {
  43. console.log(e);
  44. if (servers.slice(1).length > 0) {
  45. sequentialFetch(servers.slice(1), extension, auth, user);
  46. } else {
  47. notifyUser(`CR-Unblocker couldn't get a session id`);
  48. }
  49. });
  50. }
  51.  
  52. /**
  53. * Fetch a session ID from a server
  54. * @param {Object} server Object describing backend server to use
  55. * @param {String} auth Auth token to login user
  56. * @param {Object} user User data for the user to log in
  57. * @return {Promise} A promise resolving to the the session data containing session id and possibly user/auth data
  58. */
  59. function fetchServer(server, auth, user) {
  60. return new Promise((resolve, reject) => {
  61. let uri = server.url + auth;
  62. if (server.sendUserId && user !== null && user.userId !== null && auth !== '') {
  63. uri += `&user_id=${encodeURIComponent(user.userId)}`;
  64. }
  65. if (server.generateDeviceId) {
  66. uri += `&device_id=${generateDeviceId()}`;
  67. }
  68. fetch(uri)
  69. .then(res => {
  70. if (res.ok) {
  71. return res.json();
  72. }
  73. reject(new Error(res.status));
  74. })
  75. .then(json => {
  76. if (json.error === true) {
  77. reject(new Error(json.message));
  78. } else if (json.data.country_code !== 'US') {
  79. reject(new Error('Session id not from the US'));
  80. } else {
  81. resolve(json.data);
  82. }
  83. })
  84. .catch((e) => reject(e));
  85. });
  86. }
  87.  
  88. /**
  89. * Update the cookies to the new values
  90. * Nested callbacks for Edge compatibility
  91. * @param {String} extension hostname extension
  92. * @param {Object} sessionData New session data
  93. */
  94. function updateCookies(extension, sessionData) {
  95. console.log(`got session id. Setting cookie ${sessionData.session_id}.`);
  96. browser.cookies.set({
  97. url: `http://crunchyroll${extension}`,
  98. name: 'session_id',
  99. value: sessionData.session_id,
  100. domain: `crunchyroll${extension}`,
  101. httpOnly: true
  102. }, () => {
  103. browser.cookies.set({
  104. url: `http://crunchyroll${extension}`,
  105. name: 'sess_id',
  106. value: sessionData.session_id,
  107. domain: `crunchyroll${extension}`,
  108. httpOnly: true
  109. }, () => {
  110. browser.cookies.set({
  111. url: `http://crunchyroll${extension}`,
  112. name: 'c_locale',
  113. value: 'enUS',
  114. domain: `crunchyroll${extension}`,
  115. httpOnly: true
  116. }, () => doLogin(sessionData));
  117. });
  118. });
  119. }
  120.  
  121. /**
  122. * Logs in a user using the given login data
  123. * @param {String} sessionId current session id
  124. * @param {Object} loginData login data (properties username and password are needed)
  125. * @return {Promise} A promise resolving to the login data (contains user data, auth and expiration)
  126. */
  127. function loginUser(sessionId, loginData) {
  128. return new Promise((resolve, reject) => {
  129. if (typeof loginData.password === 'string') {
  130. // if the password is decrypted, login using the API
  131. fetch(`https://api.crunchyroll.com/login.0.json?session_id=${sessionId}&locale=enUS&account=${encodeURIComponent(loginData.username)}&password=${encodeURIComponent(loginData.password)}`)
  132. .then((res) => res.json())
  133. .then((res) => {
  134. if (res.error === true) {
  135. reject(new Error(res.message));
  136. } else {
  137. resolve(res.data);
  138. }
  139. })
  140. .catch((e) => reject(e));
  141. } else {
  142. // password isn't in plain text, decrypt it
  143. decrypt(loginData.username, loginData.password)
  144. .then(password => {
  145. loginUser(sessionId, { username: loginData.username, password: password })
  146. .then(data => resolve(data))
  147. .catch(_e => reject(_e));
  148. })
  149. .catch(_e => reject(_e));
  150. }
  151. });
  152. }
  153.  
  154. /**
  155. * Function called after the cookies are set
  156. * Login user if needed
  157. * @param {Object} sessionData current session data
  158. */
  159. function doLogin(sessionData) {
  160. let settings = this.settings.get();
  161. browser.storage.local.get({ loginData: null }, (item) => {
  162. if (sessionData.user === null && settings.saveLogin && item.loginData !== null) {
  163. // login data stored, log the user in
  164. console.log('Logging in using username/password');
  165. if (typeof item.loginData.password === 'string') {
  166. // delete password if stored unencrypted (auth token will be stored)
  167. browser.storage.local.remove(['loginData']);
  168. }
  169. loginUser(sessionData.session_id, item.loginData)
  170. .then((data) => {
  171. console.log(`User logged in until ${data.expires}`);
  172. // store auth, expiration and userId, then reload
  173. browser.storage.local.set({ login: { auth: data.auth, expiration: data.expires }, user: { userId: data.user.user_id } }, reloadTab);
  174. })
  175. .catch((_e) => {
  176. notifyUser('Failed to login, please log in manually.');
  177. console.log(_e);
  178. reloadTab();
  179. });
  180. } else if (sessionData.user !== null && settings.saveLogin) {
  181. // user was already logged in when starting the session, store the new auth and expiration
  182. console.log(`Logged in until ${sessionData.expires}`);
  183. browser.storage.local.set({ login: { auth: sessionData.auth, expiration: sessionData.expires } }, reloadTab);
  184. } else {
  185. // no need to login, reload immediately
  186. reloadTab();
  187. }
  188. });
  189. }
  190.  
  191. /**
  192. * Function called after the user is logged in (if they want to)
  193. * Reload the current tab to take the changes into account
  194. */
  195. function reloadTab() {
  196. console.log('Done!');
  197. browser.tabs.query({
  198. currentWindow: true,
  199. active: true
  200. }, tabs => {
  201. tabs.forEach(tab => {
  202. console.log('Reload tab via content script');
  203. browser.tabs.sendMessage(tab.id, { action: 'reload' });
  204. });
  205. });
  206. }
  207.  
  208. /**
  209. * Emit a notification to the user with an error message
  210. * @param {String} msg Error message
  211. */
  212. function notifyUser(msg) {
  213. browser.notifications.create({
  214. type: 'basic',
  215. iconUrl: 'icons/Crunchyroll-128.png',
  216. title: 'CR-Unblocker has encountered an error',
  217. message: msg
  218. });
  219. }
  220.  
  221. /**
  222. * Set a cookie with the extension transmitted from the content script
  223. */
  224. browser.runtime.onMessage.addListener((message) => {
  225. if (message.action === 'localizeToUs') {
  226. localizeToUs(message.extension);
  227. }
  228. });
  229.  
  230. /**
  231. * Open the changelog page after update or installation
  232. */
  233. browser.runtime.onInstalled.addListener((detail) => {
  234. if (detail.reason === 'install' || detail.reason === 'update') {
  235. browser.tabs.create({ url: 'https://cr-unblocker.com' });
  236. }
  237. });
  238.  
  239. /**
  240. * Add a method to shuffle arrays randomly
  241. */
  242. Array.prototype.shuffle = function shuffle() {
  243. var swapIndex, tempElement;
  244. for (var i = this.length; i; i--) {
  245. swapIndex = Math.floor(Math.random() * i);
  246. tempElement = this[i - 1];
  247. this[i - 1] = this[swapIndex];
  248. this[swapIndex] = tempElement;
  249. }
  250. };
  251.  
  252. /**
  253. * Generate a random 32 character long device ID
  254. * @return {String} Generated device ID
  255. */
  256. function generateDeviceId() {
  257. let id = '';
  258. const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  259. for (var i = 0; i < 32; i++) {
  260. id += possible.charAt(Math.floor(Math.random() * possible.length));
  261. }
  262. return id;
  263. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement