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 +22 -32
- package/bin/cli.js +2 -2
- package/crextractor.js +34 -10
- package/package.json +36 -20
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
|
-
###
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
32
|
-
|
|
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
|
-
|
|
44
|
+
### Extract fresh credentials from the latest APK using jadx
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
import { extract } from 'crextractor';
|
|
46
|
+
#### Prerequisites
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
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
package/crextractor.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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',
|
|
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(
|
|
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
|
|
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(
|
|
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
|
-
|
|
163
|
+
export { extract, pull };
|
package/package.json
CHANGED
|
@@ -1,8 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crextractor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Utility for extracting credentials from the Crunchyroll Android app",
|
|
5
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
42
|
-
|
|
52
|
+
"oxfmt": "^0.28.0",
|
|
53
|
+
"typescript": "^5.9.3"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=24"
|
|
57
|
+
},
|
|
58
|
+
"readmeFilename": "README.md"
|
|
43
59
|
}
|