websӘFacebook ite

This commit is contained in:
Reid 2025-04-29 17:56:59 -07:00
parent f233d9e64f
commit 76543fd220
Signed by: reidlab
GPG key ID: DAF5EAF6665839FD
20 changed files with 1051 additions and 96 deletions

495
package-lock.json generated
View file

@ -15,6 +15,8 @@
"data-uri-to-buffer": "^6.0.2",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"express-handlebars": "^8.0.1",
"git-rev-sync": "^3.0.2",
"node-widevine": "https://github.com/wangziyingwen/node-widevine",
"parse-hls": "^1.0.7",
"pssh-tools": "^1.2.0",
@ -26,6 +28,7 @@
},
"devDependencies": {
"@types/express": "^5.0.0",
"@types/git-rev-sync": "^2.0.2",
"@types/source-map-support": "^0.5.10",
"@typescript-eslint/parser": "^7.12.0",
"concurrently": "^9.1.2",
@ -373,6 +376,102 @@
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"license": "ISC",
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
"strip-ansi": "^7.0.1",
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
"wrap-ansi": "^8.1.0",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@isaacs/cliui/node_modules/ansi-regex": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/@isaacs/cliui/node_modules/ansi-styles": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/@isaacs/cliui/node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"license": "MIT"
},
"node_modules/@isaacs/cliui/node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"license": "MIT",
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@isaacs/cliui/node_modules/strip-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -522,6 +621,13 @@
"@types/send": "*"
}
},
"node_modules/@types/git-rev-sync": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@types/git-rev-sync/-/git-rev-sync-2.0.2.tgz",
"integrity": "sha512-ygFM5I5q4VJjU+xrb2MSzgj4BpC6HUzMnmfWp4d8bgAw/XFkJTiKn1uaNpOOT1gw+IxELyfY97JA6sRBv7J9sA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/http-errors": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
@ -861,7 +967,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@ -871,7 +976,6 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
@ -942,7 +1046,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/body-parser": {
@ -988,7 +1091,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
@ -1094,7 +1196,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
@ -1107,7 +1208,6 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"license": "MIT"
},
"node_modules/combined-stream": {
@ -1126,7 +1226,6 @@
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true,
"license": "MIT"
},
"node_modules/concurrently": {
@ -1241,7 +1340,6 @@
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
@ -1365,6 +1463,12 @@
"node": ">= 0.4"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"license": "MIT"
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@ -1375,7 +1479,6 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true,
"license": "MIT"
},
"node_modules/encodeurl": {
@ -1708,6 +1811,58 @@
"url": "https://opencollective.com/express"
}
},
"node_modules/express-handlebars": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/express-handlebars/-/express-handlebars-8.0.1.tgz",
"integrity": "sha512-mdas0PTbgQnwSyAjcYM7OMaftM8nJ3Kqz6yAyK4iCFvMOGGvh6pv42IHwcE5PBpS6ffYeZRSsgAdYUMG4CSjhQ==",
"license": "BSD-3-Clause",
"dependencies": {
"glob": "^11.0.0",
"graceful-fs": "^4.2.11",
"handlebars": "^4.7.8"
},
"engines": {
"node": ">=20"
}
},
"node_modules/express-handlebars/node_modules/glob": {
"version": "11.0.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.2.tgz",
"integrity": "sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^4.0.1",
"minimatch": "^10.0.0",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^2.0.0"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/express-handlebars/node_modules/minimatch": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz",
"integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/express/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@ -1902,6 +2057,22 @@
}
}
},
"node_modules/foreground-child": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@ -1924,7 +2095,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true,
"license": "ISC"
},
"node_modules/function-bind": {
@ -1983,12 +2153,37 @@
"node": ">= 0.4"
}
},
"node_modules/git-rev-sync": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/git-rev-sync/-/git-rev-sync-3.0.2.tgz",
"integrity": "sha512-Nd5RiYpyncjLv0j6IONy0lGzAqdRXUaBctuGBbrEA2m6Bn4iDrN/9MeQTXuiquw8AEKL9D2BW0nw5m/lQvxqnQ==",
"license": "MIT",
"dependencies": {
"escape-string-regexp": "1.0.5",
"graceful-fs": "4.1.15",
"shelljs": "0.8.5"
}
},
"node_modules/git-rev-sync/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"license": "MIT",
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/git-rev-sync/node_modules/graceful-fs": {
"version": "4.1.15",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
"integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==",
"license": "ISC"
},
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"dev": true,
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
@ -2022,7 +2217,6 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
@ -2033,7 +2227,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
@ -2091,6 +2284,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"license": "ISC"
},
"node_modules/graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@ -2098,6 +2297,27 @@
"dev": true,
"license": "MIT"
},
"node_modules/handlebars": {
"version": "4.7.8",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
"integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==",
"license": "MIT",
"dependencies": {
"minimist": "^1.2.5",
"neo-async": "^2.6.2",
"source-map": "^0.6.1",
"wordwrap": "^1.0.0"
},
"bin": {
"handlebars": "bin/handlebars"
},
"engines": {
"node": ">=0.4.7"
},
"optionalDependencies": {
"uglify-js": "^3.1.4"
}
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@ -2217,7 +2437,6 @@
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
"dev": true,
"license": "ISC",
"dependencies": {
"once": "^1.3.0",
@ -2230,6 +2449,15 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/interpret": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz",
"integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==",
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
@ -2239,6 +2467,21 @@
"node": ">= 0.10"
}
},
"node_modules/is-core-module": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
"license": "MIT",
"dependencies": {
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@ -2253,7 +2496,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@ -2296,9 +2538,23 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true,
"license": "ISC"
},
"node_modules/jackspeak": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.0.tgz",
"integrity": "sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==",
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
@ -2393,6 +2649,15 @@
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"license": "Apache-2.0"
},
"node_modules/lru-cache": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz",
"integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==",
"license": "ISC",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@ -2502,6 +2767,24 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@ -2524,6 +2807,12 @@
"node": ">= 0.6"
}
},
"node_modules/neo-async": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"license": "MIT"
},
"node_modules/node-forge": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
@ -2572,7 +2861,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dev": true,
"license": "ISC",
"dependencies": {
"wrappy": "1"
@ -2628,6 +2916,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"license": "BlueOak-1.0.0"
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@ -2680,7 +2974,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@ -2690,12 +2983,33 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"license": "MIT"
},
"node_modules/path-scurry": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz",
"integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==",
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^11.0.0",
"minipass": "^7.1.2"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/path-to-regexp": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
@ -2857,6 +3171,17 @@
"node": ">= 0.8"
}
},
"node_modules/rechoir": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
"integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==",
"dependencies": {
"resolve": "^1.1.6"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@ -2867,6 +3192,26 @@
"node": ">=0.10.0"
}
},
"node_modules/resolve": {
"version": "1.22.10",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
"license": "MIT",
"dependencies": {
"is-core-module": "^2.16.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
"bin": {
"resolve": "bin/resolve"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@ -3051,7 +3396,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
@ -3064,7 +3408,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@ -3083,6 +3426,23 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/shelljs": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz",
"integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==",
"license": "BSD-3-Clause",
"dependencies": {
"glob": "^7.0.0",
"interpret": "^1.0.0",
"rechoir": "^0.6.2"
},
"bin": {
"shjs": "bin/shjs"
},
"engines": {
"node": ">=4"
}
},
"node_modules/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
@ -3155,6 +3515,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"license": "ISC",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@ -3197,7 +3569,21 @@
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/string-width-cjs": {
"name": "string-width",
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
@ -3212,7 +3598,19 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi-cjs": {
"name": "strip-ansi",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
@ -3247,6 +3645,18 @@
"node": ">=8"
}
},
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@ -3397,6 +3807,19 @@
}
}
},
"node_modules/uglify-js": {
"version": "3.19.3",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
"integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
"license": "BSD-2-Clause",
"optional": true,
"bin": {
"uglifyjs": "bin/uglifyjs"
},
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
@ -3444,7 +3867,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
@ -3466,6 +3888,12 @@
"node": ">=0.10.0"
}
},
"node_modules/wordwrap": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
"integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
"license": "MIT"
},
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
@ -3484,11 +3912,28 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs": {
"name": "wrap-ansi",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true,
"license": "ISC"
},
"node_modules/y18n": {

View file

@ -4,7 +4,7 @@
"description": "amdl",
"type": "module",
"scripts": {
"dev": "concurrently 'node --watch dist/index.js' 'tsc --watch'",
"dev": "concurrently --prefix none 'node --watch dist/index.js' 'tsc --watch'",
"build": "npm run lint && tsc",
"lint": "eslint .",
"lint:fix": "eslint . --fix"
@ -18,6 +18,8 @@
"data-uri-to-buffer": "^6.0.2",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"express-handlebars": "^8.0.1",
"git-rev-sync": "^3.0.2",
"node-widevine": "https://github.com/wangziyingwen/node-widevine",
"parse-hls": "^1.0.7",
"pssh-tools": "^1.2.0",
@ -29,6 +31,7 @@
},
"devDependencies": {
"@types/express": "^5.0.0",
"@types/git-rev-sync": "^2.0.2",
"@types/source-map-support": "^0.5.10",
"@typescript-eslint/parser": "^7.12.0",
"concurrently": "^9.1.2",

BIN
public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

116
public/styles/main.css Normal file
View file

@ -0,0 +1,116 @@
:root {
font-family: system-ui;
font-size: 1em;
line-height: 1.25;
color: var(--foreground);
background-color: var(--background);
--foreground: #cdd6f4;
--foreground-muted: #a6adc8;
--background: #1e1e2e;
--background-surface: #313244;
--shadow: #11111b;
}
body {
display: grid;
grid-template-columns: auto;
grid-template-rows: min-content auto min-content;
grid-auto-flow: column;
width: 100%;
height: 100%;
min-width: 100vw;
min-height: 100vh;
}
main {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1em;
padding: 1em;
width: 100%;
height: 100%;
}
h1, h2, h3, h4, h5, h6, p {
margin-top: .8em;
margin-bottom: .8em;
}
/* this isn't great in terms of accessibility */
/* makes sense for most the stuff we got tho */
a {
color: var(--foreground);
text-decoration: none;
}
a:hover {
color: var(--foreground-muted);
text-decoration: underline;
}
.light {
color: var(--foreground-muted);
}
input::placeholder {
color: var(--foreground-muted);
}
input[type="search"] {
color: var(--foreground);
background-color: var(--background-surface);
border: 0;
border-radius: 0.5em;
padding: 0.5em 1em;
box-shadow: 0 0 2em var(--shadow);
}
header {
background-color: var(--background-surface);
padding-left: 1em;
padding-right: 1em;
box-shadow: 0 0 2em var(--shadow);
}
header h1 {
font-size: 1.2em;
}
footer {
background-color: var(--background-surface);
padding: 1em;
box-shadow: 0 0 2em var(--shadow);
}
.result {
display: flex;
flex-direction: column;
width: 60%;
gap: 1em;
padding: 1em;
background-color: var(--background-surface);
box-shadow: 0 0 2em var(--shadow);
border-radius: 0.5em;
}
.result-info {
display: flex;
flex-direction: row;
gap: 1em;
}
.result-info img {
width: 5.5em;
height: 5.5em;
border-radius: 0.5em;
}
.result-text {
display: flex;
flex-direction: column;
}
.result-text h2 {
font-size: 1em;
margin-top: 0;
margin-bottom: 0;
}

223
public/styles/normalize.css vendored Normal file
View file

@ -0,0 +1,223 @@
/*! modern-normalize v3.0.1 | MIT License | https://github.com/sindresorhus/modern-normalize */
/*
Document
========
*/
/**
Use a better box model (opinionated).
*/
*,
::before,
::after {
box-sizing: border-box;
}
/**
1. Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3)
2. Correct the line height in all browsers.
3. Prevent adjustments of font size after orientation changes in iOS.
4. Use a more readable tab size (opinionated).
*/
html {
font-family:
system-ui,
'Segoe UI',
Roboto,
Helvetica,
Arial,
sans-serif,
'Apple Color Emoji',
'Segoe UI Emoji'; /* 1 */
line-height: 1.15; /* 2 */
-webkit-text-size-adjust: 100%; /* 3 */
tab-size: 4; /* 4 */
}
/*
Sections
========
*/
/**
Remove the margin in all browsers.
*/
body {
margin: 0;
}
/*
Text-level semantics
====================
*/
/**
Add the correct font weight in Chrome and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
1. Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3)
2. Correct the odd 'em' font sizing in all browsers.
*/
code,
kbd,
samp,
pre {
font-family:
ui-monospace,
SFMono-Regular,
Consolas,
'Liberation Mono',
Menlo,
monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
Prevent 'sub' and 'sup' elements from affecting the line height in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/*
Tabular data
============
*/
/**
Correct table border color inheritance in Chrome and Safari. (https://issues.chromium.org/issues/40615503, https://bugs.webkit.org/show_bug.cgi?id=195016)
*/
table {
border-color: currentcolor;
}
/*
Forms
=====
*/
/**
1. Change the font styles in all browsers.
2. Remove the margin in Firefox and Safari.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
}
/**
Correct the inability to style clickable types in iOS and Safari.
*/
button,
[type='button'],
[type='reset'],
[type='submit'] {
-webkit-appearance: button;
}
/**
Remove the padding so developers are not caught out when they zero out 'fieldset' elements in all browsers.
*/
legend {
padding: 0;
}
/**
Add the correct vertical alignment in Chrome and Firefox.
*/
progress {
vertical-align: baseline;
}
/**
Correct the cursor style of increment and decrement buttons in Safari.
*/
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
height: auto;
}
/**
1. Correct the odd appearance in Chrome and Safari.
2. Correct the outline style in Safari.
*/
[type='search'] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
Remove the inner padding in Chrome and Safari on macOS.
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
1. Correct the inability to style clickable types in iOS and Safari.
2. Change font properties to 'inherit' in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/*
Interactive
===========
*/
/*
Add the correct display in Chrome and Safari.
*/
summary {
display: list-item;
}

View file

@ -3,10 +3,9 @@ import { spawn } from "node:child_process";
import path from "node:path";
import { addToCache, isCached } from "../cache.js";
// TODO: make this have a return type (file path)
// TODO: refresh cache timer on download
// TODO: remux to m4a?
export async function downloadSong(streamUrl: string, decryptionKey: string, songCodec: RegularCodec | WebplaybackCodec): Promise<void> {
export async function downloadSong(streamUrl: string, decryptionKey: string, songCodec: RegularCodec | WebplaybackCodec): Promise<string> {
let baseOutputName = streamUrl.split("/").at(-1)?.split("?").at(0)?.split(".").splice(0, 1).join(".")?.trim();
if (!baseOutputName) { throw "could not get base output name from stream url"; }
baseOutputName += `_${songCodec}`;
@ -18,7 +17,7 @@ export async function downloadSong(streamUrl: string, decryptionKey: string, son
if ( // TODO: remove check for encrypted file/cache for encrypted?
isCached(encryptedName) &&
isCached(decryptedName)
) { return; }
) { return decryptedPath; }
await new Promise<void>((res, rej) => {
const child = spawn(config.downloader.ytdlp_path, [
@ -50,6 +49,8 @@ export async function downloadSong(streamUrl: string, decryptionKey: string, son
addToCache(encryptedName);
addToCache(decryptedName);
return decryptedPath;
}
// TODO: find a better spot for this

View file

@ -1,13 +1,11 @@
import { appleMusicApi } from "../api/index.js";
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";
import { getWidevineDecryptionKey } from "./keygen.js";
import { widevine, playready, fairplay } from "../constants/keyFormats.js";
import { songCodecRegex } from "../constants/codecs.js";
import type { WebplaybackResponse } from "api/appleMusicApi.js";
import { downloadSong, RegularCodec, WebplaybackCodec } from "./index.js";
import { RegularCodec, WebplaybackCodec } from "./index.js";
// why is this private
// i wish pain on the person who wrote this /j :smile:
@ -188,31 +186,3 @@ function getDrmData(drmInfos: DrmInfos, drmIds: string[], drmKey: string): strin
const getWidevinePssh = (drmInfos: DrmInfos, drmIds: string[]): string | undefined => getDrmData(drmInfos, drmIds, widevine);
const getPlayreadyPssh = (drmInfos: DrmInfos, drmIds: string[]): string | undefined => getDrmData(drmInfos, drmIds, playready);
const getFairplayKey = (drmInfos: DrmInfos, drmIds: string[]): string | undefined => getDrmData(drmInfos, drmIds, fairplay);
// TODO: remove later, this is just for testing
// const streamInfo2 = await StreamInfo.fromTrackMetadata((await appleMusicApi.getSong("1615276490")).data[0].attributes, RegularCodec.Aac);
const streamCodec1 = WebplaybackCodec.AacLegacy;
const streamInfo1 = await StreamInfo.fromWebplayback(await appleMusicApi.getWebplayback("1705366148"), streamCodec1);
if (streamInfo1.widevinePssh !== undefined) {
await downloadSong(
streamInfo1.streamUrl,
await getWidevineDecryptionKey(streamInfo1.widevinePssh, streamInfo1.trackId),
streamCodec1
);
}
// try {
// const streamCodec2 = RegularCodec.AacHe;
// const streamInfo2 = await StreamInfo.fromTrackMetadata((await appleMusicApi.getSong("1705366148")).data[0].attributes, streamCodec2);
// if (streamInfo2.widevinePssh !== undefined) {
// await downloadSong(
// streamInfo2.streamUrl,
// await getWidevineDecryptionKey(streamInfo2.widevinePssh, streamInfo2.trackId),
// streamCodec2
// );
// }
// } catch (err) {
// log.error("failed to download song");
// log.error(err);
// }

View file

@ -1,43 +1,8 @@
import { config } from "./config.js";
import express, { type NextFunction, type Request, type Response } from "express";
import process from "node:process";
import * as log from "./log.js";
import { appleMusicApi } from "./api/index.js";
export class HttpException extends Error {
public readonly status?: number;
constructor(status: number, message: string) {
super(message);
this.status = status;
this.message = message;
}
}
const app = express();
app.disable("x-powered-by");
app.set("trust proxy", ["loopback", "uniquelocal"]);
app.use("/", express.static("public"));
app.use("/data", express.static(config.downloader.cache.directory, { extensions: ["mp4"] }));
app.use((req, _res, next) => {
next(new HttpException(404, `${req.path} not found`));
});
app.use((err: HttpException, _req: Request, res: Response, _next: NextFunction) => {
if (!err.status || err.status % 500 < 100) {
log.error(err);
}
const status = err.status ?? 500;
const message = err.message;
res.status(status).send(message);
});
import { app } from "./web/index.js";
await appleMusicApi.login().catch((err) => {
log.error("failed to login to apple music api");

View file

@ -0,0 +1,35 @@
import { getWidevineDecryptionKey } from "../../../downloader/keygen.js";
import { downloadSong, RegularCodec } from "../../../downloader/index.js";
import express from "express";
import StreamInfo from "../../../downloader/streamInfo.js";
import { appleMusicApi } from "../../../api/index.js";
const router = express.Router();
// TODO: support more encryption schemes
// TODO: some type of agnostic-ness for the encryption keys
router.get("/dlTrackMetadata", async (req, res, next) => {
try {
const { trackId, codec } = req.query;
if (typeof trackId !== "string") { res.status(400).send("trackId is required and must be a string!"); return; }
if (typeof codec !== "string") { res.status(400).send("codec is required and must be a string!"); return; }
const c = Object.values(RegularCodec).find((c) => { return c === codec; });
if (c === undefined) { res.status(400).send("codec is invalid!"); return; }
const trackMetadata = await appleMusicApi.getSong(trackId);
const trackAttributes = trackMetadata.data[0].attributes;
const streamInfo = await StreamInfo.fromTrackMetadata(trackAttributes, c);
if (streamInfo.widevinePssh !== undefined) {
const decryptionKey = await getWidevineDecryptionKey(streamInfo.widevinePssh, streamInfo.trackId);
const filePath = await downloadSong(streamInfo.streamUrl, decryptionKey, c);
res.download(filePath);
} else {
res.status(400).send("no decryption key found!");
}
} catch (err) {
next(err);
}
});
export default router;

View file

@ -0,0 +1,34 @@
import { getWidevineDecryptionKey } from "../../../downloader/keygen.js";
import { downloadSong, WebplaybackCodec } from "../../../downloader/index.js";
import express from "express";
import StreamInfo from "../../../downloader/streamInfo.js";
import { appleMusicApi } from "../../../api/index.js";
const router = express.Router();
router.get("/dlWebplayback", async (req, res, next) => {
try {
const { trackId, codec } = req.query;
if (typeof trackId !== "string") { res.status(400).send("trackId is required and must be a string!"); return; }
if (typeof codec !== "string") { res.status(400).send("codec is required and must be a string!"); return; }
const c = Object.values(WebplaybackCodec).find((c) => { return c === codec; });
if (c === undefined) { res.status(400).send("codec is invalid!"); return; }
// TODO: check if this returns an error
const webplaybackResponse = await appleMusicApi.getWebplayback(trackId);
console.log(webplaybackResponse);
const streamInfo = await StreamInfo.fromWebplayback(webplaybackResponse, c);
if (streamInfo.widevinePssh !== undefined) {
const decryptionKey = await getWidevineDecryptionKey(streamInfo.widevinePssh, streamInfo.trackId);
const filePath = await downloadSong(streamInfo.streamUrl, decryptionKey, c);
res.download(filePath);
} else {
res.status(400).send("no decryption key found!");
}
} catch (err) {
next(err);
}
});
export default router;

View file

@ -0,0 +1,23 @@
import { appleMusicApi } from "../../../api/index.js";
import express from "express";
const router = express.Router();
// this endpoint isn't actually used for anything by us
// it's for people who want to implement apple music downloading into their own apps
// it makes it a bit easier to get the metadata for a track knowing the trackId
router.get("/getTrackMetadata", async (req, res, next) => {
try {
const { trackId } = req.query;
if (typeof trackId !== "string") { res.status(400).send("trackId is required and must be a string!"); return; }
const trackMetadata = await appleMusicApi.getSong(trackId);
const trackAttributes = trackMetadata.data[0].attributes;
res.json(trackAttributes);
} catch (err) {
next(err);
}
});
export default router;

View file

@ -0,0 +1,45 @@
import express from "express";
import gitRepoInfo from "git-rev-sync";
// TODO: move this into a helper or whatever?
// i don't wanna do this for every single page lol
const hash = gitRepoInfo.short("./");
const dirty = gitRepoInfo.isDirty();
const router = express.Router();
// TODO: implement this
// TODO: show tracks
// TODO: add a download button
router.get("/", (req, res) => {
res.render("search", {
title: "search",
hash: hash,
dirty: dirty,
query: req.query.q,
results: [
{
name: "Revengeseekerz",
artists: ["Jane Remover"],
cover: "https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/18/cf/f6/18cff6df-c7b6-0ca1-8067-83743f6c1f8a/193436418720_coverGriffinMcMahon.jpg/592x592bb.webp"
}
// {
// name: "Carousel (An Examination of the Shadow, Creekflow, And its Life as an Afterthought) ",
// artists: ["Vylet Pony"],
// tracks: [
// {
// artists: ["Vylet Pony"],
// name: "Carousel"
// },
// {
// artists: ["Vylet Pony", "Namii"],
// name: "The Shadow"
// }
// ],
// cover: "https://is1-ssl.mzstatic.com/image/thumb/Music116/v4/7c/f0/94/7cf09429-4942-a9cb-1287-b8bbc53a4d61/artwork.jpg/592x592bb.webp"
// }
]
});
});
export default router;

50
src/web/index.ts Normal file
View file

@ -0,0 +1,50 @@
import * as log from "../log.js";
import express, { type NextFunction, type Request, type Response } from "express";
import { engine } from "express-handlebars";
import dlTrackMetadata from "./endpoints/back/dlTrackMetadata.js";
import dlWebplayback from "./endpoints/back/dlWebplayback.js";
import getTrackMetadata from "./endpoints/back/getTrackMetadata.js";
import search from "./endpoints/front/search.js";
export class HttpException extends Error {
public readonly status?: number;
constructor(status: number, message: string) {
super(message);
this.status = status;
this.message = message;
}
}
const app = express();
app.set("trust proxy", ["loopback", "uniquelocal"]);
app.engine("handlebars", engine());
app.set("view engine", "handlebars");
app.set("views", "./views");
app.use("/", express.static("public"));
app.use(dlTrackMetadata);
app.use(dlWebplayback);
app.use(getTrackMetadata);
app.use(search);
app.use((req, _res, next) => {
next(new HttpException(404, `${req.path} not found`));
});
app.use((err: HttpException, _req: Request, res: Response, _next: NextFunction) => {
if (!err.status || err.status % 500 < 100) {
log.error(err);
}
const status = err.status ?? 500;
const message = err.message;
res.status(status).send(message);
});
export { app };

1
views/index.handlebars Normal file
View file

@ -0,0 +1 @@
{{> search}}

View file

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/styles/normalize.css">
<link rel="stylesheet" href="/styles/main.css">
<link rel="icon" href="/favicon.png" type="image/png">
<title>amdl - {{title}}</title>
</head>
<body>
{{> header}}
<main>
{{{body}}}
</main>
{{> footer}}
</body>
</html>

View file

@ -0,0 +1,5 @@
<footer>
<a href="https://git.reidlab.pink/reidlab/amdl" target="_blank">source [<code>{{hash}}{{#if dirty}}-dirty{{/if}}</code>]</a>
&middot;
<a href="https://reidlab.pink/socials" target="_blank">need to contact me?</a>
</footer>

View file

@ -0,0 +1,3 @@
<header>
<a href="/"><h1>amdl</h1></a>
</header>

View file

@ -0,0 +1,11 @@
<div class="result">
<div class="result-info">
<img src="{{cover}}" loading="lazy" decoding="async"/>
<div class="result-text">
<h2>{{name}}</h2>
<span class="light">{{#each artists as |artist|}}
{{artist}}{{#unless @last}},{{/unless}}
{{/each}}</span>
</div>
</div>
</div>

View file

@ -0,0 +1,3 @@
<form action="/" method="get">
<input type="search" name="q" placeholder="search for something" value="{{query}}">
</form>

4
views/search.handlebars Normal file
View file

@ -0,0 +1,4 @@
{{> search query=query}}
{{#each results as |result|}}
{{> result name=result.name}}
{{/each}}