diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 588d8ec..dd49f58 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,52 +1,26 @@ # ------------------------------------------------------------ # Base Image # ------------------------------------------------------------ -FROM debian:unstable-slim - -ENV DEBIAN_FRONTEND=noninteractive -ENV LANG=C.UTF-8 -ENV LC_ALL=C.UTF-8 +FROM mcr.microsoft.com/devcontainers/base:debian # ------------------------------------------------------------ # System Dependencies # ------------------------------------------------------------ -RUN apt-get update && apt-get upgrade -y && \ +RUN apt update && apt upgrade -y && \ apt-get install -y --no-install-recommends \ - curl \ - ca-certificates \ git \ - build-essential \ - sudo \ + git-lfs \ fish \ - unzip \ - xz-utils \ - libatomic1 \ - libc6 \ - wget \ nodejs \ - npm && \ - rm -rf /var/lib/apt/lists/* - -# ------------------------------------------------------------ -# Create Non-Root User -# ------------------------------------------------------------ -ARG USERNAME=devuser -ARG UID=1000 -ARG GID=1000 - -RUN groupadd --gid ${GID} ${USERNAME} && \ - useradd --uid ${UID} --gid ${GID} -m -s /usr/bin/fish ${USERNAME} && \ - echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers - -USER ${USERNAME} -WORKDIR /home/${USERNAME} + npm \ + curl # ------------------------------------------------------------ # Install Bun (Non-Root) # ------------------------------------------------------------ -ENV BUN_INSTALL=/home/${USERNAME}/.bun -ENV PATH="${BUN_INSTALL}/bin:${PATH}" - +ENV BUN_INSTALL="$HOME/.bun" +ENV PATH="$BUN_INSTALL/bin:$PATH" + RUN curl -fsSL https://bun.sh/install | bash # ------------------------------------------------------------ @@ -58,7 +32,7 @@ RUN curl -fsSL https://opencode.ai/install -o opencode-install && \ rm opencode-install # Add OpenCode to PATH permanently -ENV PATH="/home/${USERNAME}/.opencode/bin:${PATH}" +ENV PATH="$HOME/.opencode/bin:$PATH" # ------------------------------------------------------------ # Ensure fish is Default Shell diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3aee656..bd87db0 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,22 +1,14 @@ { - "name": "debian-npm-fish-devcontainer", + "name": "Monochrome Dev Container", "build": { - "dockerfile": "Dockerfile" + "context": "..", + "dockerfile": "./Dockerfile" }, - - "remoteUser": "devuser", - - "features": {}, - + "postCreateCommand": "git config --local core.editor \"code --wait\" && git config --local commit.gpgsign false && npm install && bun install", "customizations": { "vscode": { "extensions": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] } }, - - "postCreateCommand": "npm install", - - "remoteEnv": { - "SHELL": "/usr/bin/fish" - } + "mounts": ["source=${env:HOME}/.gitconfig,target=/home/vscode/.gitconfig,type=bind,consistency=cached"] } diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..6938754 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,23 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "build", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [], + "label": "npm: build", + "detail": "vite build" + }, + { + "type": "npm", + "script": "dev", + "problemMatcher": [], + "label": "npm: dev", + "detail": "vite" + } + ] +} diff --git a/bun.lock b/bun.lock index 9b8e910..7a7217b 100644 --- a/bun.lock +++ b/bun.lock @@ -16,6 +16,7 @@ "cookie-session": "^2.1.1", "dashjs": "^5.1.1", "fuse.js": "^7.1.0", + "hls.js": "^1.6.15", "jose": "^6.2.0", "npm": "^11.11.0", "pocketbase": "^0.26.8", @@ -27,6 +28,7 @@ "@types/node": "^25.3.5", "eslint": "^9.39.3", "eslint-config-prettier": "^10.1.8", + "formidable": "^3.5.4", "globals": "^17.4.0", "htmlhint": "^1.9.2", "miniflare": "^4.20260301.1", @@ -42,6 +44,7 @@ }, }, "overrides": { + "serialize-javascript": "^7.0.3", "source-map": "^0.7.4", "sourcemap-codec": "npm:@jridgewell/sourcemap-codec@^1.4.14", }, @@ -422,12 +425,16 @@ "@neutralinojs/neu": ["@neutralinojs/neu@11.7.0", "", { "dependencies": { "@electron/asar": "^3.0.3", "chalk": "^4.1.0", "chokidar": "^4.0.3", "commander": "^7.2.0", "configstore": "^5.0.1", "edit-json-file": "^1.6.2", "follow-redirects": "^1.13.1", "fs-extra": "^9.0.1", "pe-library": "^1.0.1", "png2icons": "^2.0.1", "postject": "1.0.0-alpha.6", "recursive-readdir": "^2.2.2", "resedit": "^2.0.2", "spawn-command": "^1.0.0", "tcp-port-used": "^1.0.2", "uuid": "^8.3.2", "websocket": "^1.0.35", "zip-lib": "^1.0.4" }, "bin": { "neu": "bin/neu.js" } }, "sha512-fUqvR70a+BpKI9mrD92ldZkVC24Rs8XL/9m7zmOCLgCRys3yuWy7vEsxpHzKMzqTiQJkTYIsLmcR8VMzNIjuSw=="], + "@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + "@paralleldrive/cuid2": ["@paralleldrive/cuid2@2.3.1", "", { "dependencies": { "@noble/hashes": "^1.1.5" } }, "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw=="], + "@poppinss/colors": ["@poppinss/colors@4.1.6", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg=="], "@poppinss/dumper": ["@poppinss/dumper@0.6.5", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@sindresorhus/is": "^7.0.2", "supports-color": "^10.0.0" } }, "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw=="], @@ -552,6 +559,8 @@ "arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="], + "asap": ["asap@2.0.6", "", {}, "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="], + "astral-regex": ["astral-regex@2.0.0", "", {}, "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="], "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], @@ -678,6 +687,8 @@ "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "dezalgo": ["dezalgo@1.0.4", "", { "dependencies": { "asap": "^2.0.0", "wrappy": "1" } }, "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig=="], + "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], "dot-prop": ["dot-prop@5.3.0", "", { "dependencies": { "is-obj": "^2.0.0" } }, "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q=="], @@ -778,7 +789,7 @@ "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], - "flatted": ["flatted@3.3.4", "", {}, "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA=="], + "flatted": ["flatted@3.4.0", "", {}, "sha512-kC6Bb+ooptOIvWj5B63EQWkF0FEnNjV2ZNkLMLZRDDduIiWeFF4iKnslwhiWxjAdbg4NzTNo6h0qLuvFrcx+Sw=="], "follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="], @@ -786,6 +797,8 @@ "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + "formidable": ["formidable@3.5.4", "", { "dependencies": { "@paralleldrive/cuid2": "^2.2.2", "dezalgo": "^1.0.4", "once": "^1.4.0" } }, "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug=="], + "fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], @@ -848,6 +861,8 @@ "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + "hls.js": ["hls.js@1.6.15", "", {}, "sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA=="], + "hookified": ["hookified@1.15.1", "", {}, "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg=="], "html-entities": ["html-entities@2.6.0", "", {}, "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ=="], @@ -1148,8 +1163,6 @@ "r-json": ["r-json@1.3.1", "", { "dependencies": { "w-json": "1.3.10" } }, "sha512-5nhRFfjVMQdrwKUfUlRpDUCocdKtjSnYZ1R/86mpZDV3MfsZ3dYYNjSGuMX+mPBvFvQBhdzxSqxkuLPLv4uFGg=="], - "randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="], - "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], "recursive-readdir": ["recursive-readdir@2.2.3", "", { "dependencies": { "minimatch": "^3.0.5" } }, "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA=="], @@ -1196,7 +1209,7 @@ "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - "serialize-javascript": ["serialize-javascript@6.0.2", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g=="], + "serialize-javascript": ["serialize-javascript@7.0.4", "", {}, "sha512-DuGdB+Po43Q5Jxwpzt1lhyFSYKryqoNjQSA9M92tyw0lyHIOur+XCalOUe0KTJpyqzT8+fQ5A0Jf7vCx/NKmIg=="], "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], diff --git a/functions/album/[id].js b/functions/album/[id].js index 95f6fbf..8a5e2a9 100644 --- a/functions/album/[id].js +++ b/functions/album/[id].js @@ -84,7 +84,7 @@ class ServerAPI { getCoverUrl(id, size = '1280') { if (!id) return ''; - const formattedId = id.replace(/-/g, '/'); + const formattedId = String(id).replace(/-/g, '/'); return `https://resources.tidal.com/images/${formattedId}/${size}x${size}.jpg`; } } diff --git a/functions/playlist/[id].js b/functions/playlist/[id].js index ae41d1a..c20a6ac 100644 --- a/functions/playlist/[id].js +++ b/functions/playlist/[id].js @@ -85,7 +85,7 @@ class ServerAPI { getCoverUrl(id, size = '1080') { if (!id) return ''; - const formattedId = id.replace(/-/g, '/'); + const formattedId = String(id).replace(/-/g, '/'); return `https://resources.tidal.com/images/${formattedId}/${size}x${size}.jpg`; } } diff --git a/functions/track/[id].js b/functions/track/[id].js index fad3f21..e96fa94 100644 --- a/functions/track/[id].js +++ b/functions/track/[id].js @@ -98,7 +98,7 @@ class ServerAPI { getCoverUrl(id, size = '1280') { if (!id) return ''; - const formattedId = id.replace(/-/g, '/'); + const formattedId = String(id).replace(/-/g, '/'); return `https://resources.tidal.com/images/${formattedId}/${size}x${size}.jpg`; } diff --git a/index.html b/index.html index 5b4b4b7..d8e8e0c 100644 --- a/index.html +++ b/index.html @@ -36,32 +36,37 @@ - +
@@ -3665,10 +3700,13 @@
+ + +
Full-screen Visualizer - Enable particle visualizer in full-screen mode + Enable the visualizer in full-screen mode
@@ -3740,6 +3779,31 @@ >
+
+
+ Visualizer Brightness + Adjust the brightness of the visualizer. Lower this if the visualizer is + too bright for you. +
+
+ + 100% +
+
+
+
+
+ Compact Artists + Show artist cards in a compact, horizontal layout +
+ +
+
+
+ Compact Albums + Show album cards in a compact, horizontal layout +
+ +
+
+ - + + +
+
-
- Compact Artists - Show artist cards in a compact, horizontal layout +
+
+ Zipped Bulk Downloads + Download multiple tracks as a single ZIP file (requires browser + support) +
+
- -
-
-
- Compact Albums - Show album cards in a compact, horizontal layout +
+
+ Download Lyrics + Include .lrc files when downloading tracks/albums +
+
- -
-
-
-
-
-
-
-
-
- Zipped Bulk Downloads - Download multiple tracks as a single ZIP file (requires browser support) +
+
+ Romaji Lyrics + Convert Japanese lyrics to Romaji (Latin characters) +
+
- -
-
-
- Download Lyrics - Include .lrc files when downloading tracks/albums +
+
+ Download Quality + Quality for track downloads +
+
- -
-
-
- Romaji Lyrics - Convert Japanese lyrics to Romaji (Latin characters) +
+
+ Lossless Container + Container format for lossless downloads +
+
- -
-
-
- Filename Template - Customize download filenames. Available: {trackNumber}, {artist}, {title}, - {album} -
- -
-
-
- ZIP Folder Template - Customize album folder names. Available: {albumTitle}, {albumArtist}, - {year} -
- -
-
-
- Generate M3U - Include M3U playlist files in downloads -
- -
-
-
- Generate M3U8 - Include extended M3U8 playlist files in downloads -
- -
-
-
- Generate CUE - Include CUE sheets for gapless playback in downloads -
- -
-
-
- Generate NFO - Include NFO files for media center compatibility -
- -
-
-
- Generate JSON - Include JSON files with rich metadata -
- -
-
-
- Relative Paths - Use relative paths in playlist files -
- -
-
-
- Separate Discs in ZIP - Put tracks in Disc folders when a release has multiple discs -
- -
-
-
-
-
-
-
-
-
- Keyboard Shortcuts - View and customize keyboard shortcuts -
- -
-
-
- Cache - Stores API responses to reduce requests -
- -
-
-
- Auto-Update App - Automatically reload when a new version is available -
- -
- -
-
- Analytics - Send anonymous usage data to help improve the app -
- -
-
-
- Reset Local Data - Clear all local storage and cached data (does not affect cloud sync) -
- -
-
-
- Clear Cloud Data - Delete all your data from the cloud (cannot be undone) -
- -
-
-
- Backup & Restore - Export or import your library and history as JSON -
-
- - - -
-
-
-
- Export All Settings - Export all app settings as JSON -
-
- - +
+
+ Cover Art Size + Size for downloaded/embedded cover art +
+
+
+
+ Filename Template + Customize download filenames. Available: {trackNumber}, {artist}, {title}, + {album} +
+ +
+
+
+ ZIP Folder Template + Customize album folder names. Available: {albumTitle}, {albumArtist}, + {year} +
+
-
-
- ADVANCED: Custom Database/Auth - Configure custom PocketBase and Firebase instances -
- -
-
-
+ +
+
- API Instances - Manage and prioritize API instances. + Generate M3U + Include M3U playlist files in downloads
- + +
+
+
+ Generate M3U8 + Include extended M3U8 playlist files in downloads +
+ +
+
+
+ Generate CUE + Include CUE sheets for gapless playback in downloads +
+ +
+
+
+ Generate NFO + Include NFO files for media center compatibility +
+ +
+
+
+ Generate JSON + Include JSON files with rich metadata +
+
-
    -
    -
    - Blocked Content - Manage artists, albums, and tracks you've blocked from recommendations +
    +
    +
    + Relative Paths + Use relative paths in playlist files +
    +
    -
    - -
    +
    +
    + +
    +
    +
    +
    +
    + ADVANCED: Custom Database/Auth + Configure custom PocketBase and Firebase instances +
    + +
    +
    + +
    +
    +
    +
    + API Instances + Manage and prioritize API instances. +
    + +
    +
      +
      +
      +
      +
      + +
      +
      +
      +
      +
      + Keyboard Shortcuts + View and customize keyboard shortcuts +
      + +
      +
      +
      + Cache + Stores API responses to reduce requests +
      + +
      +
      +
      + Auto-Update App + Automatically reload when a new version is available +
      + +
      + +
      +
      + Analytics + Send anonymous usage data to help improve the app +
      + +
      +
      + +
      +
      +
      + Reset Local Data + Clear all local storage and cached data (does not affect cloud sync) +
      + +
      +
      +
      + Clear Cloud Data + Delete all your data from the cloud (cannot be undone) +
      +
      -