From 0da467d1e0074b295442a3ae3d2db3f71206376d Mon Sep 17 00:00:00 2001 From: Eclypsed Date: Sat, 6 Jan 2024 22:05:51 -0500 Subject: [PATCH] Gonna try to start using git properly --- .prettierrc | 2 +- feature-ideas.txt | 3 +- package-lock.json | 1162 +++++++++++++++-- package.json | 14 +- problems.txt | 17 + src/app.css | 24 + src/hooks.server.js | 27 + src/lib/Jellyfin-api.js | 74 -- src/lib/audio-manager.js | 21 - src/lib/{ => components/media}/albumBG.svelte | 0 .../{ => components/media}/albumCard.svelte | 4 +- src/lib/{ => components/media}/header.svelte | 0 .../{ => components/media}/listItem.svelte | 5 +- .../media}/mediaControl.svelte | 0 .../{ => components/media}/mediaPlayer.svelte | 13 +- src/lib/components/utility/alert.svelte | 41 + src/lib/components/utility/alertBox.svelte | 30 + .../components/utility/hamburgerMenu.svelte | 68 + src/lib/components/utility/iconButton.svelte | 32 + src/lib/components/utility/navbar.svelte | 48 + src/lib/components/utility/searchBar.svelte | 76 ++ src/lib/components/utility/toggle.svelte | 30 + src/lib/navbar.svelte | 16 - src/lib/server/db/users.db | Bin 0 -> 20480 bytes src/lib/server/db/users.js | 70 + src/lib/services.json | 17 + src/lib/stores/alertStore.js | 3 + src/lib/utils.js | 20 - src/lib/utils/animations.js | 23 + src/lib/utils/utils.js | 71 + src/routes/+layout.svelte | 53 +- src/routes/+page.server.js | 6 + src/routes/+page.svelte | 7 +- src/routes/album/[id]/+page.server.js | 11 + src/routes/album/[id]/+page.svelte | 5 +- .../jellyfin/album/+server.js} | 86 +- src/routes/api/jellyfin/artist/+server.js | 39 + src/routes/api/jellyfin/auth/+server.js | 42 + src/routes/api/jellyfin/song/+server.js | 20 + src/routes/api/user/connections/+server.js | 67 + src/routes/api/youtube-music/media/+server.js | 27 + src/routes/artist/[id]/+page.js | 5 - src/routes/artist/[id]/+page.server.js | 10 + src/routes/artist/[id]/+page.svelte | 15 +- src/routes/login/+page.server.js | 71 + src/routes/login/+page.svelte | 91 ++ src/routes/settings/+layout.svelte | 68 + src/routes/settings/+page.svelte | 13 + .../settings/connections/+page.server.js | 76 ++ src/routes/settings/connections/+page.svelte | 172 +++ .../connections/jellyfinAuthBox.svelte | 55 + src/routes/song/[id]/+page.js | 5 - src/routes/song/[id]/+page.server.js | 10 + src/routes/song/[id]/+page.svelte | 22 +- src/routes/youtube-music/+page.server.js | 11 + src/routes/youtube-music/+page.svelte | 24 + tailwind.config.js | 11 +- 57 files changed, 2592 insertions(+), 341 deletions(-) create mode 100644 problems.txt create mode 100644 src/hooks.server.js delete mode 100644 src/lib/Jellyfin-api.js delete mode 100644 src/lib/audio-manager.js rename src/lib/{ => components/media}/albumBG.svelte (100%) rename src/lib/{ => components/media}/albumCard.svelte (79%) rename src/lib/{ => components/media}/header.svelte (100%) rename src/lib/{ => components/media}/listItem.svelte (77%) rename src/lib/{ => components/media}/mediaControl.svelte (100%) rename src/lib/{ => components/media}/mediaPlayer.svelte (92%) create mode 100644 src/lib/components/utility/alert.svelte create mode 100644 src/lib/components/utility/alertBox.svelte create mode 100644 src/lib/components/utility/hamburgerMenu.svelte create mode 100644 src/lib/components/utility/iconButton.svelte create mode 100644 src/lib/components/utility/navbar.svelte create mode 100644 src/lib/components/utility/searchBar.svelte create mode 100644 src/lib/components/utility/toggle.svelte delete mode 100644 src/lib/navbar.svelte create mode 100644 src/lib/server/db/users.db create mode 100644 src/lib/server/db/users.js create mode 100644 src/lib/services.json create mode 100644 src/lib/stores/alertStore.js delete mode 100644 src/lib/utils.js create mode 100644 src/lib/utils/animations.js create mode 100644 src/lib/utils/utils.js create mode 100644 src/routes/+page.server.js create mode 100644 src/routes/album/[id]/+page.server.js rename src/routes/{album/[id]/+page.js => api/jellyfin/album/+server.js} (58%) create mode 100644 src/routes/api/jellyfin/artist/+server.js create mode 100644 src/routes/api/jellyfin/auth/+server.js create mode 100644 src/routes/api/jellyfin/song/+server.js create mode 100644 src/routes/api/user/connections/+server.js create mode 100644 src/routes/api/youtube-music/media/+server.js delete mode 100644 src/routes/artist/[id]/+page.js create mode 100644 src/routes/artist/[id]/+page.server.js create mode 100644 src/routes/login/+page.server.js create mode 100644 src/routes/login/+page.svelte create mode 100644 src/routes/settings/+layout.svelte create mode 100644 src/routes/settings/+page.svelte create mode 100644 src/routes/settings/connections/+page.server.js create mode 100644 src/routes/settings/connections/+page.svelte create mode 100644 src/routes/settings/connections/jellyfinAuthBox.svelte delete mode 100644 src/routes/song/[id]/+page.js create mode 100644 src/routes/song/[id]/+page.server.js create mode 100644 src/routes/youtube-music/+page.server.js create mode 100644 src/routes/youtube-music/+page.svelte diff --git a/.prettierrc b/.prettierrc index ce9fd06..af6b768 100644 --- a/.prettierrc +++ b/.prettierrc @@ -2,7 +2,7 @@ "tabWidth": 4, "singleQuote": true, "semi": false, - "printWidth": 250, + "printWidth": 220, "bracketSpacing": true, "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], "overrides": [ diff --git a/feature-ideas.txt b/feature-ideas.txt index 3223716..9c9878a 100644 --- a/feature-ideas.txt +++ b/feature-ideas.txt @@ -7,4 +7,5 @@ Stream from YT last.fm MusicBrainz discogs marketplace -bandcamp \ No newline at end of file +bandcamp +Plex \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f8fe781..d2c5f1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,16 +8,22 @@ "name": "lazuli", "version": "0.0.1", "dependencies": { - "@fortawesome/fontawesome-free": "^6.4.2" + "@fortawesome/fontawesome-free": "^6.4.2", + "bcrypt": "^5.1.1", + "better-sqlite3": "^9.1.1", + "joi": "^17.11.0", + "jsonwebtoken": "^9.0.2", + "youtubei.js": "^7.0.0", + "ytdl-core": "^4.11.5" }, "devDependencies": { "@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/kit": "^1.20.4", "autoprefixer": "^10.4.15", "postcss": "^8.4.28", - "prettier": "^3.0.3", - "prettier-plugin-svelte": "^3.0.3", - "prettier-plugin-tailwindcss": "^0.5.4", + "prettier": "^3.1.1", + "prettier-plugin-svelte": "^3.1.2", + "prettier-plugin-tailwindcss": "^0.5.10", "svelte": "^4.0.5", "tailwindcss": "^3.3.3", "vite": "^4.4.2" @@ -400,6 +406,14 @@ "node": ">=12" } }, + "node_modules/@fastify/busboy": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.0.0.tgz", + "integrity": "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==", + "engines": { + "node": ">=14" + } + }, "node_modules/@fortawesome/fontawesome-free": { "version": "6.4.2", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.4.2.tgz", @@ -409,6 +423,19 @@ "node": ">=6" } }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -457,6 +484,25 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -498,6 +544,24 @@ "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", "dev": true }, + "node_modules/@sideway/address": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", + "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + }, "node_modules/@sveltejs/adapter-auto": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-2.1.0.tgz", @@ -511,24 +575,25 @@ } }, "node_modules/@sveltejs/kit": { - "version": "1.22.5", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.22.5.tgz", - "integrity": "sha512-LHq+ECucoT6c6/tkrxIQtD8KVNhPFV4QQ+xOKTwBAs/Qdtff8P5gAzsIZiwEaaO6J6sYZoy5RP2VR6m8PSCgLA==", + "version": "1.30.3", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.30.3.tgz", + "integrity": "sha512-0DzVXfU4h+tChFvoc8C61IqErCyskD4ydSIDjpKS2lYlEzIYrtYrY7juSqACFxqcvZAnOEXvSY+zZ8br0+ZMMg==", "dev": true, "hasInstallScript": true, "dependencies": { - "@sveltejs/vite-plugin-svelte": "^2.4.1", + "@sveltejs/vite-plugin-svelte": "^2.5.0", "@types/cookie": "^0.5.1", "cookie": "^0.5.0", "devalue": "^4.3.1", "esm-env": "^1.0.0", "kleur": "^4.1.5", "magic-string": "^0.30.0", - "mime": "^3.0.0", + "mrmime": "^1.0.1", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", "sirv": "^2.0.2", - "undici": "~5.23.0" + "tiny-glob": "^0.2.9", + "undici": "~5.26.2" }, "bin": { "svelte-kit": "svelte-kit.js" @@ -537,21 +602,21 @@ "node": "^16.14 || >=18" }, "peerDependencies": { - "svelte": "^3.54.0 || ^4.0.0-next.0", + "svelte": "^3.54.0 || ^4.0.0-next.0 || ^5.0.0-next.0", "vite": "^4.0.0" } }, "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.4.5.tgz", - "integrity": "sha512-UJKsFNwhzCVuiZd06jM/psscyNJNDwjQC+qIeb7GBJK9iWeQCcIyfcPWDvbCudfcJggY9jtxJeeaZH7uny93FQ==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.5.3.tgz", + "integrity": "sha512-erhNtXxE5/6xGZz/M9eXsmI7Pxa6MS7jyTy06zN3Ck++ldrppOnOlJwHHTsMC7DHDQdgUp4NAc4cDNQ9eGdB/w==", "dev": true, "dependencies": { - "@sveltejs/vite-plugin-svelte-inspector": "^1.0.3", + "@sveltejs/vite-plugin-svelte-inspector": "^1.0.4", "debug": "^4.3.4", "deepmerge": "^4.3.1", "kleur": "^4.1.5", - "magic-string": "^0.30.2", + "magic-string": "^0.30.3", "svelte-hmr": "^0.15.3", "vitefu": "^0.2.4" }, @@ -559,14 +624,14 @@ "node": "^14.18.0 || >= 16" }, "peerDependencies": { - "svelte": "^3.54.0 || ^4.0.0", + "svelte": "^3.54.0 || ^4.0.0 || ^5.0.0-next.0", "vite": "^4.0.0" } }, "node_modules/@sveltejs/vite-plugin-svelte-inspector": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-1.0.3.tgz", - "integrity": "sha512-Khdl5jmmPN6SUsVuqSXatKpQTMIifoQPDanaxC84m9JxIibWvSABJyHpyys0Z+1yYrxY5TTEQm+6elh0XCMaOA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-1.0.4.tgz", + "integrity": "sha512-zjiuZ3yydBtwpF3bj0kQNV0YXe+iKE545QGZVTaylW3eAzFr+pJ/cwK8lZEaRp4JtaJXhD5DyWAV4AxLh6DgaQ==", "dev": true, "dependencies": { "debug": "^4.3.4" @@ -592,11 +657,15 @@ "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", "dev": true }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, "node_modules/acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -604,6 +673,25 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -623,6 +711,23 @@ "node": ">= 8" } }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -687,8 +792,49 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/better-sqlite3": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-9.1.1.tgz", + "integrity": "sha512-FhW7bS7cXwkB2SFnPJrSGPmQerVSCzwBgmQ1cIRcYKxLsyiKjljzCbyEqqhYXo5TTBqt5BISiBj2YE2Sy2ynaA==", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + } }, "node_modules/binary-extensions": { "version": "2.2.0", @@ -699,11 +845,28 @@ "node": ">=8" } }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -753,18 +916,34 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dev": true, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -833,6 +1012,11 @@ "node": ">= 6" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, "node_modules/code-red": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz", @@ -846,6 +1030,14 @@ "periscopic": "^3.1.0" } }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -858,8 +1050,12 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" }, "node_modules/cookie": { "version": "0.5.0", @@ -899,7 +1095,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -912,6 +1107,28 @@ } } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -921,6 +1138,11 @@ "node": ">=0.10.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -930,6 +1152,14 @@ "node": ">=6" } }, + "node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "engines": { + "node": ">=8" + } + }, "node_modules/devalue": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/devalue/-/devalue-4.3.2.tgz", @@ -948,12 +1178,33 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "dev": true }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.503", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.503.tgz", "integrity": "sha512-LF2IQit4B0VrUHFeQkWhZm97KuJSGF2WJqq1InpY+ECpFRkXd8yTIaTtJxsO0OKDmiBYwWqcrNaXOurn2T2wiA==", "dev": true }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/esbuild": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", @@ -1015,6 +1266,14 @@ "@types/estree": "^1.0.0" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "engines": { + "node": ">=6" + } + }, "node_modules/fast-glob": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", @@ -1052,6 +1311,11 @@ "reusify": "^1.0.4" } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -1077,11 +1341,37 @@ "url": "https://www.patreon.com/infusion" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.2", @@ -1103,11 +1393,34 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, "node_modules/glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -1135,6 +1448,18 @@ "node": ">=10.13.0" } }, + "node_modules/globalyzer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", + "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==", + "dev": true + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -1147,6 +1472,42 @@ "node": ">= 0.4.0" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/import-meta-resolve": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.0.0.tgz", @@ -1161,7 +1522,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -1170,8 +1530,12 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "node_modules/is-binary-path": { "version": "2.1.0", @@ -1206,6 +1570,14 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "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==", + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -1236,6 +1608,17 @@ "@types/estree": "*" } }, + "node_modules/jintr": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jintr/-/jintr-1.1.0.tgz", + "integrity": "sha512-Tu9wk3BpN2v+kb8yT6YBtue+/nbjeLFv4vvVC4PJ7oCidHKbifWhvORrAbQfxVIQZG+67am/mDagpiGSVtvrZg==", + "funding": [ + "https://github.com/sponsors/LuanRT" + ], + "dependencies": { + "acorn": "^8.8.0" + } + }, "node_modules/jiti": { "version": "1.19.3", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.3.tgz", @@ -1245,6 +1628,58 @@ "jiti": "bin/jiti.js" } }, + "node_modules/joi": { + "version": "17.11.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz", + "integrity": "sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==", + "dependencies": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", @@ -1275,10 +1710,68 @@ "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", "dev": true }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/m3u8stream": { + "version": "0.8.6", + "resolved": "https://registry.npmjs.org/m3u8stream/-/m3u8stream-0.8.6.tgz", + "integrity": "sha512-LZj8kIVf9KCphiHmH7sbFQTVe4tOemb202fWwvJwR9W5ENW/1hxJN6ksAWGhQgSBSa3jyWhnjKU1Fw1GaOdbyA==", + "dependencies": { + "miniget": "^4.2.2", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/magic-string": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.2.tgz", - "integrity": "sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==", + "version": "0.30.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -1287,6 +1780,28 @@ "node": ">=12" } }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/mdn-data": { "version": "2.0.30", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", @@ -1315,23 +1830,29 @@ "node": ">=8.6" } }, - "node_modules/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "dev": true, - "bin": { - "mime": "cli.js" - }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "engines": { - "node": ">=10.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/miniget": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/miniget/-/miniget-4.2.3.tgz", + "integrity": "sha512-SjbDPDICJ1zT+ZvQwK0hUcRY4wxlhhNpHL9nJOB2MEAXRGagTljsO8MEDzQMTFf0Q8g4QNi8P9lEm/g7e+qgzA==", + "engines": { + "node": ">=12" } }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -1339,6 +1860,61 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -1360,8 +1936,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/mz": { "version": "2.7.0", @@ -1392,12 +1967,66 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + }, + "node_modules/node-abi": { + "version": "3.51.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.51.0.tgz", + "integrity": "sha512-SQkEP4hmNWjlniS5zdnfIXTk1x7Ome85RDzHlTbBtzE97Gfwz/Ipw4v/Ryk20DWIy3yCNVLVlGKApCnmvYoJbA==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-releases": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", "dev": true }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -1416,11 +2045,21 @@ "node": ">=0.10.0" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -1438,7 +2077,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, "dependencies": { "wrappy": "1" } @@ -1447,7 +2085,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, "engines": { "node": ">=0.10.0" } @@ -1506,9 +2143,9 @@ } }, "node_modules/postcss": { - "version": "8.4.28", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz", - "integrity": "sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "dev": true, "funding": [ { @@ -1636,10 +2273,35 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, + "node_modules/prebuild-install": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", + "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/prettier": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", - "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", + "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -1652,19 +2314,19 @@ } }, "node_modules/prettier-plugin-svelte": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.0.3.tgz", - "integrity": "sha512-dLhieh4obJEK1hnZ6koxF+tMUrZbV5YGvRpf2+OADyanjya5j0z1Llo8iGwiHmFWZVG/hLEw/AJD5chXd9r3XA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.1.2.tgz", + "integrity": "sha512-7xfMZtwgAWHMT0iZc8jN4o65zgbAQ3+O32V6W7pXrqNvKnHnkoyQCGCbKeUyXKZLbYE0YhFRnamfxfkEGxm8qA==", "dev": true, "peerDependencies": { "prettier": "^3.0.0", - "svelte": "^3.2.0 || ^4.0.0-next.0" + "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" } }, "node_modules/prettier-plugin-tailwindcss": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.5.4.tgz", - "integrity": "sha512-QZzzB1bID6qPsKHTeA9qPo1APmmxfFrA5DD3LQ+vbTmAnY40eJI7t9Q1ocqel2EKMWNPLJqdTDWZj1hKYgqSgg==", + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.5.10.tgz", + "integrity": "sha512-9UGSejqFxGG6brYjFfTYlJ8zs4L/lvZg1AngFfaC5Fs1otSskASv5IWKmjPu5MlABQUtTKtMArKyYr/hWpXSUg==", "dev": true, "engines": { "node": ">=14.21.3" @@ -1673,13 +2335,13 @@ "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", - "@shufo/prettier-plugin-blade": "*", "@trivago/prettier-plugin-sort-imports": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-import-sort": "*", "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-style-order": "*", @@ -1695,9 +2357,6 @@ "@shopify/prettier-plugin-liquid": { "optional": true }, - "@shufo/prettier-plugin-blade": { - "optional": true - }, "@trivago/prettier-plugin-sort-imports": { "optional": true }, @@ -1733,6 +2392,15 @@ } } }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -1753,6 +2421,20 @@ } ] }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -1762,6 +2444,19 @@ "pify": "^2.3.0" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -1801,6 +2496,20 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/rollup": { "version": "3.28.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.0.tgz", @@ -1852,12 +2561,103 @@ "node": ">=6" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/sax": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "node_modules/set-cookie-parser": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==", "dev": true }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/sirv": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.3.tgz", @@ -1881,13 +2681,44 @@ "node": ">=0.10.0" } }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "dev": true, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/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==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, "engines": { - "node": ">=10.0.0" + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "engines": { + "node": ">=0.10.0" } }, "node_modules/sucrase": { @@ -1997,6 +2828,56 @@ "node": ">=14.0.0" } }, + "node_modules/tar": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", + "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -2018,6 +2899,16 @@ "node": ">=0.8" } }, + "node_modules/tiny-glob": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", + "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", + "dev": true, + "dependencies": { + "globalyzer": "0.1.0", + "globrex": "^0.1.2" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -2039,19 +2930,39 @@ "node": ">=6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "dev": true }, - "node_modules/undici": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.23.0.tgz", - "integrity": "sha512-1D7w+fvRsqlQ9GscLBwcAJinqcZGHUKjbOmXdlE/v8BvEGXjeWAax+341q44EuTcHXXnfyKNbKRq4Lg7OzhMmg==", - "dev": true, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dependencies": { - "busboy": "^1.6.0" + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/undici": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.26.5.tgz", + "integrity": "sha512-cSb4bPFd5qgR7qr2jYAi0hlX9n5YKK2ONKkLFkxl+v/9BvC0sOpZjBHDBSXc5lWAf5ty9oZdRXytBIHzgUcerw==", + "dependencies": { + "@fastify/busboy": "^2.0.0" }, "engines": { "node": ">=14.0" @@ -2090,13 +3001,12 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/vite": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", - "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz", + "integrity": "sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==", "dev": true, "dependencies": { "esbuild": "^0.18.10", @@ -2149,12 +3059,12 @@ } }, "node_modules/vitefu": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.4.tgz", - "integrity": "sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz", + "integrity": "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==", "dev": true, "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0" + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" }, "peerDependenciesMeta": { "vite": { @@ -2162,11 +3072,37 @@ } } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "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 + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yaml": { "version": "2.3.1", @@ -2176,6 +3112,32 @@ "engines": { "node": ">= 14" } + }, + "node_modules/youtubei.js": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/youtubei.js/-/youtubei.js-7.0.0.tgz", + "integrity": "sha512-z87cv6AAjj0c98BkD0qTJvBDTF2DdT+FntJUjmi+vHY2EV+CepeYQAE/eLsdhGvCb6LrNBgGVwVUzXpHYi8NoA==", + "funding": [ + "https://github.com/sponsors/LuanRT" + ], + "dependencies": { + "jintr": "^1.1.0", + "tslib": "^2.5.0", + "undici": "^5.19.1" + } + }, + "node_modules/ytdl-core": { + "version": "4.11.5", + "resolved": "https://registry.npmjs.org/ytdl-core/-/ytdl-core-4.11.5.tgz", + "integrity": "sha512-27LwsW4n4nyNviRCO1hmr8Wr5J1wLLMawHCQvH8Fk0hiRqrxuIu028WzbJetiYH28K8XDbeinYW4/wcHQD1EXA==", + "dependencies": { + "m3u8stream": "^0.8.6", + "miniget": "^4.2.2", + "sax": "^1.1.3" + }, + "engines": { + "node": ">=12" + } } } } diff --git a/package.json b/package.json index 5acae73..d9476ab 100644 --- a/package.json +++ b/package.json @@ -12,15 +12,21 @@ "@sveltejs/kit": "^1.20.4", "autoprefixer": "^10.4.15", "postcss": "^8.4.28", - "prettier": "^3.0.3", - "prettier-plugin-svelte": "^3.0.3", - "prettier-plugin-tailwindcss": "^0.5.4", + "prettier": "^3.1.1", + "prettier-plugin-svelte": "^3.1.2", + "prettier-plugin-tailwindcss": "^0.5.10", "svelte": "^4.0.5", "tailwindcss": "^3.3.3", "vite": "^4.4.2" }, "type": "module", "dependencies": { - "@fortawesome/fontawesome-free": "^6.4.2" + "@fortawesome/fontawesome-free": "^6.4.2", + "bcrypt": "^5.1.1", + "better-sqlite3": "^9.1.1", + "joi": "^17.11.0", + "jsonwebtoken": "^9.0.2", + "youtubei.js": "^7.0.0", + "ytdl-core": "^4.11.5" } } diff --git a/problems.txt b/problems.txt new file mode 100644 index 0000000..e5d4952 --- /dev/null +++ b/problems.txt @@ -0,0 +1,17 @@ +Big Problems: +- The stream services being utilized still need to be able to gather which songs you are listening to to provide recommendations, + so requests need to be proxied through the actual streaming service. +- Authentication for every other potential streaming service: + - YouTube Music:? https://ytmusicapi.readthedocs.io/en/stable/setup/oauth.html + - Spotify: https://developer.spotify.com/documentation/web-api/concepts/authorization + +Little Problems: +- Video and audio need to be kept in sync, accounting for buffering and latency. + +Fixed Problems: +- Fucking Jellyfin Authentication (Missing Header) +- Continuous session verification (Fixed with JWTs) + +Looking for Style Guidlines?: +- URL structure: https://developers.google.com/search/docs/crawling-indexing/url-structure +- API best practicies: https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design \ No newline at end of file diff --git a/src/app.css b/src/app.css index cd2a422..bc4ddd2 100644 --- a/src/app.css +++ b/src/app.css @@ -12,3 +12,27 @@ -ms-overflow-style: none; /* IE and Edge */ scrollbar-width: none; /* Firefox */ } + +/* Default scrollbar for Chrome, Safari, Edge and Opera */ +::-webkit-scrollbar { + width: 20px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + border-radius: 10px; + box-shadow: inset 10px 10px rgba(100, 100, 100, 0.6); + border: solid 7px transparent; +} +/* Default scrollbar for Chrome, Safari, Edge, and Opera */ + +:root { + scrollbar-width: thin; /* Default scrollbar width for Firefox */ + scrollbar-color: grey transparent; /* Default scrollbar colors for Firefox */ + --jellyfin-purple: #aa5cc3; + --jellyfin-blue: #00a4dc; + --lazuli-primary: #ed6713; +} diff --git a/src/hooks.server.js b/src/hooks.server.js new file mode 100644 index 0000000..812be77 --- /dev/null +++ b/src/hooks.server.js @@ -0,0 +1,27 @@ +import { redirect } from '@sveltejs/kit' +import { SECRET_JWT_KEY, SECRET_INTERNAL_API_KEY } from '$env/static/private' +import jwt from 'jsonwebtoken' + +/** @type {import('@sveltejs/kit').Handle} */ +export async function handle({ event, resolve }) { + const nonProtectedRoutes = ['/login'] + const urlpath = event.url.pathname + + if (urlpath.startsWith('/api') && event.request.headers.get('apikey') !== SECRET_INTERNAL_API_KEY) { + return new Response('Unauthorized', { status: 400 }) + } + + if (!nonProtectedRoutes.some((route) => urlpath.startsWith(route))) { + const authToken = event.cookies.get('lazuli-auth') + if (!authToken) throw redirect(303, `/login?redirect=${urlpath}`) + + const tokenData = jwt.verify(authToken, SECRET_JWT_KEY) + if (!tokenData) throw redirect(303, `/login?redirect=${urlpath}`) + + event.locals.userId = tokenData.id + event.locals.username = tokenData.user + } + + const response = await resolve(event) + return response +} diff --git a/src/lib/Jellyfin-api.js b/src/lib/Jellyfin-api.js deleted file mode 100644 index e87580f..0000000 --- a/src/lib/Jellyfin-api.js +++ /dev/null @@ -1,74 +0,0 @@ -export const ROOT_URL = 'http://eclypsecloud:8096/' -export const API_KEY = 'fd4bf4c18e5f4bb08c2cb9f6a1542118' -export const USER_ID = '7364ce5928c64b90b5765e56ca884053' - -const baseURLGenerator = { - Items: () => `Users/${USER_ID}/Items`, - Image: (params) => `Items/${params.id}/Images/Primary`, - Audio: (params) => `Audio/${params.id}/universal`, -} - -export const generateURL = ({ type, pathParams, queryParams }) => { - const baseURLFunction = baseURLGenerator[type] - - if (baseURLFunction) { - const baseURL = ROOT_URL.concat(baseURLFunction(pathParams)) - const queryParamList = queryParams ? Object.entries(queryParams).map(([key, value]) => `${key}=${value}`) : [] - queryParamList.push(`api_key=${API_KEY}`) - - return baseURL.concat('?' + queryParamList.join('&')) - } else { - throw new Error('API Url Type does not exist') - } -} - -export const fetchArtistItems = async (artistId) => { - try { - const response = await fetch( - generateURL({ - type: 'Items', - queryParams: { artistIds: artistId, recursive: true }, - }), - ) - const data = await response.json() - - const artistItems = { - albums: [], - singles: [], - appearances: [], - } - - // Filters the raw list of items to only the albums that were produced in fully or in part by the specified artist - artistItems.albums = data.Items.filter((item) => item.Type === 'MusicAlbum' && item.AlbumArtists.some((artist) => artist.Id === artistId)) - - data.Items.forEach((item) => { - if (item.Type === 'Audio') { - if (!('AlbumId' in item)) { - artistItems.singles.push(item) - } else if (!artistItems.albums.some((album) => album.Id === item.AlbumId)) { - artistItems.appearances.push(item) - } - } - }) - - return artistItems - } catch (error) { - console.log('Error Fetching Artist Items:', error) - } -} - -export const fetchSong = async (songId) => { - try { - const response = await fetch( - generateURL({ - type: 'Items', - queryParams: { ids: songId, recursive: true }, - }), - ) - const data = await response.json() - - return data.Items[0] - } catch (error) { - console.log('Error Fetch Song', error) - } -} diff --git a/src/lib/audio-manager.js b/src/lib/audio-manager.js deleted file mode 100644 index ad1bba5..0000000 --- a/src/lib/audio-manager.js +++ /dev/null @@ -1,21 +0,0 @@ -import { generateURL } from '$lib/Jellyfin-api' -import { USER_ID } from '$lib/Jellyfin-api' - -const paramPresets = { - default: { - MaxStreamingBitrate: '999999999', - Container: 'opus,webm|opus,mp3,aac,m4a|aac,m4b|aac,flac,webma,webm|webma,wav,ogg', - TranscodingContainer: 'ts', - TranscodingProtocol: 'hls', - AudioCodec: 'aac', - userId: USER_ID, - }, -} - -export const buildAudioEndpoint = (id, params) => { - return generateURL({ - type: 'Audio', - pathParams: { id: id }, - queryParams: paramPresets[params], - }) -} diff --git a/src/lib/albumBG.svelte b/src/lib/components/media/albumBG.svelte similarity index 100% rename from src/lib/albumBG.svelte rename to src/lib/components/media/albumBG.svelte diff --git a/src/lib/albumCard.svelte b/src/lib/components/media/albumCard.svelte similarity index 79% rename from src/lib/albumCard.svelte rename to src/lib/components/media/albumCard.svelte index f47d378..2f057c3 100644 --- a/src/lib/albumCard.svelte +++ b/src/lib/components/media/albumCard.svelte @@ -2,7 +2,7 @@ export let item; export let cardType; - import { generateURL } from '$lib/Jellyfin-api.js'; + import { JellyfinUtils } from '$lib/utils' const getAlbumCardLink = (item) => { if (cardType === "albums") { @@ -19,7 +19,7 @@
- jacket + jacket {item.Name}
diff --git a/src/lib/header.svelte b/src/lib/components/media/header.svelte similarity index 100% rename from src/lib/header.svelte rename to src/lib/components/media/header.svelte diff --git a/src/lib/listItem.svelte b/src/lib/components/media/listItem.svelte similarity index 77% rename from src/lib/listItem.svelte rename to src/lib/components/media/listItem.svelte index ead0189..a5260b8 100644 --- a/src/lib/listItem.svelte +++ b/src/lib/components/media/listItem.svelte @@ -1,11 +1,10 @@ + +{#if show} +
+
+
+ {alertMessage} +
+ +
+
+{/if} diff --git a/src/lib/components/utility/alertBox.svelte b/src/lib/components/utility/alertBox.svelte new file mode 100644 index 0000000..72dd2a5 --- /dev/null +++ b/src/lib/components/utility/alertBox.svelte @@ -0,0 +1,30 @@ + + +
diff --git a/src/lib/components/utility/hamburgerMenu.svelte b/src/lib/components/utility/hamburgerMenu.svelte new file mode 100644 index 0000000..3f159c7 --- /dev/null +++ b/src/lib/components/utility/hamburgerMenu.svelte @@ -0,0 +1,68 @@ + + +
+ + {#if open} + + {/if} +
+ + diff --git a/src/lib/components/utility/iconButton.svelte b/src/lib/components/utility/iconButton.svelte new file mode 100644 index 0000000..28c21e4 --- /dev/null +++ b/src/lib/components/utility/iconButton.svelte @@ -0,0 +1,32 @@ + + + + + diff --git a/src/lib/components/utility/navbar.svelte b/src/lib/components/utility/navbar.svelte new file mode 100644 index 0000000..f5e031f --- /dev/null +++ b/src/lib/components/utility/navbar.svelte @@ -0,0 +1,48 @@ + + + + diff --git a/src/lib/components/utility/searchBar.svelte b/src/lib/components/utility/searchBar.svelte new file mode 100644 index 0000000..8610915 --- /dev/null +++ b/src/lib/components/utility/searchBar.svelte @@ -0,0 +1,76 @@ + + + { + setTimeout(() => { + // This is a completely stupid thing you have to do, if there is not timeout, the active element will be the body of the document and not the newly focused element + if (!searchBar.contains(document.activeElement)) { + toggleSearchMenu(false) + } + }, 1) + }} +> + + toggleSearchMenu(true)} + on:keypress={(event) => { + if (event.key === 'Enter') triggerSearch(searchInput.value) + }} + /> + {#if searchRecommendations} +
+ {#each searchRecommendations as recommendation} + + {/each} +
+ {/if} +
+ + diff --git a/src/lib/components/utility/toggle.svelte b/src/lib/components/utility/toggle.svelte new file mode 100644 index 0000000..4675682 --- /dev/null +++ b/src/lib/components/utility/toggle.svelte @@ -0,0 +1,30 @@ + + + diff --git a/src/lib/navbar.svelte b/src/lib/navbar.svelte deleted file mode 100644 index ceb2857..0000000 --- a/src/lib/navbar.svelte +++ /dev/null @@ -1,16 +0,0 @@ - - - \ No newline at end of file diff --git a/src/lib/server/db/users.db b/src/lib/server/db/users.db new file mode 100644 index 0000000000000000000000000000000000000000..eb84964edb605b13c5c94188fc960b33d8eaac61 GIT binary patch literal 20480 zcmeI(zfRjg90%}o{t#$IT~(DlA>F_bi>UlbFl4CIG#tTd;(&2zGewSb5r*IdUqWGo zzDQr9k5Msqt@;R6)v;$oAwjCtp+jlEC)@YOcfQ|!5Bz?WhEKUh|OkJ0wOc^Wxs+cCv!uPZ4caoa@L{vll_~?r)AVB~E5P$##AOHaf zKmY;|fWV&wt zD%n}1pwrtPuIF(foc z>(pkO%w|@Z?Z&GRGIm|xPEJJ<%@{%)_v8=Wmf*)D9>{4YtEmZyJ14V&TtZHc)X$$P z8BNoai_8sfOezu+<0Hr@gx}?hu0KK4u*{tX8$PEbs5P$##AOHafKmY;|fB*y_0D-?r zz*N#??mTVz-hRL5iuslH{PNQLsL*YU4B_^?L6vW{>t^8Oi}mKou6yVh8=b+S(J{_O zl>>Lr*;-pPPiuR_=IaM?U5fgZ$PEbs5P$##AOHafKmY;|fB*y_0D*r<;9+uZ=I4J0 z(ewX=`kAQTw3K7wpWWk0IH<$x&QzG literal 0 HcmV?d00001 diff --git a/src/lib/server/db/users.js b/src/lib/server/db/users.js new file mode 100644 index 0000000..89b6936 --- /dev/null +++ b/src/lib/server/db/users.js @@ -0,0 +1,70 @@ +import Database from 'better-sqlite3' +import Services from '$lib/services.json' + +const db = new Database('./src/lib/server/db/users.db', { verbose: console.info }) +db.pragma('foreign_keys = ON') +const initUsersTable = 'CREATE TABLE IF NOT EXISTS Users(id INTEGER PRIMARY KEY AUTOINCREMENT, username VARCHAR(64) UNIQUE NOT NULL, password VARCHAR(72) NOT NULL)' +const initUserConnectionsTable = + 'CREATE TABLE IF NOT EXISTS UserConnections(id INTEGER PRIMARY KEY AUTOINCREMENT, userId INTEGER NOT NULL, serviceName VARCHAR(64) NOT NULL, accessToken TEXT, refreshToken TEXT, expiry DATETIME, FOREIGN KEY(userId) REFERENCES Users(id))' +const initJellyfinAuthTable = 'CREATE TABLE IF NOT EXISTS JellyfinConnections(id INTEGER PRIMARY KEY AUTOINCREMENT, user TEXT, accesstoken TEXT, serverid TEXT)' +const initYouTubeMusicConnectionsTable = '' +const initSpotifyConnectionsTable = '' +db.exec(initUsersTable) +db.exec(initUserConnectionsTable) + +export class Users { + static addUser = (username, hashedPassword) => { + try { + db.prepare('INSERT INTO Users(username, password) VALUES(?, ?)').run(username, hashedPassword) + return this.queryUsername(username) + } catch { + return null + } + } + + static queryUsername = (username) => { + return db.prepare('SELECT * FROM Users WHERE lower(username) = ?').get(username.toLowerCase()) + } +} + +export class UserConnections { + static validServices = Object.keys(Services) + + static getConnections = (userId, serviceNames = null) => { + if (!serviceNames) { + const connections = db.prepare('SELECT * FROM UserConnections WHERE userId = ?').all(userId) + if (connections.length === 0) return null + return connections + } + + if (!Array.isArray(serviceNames)) { + if (typeof serviceNames !== 'string') throw new Error('Service names must be a string or array of strings') + serviceNames = [serviceNames] + } + + serviceNames = serviceNames.filter((service) => this.validServices.includes(service)) + + const placeholders = serviceNames.map(() => '?').join(', ') // This is SQL-injection safe, the placeholders are just ?, ?, ?.... + const connections = db.prepare(`SELECT * FROM UserConnections WHERE userId = ? AND serviceName IN (${placeholders})`).all(userId, ...serviceNames) + if (connections.length === 0) return null + return connections + } + + // May want to give accessToken a default of null in the future if one of the services does not use access tokens + static setConnection = (userId, serviceName, accessToken, refreshToken = null, expiry = null) => { + if (!this.validServices.includes(serviceName)) throw new Error(`Service name ${serviceName} is invalid`) + + const existingConnection = this.getConnections(userId, serviceName) + if (existingConnection) { + db.prepare('UPDATE UserConnections SET accessToken = ?, refreshToken = ?, expiry = ? WHERE userId = ? AND serviceName = ?').run(accessToken, refreshToken, expiry, userId, serviceName) + } else { + db.prepare('INSERT INTO UserConnections(userId, serviceName, accessToken, refreshToken, expiry) VALUES(?, ?, ?, ?, ?)').run(userId, serviceName, accessToken, refreshToken, expiry) + } + // return this.getConnections(userId, serviceName) <--- Uncomment this if want to return new connection data after update + } + + static deleteConnection = (userId, serviceName) => { + const info = db.prepare('DELETE FROM UserConnections WHERE userId = ? AND serviceName = ?').run(userId, serviceName) + if (!info.changes === 0) throw new Error(`User does not have connection: ${serviceName}`) + } +} diff --git a/src/lib/services.json b/src/lib/services.json new file mode 100644 index 0000000..fc73c1c --- /dev/null +++ b/src/lib/services.json @@ -0,0 +1,17 @@ +{ + "jellyfin": { + "displayName": "Jellyfin", + "type": ["streaming"], + "icon": "https://raw.githubusercontent.com/jellyfin/jellyfin-ux/55616553b692b1a6c7d8e786eeb7d8216e9b50df/branding/SVG/icon-transparent.svg" + }, + "youtube-music": { + "displayName": "YouTube Music", + "type": ["streaming"], + "icon": "https://upload.wikimedia.org/wikipedia/commons/6/6a/Youtube_Music_icon.svg" + }, + "spotify": { + "displayName": "Spotify", + "type": ["streaming"], + "icon": "https://upload.wikimedia.org/wikipedia/commons/8/84/Spotify_icon.svg" + } +} diff --git a/src/lib/stores/alertStore.js b/src/lib/stores/alertStore.js new file mode 100644 index 0000000..48defd4 --- /dev/null +++ b/src/lib/stores/alertStore.js @@ -0,0 +1,3 @@ +import { writable } from 'svelte/store' + +export const newestAlert = writable([null, null]) diff --git a/src/lib/utils.js b/src/lib/utils.js deleted file mode 100644 index 78c6411..0000000 --- a/src/lib/utils.js +++ /dev/null @@ -1,20 +0,0 @@ -export const ticksToTime = (ticks) => { - const totalSeconds = ~~(ticks / 10000000) - const totalMinutes = ~~(totalSeconds / 60) - const hours = ~~(totalMinutes / 60) - - const remainderMinutes = totalMinutes - hours * 60 - const remainderSeconds = totalSeconds - totalMinutes * 60 - - const format = (value) => { - return value < 10 ? `0${value}` : value - } - - if (hours > 0) { - return `${hours}:${format(remainderMinutes)}:${format( - remainderSeconds - )}` - } else { - return `${remainderMinutes}:${format(remainderSeconds)}` - } -} diff --git a/src/lib/utils/animations.js b/src/lib/utils/animations.js new file mode 100644 index 0000000..1a64c9f --- /dev/null +++ b/src/lib/utils/animations.js @@ -0,0 +1,23 @@ +export const shake = (strength = 1) => { + return { + transform: [ + `translateX(-${strength}px)`, + `translateX(${strength * 2}px)`, + `translateX(-${strength * 4}px)`, + `translateX(${strength * 4}px)`, + `translateX(-${strength * 4}px)`, + `translateX(${strength * 4}px)`, + `translateX(-${strength * 4}px)`, + `translateX(${strength * 2}px)`, + `translateX(-${strength}px)`, + ], + offset: [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9], + } +} + +export const spin = (rotations = 1) => { + return [ + { rotate: '0deg', easing: 'ease-in-out' }, + { rotate: `${rotations * 360}deg`, easing: 'ease-in-out' }, + ] +} diff --git a/src/lib/utils/utils.js b/src/lib/utils/utils.js new file mode 100644 index 0000000..0acb7d1 --- /dev/null +++ b/src/lib/utils/utils.js @@ -0,0 +1,71 @@ +export const ticksToTime = (ticks) => { + const totalSeconds = ~~(ticks / 10000000) + const totalMinutes = ~~(totalSeconds / 60) + const hours = ~~(totalMinutes / 60) + + const remainderMinutes = totalMinutes - hours * 60 + const remainderSeconds = totalSeconds - totalMinutes * 60 + + const format = (value) => { + return value < 10 ? `0${value}` : value + } + + if (hours > 0) { + return `${hours}:${format(remainderMinutes)}:${format(remainderSeconds)}` + } else { + return `${remainderMinutes}:${format(remainderSeconds)}` + } +} + +export class JellyfinUtils { + static #ROOT_URL = 'http://eclypsecloud:8096/' + static #API_KEY = 'fd4bf4c18e5f4bb08c2cb9f6a1542118' + static #USER_ID = '7364ce5928c64b90b5765e56ca884053' + static #AUDIO_PRESETS = { + default: { + MaxStreamingBitrate: '999999999', + Container: 'opus,webm|opus,mp3,aac,m4a|aac,m4b|aac,flac,webma,webm|webma,wav,ogg', + TranscodingContainer: 'ts', + TranscodingProtocol: 'hls', + AudioCodec: 'aac', + userId: this.#USER_ID, + }, + } + + static #buildUrl(baseURL, queryParams) { + const queryParamList = queryParams ? Object.entries(queryParams).map(([key, value]) => `${key}=${value}`) : [] + queryParamList.push(`api_key=${this.#API_KEY}`) + return baseURL.concat('?' + queryParamList.join('&')) + } + + static getItemsEnpt(itemParams) { + const baseUrl = this.#ROOT_URL + `Users/${this.#USER_ID}/Items` + const endpoint = this.#buildUrl(baseUrl, itemParams) + return endpoint + } + + static getImageEnpt(id, imageParams) { + const baseUrl = this.#ROOT_URL + `Items/${id}/Images/Primary` + const endpoint = this.#buildUrl(baseUrl, imageParams) + return endpoint + } + + static getAudioEnpt(id, audioPreset) { + const baseUrl = this.#ROOT_URL + `Audio/${id}/universal` + const presetParams = this.#AUDIO_PRESETS[audioPreset] + const endpoint = this.#buildUrl(baseUrl, presetParams) + return endpoint + } + + static getLocalDeviceUUID() { + const existingUUID = localStorage.getItem('lazuliDeviceUUID') + + if (!existingUUID) { + const newUUID = '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, (c) => (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16)) + localStorage.setItem('lazuliDeviceUUID', newUUID) + return newUUID + } + + return existingUUID + } +} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 7c707e4..92dfdbb 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,6 +1,57 @@ - +{#if $page.url.pathname === '/api'} + +{:else} +
+ {#if $page.url.pathname === '/login'} +
+ +
+ {:else} +
+ +
+ {#if loaded} + + + {/if} +
+ +
+ +
+ {/if} + +
+{/if} + + diff --git a/src/routes/+page.server.js b/src/routes/+page.server.js new file mode 100644 index 0000000..be91102 --- /dev/null +++ b/src/routes/+page.server.js @@ -0,0 +1,6 @@ +/** @type {import('./$types').PageServerLoad} */ +export const load = ({ locals }) => { + return { + user: locals.user, + } +} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index e6b49fa..a84c2a0 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,2 +1,5 @@ -

Welcome to SvelteKit

-

Visit kit.svelte.dev to read the documentation

\ No newline at end of file + diff --git a/src/routes/album/[id]/+page.server.js b/src/routes/album/[id]/+page.server.js new file mode 100644 index 0000000..6e24008 --- /dev/null +++ b/src/routes/album/[id]/+page.server.js @@ -0,0 +1,11 @@ +export async function load({ fetch, params }) { + const albumId = params.id + const response = await fetch(`/api/jellyfin/album?albumId=${albumId}`) + const responseData = await response.json() + + return { + id: albumId, + albumItemsData: responseData.albumItems, + albumData: responseData.albumData, + } +} diff --git a/src/routes/album/[id]/+page.svelte b/src/routes/album/[id]/+page.svelte index bc2a5e6..477485b 100644 --- a/src/routes/album/[id]/+page.svelte +++ b/src/routes/album/[id]/+page.svelte @@ -2,7 +2,7 @@ import { fly, slide } from 'svelte/transition' import { cubicIn, cubicOut } from 'svelte/easing' - import { generateURL } from '$lib/Jellyfin-api.js' + import { JellyfinUtils } from '$lib/utils' import AlbumBg from '$lib/albumBG.svelte' import Navbar from '$lib/navbar.svelte' import ListItem from '$lib/listItem.svelte' @@ -10,8 +10,7 @@ import MediaPlayer from '$lib/mediaPlayer.svelte' export let data - let albumImg = generateURL({ type: 'Image', pathParams: { id: data.id } }) - // console.log(generateURL({type: 'Items', queryParams: {'albumIds': data.albumData.Id, 'recursive': true}})) + let albumImg = JellyfinUtils.getImageEnpt(data.id) let discArray = Array.from({ length: data.albumData?.discCount ? data.albumData.discCount : 1 }, (_, i) => { return data.albumItemsData.filter((item) => item?.ParentIndexNumber === i + 1 || !item?.ParentIndexNumber) }) diff --git a/src/routes/album/[id]/+page.js b/src/routes/api/jellyfin/album/+server.js similarity index 58% rename from src/routes/album/[id]/+page.js rename to src/routes/api/jellyfin/album/+server.js index 1b4bab4..a12ce12 100644 --- a/src/routes/album/[id]/+page.js +++ b/src/routes/api/jellyfin/album/+server.js @@ -1,40 +1,46 @@ -import { generateURL } from '$lib/Jellyfin-api.js' - -export async function load({ fetch, params }) { - const response = await fetch( - generateURL({ - type: 'Items', - queryParams: { albumIds: params.id, recursive: true }, - }) - ) - const albumItemsData = await response.json() - - // This handles rare circumstances where a song is part of an album but is not meant to be included in the track list - // Example: Xronial Xero (Laur Remix) - Is tagged with the album Xronial Xero, but was a bonus track and not included as part of the album's track list. - const items = albumItemsData.Items.filter((item) => 'IndexNumber' in item) - - // Idk if it's efficient, but this is a beautiful one liner that accomplishes 1. Checking whether or not there are multiple discs, 2. Sorting the Items - // primarily by disc number, and secondarily by track number, and 3. Defaulting to just sorting by track number if the album is only one disc. - items.sort((a, b) => - a?.ParentIndexNumber !== b?.ParentIndexNumber - ? a.ParentIndexNumber - b.ParentIndexNumber - : a.IndexNumber - b.IndexNumber - ) - - const albumData = { - name: items[0].Album, - id: items[0].AlbumId, - artists: items[0].AlbumArtists, - year: items[0].ProductionYear, - discCount: Math.max(...items.map((x) => x?.ParentIndexNumber)), - length: items - .map((x) => x.RunTimeTicks) - .reduce((accumulator, currentValue) => accumulator + currentValue), - } - - return { - id: params.id, - albumItemsData: items, - albumData: albumData, - } -} +import { JellyfinUtils } from '$lib/utils' + +/** @type {import('./$types').RequestHandler} */ +export async function GET({ url, fetch }) { + const albumId = url.searchParams.get('albumId') + if (!albumId) { + return new Response('Requires albumId Query Parameter', { status: 400 }) + } + + const endpoint = JellyfinUtils.getItemsEnpt({ albumIds: albumId, recursive: true }) + const response = await fetch(endpoint) + const data = await response.json() + + // This handles rare circumstances where a song is part of an album but is not meant to be included in the track list + // Example: Xronial Xero (Laur Remix) - Is tagged with the album Xronial Xero, but was a bonus track and not included as part of the album's track list. + const items = data.Items.filter((item) => 'IndexNumber' in item) + + // Idk if it's efficient, but this is a beautiful one liner that accomplishes 1. Checking whether or not there are multiple discs, 2. Sorting the Items + // primarily by disc number, and secondarily by track number, and 3. Defaulting to just sorting by track number if the album is only one disc. + items.sort((a, b) => + a?.ParentIndexNumber !== b?.ParentIndexNumber + ? a.ParentIndexNumber - b.ParentIndexNumber + : a.IndexNumber - b.IndexNumber + ) + + const albumData = { + name: items[0].Album, + id: albumId, + artists: items[0].AlbumArtists, + year: items[0].ProductionYear, + discCount: Math.max(...items.map((x) => x?.ParentIndexNumber)), + length: items + .map((x) => x.RunTimeTicks) + .reduce((accumulator, currentValue) => accumulator + currentValue), + } + + const responseData = JSON.stringify({ + albumItems: items, + albumData: albumData, + }) + const responseHeaders = new Headers({ + 'Content-Type': 'application/json', + }) + + return new Response(responseData, {headers: responseHeaders}) +} diff --git a/src/routes/api/jellyfin/artist/+server.js b/src/routes/api/jellyfin/artist/+server.js new file mode 100644 index 0000000..e9f876a --- /dev/null +++ b/src/routes/api/jellyfin/artist/+server.js @@ -0,0 +1,39 @@ +import { JellyfinUtils } from '$lib/utils' + +/** @type {import('./$types').RequestHandler} */ +export async function GET({ url, fetch }) { + const artistId = url.searchParams.get('artistId') + if (!artistId) { + return new Response('Requires artistId Query Parameter', { status: 400 }) + } + + const endpoint = JellyfinUtils.getItemsEnpt({ artistIds: artistId, recursive: true }) + const response = await fetch(endpoint) + const data = await response.json() + + const artistItems = { + albums: [], + singles: [], + appearances: [], + } + + // Filters the raw list of items to only the albums that were produced fully or in part by the specified artist + artistItems.albums = data.Items.filter((item) => item.Type === 'MusicAlbum' && item.AlbumArtists.some((artist) => artist.Id === artistId)) + + data.Items.forEach((item) => { + if (item.Type === 'Audio') { + if (!('AlbumId' in item)) { + artistItems.singles.push(item) + } else if (!artistItems.albums.some((album) => album.Id === item.AlbumId)) { + artistItems.appearances.push(item) + } + } + }) + + const responseData = JSON.stringify(artistItems) + const responseHeaders = new Headers({ + 'Content-Type': 'application/json', + }) + + return new Response(responseData, { headers: responseHeaders }) +} diff --git a/src/routes/api/jellyfin/auth/+server.js b/src/routes/api/jellyfin/auth/+server.js new file mode 100644 index 0000000..99297e9 --- /dev/null +++ b/src/routes/api/jellyfin/auth/+server.js @@ -0,0 +1,42 @@ +/** @type {import('./$types').RequestHandler} */ +export async function GET({ url, fetch }) { + const { serverUrl, username, password, deviceId } = Object.fromEntries(url.searchParams) + if (!(serverUrl && username && password && deviceId)) return new Response('Missing authentication parameter', { status: 400 }) + + let authResponse + try { + const authUrl = new URL('/Users/AuthenticateByName', serverUrl).href + authResponse = await fetch(authUrl, { + method: 'POST', + body: JSON.stringify({ + Username: username, + Pw: password, + }), + headers: { + 'Content-Type': 'application/json; charset=utf-8', + 'X-Emby-Authorization': `MediaBrowser Client="Lazuli", Device="Chrome", DeviceId="${deviceId}", Version="1.0.0.0"`, + }, + }) + } catch { + authResponse = new Response('Invalid server URL', { status: 400 }) + } + + if (!authResponse.ok) { + authResponse = (await authResponse.text()) === 'Error processing request.' ? new Response('Invalid credentials', { status: 400 }) : authResponse + return authResponse + } + + if (!authResponse.headers.get('content-type').includes('application/json')) return new Response('Jellyfin server returned invalid data', { status: 500 }) + + const data = await authResponse.json() + const requiredData = ['User', 'SessionInfo', 'AccessToken', 'ServerId'] + + if (!requiredData.every((key) => Object.keys(data).includes(key))) return new Response('Data missing from Jellyfin server response', { status: 500 }) + + const responseData = JSON.stringify(data) + const responseHeaders = new Headers({ + 'Content-Type': 'application/json', + }) + + return new Response(responseData, { headers: responseHeaders }) +} diff --git a/src/routes/api/jellyfin/song/+server.js b/src/routes/api/jellyfin/song/+server.js new file mode 100644 index 0000000..03101dd --- /dev/null +++ b/src/routes/api/jellyfin/song/+server.js @@ -0,0 +1,20 @@ +import { JellyfinUtils } from '$lib/utils' + +/** @type {import('./$types').RequestHandler} */ +export async function GET({ url, fetch }) { + const songId = url.searchParams.get('songId') + if (!artistId) { + return new Response('Requires songId Query Parameter', { status: 400 }) + } + + const endpoint = JellyfinUtils.getItemsEnpt({ ids: songId, recursive: true }) + const response = await fetch(endpoint) + const data = await response.json() + + const responseData = JSON.stringify(data.Items[0]) + const responseHeaders = new Headers({ + 'Content-Type': 'application/json', + }) + + return new Response(responseData, {headers: responseHeaders}) +} diff --git a/src/routes/api/user/connections/+server.js b/src/routes/api/user/connections/+server.js new file mode 100644 index 0000000..3ad059c --- /dev/null +++ b/src/routes/api/user/connections/+server.js @@ -0,0 +1,67 @@ +import { UserConnections } from '$lib/server/db/users' +import Joi from 'joi' + +/** @type {import('./$types').RequestHandler} */ +export async function GET({ request, url }) { + const schema = Joi.number().required() + const userId = request.headers.get('userId') + + const validation = schema.validate(userId) + if (validation.error) return new Response(validation.error.message, { status: 400 }) + + const responseHeaders = new Headers({ + 'Content-Type': 'application/json', + }) + + const filter = url.searchParams.get('filter') + if (filter) { + const requestedConnections = filter.split(',').map((item) => item.toLowerCase()) + const userConnections = UserConnections.getConnections(userId, requestedConnections) + return new Response(JSON.stringify(userConnections), { headers: responseHeaders }) + } + + const userConnections = UserConnections.getConnections(userId) + return new Response(JSON.stringify(userConnections), { headers: responseHeaders }) +} + +// May need to add support for refresh token and expiry in the future +/** @type {import('./$types').RequestHandler} */ +export async function PATCH({ request }) { + const schema = Joi.object({ + userId: Joi.number().required(), + connection: Joi.object({ + serviceName: Joi.string().required(), + accessToken: Joi.string().required(), + }).required(), + }) + + const userId = request.headers.get('userId') + const connection = await request.json() + + const validation = schema.validate({ userId, connection }) + if (validation.error) return new Response(validation.error.message, { status: 400 }) + + UserConnections.setConnection(userId, connection.serviceName, connection.accessToken) + + return new Response('Updated Connection') +} + +/** @type {import('./$types').RequestHandler} */ +export async function DELETE({ request }) { + const schema = Joi.object({ + userId: Joi.number().required(), + connection: Joi.object({ + serviceName: Joi.string().required(), + }).required(), + }) + + const userId = request.headers.get('userId') + const connection = await request.json() + + const validation = schema.validate({ userId, connection }) + if (validation.error) return new Response(validation.error.message, { status: 400 }) + + UserConnections.deleteConnection(userId, connection.serviceName) + + return new Response('Deleted Connection') +} diff --git a/src/routes/api/youtube-music/media/+server.js b/src/routes/api/youtube-music/media/+server.js new file mode 100644 index 0000000..678f8c1 --- /dev/null +++ b/src/routes/api/youtube-music/media/+server.js @@ -0,0 +1,27 @@ +import ytdl from 'ytdl-core' + +/** @type {import('./$types').RequestHandler} */ +export async function GET({ url }) { + const videoId = url.searchParams.get('videoId') + if (!videoId) { + return new Response('Requires videoId Query Parameter', { status: 400 }) + } + + const videoUrl = `https://www.youtube.com/watch?v=${videoId}` + const info = await ytdl.getInfo(videoUrl) + const videoFormat = ytdl.chooseFormat(info.formats, { + filter: (format) => format.hasVideo && !format.isDashMPD && 'contentLength' in format, + quality: 'highestvideo', + }) + const audioFormat = ytdl.chooseFormat(info.formats, { + filter: 'audioonly', + quality: 'highestaudio', + }) + + const responseData = JSON.stringify({ video: videoFormat.url, audio: audioFormat.url }) + const responseHeaders = new Headers({ + 'Content-Type': 'application/json', + }) + + return new Response(responseData, { headers: responseHeaders }) +} diff --git a/src/routes/artist/[id]/+page.js b/src/routes/artist/[id]/+page.js deleted file mode 100644 index 633de90..0000000 --- a/src/routes/artist/[id]/+page.js +++ /dev/null @@ -1,5 +0,0 @@ -export const load = ({ params }) => { - return { - id: params.id, - } -} diff --git a/src/routes/artist/[id]/+page.server.js b/src/routes/artist/[id]/+page.server.js new file mode 100644 index 0000000..2e3982a --- /dev/null +++ b/src/routes/artist/[id]/+page.server.js @@ -0,0 +1,10 @@ +export async function load({ params, fetch }) { + const artistId = params.id + const response = await fetch(`/api/jellyfin/artist?artistId=${artistId}`) + const responseData = await response.json() + + return { + id: artistId, + artistItems: responseData, + } +} diff --git a/src/routes/artist/[id]/+page.svelte b/src/routes/artist/[id]/+page.svelte index 9174201..cce6667 100644 --- a/src/routes/artist/[id]/+page.svelte +++ b/src/routes/artist/[id]/+page.svelte @@ -1,18 +1,8 @@
@@ -30,6 +20,9 @@ { /each }
+
+ Test +
diff --git a/src/routes/settings/+page.svelte b/src/routes/settings/+page.svelte new file mode 100644 index 0000000..b0e21f8 --- /dev/null +++ b/src/routes/settings/+page.svelte @@ -0,0 +1,13 @@ + + + +
+ + diff --git a/src/routes/settings/connections/+page.server.js b/src/routes/settings/connections/+page.server.js new file mode 100644 index 0000000..8601db1 --- /dev/null +++ b/src/routes/settings/connections/+page.server.js @@ -0,0 +1,76 @@ +import { fail } from '@sveltejs/kit' +import { SECRET_INTERNAL_API_KEY } from '$env/static/private' + +/** @type {import('./$types').PageServerLoad} */ +export const load = async ({ fetch, locals }) => { + const response = await fetch('/api/user/connections', { + headers: { + apikey: SECRET_INTERNAL_API_KEY, + userId: locals.userId, + }, + }) + if (response.ok) { + const connectionsData = await response.json() + if (connectionsData) { + const serviceNames = connectionsData.map((connection) => connection.serviceName) + return { existingConnections: serviceNames } + } + } else { + const error = await response.text() + console.log(error) + } +} + +/** @type {import('./$types').Actions}} */ +export const actions = { + authenticateJellyfin: async ({ request, fetch, locals }) => { + const formData = await request.formData() + const queryParams = new URLSearchParams() + for (let field of formData) { + const [key, value] = field + queryParams.append(key, value) + } + const jellyfinAuthResponse = await fetch(`/api/jellyfin/auth?${queryParams.toString()}`, { + headers: { + apikey: SECRET_INTERNAL_API_KEY, + }, + }) + + if (!jellyfinAuthResponse.ok) { + const jellyfinAuthError = await jellyfinAuthResponse.text() + return fail(jellyfinAuthResponse.status, { message: jellyfinAuthError }) + } + + const jellyfinAuthData = await jellyfinAuthResponse.json() + const jellyfinAccessToken = jellyfinAuthData.AccessToken + const updateConnectionsResponse = await fetch('/api/user/connections', { + method: 'PATCH', + headers: { + apikey: SECRET_INTERNAL_API_KEY, + userId: locals.userId, + }, + body: JSON.stringify({ serviceName: 'jellyfin', accessToken: jellyfinAccessToken }), + }) + + if (!updateConnectionsResponse.ok) return fail(500, { message: 'Internal Server Error' }) + + return { message: 'Updated Jellyfin connection' } + }, + deleteConnection: async ({ request, fetch, locals }) => { + const formData = await request.formData() + const serviceName = formData.get('service') + + const deleteConnectionResponse = await fetch('/api/user/connections', { + method: 'DELETE', + headers: { + apikey: SECRET_INTERNAL_API_KEY, + userId: locals.userId, + }, + body: JSON.stringify({ serviceName }), + }) + + if (!deleteConnectionResponse.ok) return fail(500, { message: 'Internal Server Error' }) + + return { message: 'Connection deleted' } + }, +} diff --git a/src/routes/settings/connections/+page.svelte b/src/routes/settings/connections/+page.svelte new file mode 100644 index 0000000..d426b25 --- /dev/null +++ b/src/routes/settings/connections/+page.svelte @@ -0,0 +1,172 @@ + + +
+
+

Add Connection

+
+ {#each Object.entries(testServices) as [serviceType, serviceData]} + {#if !existingConnections.includes(serviceType)} + + {/if} + {/each} +
+
+ {#if existingConnections} +
+ {#each existingConnections as connectionType} + {@const service = Services[connectionType]} +
+
+ {service.displayName} icon +
+
Account Name
+
{service.displayName}
+
+
+ (modal = `delete-${connectionType}`)}> + + +
+
+
+
+
+ console.log(event.detail.toggleState)} /> + Enable Connection +
+
+
+ {/each} +
+ {/if} + {#if modal} +
+ {#if typeof modal === 'string'} + {@const connectionType = modal.replace('delete-', '')} + {@const service = Services[connectionType]} +
+

Delete {service.displayName}?

+
+ + + +
+
+ {:else} + (modal = null)} /> + {/if} + + {/if} +
diff --git a/src/routes/settings/connections/jellyfinAuthBox.svelte b/src/routes/settings/connections/jellyfinAuthBox.svelte new file mode 100644 index 0000000..7350e77 --- /dev/null +++ b/src/routes/settings/connections/jellyfinAuthBox.svelte @@ -0,0 +1,55 @@ + + +
+

Jellyfin Sign In

+
+ +
+ + +
+
+
+ + +
+
+ + diff --git a/src/routes/song/[id]/+page.js b/src/routes/song/[id]/+page.js deleted file mode 100644 index 633de90..0000000 --- a/src/routes/song/[id]/+page.js +++ /dev/null @@ -1,5 +0,0 @@ -export const load = ({ params }) => { - return { - id: params.id, - } -} diff --git a/src/routes/song/[id]/+page.server.js b/src/routes/song/[id]/+page.server.js new file mode 100644 index 0000000..f77632f --- /dev/null +++ b/src/routes/song/[id]/+page.server.js @@ -0,0 +1,10 @@ +export async function load ({ params, fetch }) { + const songId = params.id + const response = await fetch(`/api/jellyfin/song?songId=${songId}`) + const responseData = await response.json() + + return { + id: songId, + songData: responseData + } +} diff --git a/src/routes/song/[id]/+page.svelte b/src/routes/song/[id]/+page.svelte index d599b2e..3879f55 100644 --- a/src/routes/song/[id]/+page.svelte +++ b/src/routes/song/[id]/+page.svelte @@ -1,25 +1,9 @@
- { #await fetchedData} -

Loading

- {:then songData} -

{songData.Name}

- {/await} -
- - \ No newline at end of file +

{songData.Name}

+ \ No newline at end of file diff --git a/src/routes/youtube-music/+page.server.js b/src/routes/youtube-music/+page.server.js new file mode 100644 index 0000000..366f011 --- /dev/null +++ b/src/routes/youtube-music/+page.server.js @@ -0,0 +1,11 @@ +/** @type {import('./$types').PageServerLoad} */ +export async function load({ url, fetch }) { + const videoId = url.searchParams.get('videoId') + const response = await fetch(`/api/yt/media?videoId=${videoId}`) + const responseData = await response.json() + return { + videoId: videoId, + videoUrl: responseData.video, + audioUrl: responseData.audio, + } +} diff --git a/src/routes/youtube-music/+page.svelte b/src/routes/youtube-music/+page.svelte new file mode 100644 index 0000000..3e087b2 --- /dev/null +++ b/src/routes/youtube-music/+page.svelte @@ -0,0 +1,24 @@ + + + + \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js index 8e5022c..897b0aa 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -6,10 +6,13 @@ export default { theme: { extend: { fontFamily: { - notoSans: [ - "'Noto Sans', 'Noto Sans HK', 'Noto Sans JP', 'Noto Sans KR', 'Noto Sans SC', 'Noto Sans TC'", - ...defaultTheme.fontFamily.sans, - ], + notoSans: ["'Noto Sans', 'Noto Sans HK', 'Noto Sans JP', 'Noto Sans KR', 'Noto Sans SC', 'Noto Sans TC'", ...defaultTheme.fontFamily.sans], + }, + colors: { + 'lazuli-primary': '#ed6713', + 'neutral-925': 'rgb(16, 16, 16)', + 'jellyfin-purple': '#aa5cc3', + 'jellyfin-blue': '#00a4dc', }, }, },