diff --git a/.github/workflows/releaseDev.yml b/.github/workflows/releaseDev.yml
index 0af21bb..87e4026 100644
--- a/.github/workflows/releaseDev.yml
+++ b/.github/workflows/releaseDev.yml
@@ -37,3 +37,11 @@ jobs:
prerelease: true
title: Release ${{ env.VERSION }}
files: ./dist/luna.zip
+
+ - uses: actions/checkout@v3
+ - uses: cachix/install-nix-action@v18
+ - uses: workflow/nix-shell-action@v3
+ with:
+ packages: nix-update
+ script: |
+ nix-update --flake --version=unstable --subpackage pnpmDeps injection
\ No newline at end of file
diff --git a/.github/workflows/releaseMaster.yml b/.github/workflows/releaseMaster.yml
index d715a27..a00555f 100644
--- a/.github/workflows/releaseMaster.yml
+++ b/.github/workflows/releaseMaster.yml
@@ -37,3 +37,11 @@ jobs:
prerelease: false
title: Release ${{ env.VERSION }}
files: ./dist/luna.zip
+
+ - uses: actions/checkout@v3
+ - uses: cachix/install-nix-action@v18
+ - uses: workflow/nix-shell-action@v3
+ with:
+ packages: nix-update
+ script: |
+ nix-update --flake --version=unstable --subpackage pnpmDeps injection
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/TidaLuna.iml b/.idea/TidaLuna.iml
new file mode 100644
index 0000000..df1df64
--- /dev/null
+++ b/.idea/TidaLuna.iml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..62b58ba
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..79ee123
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml
new file mode 100644
index 0000000..f49563c
--- /dev/null
+++ b/.idea/dictionaries/project.xml
@@ -0,0 +1,8 @@
+
+
+
+ cachix
+ pkgs
+
+
+
\ No newline at end of file
diff --git a/.idea/discord.xml b/.idea/discord.xml
new file mode 100644
index 0000000..104c42f
--- /dev/null
+++ b/.idea/discord.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml
new file mode 100644
index 0000000..64ca0f4
--- /dev/null
+++ b/.idea/material_theme_project_new.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..1513acb
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/prettier.xml b/.idea/prettier.xml
new file mode 100644
index 0000000..b0c1c68
--- /dev/null
+++ b/.idea/prettier.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 9bfcd6f..65e6130 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,11 @@ Use one of the following projects:
- Ensure that Tidal is closed when installing or installation may fail.
- You shouldnt need to run as Admin for installing.
+### Nix install
+1. Add this flake in your inputs: inputs.tidaLuna.url = github:Inrixia/TidaLuna
+2. then install the package from the input: inputs'.tidaLuna.packages.default (with flake-parts)
+
+
### Manual Install
1. Download the **luna.zip** release you want to install from https://github.com/Inrixia/TidaLuna/releases
2. Go to your Tidal install resources folder, typically found in:
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..c03cf5a
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,309 @@
+{
+ "nodes": {
+ "cachix": {
+ "inputs": {
+ "devenv": [
+ "devenv"
+ ],
+ "flake-compat": [
+ "devenv"
+ ],
+ "git-hooks": [
+ "devenv"
+ ],
+ "nixpkgs": "nixpkgs"
+ },
+ "locked": {
+ "lastModified": 1744206633,
+ "narHash": "sha256-pb5aYkE8FOoa4n123slgHiOf1UbNSnKe5pEZC+xXD5g=",
+ "owner": "cachix",
+ "repo": "cachix",
+ "rev": "8a60090640b96f9df95d1ab99e5763a586be1404",
+ "type": "github"
+ },
+ "original": {
+ "owner": "cachix",
+ "ref": "latest",
+ "repo": "cachix",
+ "type": "github"
+ }
+ },
+ "devenv": {
+ "inputs": {
+ "cachix": "cachix",
+ "flake-compat": "flake-compat",
+ "git-hooks": "git-hooks",
+ "nix": "nix",
+ "nixpkgs": "nixpkgs_3"
+ },
+ "locked": {
+ "lastModified": 1748273445,
+ "narHash": "sha256-5V0dzpNgQM0CHDsMzh+ludYeu1S+Y+IMjbaskSSdFh0=",
+ "owner": "cachix",
+ "repo": "devenv",
+ "rev": "668a50d8b7bdb19a0131f53c9f6c25c9071e1ffb",
+ "type": "github"
+ },
+ "original": {
+ "owner": "cachix",
+ "repo": "devenv",
+ "type": "github"
+ }
+ },
+ "flake-compat": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1733328505,
+ "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
+ "type": "github"
+ },
+ "original": {
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "type": "github"
+ }
+ },
+ "flake-parts": {
+ "inputs": {
+ "nixpkgs-lib": [
+ "devenv",
+ "nix",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1712014858,
+ "narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
+ "owner": "hercules-ci",
+ "repo": "flake-parts",
+ "rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hercules-ci",
+ "repo": "flake-parts",
+ "type": "github"
+ }
+ },
+ "flake-parts_2": {
+ "inputs": {
+ "nixpkgs-lib": "nixpkgs-lib"
+ },
+ "locked": {
+ "lastModified": 1743550720,
+ "narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=",
+ "owner": "hercules-ci",
+ "repo": "flake-parts",
+ "rev": "c621e8422220273271f52058f618c94e405bb0f5",
+ "type": "github"
+ },
+ "original": {
+ "id": "flake-parts",
+ "type": "indirect"
+ }
+ },
+ "git-hooks": {
+ "inputs": {
+ "flake-compat": [
+ "devenv"
+ ],
+ "gitignore": "gitignore",
+ "nixpkgs": [
+ "devenv",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1746537231,
+ "narHash": "sha256-Wb2xeSyOsCoTCTj7LOoD6cdKLEROyFAArnYoS+noCWo=",
+ "owner": "cachix",
+ "repo": "git-hooks.nix",
+ "rev": "fa466640195d38ec97cf0493d6d6882bc4d14969",
+ "type": "github"
+ },
+ "original": {
+ "owner": "cachix",
+ "repo": "git-hooks.nix",
+ "type": "github"
+ }
+ },
+ "gitignore": {
+ "inputs": {
+ "nixpkgs": [
+ "devenv",
+ "git-hooks",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1709087332,
+ "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
+ "owner": "hercules-ci",
+ "repo": "gitignore.nix",
+ "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hercules-ci",
+ "repo": "gitignore.nix",
+ "type": "github"
+ }
+ },
+ "libgit2": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1697646580,
+ "narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=",
+ "owner": "libgit2",
+ "repo": "libgit2",
+ "rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5",
+ "type": "github"
+ },
+ "original": {
+ "owner": "libgit2",
+ "repo": "libgit2",
+ "type": "github"
+ }
+ },
+ "nix": {
+ "inputs": {
+ "flake-compat": [
+ "devenv"
+ ],
+ "flake-parts": "flake-parts",
+ "libgit2": "libgit2",
+ "nixpkgs": "nixpkgs_2",
+ "nixpkgs-23-11": [
+ "devenv"
+ ],
+ "nixpkgs-regression": [
+ "devenv"
+ ],
+ "pre-commit-hooks": [
+ "devenv"
+ ]
+ },
+ "locked": {
+ "lastModified": 1745930071,
+ "narHash": "sha256-bYyjarS3qSNqxfgc89IoVz8cAFDkF9yPE63EJr+h50s=",
+ "owner": "domenkozar",
+ "repo": "nix",
+ "rev": "b455edf3505f1bf0172b39a735caef94687d0d9c",
+ "type": "github"
+ },
+ "original": {
+ "owner": "domenkozar",
+ "ref": "devenv-2.24",
+ "repo": "nix",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1733212471,
+ "narHash": "sha256-M1+uCoV5igihRfcUKrr1riygbe73/dzNnzPsmaLCmpo=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "55d15ad12a74eb7d4646254e13638ad0c4128776",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs-lib": {
+ "locked": {
+ "lastModified": 1743296961,
+ "narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=",
+ "owner": "nix-community",
+ "repo": "nixpkgs.lib",
+ "rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "repo": "nixpkgs.lib",
+ "type": "github"
+ }
+ },
+ "nixpkgs_2": {
+ "locked": {
+ "lastModified": 1717432640,
+ "narHash": "sha256-+f9c4/ZX5MWDOuB1rKoWj+lBNm0z0rs4CK47HBLxy1o=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "88269ab3044128b7c2f4c7d68448b2fb50456870",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "release-24.05",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs_3": {
+ "locked": {
+ "lastModified": 1746807397,
+ "narHash": "sha256-zU2z0jlkJGWLhdNr/8AJSxqK8XD0IlQgHp3VZcP56Aw=",
+ "owner": "cachix",
+ "repo": "devenv-nixpkgs",
+ "rev": "c5208b594838ea8e6cca5997fbf784b7cca1ca90",
+ "type": "github"
+ },
+ "original": {
+ "owner": "cachix",
+ "ref": "rolling",
+ "repo": "devenv-nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs_4": {
+ "locked": {
+ "lastModified": 1748217807,
+ "narHash": "sha256-P3u2PXxMlo49PutQLnk2PhI/imC69hFl1yY4aT5Nax8=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "3108eaa516ae22c2360928589731a4f1581526ef",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixpkgs-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "devenv": "devenv",
+ "flake-parts": "flake-parts_2",
+ "nixpkgs": "nixpkgs_4",
+ "systems": "systems"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..a04726a
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,26 @@
+{
+ inputs = {
+ nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
+ systems.url = "github:nix-systems/default";
+ };
+
+ outputs = inputs@{ self, systems, flake-parts, nixpkgs, ... }: flake-parts.lib.mkFlake { inherit inputs; } {
+ systems = import systems;
+
+ imports = [
+ ./nix
+ ];
+
+ perSystem = { config, pkgs, system, ... }: {
+ _module.args.pkgs = import inputs.nixpkgs {
+ inherit system;
+ overlays = [ self.overlays.default ];
+ };
+
+ packages.injection =
+ pkgs.callPackage ./nix/package.nix { inherit (pkgs) nodejs fetchFromGitHub; pnpm = pkgs.pnpm_9; };
+
+ packages.default = pkgs.tidaLuna;
+ };
+ };
+}
\ No newline at end of file
diff --git a/nix/default.nix b/nix/default.nix
new file mode 100644
index 0000000..6aee9d1
--- /dev/null
+++ b/nix/default.nix
@@ -0,0 +1,23 @@
+{ withSystem, ... }: {
+ flake.overlays.default = final: prev:
+ withSystem prev.stdenv.hostPlatform.system (
+ { config, ... }:
+ let
+ injection = prev.callPackage ./package.nix { nodejs = prev.nodejs; pnpm = prev.pnpm_9; };
+ in
+ {
+ tidaLuna = prev.tidal-hifi.overrideAttrs (old: {
+ postInstall = ''
+ # Rename app.asar to original.asar
+ mv $out/share/tidal-hifi/resources/app.asar $out/share/tidal-hifi/resources/original.asar
+
+ # Move injection into app folder
+ mkdir -p "$out/share/tidal-hifi/resources/app/"
+ cp -R ${injection}/* $out/share/tidal-hifi/resources/app/
+ '';
+
+ tidaLuna = final.tidal-hifi;
+ });
+ }
+ );
+}
\ No newline at end of file
diff --git a/nix/package.nix b/nix/package.nix
new file mode 100644
index 0000000..51d3535
--- /dev/null
+++ b/nix/package.nix
@@ -0,0 +1,41 @@
+{ stdenv, nodejs, pnpm, fetchFromGitHub, ... }:
+stdenv.mkDerivation (finalAttrs: rec {
+ name = "TidaLuna";
+ pname = "${name}";
+ version = "1.1.0-alpha";
+ src = fetchFromGitHub {
+ owner = "Inrixia";
+ repo = "${name}";
+ rev = "${version}";
+ hash = "sha256-T6J6mI3oxQ7tD8b76M15ajka1S8G9QW8Am3rqj0MaTo=";
+ };
+
+ nativeBuildInputs = [
+ nodejs
+ pnpm.configHook
+ ];
+
+ pnpmDeps = pnpm.fetchDeps {
+ inherit (finalAttrs) pname src version;
+ hash = "sha256-pnuDLjUAOxQUFlf+2FXeX2mpZcahzzVPMRpWZpbyDE4=";
+ };
+
+ buildPhase = ''
+ runHook preBuild
+
+ pnpm install
+ pnpm run build
+
+
+ runHook postBuild
+ '';
+
+ installPhase = ''
+ runHook preInstall
+
+ cp -R "dist" "$out"
+
+ runHook postInstall
+ '';
+
+})
diff --git a/package.json b/package.json
index c5dea64..8d5907b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "luna",
- "version": "1.2.6-alpha",
+ "version": "1.2.9-alpha",
"description": "A client mod for the Tidal music app for plugins",
"author": {
"name": "Inrixia",
diff --git a/plugins/lib/src/classes/Album.ts b/plugins/lib/src/classes/Album.ts
index 6645d4c..0bf1476 100644
--- a/plugins/lib/src/classes/Album.ts
+++ b/plugins/lib/src/classes/Album.ts
@@ -82,8 +82,8 @@ export class Album extends ContentBase implements MediaCollection {
return this.tidalAlbum.numberOfTracks!;
}
public tMediaItems: () => Promise = memoize(async () => {
- const playlistIitems = await TidalApi.albumItems(this.id);
- return playlistIitems?.items ?? [];
+ const albumItems = await TidalApi.albumItems(this.id);
+ return albumItems ?? [];
});
public async mediaItems() {
return MediaItem.fromTMediaItems(await this.tMediaItems());
diff --git a/plugins/lib/src/classes/ContextMenu.ts b/plugins/lib/src/classes/ContextMenu.ts
index 6ac63e0..9e4db73 100644
--- a/plugins/lib/src/classes/ContextMenu.ts
+++ b/plugins/lib/src/classes/ContextMenu.ts
@@ -19,7 +19,7 @@ export class ContextMenu {
* Will return null if the element is not found (usually means no context menu is open)
*/
public static async getCurrent() {
- const contextMenu = await observePromise(`[data-type="list-container__context-menu"]`, 1000);
+ const contextMenu = await observePromise(unloads, `[data-type="list-container__context-menu"]`, 1000);
if (contextMenu !== null) {
const templateButton = contextMenu.querySelector(`div[data-type="contextmenu-item"]`) as Element | undefined;
contextMenu.addButton = (text, onClick) => {
diff --git a/plugins/lib/src/classes/MediaItem/MediaItem.download.native.ts b/plugins/lib/src/classes/MediaItem/MediaItem.download.native.ts
index 72317de..4c9cd20 100644
--- a/plugins/lib/src/classes/MediaItem/MediaItem.download.native.ts
+++ b/plugins/lib/src/classes/MediaItem/MediaItem.download.native.ts
@@ -13,7 +13,7 @@ import type { PlaybackInfo } from "../../helpers";
import type { MetaTags } from "./MediaItem.tags";
const downloads: Record } | undefined> = {};
-export const downloadProgress = async (trackId: redux.ItemId) => downloads[trackId];
+export const downloadProgress = async (trackId: redux.ItemId) => downloads[trackId]?.progress;
export const download = async (playbackInfo: PlaybackInfo, path: string, tags?: MetaTags): Promise => {
if (downloads[playbackInfo.trackId] !== undefined) return downloads[playbackInfo.trackId]!.promise;
try {
diff --git a/plugins/lib/src/classes/MediaItem/MediaItem.ts b/plugins/lib/src/classes/MediaItem/MediaItem.ts
index 081092f..1789ce2 100644
--- a/plugins/lib/src/classes/MediaItem/MediaItem.ts
+++ b/plugins/lib/src/classes/MediaItem/MediaItem.ts
@@ -3,7 +3,7 @@ import type { IRecording, ITrack } from "musicbrainz-api";
import { ftch, ReactiveStore, type Tracer } from "@luna/core";
-import { getPlaybackInfo, type PlaybackInfo } from "../../helpers";
+import { getPlaybackInfo, parseDate, type PlaybackInfo } from "../../helpers";
import { libTrace, unloads } from "../../index.safe";
import * as redux from "../../redux";
import { Album } from "../Album";
@@ -13,7 +13,7 @@ import { PlayState } from "../PlayState";
import { Quality } from "../Quality";
import { TidalApi } from "../TidalApi";
import { download, downloadProgress } from "./MediaItem.download.native";
-import { makeTags, MetaTags } from "./MediaItem.tags";
+import { availableTags, makeTags, MetaTags } from "./MediaItem.tags";
type MediaFormat = {
bitDepth?: number;
@@ -29,6 +29,7 @@ type MediaItemCache = {
export class MediaItem extends ContentBase {
public static readonly trace: Tracer = libTrace.withSource(".MediaItem").trace;
+ public static readonly availableTags = availableTags;
private static cache = ReactiveStore.getStore("@luna/MediaItemCache");
@@ -55,7 +56,7 @@ export class MediaItem extends ContentBase {
return super.fromStore(itemId, "mediaItems", async (mediaItem) => {
mediaItem = mediaItem ??= await this.fetchMediaItem(itemId, contentType);
if (mediaItem === undefined) return;
- return new MediaItem(itemId, mediaItem, await mediaItemCache);
+ return new MediaItem(itemId, mediaItem, contentType, await mediaItemCache);
});
}
public static fromIsrc: (isrc: string) => Promise = memoize(async (isrc) => {
@@ -145,6 +146,7 @@ export class MediaItem extends ContentBase {
constructor(
public readonly id: redux.ItemId,
tidalMediaItem: redux.MediaItem,
+ public readonly contentType: redux.ContentType,
private readonly cache: MediaItemCache,
) {
super();
@@ -230,7 +232,7 @@ export class MediaItem extends ContentBase {
});
public async *isrcs(): AsyncIterable {
- if (this.tidalItem.contentType !== "track") return;
+ if (this.contentType !== "track") return;
const seen = new Set();
if (this.tidalItem.isrc) {
yield this.tidalItem.isrc;
@@ -259,17 +261,20 @@ export class MediaItem extends ContentBase {
});
public releaseDate: () => Promise = memoize(async () => {
- let releaseDate = this.tidalItem.releaseDate ?? this.tidalItem.streamStartDate;
+ let releaseDate = parseDate(this.tidalItem.releaseDate) ?? parseDate(this.tidalItem.streamStartDate);
if (releaseDate === undefined) {
const brainzItem = await this.brainzItem();
- releaseDate = brainzItem?.recording?.["first-release-date"] ?? releaseDate;
+ releaseDate = parseDate(brainzItem?.recording?.["first-release-date"]);
}
if (releaseDate === undefined) {
const album = await this.album();
- releaseDate = album?.releaseDate ?? releaseDate;
- releaseDate ??= (await album?.brainzAlbum())?.date ?? releaseDate;
+ releaseDate = parseDate(album?.releaseDate);
+ if (releaseDate === undefined) {
+ const brainzAlbum = await album?.brainzAlbum();
+ releaseDate ??= parseDate(brainzAlbum?.date);
+ }
}
- if (releaseDate) return new Date(releaseDate);
+ return releaseDate;
});
/**
@@ -298,9 +303,6 @@ export class MediaItem extends ContentBase {
// #endregion
// #region Properties
- public get contentType() {
- return this.tidalItem.contentType;
- }
public get trackNumber() {
return this.tidalItem.trackNumber;
}
@@ -311,18 +313,18 @@ export class MediaItem extends ContentBase {
return this.tidalItem.peak;
}
public get replayGain(): number {
- if (this.tidalItem.contentType !== "track") return 0;
+ if (this.contentType !== "track") return 0;
return this.tidalItem.replayGain;
}
public get url(): string {
return this.tidalItem.url;
}
public get qualityTags(): Quality[] {
- if (this.tidalItem.contentType !== "track") return [];
+ if (this.contentType !== "track") return [];
return Quality.fromMetaTags(this.tidalItem.mediaMetadata?.tags);
}
public get bestQuality(): Quality {
- if (this.tidalItem.contentType !== "track") {
+ if (this.contentType !== "track") {
this.trace.warn("MediaItem quality called on non-track!", this);
return Quality.High;
}
diff --git a/plugins/lib/src/classes/TidalApi/index.ts b/plugins/lib/src/classes/TidalApi/index.ts
index 4304fb3..2147b11 100644
--- a/plugins/lib/src/classes/TidalApi/index.ts
+++ b/plugins/lib/src/classes/TidalApi/index.ts
@@ -8,6 +8,7 @@ import { getCredentials } from "../../helpers";
import { libTrace } from "../../index.safe";
import * as redux from "../../redux";
+import type { AlbumPage } from "./types/AlbumPage";
import { PlaybackInfoResponse } from "./types/PlaybackInfo";
export type * from "./types";
@@ -61,9 +62,16 @@ export class TidalApi {
return this.fetch(`https://desktop.tidal.com/v1/albums/${albumId}?${this.queryArgs()}`);
}
public static async albumItems(albumId: redux.ItemId) {
- return this.fetch<{ items: redux.MediaItem[]; totalNumberOfItems: number; offset: number; limit: -1 }>(
- `https://desktop.tidal.com/v1/albums/${albumId}/items?${this.queryArgs()}&limit=-1`,
+ const albumPage = await this.fetch(
+ `https://desktop.tidal.com/v1/pages/album?albumId=${albumId}&countryCode=NZ&locale=en_US&deviceType=DESKTOP`,
);
+ for (const row of albumPage?.rows ?? []) {
+ for (const module of row.modules) {
+ if (module.type === "ALBUM_ITEMS" && module.pagedList) {
+ return module.pagedList.items;
+ }
+ }
+ }
}
public static playlist(playlistUUID: redux.ItemId) {
diff --git a/plugins/lib/src/classes/TidalApi/types/AlbumPage.ts b/plugins/lib/src/classes/TidalApi/types/AlbumPage.ts
new file mode 100644
index 0000000..a3f40c1
--- /dev/null
+++ b/plugins/lib/src/classes/TidalApi/types/AlbumPage.ts
@@ -0,0 +1,15 @@
+import type { redux } from "@luna/lib";
+
+export type AlbumPage = {
+ id: string;
+ title: string;
+ rows: {
+ modules: {
+ /** ALBUM_ITEMS is what we want */
+ type: string;
+ pagedList?: {
+ items: redux.MediaItem[];
+ };
+ }[];
+ }[];
+};
diff --git a/plugins/lib/src/helpers/index.ts b/plugins/lib/src/helpers/index.ts
index 7e4b1f7..b38e3d3 100644
--- a/plugins/lib/src/helpers/index.ts
+++ b/plugins/lib/src/helpers/index.ts
@@ -2,4 +2,5 @@ export * from "./getCredentials";
export * from "./getPlaybackInfo";
export * from "./getPlaybackInfo.dasha.native";
export * from "./observable";
+export * from "./parseDate";
export * from "./safeTimeout";
diff --git a/plugins/lib/src/helpers/observable.ts b/plugins/lib/src/helpers/observable.ts
index 94bd26d..b974939 100644
--- a/plugins/lib/src/helpers/observable.ts
+++ b/plugins/lib/src/helpers/observable.ts
@@ -1,6 +1,7 @@
// based on: https://github.com/KaiHax/kaihax/blob/master/src/patcher.ts
import type { VoidFn } from "@inrixia/helpers";
+import type { LunaUnloads } from "@luna/core";
import { unloads } from "../index.safe";
export type ObserveCallback = (elem: E) => unknown;
@@ -31,7 +32,7 @@ const observer = new MutationObserver((records) => {
* @param cb The callback function to execute when a matching element is found cast to type T.
* @returns An `Unload` function that, when called, will stop observing for this selector and callback pair.
*/
-export const observe = (selector: string, cb: ObserveCallback): VoidFn => {
+export const observe = (unloads: LunaUnloads, selector: string, cb: ObserveCallback): VoidFn => {
if (observables.size === 0)
observer.observe(document.body, {
subtree: true,
@@ -39,10 +40,12 @@ export const observe = (selector: string, cb: Obser
});
const entry: ObserveEntry = [selector, cb as ObserveCallback];
observables.add(entry);
- return () => {
+ const unload = () => {
observables.delete(entry);
if (observables.size === 0) observer.disconnect();
};
+ unloads.add(unload);
+ return unload;
};
// Disconnect and remove observables on unload
@@ -56,9 +59,9 @@ unloads.add(observables.clear.bind(observables));
* @param timeoutMs The maximum time (in milliseconds) to wait for the element to appear.
* @returns A Promise that resolves with the found Element (cast to type T) or null if the timeout is reached.
*/
-export const observePromise = (selector: string, timeoutMs: number = 1000): Promise =>
+export const observePromise = (unloads: LunaUnloads, selector: string, timeoutMs: number = 1000): Promise =>
new Promise((res) => {
- const unob = observe(selector, (elem) => {
+ const unob = observe(unloads, selector, (elem) => {
unob();
clearTimeout(timeout);
res(elem as T);
diff --git a/plugins/lib/src/helpers/parseDate.ts b/plugins/lib/src/helpers/parseDate.ts
new file mode 100644
index 0000000..54bee04
--- /dev/null
+++ b/plugins/lib/src/helpers/parseDate.ts
@@ -0,0 +1,6 @@
+export const parseDate = (date: string | Date | null | undefined): Date | undefined => {
+ if (date === null || date === undefined) return undefined;
+ if (typeof date === "string") date = new Date(date);
+ if (isNaN(date.getTime())) return undefined;
+ return date;
+};
diff --git a/plugins/lib/src/index.ts b/plugins/lib/src/index.ts
index 5d26b8d..ec6cb80 100644
--- a/plugins/lib/src/index.ts
+++ b/plugins/lib/src/index.ts
@@ -8,7 +8,9 @@ export * as redux from "./redux";
import { observePromise } from "./helpers/observable";
-observePromise("div[class^='_mainContainer'] > div[class^='_bar'] > div[class^='_title']", 30000).then((title) => {
+import { unloads } from "./index.safe";
+
+observePromise(unloads, "div[class^='_mainContainer'] > div[class^='_bar'] > div[class^='_title']", 30000).then((title) => {
if (title !== null) title.innerHTML = 'TIDALuna BETA';
});
diff --git a/plugins/ui/src/SettingsPage/PluginStoreTab/LunaStore.tsx b/plugins/ui/src/SettingsPage/PluginStoreTab/LunaStore.tsx
index 67cae90..df028b9 100644
--- a/plugins/ui/src/SettingsPage/PluginStoreTab/LunaStore.tsx
+++ b/plugins/ui/src/SettingsPage/PluginStoreTab/LunaStore.tsx
@@ -82,7 +82,9 @@ export const LunaStore = React.memo(({ url, onRemove }: { url: string; onRemove:
}
/>
- {pkg?.plugins.map((plugin) => } />)}
+ {pkg?.plugins.map((plugin) => (
+ } />
+ ))}
);
diff --git a/plugins/ui/src/SettingsPage/PluginStoreTab/LunaStorePlugin.tsx b/plugins/ui/src/SettingsPage/PluginStoreTab/LunaStorePlugin.tsx
index 5d505a2..5a63e77 100644
--- a/plugins/ui/src/SettingsPage/PluginStoreTab/LunaStorePlugin.tsx
+++ b/plugins/ui/src/SettingsPage/PluginStoreTab/LunaStorePlugin.tsx
@@ -18,6 +18,8 @@ export const LunaStorePlugin = React.memo(({ url }: { url: string }) => {
if (!plugin) return null;
+ const version = url.startsWith("http://127.0.0.1") ? `${plugin.package?.version ?? ""} [DEV]` : plugin.package?.version;
+
return (
setIsHovered(true)}
@@ -41,6 +43,7 @@ export const LunaStorePlugin = React.memo(({ url }: { url: string }) => {
{
};
// Devs! Add your stores here <3
-// TODO: Abstract this to a git repo with versioned stores
+// TODO: Abstract this to a git repo
addToStores("https://github.com/Inrixia/neptune-plugins/releases/download/dev/store.json");
+addToStores("https://github.com/wont-stream/lunar/releases/download/dev/store.json");
+addToStores("https://github.com/jxnxsdev/luna-plugins/releases/download/latest/store.json");
export const PluginStoreTab = React.memo(() => {
const [_storeUrls, setPluginStores] = useState(obyStore.unwrap(storeUrls));
diff --git a/plugins/ui/src/SettingsPage/PluginsTab/LunaPluginHeader.tsx b/plugins/ui/src/SettingsPage/PluginsTab/LunaPluginHeader.tsx
index 3dec59c..fa1b61d 100644
--- a/plugins/ui/src/SettingsPage/PluginsTab/LunaPluginHeader.tsx
+++ b/plugins/ui/src/SettingsPage/PluginsTab/LunaPluginHeader.tsx
@@ -10,16 +10,20 @@ import { LunaAuthorDisplay, LunaLink } from "../../components";
export interface LunaPluginComponentProps extends PropsWithChildren {
name: string;
+ version?: string;
link?: string;
loadError?: string;
author?: LunaAuthor | string;
desc?: ReactNode;
sx?: BoxProps["sx"];
}
-export const LunaPluginHeader = React.memo(({ name, loadError, author, desc, children, sx, link }: LunaPluginComponentProps) => (
+export const LunaPluginHeader = React.memo(({ name, version, loadError, author, desc, children, sx, link }: LunaPluginComponentProps) => (
- } />
+
+ {name}
+ {version && }
+
{children}
{loadError && (
(
height: 40,
...props.sx,
}}
- children={props.children}
+ children={props.children ?? props.title}
/>
}
/>
diff --git a/render/src/LunaPlugin.ts b/render/src/LunaPlugin.ts
index b52219c..6d9ea56 100644
--- a/render/src/LunaPlugin.ts
+++ b/render/src/LunaPlugin.ts
@@ -108,7 +108,7 @@ export class LunaPlugin {
if (name in this.plugins) return this.plugins[name];
// Disable liveReload on load so people dont accidentally leave it on
- storeInit.liveReload ??= false;
+ storeInit.liveReload = false;
const store = await LunaPlugin.pluginStorage.getReactive(name);
Object.assign(store, storeInit);