decryptio฻n ke(зіц)ys are rειnaðarann

This commit is contained in:
Reid 2025-04-20 03:07:30 -07:00
parent 37729aa76e
commit b10801bf29
Signed by: reidlab
GPG key ID: DAF5EAF6665839FD
10 changed files with 423 additions and 34 deletions

View file

@ -1,3 +1,4 @@
MEDIA_USER_TOKEN=RE8gTk9UIFRSVVNUIFRIRU0uIFRIRVJFIElTIFNPTUVUSElORyBISURJTkcgT04gaHR0cHM6Ly9kZWVwd29rZW4uZGV2Lw==
ITUA=US
CLIENT_ID=YTg1OGx2NmdpM3M1eWQ1YW0zaGtsN3FxOTM5Mzg3MjBrdjcxc3B4aXM1MnRscHViOGJkazl2ZGE2ZGN4dWFwYzJxMXo3ZzN6bWVsMjVuMnhhazc2cjdobHlxa2FkZjdibGYybXA4cWZkanZ6aGUydWI5bWF6ejcyajVkbmthbHA=
WIDEVINE_CLIENT_ID=YTg1OGx2NmdpM3M1eWQ1YW0zaGtsN3FxOTM5Mzg3MjBrdjcxc3B4aXM1MnRscHViOGJkazl2ZGE2ZGN4dWFwYzJxMXo3ZzN6bWVsMjVuMnhhazc2cjdobHlxa2FkZjdibGYybXA4cWZkanZ6aGUydWI5bWF6ejcyajVkbmthbHA=
WIDEVINE_PRIVATE_KEY=aGFpaWlpaWlpaWlpaSBtZW93IDozMzMgd2Fzc3VwCg==

View file

@ -1,6 +1,8 @@
# amdl
a web apple music downloader with questionable legality
![banner](./docs/banner.jpg)
a self-hostable web-ui apple music downloader widevine decryptor with questionable legality
## setup
@ -10,14 +12,14 @@ a web apple music downloader with questionable legality
`WIDEVINE_CLIENT_ID` however... oh boy. this thing kind of Sucks to obtain and i would totally recommend finding a not-so-legal spot you can obtain this from (in fact, i found one on github LOL), rather than extracting it yourself. if you want to do through the pain like i did, check [this guide](forum.videohelp.com/threads/408031-Dumping-Your-own-L3-CDM-with-Android-Studio) out!! once you have your `client_id.bin` file, convert it to base64 and slap it in the env var (`cat client_id.bin | base64 -w 0`)
`WIDEVINE_PRIVATE_KEY` is essentially the same process of obtainment, you'll get it from the same guide!! i'm not sure how to easily find one of these on the web... but i'm sure y'all can pull through. this is also in base64 (`cat private_key.pem | base64 -w 0`)
### config
most of the config is commented out in [`config.example.toml`](./config.example.toml), just copy it over to `config.toml` and go wild! i tried to make the error reporting for invalid configurations pretty good :)
most of the config is talked on in [`config.example.toml`](./config.example.toml), just copy it over to `config.toml` and go wild! i tried to make the error reporting for invalid configurations pretty good and digestable
<!-- TODO: fill this garbage section out -->
## the formats
currently you can only get basic widevine ones
## todo
support other formats

BIN
docs/banner.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

View file

