From 76543fd220355657a8ddf6362ff623c2463ca6b3 Mon Sep 17 00:00:00 2001 From: reidlab Date: Tue, 29 Apr 2025 17:56:59 -0700 Subject: [PATCH] =?UTF-8?q?webs=D3=98Facebook=20ite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 495 +++++++++++++++++++-- package.json | 5 +- public/favicon.png | Bin 0 -> 49142 bytes public/styles/main.css | 116 +++++ public/styles/normalize.css | 223 ++++++++++ src/downloader/index.ts | 7 +- src/downloader/streamInfo.ts | 32 +- src/index.ts | 37 +- src/web/endpoints/back/dlTrackMetadata.ts | 35 ++ src/web/endpoints/back/dlWebplayback.ts | 34 ++ src/web/endpoints/back/getTrackMetadata.ts | 23 + src/web/endpoints/front/search.ts | 45 ++ src/web/index.ts | 50 +++ views/index.handlebars | 1 + views/layouts/main.handlebars | 18 + views/partials/footer.handlebars | 5 + views/partials/header.handlebars | 3 + views/partials/result.handlebars | 11 + views/partials/search.handlebars | 3 + views/search.handlebars | 4 + 20 files changed, 1051 insertions(+), 96 deletions(-) create mode 100644 public/favicon.png create mode 100644 public/styles/main.css create mode 100644 public/styles/normalize.css create mode 100644 src/web/endpoints/back/dlTrackMetadata.ts create mode 100644 src/web/endpoints/back/dlWebplayback.ts create mode 100644 src/web/endpoints/back/getTrackMetadata.ts create mode 100644 src/web/endpoints/front/search.ts create mode 100644 src/web/index.ts create mode 100644 views/index.handlebars create mode 100644 views/layouts/main.handlebars create mode 100644 views/partials/footer.handlebars create mode 100644 views/partials/header.handlebars create mode 100644 views/partials/result.handlebars create mode 100644 views/partials/search.handlebars create mode 100644 views/search.handlebars diff --git a/package-lock.json b/package-lock.json index 2de7474..d43fb16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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": { diff --git a/package.json b/package.json index 2907789..a32a75b 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/public/favicon.png b/public/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..70ad31729b9ea4925d194dd8f82c6aca7e5cc4aa GIT binary patch literal 49142 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 literal 0 HcmV?d00001 diff --git a/public/styles/main.css b/public/styles/main.css new file mode 100644 index 0000000..0ae6eb5 --- /dev/null +++ b/public/styles/main.css @@ -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; +} diff --git a/public/styles/normalize.css b/public/styles/normalize.css new file mode 100644 index 0000000..b0a14ee --- /dev/null +++ b/public/styles/normalize.css @@ -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; +} diff --git a/src/downloader/index.ts b/src/downloader/index.ts index a46e0b6..431a8d3 100644 --- a/src/downloader/index.ts +++ b/src/downloader/index.ts @@ -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 { +export async function downloadSong(streamUrl: string, decryptionKey: string, songCodec: RegularCodec | WebplaybackCodec): Promise { 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((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 diff --git a/src/downloader/streamInfo.ts b/src/downloader/streamInfo.ts index 2c95f79..91715c8 100644 --- a/src/downloader/streamInfo.ts +++ b/src/downloader/streamInfo.ts @@ -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); -// } diff --git a/src/index.ts b/src/index.ts index 8806097..b2c1544 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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"); diff --git a/src/web/endpoints/back/dlTrackMetadata.ts b/src/web/endpoints/back/dlTrackMetadata.ts new file mode 100644 index 0000000..9b40abc --- /dev/null +++ b/src/web/endpoints/back/dlTrackMetadata.ts @@ -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; diff --git a/src/web/endpoints/back/dlWebplayback.ts b/src/web/endpoints/back/dlWebplayback.ts new file mode 100644 index 0000000..c2cd751 --- /dev/null +++ b/src/web/endpoints/back/dlWebplayback.ts @@ -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; diff --git a/src/web/endpoints/back/getTrackMetadata.ts b/src/web/endpoints/back/getTrackMetadata.ts new file mode 100644 index 0000000..a16e436 --- /dev/null +++ b/src/web/endpoints/back/getTrackMetadata.ts @@ -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; diff --git a/src/web/endpoints/front/search.ts b/src/web/endpoints/front/search.ts new file mode 100644 index 0000000..3a7fcb3 --- /dev/null +++ b/src/web/endpoints/front/search.ts @@ -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; diff --git a/src/web/index.ts b/src/web/index.ts new file mode 100644 index 0000000..657b069 --- /dev/null +++ b/src/web/index.ts @@ -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 }; diff --git a/views/index.handlebars b/views/index.handlebars new file mode 100644 index 0000000..49e8431 --- /dev/null +++ b/views/index.handlebars @@ -0,0 +1 @@ +{{> search}} diff --git a/views/layouts/main.handlebars b/views/layouts/main.handlebars new file mode 100644 index 0000000..fee94d1 --- /dev/null +++ b/views/layouts/main.handlebars @@ -0,0 +1,18 @@ + + + + + + + + + amdl - {{title}} + + + {{> header}} +
+ {{{body}}} +
+ {{> footer}} + + diff --git a/views/partials/footer.handlebars b/views/partials/footer.handlebars new file mode 100644 index 0000000..fa11963 --- /dev/null +++ b/views/partials/footer.handlebars @@ -0,0 +1,5 @@ + diff --git a/views/partials/header.handlebars b/views/partials/header.handlebars new file mode 100644 index 0000000..c82fd9f --- /dev/null +++ b/views/partials/header.handlebars @@ -0,0 +1,3 @@ +
+

amdl

+
diff --git a/views/partials/result.handlebars b/views/partials/result.handlebars new file mode 100644 index 0000000..903653c --- /dev/null +++ b/views/partials/result.handlebars @@ -0,0 +1,11 @@ +
+
+ +
+

{{name}}

+ {{#each artists as |artist|}} + {{artist}}{{#unless @last}},{{/unless}} + {{/each}} +
+
+
diff --git a/views/partials/search.handlebars b/views/partials/search.handlebars new file mode 100644 index 0000000..cd94fdd --- /dev/null +++ b/views/partials/search.handlebars @@ -0,0 +1,3 @@ +
+ +
diff --git a/views/search.handlebars b/views/search.handlebars new file mode 100644 index 0000000..6e23baf --- /dev/null +++ b/views/search.handlebars @@ -0,0 +1,4 @@ +{{> search query=query}} +{{#each results as |result|}} + {{> result name=result.name}} +{{/each}}