crextractor 1.3.4 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,32 +4,33 @@ Utility for extracting credentials from the Crunchyroll Android app (both TV and
4
4
 
5
5
  The [credentials](https://github.com/vitalygashkov/crextractor/blob/main/credentials.tv.json) are [automatically](https://github.com/vitalygashkov/crextractor/actions/workflows/extract.yml) updated once a week (if there are any changes).
6
6
 
7
- ## Prerequisites
8
-
9
- - [Node.js](https://nodejs.org/en)
10
- - [jadx](https://github.com/skylot/jadx)
11
-
12
7
  ## Usage
13
8
 
14
- ### Library
15
-
16
- ```bash
17
- npm i crextractor
18
- ```
19
-
20
- #### Fetch ready credentials from this GitHub repository
9
+ ### Fetch ready credentials from this GitHub repository
21
10
 
22
11
  ```js
23
- import { pull } from 'crextractor';
12
+ // Supported targets: mobile, tv
13
+ async function fetchCredentials(target = 'tv') {
14
+ const url = `https://raw.githubusercontent.com/vitalygashkov/crextractor/refs/heads/main/credentials.${target}.json`;
15
+ return fetch(url).then((response) => response.json());
16
+ }
24
17
 
25
18
  async function main() {
26
- const credentials = await pull('tv');
19
+ const credentials = fetchCredentials();
20
+
21
+ // You can use credentials to obtain access tokens for Crunchyroll APIs
22
+
23
+ // 3.54.5 (22304) -> 3.54.5_22304
24
+ const userAgentAppVersion = credentials.version
25
+ .replace(' ', '_')
26
+ .replace('(', '')
27
+ .replace(')', '');
27
28
 
28
- // You can use the extracted credentials to obtain access tokens for Crunchyroll APIs
29
29
  const response = await fetch('https://beta-api.crunchyroll.com/auth/v1/token', {
30
30
  headers: {
31
- Authorization: credentials.authorization, // Ready HTTP header in the format `Basic <encoded>`, can be used to access some Crunchyroll APIs
32
- 'User-Agent': 'Crunchyroll/ANDROIDTV/3.42.1_22267 (Android 16; en-US; sdk_gphone64_x86_64)',
31
+ // Ready HTTP header in the format `Basic <encoded>`, can be used to access some Crunchyroll APIs
32
+ Authorization: credentials.authorization,
33
+ 'User-Agent': `Crunchyroll/ANDROIDTV/${userAgentAppVersion} (Android 16; en-US; sdk_gphone64_x86_64)`,
33
34
  // ...
34
35
  },
35
36
  method: 'POST',
@@ -40,23 +41,12 @@ async function main() {
40
41
  }
41
42
  ```
42
43
 
43
- #### Extract credentials from the latest APK using jadx
44
+ ### Extract fresh credentials from the latest APK using jadx
44
45
 
45
- ```js
46
- import { extract } from 'crextractor';
46
+ #### Prerequisites
47
47
 
48
- async function main() {
49
- const { id, secret, encoded, authorization } = await extract();
50
- // id - Crunchyroll app ID
51
- // secret - Crunchyroll app secret
52
- // encoded - Base64 encoded `id:secret` string
53
- // authorization - ready HTTP header in the format `Basic <encoded>`, can be used to access some Crunchyroll APIs
54
-
55
- // Do something with the extracted credentials
56
- }
57
- ```
58
-
59
- ### Command-line interface
48
+ - [Node.js](https://nodejs.org/en)
49
+ - [jadx](https://github.com/skylot/jadx)
60
50
 
61
51
  ```bash
62
52
  npx crextractor --target mobile --output ./credentials.mobile.json
package/bin/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { parseArgs } = require('node:util');
4
- const { extract } = require('../crextractor');
3
+ import { parseArgs } from 'node:util';
4
+ import { extract } from '../crextractor.js';
5
5
 
6
6
  const args = parseArgs({
7
7
  options: {
package/crextractor.js CHANGED
@@ -1,8 +1,8 @@
1
- const { execSync } = require('node:child_process');
2
- const { join } = require('node:path');
3
- const { readdir, readFile, writeFile, rm } = require('node:fs/promises');
4
- const { existsSync } = require('node:fs');
5
- const { download } = require('molnia');
1
+ import { execSync } from 'node:child_process';
2
+ import { join } from 'node:path';
3
+ import { readdir, readFile, writeFile, rm } from 'node:fs/promises';
4
+ import { existsSync } from 'node:fs';
5
+ import { download } from 'molnia';
6
6
 
7
7
  const downloadMobileApk = async () => {
8
8
  const url = 'https://api.qqaoop.com/v11/apps/com.crunchyroll.crunchyroid/download?userId=1';
@@ -15,9 +15,21 @@ const downloadMobileApk = async () => {
15
15
  };
16
16
 
17
17
  const downloadTvApk = async () => {
18
+ const searchParams = new URLSearchParams();
19
+ searchParams.append('query', 'crunchyroll');
20
+ searchParams.append('cdn', 'web');
21
+ searchParams.append('q', 'bXlDUFU9YXJtNjQtdjhhLGFybWVhYmktdjdhLGFybWVhYmkmbGVhbmJhY2s9MA');
22
+ searchParams.append('aab', '1');
23
+ const searchUrl = `https://ws2-cache.aptoide.com/api/7/apps/search?${searchParams.toString()}`;
24
+ const searchResults = await fetch(searchUrl).then((r) => r.json());
25
+ const id = searchResults.datalist?.list?.[0]?.id;
26
+ if (!id) {
27
+ console.error('Unable to find ID for TV APK');
28
+ return;
29
+ }
18
30
  const source = 'https://webservices.aptoide.com/webservices/3/getApkInfo';
19
31
  const body = new FormData();
20
- body.append('identif', 'id:72075737');
32
+ body.append('identif', `id:${id}`);
21
33
  body.append('mode', 'json');
22
34
  const response = await fetch(source, { method: 'POST', body });
23
35
  const json = await response.json();
@@ -71,7 +83,15 @@ const parseCredentials = async (decompiledDir) => {
71
83
  if (id && secret) return { id, secret };
72
84
  }
73
85
 
74
- const constantsPath = join(decompiledDir, 'sources', 'com', 'crunchyroll', 'api', 'util', 'Constants.java');
86
+ const constantsPath = join(
87
+ decompiledDir,
88
+ 'sources',
89
+ 'com',
90
+ 'crunchyroll',
91
+ 'api',
92
+ 'util',
93
+ 'Constants.java',
94
+ );
75
95
  const constants = await readFile(constantsPath, 'utf8');
76
96
  return {
77
97
  id: constants.split(' PROD_CLIENT_ID = "')[1].split('"')[0],
@@ -83,7 +103,8 @@ const parseVersion = async (decompiledDir) => {
83
103
  const manifestJsonPath = join(decompiledDir, 'resources', 'manifest.json');
84
104
  const manifestXmlPath = join(decompiledDir, 'resources', 'AndroidManifest.xml');
85
105
  if (existsSync(manifestJsonPath)) {
86
- const manifest = require(manifestJsonPath);
106
+ const manifestContent = await readFile(manifestJsonPath, 'utf8');
107
+ const manifest = JSON.parse(manifestContent);
87
108
  const version = `${manifest.version_name} (${manifest.version_code})`;
88
109
  return version;
89
110
  } else if (existsSync(manifestXmlPath)) {
@@ -124,7 +145,10 @@ const extract = async ({ target = 'mobile', output, cleanup = false } = {}) => {
124
145
  console.log(`Authorization: ${authorization}`);
125
146
 
126
147
  if (output) {
127
- await writeFile(output, JSON.stringify({ version, id, secret, encoded, authorization }, null, 2));
148
+ await writeFile(
149
+ output,
150
+ JSON.stringify({ version, id, secret, encoded, authorization }, null, 2),
151
+ );
128
152
  }
129
153
 
130
154
  return { version, id, secret, encoded, authorization };
@@ -136,4 +160,4 @@ const pull = async ({ target = 'mobile' } = {}) => {
136
160
  return credentials;
137
161
  };
138
162
 
139
- module.exports = { extract, pull };
163
+ export { extract, pull };
package/package.json CHANGED
@@ -1,8 +1,26 @@
1
1
  {
2
2
  "name": "crextractor",
3
- "version": "1.3.4",
3
+ "version": "1.4.0",
4
4
  "description": "Utility for extracting credentials from the Crunchyroll Android app",
5
- "main": "crextractor.js",
5
+ "keywords": [
6
+ "crunchyroll"
7
+ ],
8
+ "bugs": {
9
+ "url": "https://github.com/vitalygashkov/crextractor/issues",
10
+ "email": "vitalygashkov@vk.com"
11
+ },
12
+ "license": "MIT",
13
+ "author": "Vitaly Gashkov <vitalygashkov@vk.com>",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/vitalygashkov/crextractor.git"
17
+ },
18
+ "funding": [
19
+ {
20
+ "type": "individual",
21
+ "url": "https://t.me/tribute/app?startapp=dqW2"
22
+ }
23
+ ],
6
24
  "bin": {
7
25
  "crextractor": "bin/cli.js"
8
26
  },
@@ -11,33 +29,31 @@
11
29
  "crextractor.d.ts",
12
30
  "crextractor.js"
13
31
  ],
32
+ "type": "module",
33
+ "main": "crextractor.js",
14
34
  "types": "crextractor.d.ts",
15
- "type": "commonjs",
35
+ "exports": {
36
+ ".": {
37
+ "types": "./crextractor.d.ts",
38
+ "import": "./crextractor.js",
39
+ "require": "./crextractor.js"
40
+ }
41
+ },
16
42
  "scripts": {
17
43
  "start": "node bin/cli.js",
18
44
  "extract:mobile": "node bin/cli.js --target mobile --output ./credentials.mobile.json",
19
45
  "extract:tv": "node bin/cli.js --target tv --output ./credentials.tv.json",
20
46
  "test": "echo \"Error: no test specified\" && exit 1"
21
47
  },
22
- "keywords": [
23
- "crunchyroll"
24
- ],
25
- "author": "Vitaly Gashkov <vitalygashkov@vk.com>",
26
- "license": "MIT",
27
- "readmeFilename": "README.md",
28
- "funding": [
29
- {
30
- "type": "individual",
31
- "url": "https://t.me/tribute/app?startapp=dqW2"
32
- }
33
- ],
34
- "engines": {
35
- "node": ">=20"
36
- },
37
48
  "dependencies": {
38
49
  "molnia": "^0.1.7"
39
50
  },
40
51
  "devDependencies": {
41
- "typescript": "^5.9.2"
42
- }
52
+ "oxfmt": "^0.28.0",
53
+ "typescript": "^5.9.3"
54
+ },
55
+ "engines": {
56
+ "node": ">=24"
57
+ },
58
+ "readmeFilename": "README.md"
43
59
  }