@ -17,7 +17,7 @@ export default [
// TODO: find a rule to make it so that relative imports are needed
// this is because those pass type checking, and fails at runtime
// not very typescript
// not very typescript-coded (typescript shouldnt require runtime debugging, thats the point of it)
"@typescript-eslint/explicit-function-return-type": "error",
"@typescript-eslint/no-unused-vars": [

341
package-lock.json generated
View file

@ -13,9 +13,12 @@
"axios": "^1.7.9",
"callsites": "^4.2.0",
"chalk": "^5.4.1",
"data-uri-to-buffer": "^6.0.2",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"node-widevine": "https://github.com/wangziyingwen/node-widevine",
"parse-hls": "^1.0.7",
"pssh-tools": "^1.2.0",
"source-map-support": "^0.5.21",
"toml": "^3.0.0",
"zod": "^3.24.2",
@ -30,6 +33,196 @@
"typescript-eslint": "^7.13.0"
}
},
"node_modules/@bufbuild/buf": {
"version": "1.52.1",
"resolved": "https://registry.npmjs.org/@bufbuild/buf/-/buf-1.52.1.tgz",
"integrity": "sha512-Sflcgagb/9gu3ShUF7jjJmYgMxut+YRNajPtTk20cUq5MIG8cCZeVfA6s6kDj2TG5QuAf49u5akTHPa5ZENr0A==",
"hasInstallScript": true,
"license": "Apache-2.0",
"bin": {
"buf": "bin/buf",
"protoc-gen-buf-breaking": "bin/protoc-gen-buf-breaking",
"protoc-gen-buf-lint": "bin/protoc-gen-buf-lint"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@bufbuild/buf-darwin-arm64": "1.52.1",
"@bufbuild/buf-darwin-x64": "1.52.1",
"@bufbuild/buf-linux-aarch64": "1.52.1",
"@bufbuild/buf-linux-armv7": "1.52.1",
"@bufbuild/buf-linux-x64": "1.52.1",
"@bufbuild/buf-win32-arm64": "1.52.1",
"@bufbuild/buf-win32-x64": "1.52.1"
}
},
"node_modules/@bufbuild/buf-darwin-arm64": {
"version": "1.52.1",
"resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.52.1.tgz",
"integrity": "sha512-c2iCRSvhrQVCe91xQNKhwy4ES3AN0kWP1oXnCe+bzReOrBsLef9gVoxKbDv5Ywkv+epfqhuAVbltpN37P4s9mQ==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@bufbuild/buf-darwin-x64": {
"version": "1.52.1",
"resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.52.1.tgz",
"integrity": "sha512-2v742rgJBCe8E1H/u8ObUH4oeev5jCMP9TKPRy3iQZBFB2lXXgvKQCmSP2Du6Sc7bC3L4QxSbLfhJRobdjUR3Q==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@bufbuild/buf-linux-aarch64": {
"version": "1.52.1",
"resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.52.1.tgz",
"integrity": "sha512-bnfDsmsnPlgNBueTzIemBuL4WxcASR7T+RIeTjur+VILN7kN6Pd8gUiNIQbZSvKwRdKGDRJXCfn404w+5Djo5A==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@bufbuild/buf-linux-armv7": {
"version": "1.52.1",
"resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-armv7/-/buf-linux-armv7-1.52.1.tgz",
"integrity": "sha512-+BvlP9FZu83dOoy2+fIw9/CuTq3TwDUXqfMU14b0CRDlx6eJsTbFMsYyNCQYdp9yJsLy3keEHKlBQkYdfd1wpQ==",
"cpu": [
"arm"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@bufbuild/buf-linux-x64": {
"version": "1.52.1",
"resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.52.1.tgz",
"integrity": "sha512-MIf62fmcV1FbBzojPfQAuE1BRspkgc6w5SDYdgimd85a9FYOrpUTkt31ZPjEz/swOuneGcDecayYtt6jWEcMGA==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@bufbuild/buf-win32-arm64": {
"version": "1.52.1",
"resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.52.1.tgz",
"integrity": "sha512-QCKsJlgiEeY8bUipVO2WDQHHrSkmWUU7DAqZrw/ndHNCwg+//jhuwLYEbpbRiA1qG4Va6WzosR5/3VgOetyLuw==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@bufbuild/buf-win32-x64": {
"version": "1.52.1",
"resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.52.1.tgz",
"integrity": "sha512-VzoN16/MbZ/ozLjFp5JKhSAhs9iOEPrp0hbCpO56o/0HXFQ96KNgNEP9vYD/E90DNHWW5sgoFk74zdJLXG8wXw==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@bufbuild/protobuf": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.0.tgz",
"integrity": "sha512-+imAQkHf7U/Rwvu0wk1XWgsP3WnpCWmK7B48f0XqSNzgk64+grljTKC7pnO/xBiEMUziF7vKRfbBnOQhg126qQ==",
"license": "(Apache-2.0 AND BSD-3-Clause)"
},
"node_modules/@bufbuild/protoc-gen-es": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@bufbuild/protoc-gen-es/-/protoc-gen-es-2.2.0.tgz",
"integrity": "sha512-PmUTtbJJfgcabTsoF59W0bsAr7xO5aGcMe69G8vOq0ogYV1aWmvFKhHKHDtn295pOLhTXmfrDSUNi/OTHuDdpw==",
"license": "Apache-2.0",
"dependencies": {
"@bufbuild/protobuf": "^2.2.0",
"@bufbuild/protoplugin": "2.2.0"
},
"bin": {
"protoc-gen-es": "bin/protoc-gen-es"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"@bufbuild/protobuf": "2.2.0"
},
"peerDependenciesMeta": {
"@bufbuild/protobuf": {
"optional": true
}
}
},
"node_modules/@bufbuild/protoplugin": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@bufbuild/protoplugin/-/protoplugin-2.2.0.tgz",
"integrity": "sha512-ijsCHuhtXbfTiffoBRve2uCPR7gy6cwJsMe8z5bYQtczGiZVVfiyAze55gk1J/1ruqkr40oZ9BwKAGOzz69f0g==",
"license": "Apache-2.0",
"dependencies": {
"@bufbuild/protobuf": "2.2.0",
"@typescript/vfs": "^1.5.2",
"typescript": "5.4.5"
}
},
"node_modules/@bufbuild/protoplugin/node_modules/typescript": {
"version": "5.4.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz",
@ -521,6 +714,70 @@
"node": ">= 8"
}
},
"node_modules/@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/base64": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/codegen": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/eventemitter": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
"integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/fetch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
"integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.1",
"@protobufjs/inquire": "^1.1.0"
}
},
"node_modules/@protobufjs/float": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/inquire": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
"integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/path": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/pool": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/utf8": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
"license": "BSD-3-Clause"
},
"node_modules/@types/body-parser": {
"version": "1.19.5",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
@ -586,7 +843,6 @@
"version": "22.13.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.4.tgz",
"integrity": "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.20.0"
@ -832,6 +1088,18 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript/vfs": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/@typescript/vfs/-/vfs-1.6.1.tgz",
"integrity": "sha512-JwoxboBh7Oz1v38tPbkrZ62ZXNHAk9bJ7c9x0eI5zBfBnBYGhURdbnh7Z4smN/MV48Y5OCcZb58n972UtbazsA==",
"license": "MIT",
"dependencies": {
"debug": "^4.1.1"
},
"peerDependencies": {
"typescript": "*"
}
},
"node_modules/@ungap/structured-clone": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
@ -1238,11 +1506,19 @@
"node": ">= 8"
}
},
"node_modules/data-uri-to-buffer": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
"integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/debug": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@ -2350,6 +2626,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/long": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"license": "Apache-2.0"
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@ -2490,6 +2772,26 @@
"node": ">= 0.6"
}
},
"node_modules/node-forge": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
"license": "(BSD-3-Clause OR GPL-2.0)",
"engines": {
"node": ">= 6.13.0"
}
},
"node_modules/node-widevine": {
"version": "0.3.1",
"resolved": "git+ssh://git@github.com/wangziyingwen/node-widevine.git#191b52f055f4c1822673e3a820cd2c43e7574c73",
"license": "GPL-3.0-or-later",
"dependencies": {
"@bufbuild/buf": "^1.35.1",
"@bufbuild/protobuf": "2.2.0",
"@bufbuild/protoc-gen-es": "2.2.0",
"node-forge": "^1.3.1"
}
},
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
@ -2690,6 +2992,30 @@
"node": ">= 0.8.0"
}
},
"node_modules/protobufjs": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.0.tgz",
"integrity": "sha512-Z2E/kOY1QjoMlCytmexzYfDm/w5fKAiRwpSzGtdnXW1zC88Z2yXazHHrOtwCzn+7wSxyE8PYM4rvVcMphF9sOA==",
"hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
"@protobufjs/codegen": "^2.0.4",
"@protobufjs/eventemitter": "^1.1.0",
"@protobufjs/fetch": "^1.1.0",
"@protobufjs/float": "^1.0.2",
"@protobufjs/inquire": "^1.1.0",
"@protobufjs/path": "^1.1.2",
"@protobufjs/pool": "^1.1.0",
"@protobufjs/utf8": "^1.1.0",
"@types/node": ">=13.7.0",
"long": "^5.0.0"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@ -2709,6 +3035,15 @@
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/pssh-tools": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/pssh-tools/-/pssh-tools-1.2.0.tgz",
"integrity": "sha512-HwYhvVYK17SAvj7qnG5HwXE13Z0ObRAGKxzh7vHV71RQroNAQiw/TYpr8iMiyixFaG6BhtWMpY47BbzOrtTxsw==",
"license": "BSD",
"dependencies": {
"protobufjs": "^7.2.4"
}
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@ -3249,7 +3584,6 @@
"version": "5.7.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
@ -3290,7 +3624,6 @@
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"devOptional": true,
"license": "MIT"
},
"node_modules/unpipe": {

View file

@ -16,9 +16,12 @@
"axios": "^1.7.9",
"callsites": "^4.2.0",
"chalk": "^5.4.1",
"data-uri-to-buffer": "^6.0.2",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"node-widevine": "https://github.com/wangziyingwen/node-widevine",
"parse-hls": "^1.0.7",
"pssh-tools": "^1.2.0",
"source-map-support": "^0.5.21",
"toml": "^3.0.0",
"zod": "^3.24.2",

View file

@ -18,8 +18,12 @@ export default class AppleMusicApi {
this.http = axios.create({
baseURL: ampApiUrl,
headers: {
"Origin": appleMusicHomepageUrl,
"Media-User-Token": mediaUserToken,
"Origin": appleMusicHomepageUrl
// TODO: move somewhere else
// this is only used for `getWidevineLicense`
"x-apple-music-user-token": mediaUserToken,
"x-apple-renewal": true // do i wanna know what this does?
},
params: {
"l": language
@ -28,7 +32,7 @@ export default class AppleMusicApi {
}
public async login(): Promise<void> {
this.http.defaults.headers["Authorization"] = `Bearer ${await getToken(appleMusicHomepageUrl)}`;
this.http.defaults.headers.common["Authorization"] = `Bearer ${await getToken(appleMusicHomepageUrl)}`;
}
async getSong<
@ -57,16 +61,14 @@ export default class AppleMusicApi {
trackId: string,
trackUri: string,
challenge: string
): Promise<string> {
): Promise<{ license: string | undefined }> { // dubious type, doesn't matter much
return (await this.http.post(licenseApiUrl, {
params: {
challenge: challenge,
"key-system": "com.widevine.alpha",
uri: trackUri,
adamId: trackId,
isLibrary: false,
"user-initiated": true
}
})).data;
}
}

View file

@ -22,7 +22,8 @@ const configSchema = z.object({
const envSchema = z.object({
MEDIA_USER_TOKEN: z.string(),
ITUA: z.string(),
WIDEVINE_CLIENT_ID: z.string()
WIDEVINE_CLIENT_ID: z.string(),
WIDEVINE_PRIVATE_KEY: z.string()
});
// check that `config.toml` actually exists

48
src/downloader/keygen.ts Normal file
View file

@ -0,0 +1,48 @@
import { LicenseType, Session } from "node-widevine";
import { env } from "../config.js";
import { appleMusicApi } from "../api/index.js";
import { dataUriToBuffer } from "data-uri-to-buffer";
import * as log from "../log.js";
import fs from "node:fs";
import * as psshTools from "pssh-tools";
export async function getWidevineDecryptionKey(psshDataUri: string, trackId: string): Promise<void> {
let pssh = Buffer.from(dataUriToBuffer(psshDataUri).buffer);
const privateKey = Buffer.from(env.WIDEVINE_PRIVATE_KEY, "base64");
const identifierBlob = Buffer.from(env.WIDEVINE_CLIENT_ID, "base64");
let session = new Session({ privateKey, identifierBlob }, pssh);
let challenge: Buffer;
try {
challenge = session.createLicenseRequest(LicenseType.STREAMING);
} catch (err) {
// for some reason, if gotten from a webplayback manifest, the pssh is in a completely different format
// well, somewhat. we have to rebuild the pssh
const rebuiltPssh = psshTools.widevine.encodePssh({
contentId: "Hiiii", // lol?? i don't know what this is, random slop go!!!!
dataOnly: false,
keyIds: [Buffer.from(dataUriToBuffer(psshDataUri).buffer).toString("hex")]
});
log.warn("pssh was invalid, treating it as raw data");
log.warn("this should not error, unless the pssh data is invalid, too");
pssh = Buffer.from(rebuiltPssh, "base64");
session = new Session({ privateKey, identifierBlob }, pssh);
challenge = session.createLicenseRequest(LicenseType.STREAMING);
}
const response = await appleMusicApi.getWidevineLicense(
trackId,
psshDataUri,
challenge.toString("base64")
);
if (typeof response?.license !== "string") { throw "license is missing or not a string! sign that authentication failed (unsupported codec?)"; }
const license = session.parseLicense(Buffer.from(response.license, "base64"));
if (license.length === 0) { throw "license(s) failed to be parsed. this could be an error for invalid data! (e.x. pssh/challenge)"; }
log.info(license);
fs.writeFileSync("license", response.license, { encoding: "utf-8" });
}

View file

@ -3,7 +3,7 @@ import * as log from "../log.js";
import type { SongAttributes } from "../api/types/appleMusic/attributes.js";
import hls, { Item } from "parse-hls";
import axios from "axios";
// TODO: remove
import { getWidevineDecryptionKey } from "./keygen.js";
import { select } from "@inquirer/prompts";
// ugliest type ever
@ -21,12 +21,14 @@ type HLS = ReturnType<typeof hls.default.parse>;
// havent had this issue with the small pool i tested before late 2024. what ????
// i don't get it.
// i just tried another thing from 2022 ro 2023 and it worked fine
// SOLVED. widevine keys are not always present in the m3u8 manifest that is default (you can see that in link above, thats why it exists)
// OH. it doesn't seem to give the keys you want anyway LOLLLLLLL????
// i'm sure its used for *SOMETHING* so i'll keep it
// SUPER TODO: turn this all into a streaminfo class
// this typing is dubious...
// TODO: possibly just stop using an array; use union type on generic
// TODO: add "legacy" fallback
// SUPER TODO: add "legacy", would use stuff from webplayback, default to this
// TODO: make widevine/fairplay optional (esp for above)
async function getStreamInfo(trackMetadata: SongAttributes<["extendedAssetUrls"]>): Promise<void> {
const m3u8Url = trackMetadata.extendedAssetUrls.enhancedHls;
const m3u8 = await axios.get(m3u8Url, { responseType: "text" });
@ -43,9 +45,7 @@ async function getStreamInfo(trackMetadata: SongAttributes<["extendedAssetUrls"]
const playreadyPssh = getPlayreadyPssh(drmInfos, drmIds);
const fairplayKey = getFairplayKey(drmInfos, drmIds);
log.debug("widevine pssh", widevinePssh);
log.debug("playready pssh", playreadyPssh);
log.debug("fairplay key", fairplayKey);
await getWidevineDecryptionKey(widevinePssh, "1615276490");
}
// i don't think i wanna write all of the values we need. annoying !
@ -123,6 +123,5 @@ const getPlayreadyPssh = (drmInfos: DrmInfos, drmIds: string[]): string => getDr
const getFairplayKey = (drmInfos: DrmInfos, drmIds: string[]): string => getDrmData(drmInfos, drmIds, "com.apple.streamingkeydelivery");
// TODO: remove later, this is just for testing
log.debug(await appleMusicApi.getWebplayback("1758429584"));
await getStreamInfo((await appleMusicApi.getSong("1758429584")).data[0].attributes);
log.debug(await appleMusicApi.getWebplayback("1615276490"));
await getStreamInfo((await appleMusicApi.getSong("1615276490")).data[0].attributes);