diff --git a/README.md b/README.md index c30e7e2..f4a7d05 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Crunchys +# Crextractor -A utility to extract secrets from Crunchyroll mobile app +Utility for extracting secrets from Crunchyroll mobile app ## Prerequisites @@ -10,7 +10,7 @@ A utility to extract secrets from Crunchyroll mobile app ## Installation ```bash -npm i crunchys +npm i crextractor ``` ## Usage @@ -18,7 +18,7 @@ npm i crunchys #### Library ```js -import { extractSecrets } from 'crunchys'; +import { extractSecrets } from 'crextractor'; const { id, secret, encoded, header } = await extractSecrets(); @@ -28,7 +28,7 @@ const { id, secret, encoded, header } = await extractSecrets(); #### Command-line interface ```bash -npx crunchys +npx crextractor ``` ## License diff --git a/bin/cli.js b/bin/cli.js new file mode 100755 index 0000000..5cd8e1b --- /dev/null +++ b/bin/cli.js @@ -0,0 +1,5 @@ +#!/usr/bin/env node + +const { extractSecrets } = require('../crextractor'); + +extractSecrets({ output: 'secrets.json' }); diff --git a/crunchys.d.ts b/crextractor.d.ts similarity index 65% rename from crunchys.d.ts rename to crextractor.d.ts index fac7cb0..122465e 100644 --- a/crunchys.d.ts +++ b/crextractor.d.ts @@ -5,6 +5,6 @@ export function extractSecrets(): Promise<{ secret: string; // Base64 encoded `id:secret` string encoded: string; - // Basic `Authorization` header to access Crunchyroll mobile APIs - header: string; + // HTTP header with Basic Authorization to access Crunchyroll mobile APIs + authorization: string; }>; diff --git a/crunchys.js b/crextractor.js similarity index 63% rename from crunchys.js rename to crextractor.js index ff4e5f3..ac884fe 100644 --- a/crunchys.js +++ b/crextractor.js @@ -1,25 +1,20 @@ -const { join, basename } = require('node:path'); -const { createWriteStream } = require('node:fs'); -const { readdir, readFile, rm } = require('node:fs/promises'); -const { execSync } = require('node:child_process'); -const { Readable } = require('node:stream'); +const { join } = require('node:path'); +const { readdir, readFile, writeFile, rm } = require('node:fs/promises'); +const { download } = require('molnia'); const downloadLatestApk = async () => { - const source = 'https://www.apk20.com/apk/com.crunchyroll.crunchyroid/download/'; + const source = 'https://apkcombo.com/crunchyroll/com.crunchyroll.crunchyroid/download/apk'; const page = await fetch(source); const html = await page.text(); - const url = html.split(' writer.on('finish', resolve)); - } - return join(process.cwd(), fileName); + const route = '/r2' + html.split('/r2')[1]?.split('"')[0]; + const url = `https://apkcombo.com${route}`; + const filepath = join(process.cwd(), 'crunchyroll.xapk'); + await download(url, { + output: filepath, + onProgress: (progress) => console.log(progress), + onError: (error) => console.error(error), + }); + return filepath; }; const decompileApk = (apkPath) => { @@ -58,11 +53,11 @@ const parseSecrets = (contents) => { .map((line) => line.trim()); const [, , id, secret] = results; const encoded = Buffer.from(`${id}:${secret}`).toString('base64'); - const header = `Basic ${encoded}`; - return { id, secret, encoded, header }; + const authorization = `Basic ${encoded}`; + return { id, secret, encoded, authorization }; }; -const extractSecrets = async ({ cleanup = true } = {}) => { +const extractSecrets = async ({ output, cleanup = true } = {}) => { console.log('Downloading latest APK...'); const apkPath = await downloadLatestApk(); @@ -71,21 +66,29 @@ const extractSecrets = async ({ cleanup = true } = {}) => { console.log('Searching for secrets...'); const sourcesDir = join(decompiledDir, 'sources'); + const manifest = require(join(sourcesDir, 'resources', 'manifest.json')); + const version = manifest.version_name; const configurationImpl = await findConfigurationImpl(sourcesDir); - if (!configurationImpl) return console.error('Could not find ConfigurationImpl.kt'); + if (!configurationImpl) throw new Error('Could not find ConfigurationImpl.kt'); console.log('Parsing secrets...'); - const { id, secret, encoded, header } = parseSecrets(configurationImpl); + const { id, secret, encoded, authorization } = parseSecrets(configurationImpl); console.log(`Cleaning up files...`); if (cleanup) await rm(apkPath, { recursive: true, force: true }); if (cleanup) await rm(decompiledDir, { recursive: true, force: true }); + console.log(`Version: ${version}`); console.log(`ID: ${id}`); console.log(`Secret: ${secret}`); console.log(`Encoded ID with secret: ${encoded}`); + console.log(`Authorization: ${authorization}`); - return { id, secret, encoded, header }; + if (output) { + await writeFile(output, JSON.stringify({ version, id, secret, encoded, authorization }, null, 2)); + } + + return { version, id, secret, encoded, authorization }; }; module.exports = { extractSecrets }; diff --git a/crunchys-cli.js b/crunchys-cli.js deleted file mode 100755 index e277aaf..0000000 --- a/crunchys-cli.js +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env node - -const { extractSecrets } = require('./crunchys'); - -extractSecrets(); diff --git a/package-lock.json b/package-lock.json index cbb47f5..51ee202 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,24 +1,81 @@ { "name": "crunchys", - "version": "1.0.0", + "version": "1.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "crunchys", - "version": "1.0.0", + "version": "1.0.1", + "funding": [ + { + "type": "individual", + "url": "https://boosty.to/vitalygashkov" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/vitalygashkov" + } + ], "license": "MIT", + "dependencies": { + "molnia": "^0.1.5" + }, "bin": { - "crunchys": "cli.js" + "crunchys": "crunchys-cli.js" }, "devDependencies": { - "typescript": "^5.8.2" + "typescript": "^5.9.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/molnia": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/molnia/-/molnia-0.1.5.tgz", + "integrity": "sha512-Kb3bhlvNVWJ9Z8tGZGkhmChpXQY8OLFPkxXFvLJb+foLwfH7JyyJl+DbKuUKAIK1vnxs5sBFuQb3wxOIwWBpiA==", + "funding": [ + { + "type": "individual", + "url": "https://t.me/tribute/app?startapp=dqW2" + } + ], + "license": "MIT", + "dependencies": { + "fastq": "^1.19.1", + "undici": "^7.15.0" + }, + "bin": { + "molnia": "molnia.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" } }, "node_modules/typescript": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", - "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, "license": "Apache-2.0", "bin": { @@ -28,6 +85,15 @@ "engines": { "node": ">=14.17" } + }, + "node_modules/undici": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.15.0.tgz", + "integrity": "sha512-7oZJCPvvMvTd0OlqWsIxTuItTpJBpU1tcbVl24FMn3xt3+VSunwUasmfPJRE57oNO1KsZ4PgA1xTdAX4hq8NyQ==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } } } } diff --git a/package.json b/package.json index d04cee7..5c1880b 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,12 @@ { - "name": "crunchys", + "name": "crextractor", "version": "1.0.1", "description": "A utility to extract secrets from Crunchyroll mobile app", - "main": "crunchys.js", - "bin": "crunchys-cli.js", - "types": "crunchys.d.ts", + "main": "crextractor.js", + "bin": { + "crextractor": "bin/cli.js" + }, + "types": "crextractor.d.ts", "type": "commonjs", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" @@ -18,17 +20,16 @@ "funding": [ { "type": "individual", - "url": "https://boosty.to/vitalygashkov" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/vitalygashkov" + "url": "https://t.me/tribute/app?startapp=dqW2" } ], "engines": { - "node": "20 || 21 || 22 || 23" + "node": ">=20" + }, + "dependencies": { + "molnia": "^0.1.5" }, "devDependencies": { - "typescript": "^5.8.2" + "typescript": "^5.9.2" } }