From 33e5903098a4f9e252d3e2b721ae1d4aea88d3e1 Mon Sep 17 00:00:00 2001 From: HammerPot <171651869+HammerPot@users.noreply.github.com> Date: Thu, 23 Oct 2025 20:44:07 -0400 Subject: [PATCH 01/52] Added to nowPlaying JSON object --- src/lib/components/controls.svelte | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/components/controls.svelte b/src/lib/components/controls.svelte index 312bc16..2ae448e 100644 --- a/src/lib/components/controls.svelte +++ b/src/lib/components/controls.svelte @@ -41,12 +41,16 @@ await UserManager.setAlbumArt(imageFile); await new Promise(resolve => setTimeout(resolve, 100)); + const formatted = new Date().toISOString().slice(0, 19).replace('T', ' '); + let friendPlaying = { title: song.title, artist: song.artist, album: song.album, discord: UserSettings.discord.enabled, - id: $SavedUser.id + id: $SavedUser.id, + timePlayed: formatted, + source: 'Web' }; $socket?.emit('nowPlaying', { nowPlaying: friendPlaying }); From a82cb52079b64d6f954309442ac482aa9072ae22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 04:00:10 +0000 Subject: [PATCH 02/52] Bump validator and express-validator in /api Bumps [validator](https://github.com/validatorjs/validator.js) and [express-validator](https://github.com/express-validator/express-validator). These dependencies needed to be updated together. Updates `validator` from 13.12.0 to 13.15.23 - [Release notes](https://github.com/validatorjs/validator.js/releases) - [Changelog](https://github.com/validatorjs/validator.js/blob/master/CHANGELOG.md) - [Commits](https://github.com/validatorjs/validator.js/compare/13.12.0...13.15.23) Updates `express-validator` from 7.2.1 to 7.3.1 - [Release notes](https://github.com/express-validator/express-validator/releases) - [Commits](https://github.com/express-validator/express-validator/compare/v7.2.1...v7.3.1) --- updated-dependencies: - dependency-name: validator dependency-version: 13.15.23 dependency-type: direct:production - dependency-name: express-validator dependency-version: 7.3.1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- api/package-lock.json | 21 ++++++++++++--------- api/package.json | 4 ++-- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/api/package-lock.json b/api/package-lock.json index 095fe77..1da2378 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -15,7 +15,7 @@ "dotenv": "^16.4.7", "express": "^4.21.2", "express-rate-limit": "^7.5.0", - "express-validator": "^7.2.1", + "express-validator": "^7.3.1", "jsonwebtoken": "^9.0.2", "multer": "^1.4.5-lts.1", "mysql": "^2.18.1", @@ -23,7 +23,7 @@ "peer": "^1.0.2", "socket.io": "^4.8.1", "uuid": "^11.0.5", - "validator": "^13.12.0" + "validator": "^13.15.23" } }, "node_modules/@mapbox/node-pre-gyp": { @@ -803,6 +803,7 @@ "version": "4.21.2", "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -859,12 +860,13 @@ } }, "node_modules/express-validator": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.2.1.tgz", - "integrity": "sha512-CjNE6aakfpuwGaHQZ3m8ltCG2Qvivd7RHtVMS/6nVxOM7xVGqr4bhflsm4+N5FP5zI7Zxp+Hae+9RE+o8e3ZOQ==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.3.1.tgz", + "integrity": "sha512-IGenaSf+DnWc69lKuqlRE9/i/2t5/16VpH5bXoqdxWz1aCpRvEdrBuu1y95i/iL5QP8ZYVATiwLFhwk3EDl5vg==", + "license": "MIT", "dependencies": { "lodash": "^4.17.21", - "validator": "~13.12.0" + "validator": "~13.15.23" }, "engines": { "node": ">= 8.0.0" @@ -2285,9 +2287,10 @@ } }, "node_modules/validator": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", - "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "version": "13.15.23", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.23.tgz", + "integrity": "sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw==", + "license": "MIT", "engines": { "node": ">= 0.10" } diff --git a/api/package.json b/api/package.json index 0a4bf53..344bd18 100644 --- a/api/package.json +++ b/api/package.json @@ -26,7 +26,7 @@ "dotenv": "^16.4.7", "express": "^4.21.2", "express-rate-limit": "^7.5.0", - "express-validator": "^7.2.1", + "express-validator": "^7.3.1", "jsonwebtoken": "^9.0.2", "multer": "^1.4.5-lts.1", "mysql": "^2.18.1", @@ -34,6 +34,6 @@ "peer": "^1.0.2", "socket.io": "^4.8.1", "uuid": "^11.0.5", - "validator": "^13.12.0" + "validator": "^13.15.23" } } From afb11489d2aea2cf5cc7bce5a48900e25e25ec4c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 18:45:32 +0000 Subject: [PATCH 03/52] Bump vite from 5.4.11 to 5.4.21 Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.11 to 5.4.21. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v5.4.21/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v5.4.21/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-version: 5.4.21 dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2bbe058..54eaaaf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -64,7 +64,7 @@ "tailwindcss": "^3.3.6", "typescript": "^5.7.3", "vaul-svelte": "^0.3.2", - "vite": "^5.0.3", + "vite": "^5.4.21", "vite-plugin-pwa": "^1.0.0" } }, @@ -12620,9 +12620,9 @@ } }, "node_modules/vite": { - "version": "5.4.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", - "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "license": "MIT", "dependencies": { "esbuild": "^0.21.3", diff --git a/package.json b/package.json index 633404f..c57d8ba 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "tailwindcss": "^3.3.6", "typescript": "^5.7.3", "vaul-svelte": "^0.3.2", - "vite": "^5.0.3", + "vite": "^5.4.21", "vite-plugin-pwa": "^1.0.0" }, "type": "module", From 8aaad763d03afed7733397a59ce224a6a83b2859 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 18:45:38 +0000 Subject: [PATCH 04/52] Bump qs and express Bumps [qs](https://github.com/ljharb/qs) to 6.14.1 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together. Updates `qs` from 6.13.0 to 6.14.1 - [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md) - [Commits](https://github.com/ljharb/qs/compare/v6.13.0...v6.14.1) Updates `express` from 4.21.2 to 4.22.1 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/v4.22.1/History.md) - [Commits](https://github.com/expressjs/express/compare/4.21.2...v4.22.1) --- updated-dependencies: - dependency-name: qs dependency-version: 6.14.1 dependency-type: indirect - dependency-name: express dependency-version: 4.22.1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- package-lock.json | 50 +++++++++++++++++++++++++++++++---------------- package.json | 2 +- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2bbe058..1685b00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "cmdk-sv": "^0.0.17", "colorthief": "^2.6.0", "comlink": "^4.4.1", - "express": "^4.21.2", + "express": "^4.22.1", "extract-colors": "^4.1.1", "idb": "^8.0.0", "idb-keyval": "^6.2.1", @@ -7293,38 +7293,39 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -7363,6 +7364,21 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, + "node_modules/express/node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/extract-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/extract-colors/-/extract-colors-4.1.1.tgz", diff --git a/package.json b/package.json index 633404f..b3e2988 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "cmdk-sv": "^0.0.17", "colorthief": "^2.6.0", "comlink": "^4.4.1", - "express": "^4.21.2", + "express": "^4.22.1", "extract-colors": "^4.1.1", "idb": "^8.0.0", "idb-keyval": "^6.2.1", From c7f1f20b5d0d5c93bccf90131d0cd3021d9c7399 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 18:45:51 +0000 Subject: [PATCH 05/52] Bump multer from 1.4.5-lts.1 to 2.0.2 in /api Bumps [multer](https://github.com/expressjs/multer) from 1.4.5-lts.1 to 2.0.2. - [Release notes](https://github.com/expressjs/multer/releases) - [Changelog](https://github.com/expressjs/multer/blob/main/CHANGELOG.md) - [Commits](https://github.com/expressjs/multer/compare/v1.4.5-lts.1...v2.0.2) --- updated-dependencies: - dependency-name: multer dependency-version: 2.0.2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- api/package-lock.json | 61 +++++++++++-------------------------------- api/package.json | 2 +- 2 files changed, 16 insertions(+), 47 deletions(-) diff --git a/api/package-lock.json b/api/package-lock.json index 1da2378..fddc5c9 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -17,7 +17,7 @@ "express-rate-limit": "^7.5.0", "express-validator": "^7.3.1", "jsonwebtoken": "^9.0.2", - "multer": "^1.4.5-lts.1", + "multer": "^2.0.2", "mysql": "^2.18.1", "mysql2": "^3.12.0", "peer": "^1.0.2", @@ -447,50 +447,20 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", "engines": [ - "node >= 0.8" + "node >= 6.0" ], "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", - "readable-stream": "^2.2.2", + "readable-stream": "^3.0.2", "typedarray": "^0.0.6" } }, - "node_modules/concat-stream/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/concat-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/concat-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -803,7 +773,6 @@ "version": "4.21.2", "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -1469,21 +1438,21 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/multer": { - "version": "1.4.5-lts.1", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", - "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", + "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", "license": "MIT", "dependencies": { "append-field": "^1.0.0", - "busboy": "^1.0.0", - "concat-stream": "^1.5.2", - "mkdirp": "^0.5.4", + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "mkdirp": "^0.5.6", "object-assign": "^4.1.1", - "type-is": "^1.6.4", - "xtend": "^4.0.0" + "type-is": "^1.6.18", + "xtend": "^4.0.2" }, "engines": { - "node": ">= 6.0.0" + "node": ">= 10.16.0" } }, "node_modules/multer/node_modules/mkdirp": { diff --git a/api/package.json b/api/package.json index 344bd18..7f37953 100644 --- a/api/package.json +++ b/api/package.json @@ -28,7 +28,7 @@ "express-rate-limit": "^7.5.0", "express-validator": "^7.3.1", "jsonwebtoken": "^9.0.2", - "multer": "^1.4.5-lts.1", + "multer": "^2.0.2", "mysql": "^2.18.1", "mysql2": "^3.12.0", "peer": "^1.0.2", From 1598eabb29c6a97bdacfd516eac942d0ed6c0607 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 18:46:07 +0000 Subject: [PATCH 06/52] Bump node-forge from 1.3.1 to 1.3.3 Bumps [node-forge](https://github.com/digitalbazaar/forge) from 1.3.1 to 1.3.3. - [Changelog](https://github.com/digitalbazaar/forge/blob/main/CHANGELOG.md) - [Commits](https://github.com/digitalbazaar/forge/compare/v1.3.1...v1.3.3) --- updated-dependencies: - dependency-name: node-forge dependency-version: 1.3.3 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2bbe058..34fc9b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9497,9 +9497,9 @@ } }, "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz", + "integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==", "dev": true, "license": "(BSD-3-Clause OR GPL-2.0)", "peer": true, From 662e442639e6d5c4505bb579c20ab565d61c506d Mon Sep 17 00:00:00 2001 From: Logan Date: Fri, 16 Jan 2026 01:47:00 -0500 Subject: [PATCH 07/52] Update links in README for web and iOS --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bae94b5..4d45905 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@

- stable · beta + web · beta · iOS

> Need Help? Join our [Development Server](https://discord.gg/Wxrp73HVj4) From 71af37696f579eeb13c19522a37a1794005ea11a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 06:51:57 +0000 Subject: [PATCH 08/52] Bump devalue from 5.1.1 to 5.6.2 Bumps [devalue](https://github.com/sveltejs/devalue) from 5.1.1 to 5.6.2. - [Release notes](https://github.com/sveltejs/devalue/releases) - [Changelog](https://github.com/sveltejs/devalue/blob/main/CHANGELOG.md) - [Commits](https://github.com/sveltejs/devalue/compare/v5.1.1...v5.6.2) --- updated-dependencies: - dependency-name: devalue dependency-version: 5.6.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4e1f2da..cb68e37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6580,9 +6580,9 @@ } }, "node_modules/devalue": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz", - "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.2.tgz", + "integrity": "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==", "license": "MIT" }, "node_modules/didyoumean": { From 22e33c55681707d6c33565520326475d263c95a7 Mon Sep 17 00:00:00 2001 From: Logan Date: Fri, 16 Jan 2026 01:58:59 -0500 Subject: [PATCH 09/52] fix some credit @Nailington --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4d45905..7fdafa2 100644 --- a/README.md +++ b/README.md @@ -146,10 +146,10 @@ Maple was inspired by various experiences, such as being on a plane without inte ## Credits -### Lead & Only Developer +### Lead Developer - Cattn -### Logos & Branding +### Logos & Various Help -- Nailington +- Nailington ([@Nailington](https://github.com/Nailington)) From 2ef284dda7c4d82c9d6302ad7c37ff97c9e170de Mon Sep 17 00:00:00 2001 From: Logan Date: Wed, 21 Jan 2026 13:25:18 -0500 Subject: [PATCH 10/52] friends & socket & user management --- src/lib/api/UserManager.ts | 217 +++++++++++++++++++++++++++++- src/lib/components/SideBar.svelte | 40 +++--- src/lib/refreshFriends.ts | 43 ++++++ src/lib/socketManager.ts | 65 +++++++++ src/routes/+layout.svelte | 36 ++++- src/routes/friends/+page.svelte | 204 ++++++++++++++++++++++++++++ src/routes/settings/+page.svelte | 73 +++++++++- 7 files changed, 646 insertions(+), 32 deletions(-) create mode 100644 src/lib/refreshFriends.ts create mode 100644 src/lib/socketManager.ts diff --git a/src/lib/api/UserManager.ts b/src/lib/api/UserManager.ts index 9a01899..c5abc72 100644 --- a/src/lib/api/UserManager.ts +++ b/src/lib/api/UserManager.ts @@ -1,5 +1,6 @@ import { SERVER } from '$lib/api/server'; -import { SavedUser, UserInfo } from '$lib/store'; +import { refreshFriends, refreshRequests } from '$lib/refreshFriends'; +import { friendNowPlaying, SavedUser, UserInfo } from '$lib/store'; import type { User } from '$lib/types'; import { get } from 'svelte/store'; @@ -85,4 +86,218 @@ export class UserManager { return null; } }; + + public static updateProfilePicture = async (file: File) => { + try { + const userId = get(UserInfo)?.id; + if (!userId) return { error: 'Not logged in' }; + + const formData = new FormData(); + formData.append('pfp', file); + + const response = await fetch(`${SERVER}/user/manage/setProfile/${userId}`, { + credentials: 'include', + method: 'POST', + body: formData + }); + + if (response.ok) { + await this.getUser(); + return { success: true }; + } + const data = await response.json(); + return { error: data.error || 'Failed to update profile picture' }; + } catch (e) { + console.error('Error:', e); + return { error: 'Failed to update profile picture' }; + } + }; + + public static updateDisplayName = async (displayName: string) => { + try { + const userId = get(UserInfo)?.id; + if (!userId) return { error: 'Not logged in' }; + + const response = await fetch(`${SERVER}/user/manage/setDisplayName/${userId}`, { + credentials: 'include', + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ displayName }) + }); + + if (response.ok) { + await this.getUser(); + return { success: true }; + } + const data = await response.json(); + return { error: data.error || 'Failed to update display name' }; + } catch (e) { + console.error('Error:', e); + return { error: 'Failed to update display name' }; + } + }; + + public static getRequests = async () => { + try { + const userId = get(UserInfo)?.id; + if (!userId) return { error: 'Not logged in' }; + + const response = await fetch(`${SERVER}/user/friends/get/requests/${userId}`, { + credentials: 'include', + method: 'GET' + }); + const data = await response.json(); + if (response.ok) { + return data; + } + } catch (error) { + return console.error('Error:', error); + } + }; + + public static getFriends = async () => { + try { + const userId = get(UserInfo)?.id; + if (!userId) return { error: 'Not logged in' }; + + const response = await fetch(`${SERVER}/user/friends/get/friends/${userId}`, { + credentials: 'include', + method: 'GET' + }); + const data = await response.json(); + if (response.ok) { + return data; + } + } catch (error) { + return console.error('Error:', error); + } + }; + + public static removeFriend = async (id: string) => { + try { + const userId = get(UserInfo)?.id; + if (!userId) return { error: 'Not logged in' }; + + const response = await fetch(`${SERVER}/user/friends/remove/${userId}`, { + credentials: 'include', + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ friendId: id }) + }); + const data = await response.json(); + if (response.ok) { + refreshFriends(); + return data; + } + } catch (error) { + return console.error('Error:', error); + } + }; + + public static getUserbyId = async (id: string) => { + try { + const response = await fetch(`${SERVER}/public/get/user/id/${id}`, { + credentials: 'include', + method: 'GET' + }); + const data = await response.json(); + if (response.ok) { + if (data.nowPlaying) { + friendNowPlaying.set(data.nowPlaying); + } + return data; + } else { + return; + } + } catch (error) { + return console.error('Error:', error); + } + }; + + public static getUserName = async (username: string) => { + try { + const response = await fetch(`${SERVER}/public/get/user/${username}`, { + credentials: 'include', + method: 'GET' + }); + const data = await response.json(); + if (response.ok) { + return data; + } else { + return; + } + } catch (error) { + return console.error('Error:', error); + } + }; + + public static acceptRequest = async (id: string) => { + try { + const userId = get(UserInfo)?.id; + if (!userId) return { error: 'Not logged in' }; + + const response = await fetch(`${SERVER}/user/friends/accept/${userId}`, { + credentials: 'include', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ friendId: id }) + }); + const data = await response.json(); + if (response.ok) { + return data; + } + } catch (error) { + return console.error('Error:', error); + } + }; + + public static rejectRequest = async (id: string) => { + try { + const userId = get(UserInfo)?.id; + if (!userId) return { error: 'Not logged in' }; + + const response = await fetch(`${SERVER}/user/friends/decline/${userId}`, { + credentials: 'include', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ friendId: id }) + }); + const data = await response.json(); + if (response.ok) { + refreshRequests(); + refreshFriends(); + return data; + } + } catch (error) { + return console.error('Error:', error); + } + }; + + public static addFriend = async (id: string) => { + try { + const userId = get(UserInfo)?.id; + if (!userId) return { error: 'Not logged in' }; + + const response = await fetch(`${SERVER}/user/friends/add/${userId}`, { + credentials: 'include', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ friendId: id }) + }); + const data = await response.json(); + if (response.ok) { + return data; + } + } catch (error) { + return console.error('Error:', error); + } + }; } diff --git a/src/lib/components/SideBar.svelte b/src/lib/components/SideBar.svelte index 51629eb..a2c2bce 100644 --- a/src/lib/components/SideBar.svelte +++ b/src/lib/components/SideBar.svelte @@ -3,7 +3,7 @@ import { goto } from '$app/navigation'; import { page } from '$app/state'; import { createLibrary } from '$lib/library'; - import { UserInfo, SavedUser } from '$lib/store'; + import { SavedUser } from '$lib/store'; const isActive = (path: string) => page.url.pathname === path; @@ -13,22 +13,26 @@ class="bg-surface-container-high fixed top-0 left-0 flex h-[100%] flex-col items-center justify-between rounded-br-4xl p-4" >
-
- -
+ d="M12 12q-1.65 0-2.825-1.175T8 8t1.175-2.825T12 4t2.825 1.175T16 8t-1.175 2.825T12 12m-8 8v-2.8q0-.85.438-1.562T5.6 14.55q1.55-.775 3.15-1.162T12 13t3.25.388t3.15 1.162q.725.375 1.163 1.088T20 17.2V20z" + /> + + {/if} +
@@ -183,10 +187,6 @@ scale: 0.8; } - .large-button-mod :global(button) { - scale: 1.2; - } - .button-mod h1 { font-size: 10px; font-weight: 500; diff --git a/src/lib/refreshFriends.ts b/src/lib/refreshFriends.ts new file mode 100644 index 0000000..521bb5e --- /dev/null +++ b/src/lib/refreshFriends.ts @@ -0,0 +1,43 @@ +import { friends, pendingRequests, SavedUser } from '$lib/store'; +import { get } from 'svelte/store'; +import { UserManager } from './api/UserManager'; +import type { AddedFriend } from './types'; + +type FriendRelation = { user_id: string; friend_id: string }; + +export async function refreshRequests() { + pendingRequests.set(await UserManager.getRequests()); +} + +export async function refreshFriends() { + const fetchedFriends = await UserManager.getFriends(); + sortFriends(fetchedFriends); +} + +async function sortFriends(unsorted: FriendRelation[]) { + const userId = get(SavedUser).id; + const newFriends = unsorted.map((friend: FriendRelation) => { + if (friend.user_id === userId) { + return { user_id: userId, friend_id: friend.friend_id }; + } else { + return { user_id: userId, friend_id: friend.user_id }; + } + }); + const friendsList: AddedFriend[] = []; + const seenIds = new Set(); + for (const friend of newFriends) { + if (seenIds.has(friend.friend_id)) continue; + const friendData = await UserManager.getUserbyId(friend.friend_id); + if (!friendData) continue; + seenIds.add(friendData.id); + const newFriend: AddedFriend = { + id: friendData.id, + name: friendData.name, + username: friendData.username, + pfp: friendData.pfp, + nowPlaying: friendData.nowPlaying + }; + friendsList.push(newFriend); + } + friends.set(friendsList); +} \ No newline at end of file diff --git a/src/lib/socketManager.ts b/src/lib/socketManager.ts new file mode 100644 index 0000000..f4c256f --- /dev/null +++ b/src/lib/socketManager.ts @@ -0,0 +1,65 @@ +import { browser } from '$app/environment'; +import { socket as importedSocket } from '$lib/store'; +import { toast } from 'svelte-sonner'; +import { get } from 'svelte/store'; +import { UserManager } from './api/UserManager'; +import { refreshFriends, refreshRequests } from './refreshFriends'; + +export const socketManager = () => { + if (browser) { + let socket = get(importedSocket); + + socket?.on('connect', () => { + console.log('[CLIENT] Socket connected'); + }); + + socket?.on('friendRequest', async (data) => { + const id = data.id; + const friend = await UserManager.getUserbyId(id); + toast.success('Friend request from: ' + friend.name + ' (' + friend.username + ')', + { + action: { + label: 'Accept', + onClick: () => { + UserManager.acceptRequest(id); + } + }, + } + ); + refreshRequests(); + }); + + socket?.on('notFound', async () => { + toast.error('Friend request failed: User not found'); + }); + + socket?.on('error', async (data) => { + toast.error(data); + }); + + socket?.on('requestAccepted', async (data) => { + console.log(data); + const id = data.id; + const friend = await UserManager.getUserbyId(id); + toast.success(friend.name + ' (' + friend.username + ') accepted your friend request!'); + refreshFriends(); + refreshRequests(); + }); + + socket?.on("acceptedRequest", async (data) => { + console.log(data); + const id = data.id; + const friend = await UserManager.getUserbyId(id); + toast.success('You are now friends with '+ friend.name + ' (' + friend.username + ')!'); + refreshFriends(); + refreshRequests(); + }); + + socket?.on('nowPlaying', async (data) => { + console.log('[CLIENT] Received nowPlaying event:', data); + console.log('[CLIENT] Current socket ID:', socket?.id); + refreshFriends(); + refreshRequests(); + }); + } +}; \ No newline at end of file diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 9e8126e..d0ba63a 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -2,16 +2,22 @@ import '../app.css'; import '../main.css'; import SideBar from '$lib/components/SideBar.svelte'; - import TopBar from '$lib/components/TopBar.svelte'; import BottomBar from '$lib/components/BottomBar.svelte'; - import { isLoggedIn, title, loadPreferencesStore } from '$lib/store'; + import { isLoggedIn, title, loadPreferencesStore, SavedUser, socket, UserInfo } from '$lib/store'; import { UserManager } from '$lib/api/UserManager'; import { onMount } from 'svelte'; import { Snackbar } from 'm3-svelte'; import { initTheme } from '$lib/theme/theme'; import { goto } from '$app/navigation'; import { page } from '$app/stores'; + import { socketManager } from '$lib/socketManager'; + import { io } from 'socket.io-client'; import { browser } from '$app/environment'; + import UserSettings from '$lib/preferences/usersettings'; + import { refreshFriends, refreshRequests } from '$lib/refreshFriends'; + import { SERVER } from '$lib/api/server'; + + let { children, data } = $props(); @@ -26,12 +32,34 @@ goto('/onboard'); } - const user = await UserManager.checkSession(); - if (user) { + const isLoggedInValue = await UserManager.checkSession(); + if (isLoggedInValue) { isLoggedIn.set(true); } else { isLoggedIn.set(false); } + + const user = await UserManager.getUser(); + if (user) { + SavedUser.set(user); + } + + if (browser) { + if ($isLoggedIn) { + if (UserSettings.preferences.socket) { + const io2 = io(`${SERVER}`, { + withCredentials: true + }); + socket.set(io2); + $socket?.on('connect', () => { + console.log('Connected to server'); + }); + socketManager(); + refreshFriends(); + refreshRequests(); + } + } + } }); diff --git a/src/routes/friends/+page.svelte b/src/routes/friends/+page.svelte index e69de29..7147066 100644 --- a/src/routes/friends/+page.svelte +++ b/src/routes/friends/+page.svelte @@ -0,0 +1,204 @@ + + +
+
+ {#if !$isLoggedIn} +
+ + + +

You're not logged in

+

Login to manage your friends

+ +
+ {:else} +
+

Social

+

Friends

+

Manage your friends and friend requests

+
+ +
+
+

Add Friend

+

Enter a username to send a friend request

+
+ + +
+
+ + + +
+ + +
+
+
+ + {#if $pendingRequests && $pendingRequests.length > 0} +
+
+

Friend Requests

+

People who want to be your friend

+
+ + +
+ {#each $pendingRequests as request} + {#await UserManager.getUserbyId(request.user_id) then user} + {#if user} +
+
+ {#if user.pfp} + Profile + {:else} +
+ + + +
+ {/if} +
+

{user.name || user.username}

+

@{user.username}

+
+
+
+ + +
+
+ {/if} + {/await} + {/each} +
+
+
+ {/if} + +
+
+

Your Friends

+

People you're connected with

+
+ + + {#if $friends && $friends.length > 0} +
+ {#each $friends as friend} +
+
+ {#if friend.pfp} + Profile + {:else} +
+ + + +
+ {/if} +
+

{friend.name || friend.username}

+

@{friend.username}

+ {#if friend.nowPlaying} +
+ + + +

+ {friend.nowPlaying.title || 'Unknown Track'} - {friend.nowPlaying.artist || 'Unknown Artist'} +

+
+ {/if} +
+
+
+ +
+
+ {/each} +
+ {:else} +
+ + + +

No friends yet

+

Add someone using their username above

+
+ {/if} +
+
+ {/if} +
+
diff --git a/src/routes/settings/+page.svelte b/src/routes/settings/+page.svelte index 33fe4d7..a0a4ec1 100644 --- a/src/routes/settings/+page.svelte +++ b/src/routes/settings/+page.svelte @@ -6,6 +6,7 @@ import { get } from 'svelte/store'; import { title } from '$lib/store'; import { themeSettings, setThemeSettings, persistThemeSettings } from '$lib/theme/theme'; + import { UserManager } from '$lib/api/UserManager'; let name = $state(''); let initialName = $state(''); @@ -32,9 +33,15 @@ let initialThemeDarkMode = $state(initialTheme.isDarkMode); let themePreviewColor = $derived(themeSourceColor ?? initialThemeSourceColor); let themeInitialized = false; + + let pfpFile = $state(null); + let pfpPreview = $state(null); + let pfpUploading = $state(false); + let fileInput: HTMLInputElement; + $effect(() => { - name = $UserInfo?.name ?? ''; - initialName = $UserInfo?.name ?? ''; + name = $SavedUser?.name ?? ''; + initialName = $SavedUser?.name ?? ''; }); $effect(() => { @@ -59,6 +66,22 @@ } }); + const handleFileSelect = (e: Event) => { + const target = e.target as HTMLInputElement; + const file = target.files?.[0]; + if (file) { + if (file.size > 3 * 1024 * 1024) { + return; + } + pfpFile = file; + const reader = new FileReader(); + reader.onload = () => { + pfpPreview = reader.result as string; + }; + reader.readAsDataURL(file); + } + }; + let hasChanges = $derived( name !== initialName || webhookEnabled !== initialWebhookEnabled || @@ -70,10 +93,30 @@ loggingEnabled !== initialLoggingEnabled || developerModeEnabled !== initialDeveloperModeEnabled || themePreviewColor !== initialThemeSourceColor || - themeDarkMode !== initialThemeDarkMode + themeDarkMode !== initialThemeDarkMode || + pfpFile !== null ); - const saveChanges = () => { + const saveChanges = async () => { + if (pfpFile) { + pfpUploading = true; + const result = await UserManager.updateProfilePicture(pfpFile); + pfpUploading = false; + if (result.error) { + return; + } + pfpFile = null; + pfpPreview = null; + } + + if (name !== initialName) { + const result = await UserManager.updateDisplayName(name); + if (result.error) { + return; + } + initialName = name; + } + const sourceColor = themePreviewColor; persistThemeSettings({ sourceColor, isDarkMode: themeDarkMode }); initialThemeSourceColor = sourceColor; @@ -108,7 +151,20 @@
- {#if $SavedUser?.pfp} + + {#if pfpPreview} + Profile Preview + {:else if $SavedUser?.pfp} Profile fileInput.click()} + disabled={pfpUploading} + class="bg-surface text-primary ring-outline hover:bg-surface-container-high absolute -bottom-3 flex items-center gap-2 rounded-full px-3 py-1 text-xs font-semibold shadow-md ring-1 disabled:opacity-50" > Change photo + > + {#if pfpUploading}Uploading...{:else}Change photo{/if}
From 6bce634fccaf047e63505b2b04f9487896895eae Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 21 Jan 2026 18:25:31 +0000 Subject: [PATCH 11/52] style: auto-format code --- src/lib/components/SideBar.svelte | 8 +- src/lib/refreshFriends.ts | 2 +- src/lib/socketManager.ts | 22 ++-- src/routes/+layout.svelte | 2 - src/routes/friends/+page.svelte | 175 +++++++++++++++++++++++++----- 5 files changed, 158 insertions(+), 51 deletions(-) diff --git a/src/lib/components/SideBar.svelte b/src/lib/components/SideBar.svelte index a2c2bce..e59e64e 100644 --- a/src/lib/components/SideBar.svelte +++ b/src/lib/components/SideBar.svelte @@ -16,14 +16,10 @@ @@ -153,27 +222,55 @@ {#if $friends && $friends.length > 0}
{#each $friends as friend} -
+
{#if friend.pfp} - Profile + Profile {:else} -
- - +
+ +
{/if}
-

{friend.name || friend.username}

+

+ {friend.name || friend.username} +

@{friend.username}

{#if friend.nowPlaying}
- - + +

- {friend.nowPlaying.title || 'Unknown Track'} - {friend.nowPlaying.artist || 'Unknown Artist'} + {friend.nowPlaying.title || 'Unknown Track'} - {friend.nowPlaying + .artist || 'Unknown Artist'}

{/if} @@ -181,7 +278,16 @@
@@ -190,8 +296,17 @@
{:else}
- - + +

No friends yet

Add someone using their username above

From c085a5bef437453c4642deba7a61db115f7b792d Mon Sep 17 00:00:00 2001 From: Logan Date: Wed, 21 Jan 2026 13:41:01 -0500 Subject: [PATCH 12/52] nowPlaying & friend activity --- src/lib/opfs.ts | 10 ++++++++-- src/lib/refreshFriends.ts | 21 ++++++++++++++++++++- src/lib/socketManager.ts | 21 ++++++++++++++++++--- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/lib/opfs.ts b/src/lib/opfs.ts index 9c0965e..1e38a2a 100644 --- a/src/lib/opfs.ts +++ b/src/lib/opfs.ts @@ -102,6 +102,8 @@ export class OPFS { return url; } + private static saveStatsQueue: Promise = Promise.resolve(); + public static async getStats() { if (this.statsCache) return this.statsCache; try { @@ -118,9 +120,13 @@ export class OPFS { } public static async saveStats(snapshot: StatsSnapshot) { - await this.ensureConfigDir(); this.statsCache = snapshot; - await write('/config/stats.json', JSON.stringify(snapshot)); + const currentQueue = this.saveStatsQueue; + this.saveStatsQueue = currentQueue.then(async () => { + await this.ensureConfigDir(); + await write('/config/stats.json', JSON.stringify(snapshot)); + }).catch(() => {}); + await this.saveStatsQueue; } public static async addAlbum(album: Album, id: string) { diff --git a/src/lib/refreshFriends.ts b/src/lib/refreshFriends.ts index 654a276..b9d143d 100644 --- a/src/lib/refreshFriends.ts +++ b/src/lib/refreshFriends.ts @@ -30,12 +30,31 @@ async function sortFriends(unsorted: FriendRelation[]) { const friendData = await UserManager.getUserbyId(friend.friend_id); if (!friendData) continue; seenIds.add(friendData.id); + let nowPlaying = friendData.nowPlaying; + if (nowPlaying && typeof nowPlaying === 'string') { + try { + nowPlaying = JSON.parse(nowPlaying); + } catch (error) { + console.error('Error parsing nowPlaying:', error); + nowPlaying = null; + } + } + let nowPlayingFormatted = undefined; + if (nowPlaying && typeof nowPlaying === 'object' && nowPlaying !== null) { + if (nowPlaying.title || nowPlaying.artist || nowPlaying.album) { + nowPlayingFormatted = { + title: nowPlaying.title || '', + artist: nowPlaying.artist || '', + album: nowPlaying.album || '' + }; + } + } const newFriend: AddedFriend = { id: friendData.id, name: friendData.name, username: friendData.username, pfp: friendData.pfp, - nowPlaying: friendData.nowPlaying + nowPlaying: nowPlayingFormatted }; friendsList.push(newFriend); } diff --git a/src/lib/socketManager.ts b/src/lib/socketManager.ts index 36ad586..b195270 100644 --- a/src/lib/socketManager.ts +++ b/src/lib/socketManager.ts @@ -1,5 +1,5 @@ import { browser } from '$app/environment'; -import { socket as importedSocket } from '$lib/store'; +import { socket as importedSocket, friends } from '$lib/store'; import { toast } from 'svelte-sonner'; import { get } from 'svelte/store'; import { UserManager } from './api/UserManager'; @@ -56,8 +56,23 @@ export const socketManager = () => { socket?.on('nowPlaying', async (data) => { console.log('[CLIENT] Received nowPlaying event:', data); console.log('[CLIENT] Current socket ID:', socket?.id); - refreshFriends(); - refreshRequests(); + if (data.id && data.nowPlaying) { + friends.update((currentFriends) => { + return currentFriends.map((friend) => { + if (friend.id === data.id) { + return { + ...friend, + nowPlaying: { + title: data.nowPlaying.title, + artist: data.nowPlaying.artist, + album: data.nowPlaying.album + } + }; + } + return friend; + }); + }); + } }); } }; From 5ae27513986c4cb8d23037006f5d1379d31bc1c4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 21 Jan 2026 18:41:12 +0000 Subject: [PATCH 13/52] style: auto-format code --- src/lib/opfs.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lib/opfs.ts b/src/lib/opfs.ts index 1e38a2a..52973f0 100644 --- a/src/lib/opfs.ts +++ b/src/lib/opfs.ts @@ -122,10 +122,12 @@ export class OPFS { public static async saveStats(snapshot: StatsSnapshot) { this.statsCache = snapshot; const currentQueue = this.saveStatsQueue; - this.saveStatsQueue = currentQueue.then(async () => { - await this.ensureConfigDir(); - await write('/config/stats.json', JSON.stringify(snapshot)); - }).catch(() => {}); + this.saveStatsQueue = currentQueue + .then(async () => { + await this.ensureConfigDir(); + await write('/config/stats.json', JSON.stringify(snapshot)); + }) + .catch(() => {}); await this.saveStatsQueue; } From 6f84ee43af326803448cacc097b2af166225a496 Mon Sep 17 00:00:00 2001 From: Logan Date: Wed, 21 Jan 2026 13:48:42 -0500 Subject: [PATCH 14/52] settings + webhook --- src/lib/api/UserManager.ts | 30 +++++++++++ src/lib/player.ts | 68 +++++++++++++++++++++++ src/routes/settings/+page.svelte | 92 ++++++++++++++++++++++++++++---- 3 files changed, 179 insertions(+), 11 deletions(-) diff --git a/src/lib/api/UserManager.ts b/src/lib/api/UserManager.ts index c5abc72..a97a96e 100644 --- a/src/lib/api/UserManager.ts +++ b/src/lib/api/UserManager.ts @@ -300,4 +300,34 @@ export class UserManager { return console.error('Error:', error); } }; + + public static setAlbumArt = async (file: File) => { + try { + const userId = get(UserInfo)?.id; + if (!userId) return { error: 'Not logged in' }; + + const formData = new FormData(); + formData.append('albumArt', file); + + const response = await fetch(`${SERVER}/user/manage/setAlbumArt/${userId}`, { + credentials: 'include', + method: 'POST', + body: formData + }); + + if (response.ok) { + return { success: true }; + } + const data = await response.json(); + return { error: data.error || 'Failed to update album art' }; + } catch (e) { + console.error('Error:', e); + return { error: 'Failed to update album art' }; + } + }; + + public static isLoggedIn = async () => { + const userId = get(UserInfo)?.id; + return !!userId; + }; } diff --git a/src/lib/player.ts b/src/lib/player.ts index 8fb2c5d..37706a1 100644 --- a/src/lib/player.ts +++ b/src/lib/player.ts @@ -19,6 +19,7 @@ import { import { statsManager } from '$lib/stats'; import type { QueueSnapshot } from '$lib/store'; import UserSettings from '$lib/preferences/usersettings'; +import { UserManager } from './api/UserManager'; type QueueSourceState = QueueSnapshot['source']; @@ -31,6 +32,72 @@ function updateQueue(items: Song[], index: number, source?: QueueSourceState) { context.set(items); } +async function getImageFile(imagePath: string): Promise { + const response = await OPFS.get().image(imagePath); + const arrayBuffer = await response.arrayBuffer(); + return new File([arrayBuffer], imagePath, { type: 'image/jpeg' }); +} + +async function sendWebhook(song: Song) { + if (!UserSettings.webhook.enabled || !UserSettings.webhook.url) return; + + const isLoggedIn = await UserManager.isLoggedIn(); + if (!isLoggedIn) return; + + if (!song.image || typeof song.image !== 'string') return; + const imagePath: string = song.image; + + try { + const image = await getImageFile(imagePath); + const imageBuffer = await image.arrayBuffer(); + const imageFile = new File([imageBuffer], 'album.jpg', { type: 'image/jpeg' }); + await UserManager.setAlbumArt(imageFile); + await new Promise(resolve => setTimeout(resolve, 100)); + + const user = get(SavedUser); + const formData = new FormData(); + formData.append('file', image, 'album.jpg'); + formData.append( + 'payload_json', + JSON.stringify({ + embeds: [ + { + title: 'Now Playing', + description: `**${song.title}** by ${song.artist}`, + color: 0x8f4a4c, + fields: [ + { + name: 'Album', + value: song.album + }, + { + name: 'Year', + value: song.year ? song.year.toString() : 'N/A' + }, + { + name: 'Track Number', + value: song.trackNumber ? song.trackNumber.toString() : 'N/A' + } + ], + image: { + url: 'attachment://album.jpg' + } + } + ], + username: user?.name || 'Maple User', + avatar_url: `https://api.maple.music/public/get/pfp/${user?.id}` + }) + ); + + await fetch(UserSettings.webhook.url, { + method: 'POST', + body: formData + }); + } catch (error) { + console.error('Error sending webhook:', error); + } +} + async function emitNowPlaying(song: Song) { const s = get(socket); const user = get(SavedUser); @@ -46,6 +113,7 @@ async function emitNowPlaying(song: Song) { source: 'Web' }; s.emit('nowPlaying', { nowPlaying: payload }); + await sendWebhook(song); } async function playAtIndex(index: number) { diff --git a/src/routes/settings/+page.svelte b/src/routes/settings/+page.svelte index a0a4ec1..2e4e38b 100644 --- a/src/routes/settings/+page.svelte +++ b/src/routes/settings/+page.svelte @@ -1,27 +1,41 @@
+
+ +
= new Map(); - private static SERVER = 'https://api.maple.music'; + private static SERVER = SERVER; private static async getCache(path: string, cache: T[] | null): Promise { if (cache) return cache; diff --git a/src/lib/player.ts b/src/lib/player.ts index 37706a1..60f18e3 100644 --- a/src/lib/player.ts +++ b/src/lib/player.ts @@ -20,6 +20,7 @@ import { statsManager } from '$lib/stats'; import type { QueueSnapshot } from '$lib/store'; import UserSettings from '$lib/preferences/usersettings'; import { UserManager } from './api/UserManager'; +import { SERVER } from '$lib/api/server'; type QueueSourceState = QueueSnapshot['source']; @@ -84,8 +85,8 @@ async function sendWebhook(song: Song) { } } ], - username: user?.name || 'Maple User', - avatar_url: `https://api.maple.music/public/get/pfp/${user?.id}` + username: user?.name || 'User', + avatar_url: `${SERVER}/public/get/pfp/${user?.id}` }) ); diff --git a/src/lib/refreshFriends.ts b/src/lib/refreshFriends.ts index b9d143d..85b63b8 100644 --- a/src/lib/refreshFriends.ts +++ b/src/lib/refreshFriends.ts @@ -2,6 +2,7 @@ import { friends, pendingRequests, SavedUser } from '$lib/store'; import { get } from 'svelte/store'; import { UserManager } from './api/UserManager'; import type { AddedFriend } from './types'; +import { SERVER } from './api/server'; type FriendRelation = { user_id: string; friend_id: string }; @@ -49,11 +50,12 @@ async function sortFriends(unsorted: FriendRelation[]) { }; } } + const pfpUrl = `${SERVER}/public/get/pfp/${friendData.id}`; const newFriend: AddedFriend = { id: friendData.id, name: friendData.name, username: friendData.username, - pfp: friendData.pfp, + pfp: pfpUrl, nowPlaying: nowPlayingFormatted }; friendsList.push(newFriend); diff --git a/src/routes/friends/+page.svelte b/src/routes/friends/+page.svelte index f0913d1..92a1022 100644 --- a/src/routes/friends/+page.svelte +++ b/src/routes/friends/+page.svelte @@ -8,6 +8,7 @@ let selectedFriend = $state(''); let addingFriend = $state(false); + const pfpErrors = $state(new Set()); onMount(async () => { title.set('Friends'); @@ -141,11 +142,12 @@ class="bg-surface-container-high flex flex-col gap-4 rounded-xl p-4 sm:flex-row sm:items-center sm:justify-between" >
- {#if user.pfp} + {#if user.pfp && !pfpErrors.has(user.id)} Profile pfpErrors.add(user.id)} /> {:else}
- {#if friend.pfp} + {#if friend.pfp && !pfpErrors.has(friend.id)} Profile pfpErrors.add(friend.id)} /> {:else}
Date: Wed, 21 Jan 2026 20:14:22 +0000 Subject: [PATCH 17/52] style: auto-format code --- src/lib/components/BottomBar.svelte | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/lib/components/BottomBar.svelte b/src/lib/components/BottomBar.svelte index 643df17..224a1af 100644 --- a/src/lib/components/BottomBar.svelte +++ b/src/lib/components/BottomBar.svelte @@ -112,9 +112,7 @@ dragging = false; } - function openQueue() { - - } + function openQueue() {}
-
From 34ac143101dd743e9c43ea3715fb84f730fb96dd Mon Sep 17 00:00:00 2001 From: Cattn Date: Wed, 21 Jan 2026 17:12:53 -0500 Subject: [PATCH 18/52] Queue --- src/lib/components/BottomBar.svelte | 18 +++- src/lib/components/Queue.svelte | 134 ++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+), 2 deletions(-) diff --git a/src/lib/components/BottomBar.svelte b/src/lib/components/BottomBar.svelte index 224a1af..cb9a070 100644 --- a/src/lib/components/BottomBar.svelte +++ b/src/lib/components/BottomBar.svelte @@ -19,6 +19,8 @@ } from '$lib/player'; import { OPFS } from '$lib/opfs'; import { browser } from '$app/environment'; + import { page } from '$app/state'; + import Queue from './Queue.svelte'; let artwork = $state(null as string | null); let progress = $state(0); @@ -29,6 +31,12 @@ let fillPercent = $state(0); let trackEl: HTMLDivElement | null = null; let dragging = $state(false); + let queueOpen = $state(false); + + $effect(() => { + page.url.pathname; + queueOpen = false; + }); $effect(() => { if (!browser) return; @@ -112,7 +120,9 @@ dragging = false; } - function openQueue() {} + function openQueue() { + queueOpen = !queueOpen; + }
-
+{#if queueOpen} + +{/if} + diff --git a/src/lib/components/Queue.svelte b/src/lib/components/Queue.svelte index 7cd7303..91b25f7 100644 --- a/src/lib/components/Queue.svelte +++ b/src/lib/components/Queue.svelte @@ -82,7 +82,9 @@ } -
+

Queue

diff --git a/src/lib/components/SideBar.svelte b/src/lib/components/SideBar.svelte index e59e64e..e20df56 100644 --- a/src/lib/components/SideBar.svelte +++ b/src/lib/components/SideBar.svelte @@ -8,7 +8,7 @@ const isActive = (path: string) => page.url.pathname === path; -
+ diff --git a/src/lib/components/Track.svelte b/src/lib/components/Track.svelte index c6d2bfa..4c06a1c 100644 --- a/src/lib/components/Track.svelte +++ b/src/lib/components/Track.svelte @@ -394,7 +394,7 @@ -
+
{#if track?.image || playlist?.image || album?.image || artist?.image} {#await OPFS.getImageUrl((track?.image || playlist?.image || album?.image || artist?.image) as string)} @@ -497,7 +497,7 @@ draggable="false" /> {/if} -
+

{track?.title || playlist?.name || album?.name || artist?.name}

{track?.artist || '' || album?.artist || ''}

@@ -506,7 +506,7 @@ diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index f22ced3..0aaad62 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -3,6 +3,8 @@ import '../main.css'; import SideBar from '$lib/components/SideBar.svelte'; import BottomBar from '$lib/components/BottomBar.svelte'; + import MobileNowPlaying from '$lib/components/MobileNowPlaying.svelte'; + import MobileBottomNav from '$lib/components/MobileBottomNav.svelte'; import { isLoggedIn, title, loadPreferencesStore, SavedUser, socket, UserInfo } from '$lib/store'; import { UserManager } from '$lib/api/UserManager'; import { onMount } from 'svelte'; @@ -66,10 +68,20 @@ -
+
{@render children()}
- + + + +
+ + +
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 028917f..4177667 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -13,7 +13,7 @@
-
+

Welcome to Maple!

@@ -29,9 +29,9 @@
-

Recently Played

+

Recently Played

{#each recent as track} diff --git a/src/routes/albums/+page.svelte b/src/routes/albums/+page.svelte index defaeaa..c819e21 100644 --- a/src/routes/albums/+page.svelte +++ b/src/routes/albums/+page.svelte @@ -31,7 +31,7 @@ />
{#each sortedAlbums as album (album.id)}
diff --git a/src/routes/albums/album/[slug]/+page.svelte b/src/routes/albums/album/[slug]/+page.svelte index 8862387..d675d6c 100644 --- a/src/routes/albums/album/[slug]/+page.svelte +++ b/src/routes/albums/album/[slug]/+page.svelte @@ -188,25 +188,27 @@ }); -
-
+
+
{#if imageUrl} {album?.name} {:else} -
+
{/if} -
+

{album?.name}

{album?.artist}

{album?.year}

-
+
diff --git a/src/routes/artists/+page.svelte b/src/routes/artists/+page.svelte index 092fb35..74f79b6 100644 --- a/src/routes/artists/+page.svelte +++ b/src/routes/artists/+page.svelte @@ -31,7 +31,7 @@ />
{#each sortedArtists as artist (artist.id)}
diff --git a/src/routes/artists/artist/[slug]/+page.svelte b/src/routes/artists/artist/[slug]/+page.svelte index 8c601fa..4e5fb16 100644 --- a/src/routes/artists/artist/[slug]/+page.svelte +++ b/src/routes/artists/artist/[slug]/+page.svelte @@ -81,7 +81,7 @@ }); -
+
{#if imageUrl} {artist?.name} @@ -107,7 +107,7 @@ onChange={handleAlbumFiltersChange} />
{#each sortedAlbums as album (album.id)}
diff --git a/src/routes/playlists/+page.svelte b/src/routes/playlists/+page.svelte index 18952fc..5c3e78a 100644 --- a/src/routes/playlists/+page.svelte +++ b/src/routes/playlists/+page.svelte @@ -43,9 +43,9 @@ />
-
+
-
-
+
+
{#if playlist?.image} {#await OPFS.getImageUrl(playlist.image as string) then imageUrl} {playlist?.name} {:catch} -
+
{/await} {:else} -
+
{/if} {#if editModeOn}
diff --git a/src/routes/tracks/+page.svelte b/src/routes/tracks/+page.svelte index 7529bce..00bcf72 100644 --- a/src/routes/tracks/+page.svelte +++ b/src/routes/tracks/+page.svelte @@ -34,7 +34,7 @@ />
{#each sortedTracks as track (track.id)}
From ed4b35236c70effb0ac89dbf6dcf4312153eb022 Mon Sep 17 00:00:00 2001 From: Logan Date: Fri, 23 Jan 2026 12:09:12 -0500 Subject: [PATCH 25/52] improve Queue page --- src/lib/components/ListTrack.svelte | 6 +- src/lib/components/Queue.svelte | 154 ++++++++++++++++++---------- 2 files changed, 105 insertions(+), 55 deletions(-) diff --git a/src/lib/components/ListTrack.svelte b/src/lib/components/ListTrack.svelte index b1132f4..43dbdb4 100644 --- a/src/lib/components/ListTrack.svelte +++ b/src/lib/components/ListTrack.svelte @@ -237,8 +237,10 @@ ondrop={onDrop} ondragend={onDragEnd} > -
- {index} +
+ {index} {#if showThumbnail} {#if track.image} {#await OPFS.getImageUrl(track.image as string) then imageUrl} diff --git a/src/lib/components/Queue.svelte b/src/lib/components/Queue.svelte index 91b25f7..40bb4d0 100644 --- a/src/lib/components/Queue.svelte +++ b/src/lib/components/Queue.svelte @@ -82,61 +82,109 @@ } -
-
-

Queue

-
-
    - {#each itemsCache as item, index (item.id)} -
  • +
    +
    +
    +
    + - removeQueueItem(index)} - draggable={canDrag} - playbackContext={itemsCache} - playbackSource={$queueState.source} - onDragStart={(e) => { - if (!canDrag) return; - dragIndex = index; - dropIndex = index + 1; - e.dataTransfer?.setData('text/plain', String(index)); - e.dataTransfer?.setDragImage(e.currentTarget as Element, 16, 16); - }} - onDragOver={(e) => { - if (!canDrag) return; - e.preventDefault(); - e.dataTransfer && (e.dataTransfer.dropEffect = 'move'); - dropIndex = index + 1; - }} - onDrop={(e) => { - if (!canDrag) return; - e.preventDefault(); - const fromIndex = dragIndex ?? Number(e.dataTransfer?.getData('text/plain')); - const targetIndex = index; - dragIndex = null; - dropIndex = null; - if (Number.isFinite(fromIndex)) { - const adjustedTarget = fromIndex < targetIndex ? targetIndex - 1 : targetIndex; - moveQueueItem(fromIndex, Math.max(0, adjustedTarget)); - } - }} - onDragEnd={() => { - dragIndex = null; - dropIndex = null; - }} + -
  • - {/each} -
+ +
+
+

Queue

+

+ {itemsCache.length} + {itemsCache.length === 1 ? 'track' : 'tracks'} +

+
+
+
+ +
+
+
    + {#each itemsCache as item, index (item.id)} +
  • + removeQueueItem(index)} + draggable={canDrag} + playbackContext={itemsCache} + playbackSource={$queueState.source} + onDragStart={(e) => { + if (!canDrag) return; + dragIndex = index; + dropIndex = index + 1; + e.dataTransfer?.setData('text/plain', String(index)); + e.dataTransfer?.setDragImage(e.currentTarget as Element, 16, 16); + }} + onDragOver={(e) => { + if (!canDrag) return; + e.preventDefault(); + e.dataTransfer && (e.dataTransfer.dropEffect = 'move'); + dropIndex = index + 1; + }} + onDrop={(e) => { + if (!canDrag) return; + e.preventDefault(); + const fromIndex = dragIndex ?? Number(e.dataTransfer?.getData('text/plain')); + const targetIndex = index; + dragIndex = null; + dropIndex = null; + if (Number.isFinite(fromIndex)) { + const adjustedTarget = fromIndex < targetIndex ? targetIndex - 1 : targetIndex; + moveQueueItem(fromIndex, Math.max(0, adjustedTarget)); + } + }} + onDragEnd={() => { + dragIndex = null; + dropIndex = null; + }} + /> +
  • + {/each} +
+
+ + From e5e0ce64c069331207ef61d389e485463ec04bf0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 23 Jan 2026 17:09:20 +0000 Subject: [PATCH 26/52] style: auto-format code --- src/lib/components/SideBar.svelte | 2 +- src/routes/playlists/+page.svelte | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/components/SideBar.svelte b/src/lib/components/SideBar.svelte index e20df56..c42b207 100644 --- a/src/lib/components/SideBar.svelte +++ b/src/lib/components/SideBar.svelte @@ -8,7 +8,7 @@ const isActive = (path: string) => page.url.pathname === path; -