Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export DIRENV_WARN_TIMEOUT=20s

eval "$(devenv direnvrc)"

# The use_devenv function supports passing flags to the devenv command
# For example: use devenv --impure --option services.postgres.enable:bool true
use devenv
10 changes: 9 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
node_modules/
dist/
dist/

# Devenv
.devenv/
.direnv/
.devenv*

# Jetbrains IDE
.idea/
103 changes: 103 additions & 0 deletions devenv.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
{
"nodes": {
"devenv": {
"locked": {
"dir": "src/modules",
"lastModified": 1748273445,
"owner": "cachix",
"repo": "devenv",
"rev": "668a50d8b7bdb19a0131f53c9f6c25c9071e1ffb",
"type": "github"
},
"original": {
"dir": "src/modules",
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1747046372,
"owner": "edolstra",
"repo": "flake-compat",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"git-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1747372754,
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "80479b6ec16fefd9c1db3ea13aeb038c60530f46",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"git-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1746807397,
"owner": "cachix",
"repo": "devenv-nixpkgs",
"rev": "c5208b594838ea8e6cca5997fbf784b7cca1ca90",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "rolling",
"repo": "devenv-nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"git-hooks": "git-hooks",
"nixpkgs": "nixpkgs",
"pre-commit-hooks": [
"git-hooks"
]
}
}
},
"root": "root",
"version": 7
}
11 changes: 11 additions & 0 deletions devenv.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
languages.javascript = {
enable = true;
corepack.enable = true;
pnpm = {
enable = true;
install.enable = true;
};

};
}
5 changes: 5 additions & 0 deletions devenv.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
inputs:
nixpkgs:
url: github:cachix/devenv-nixpkgs/rolling

allowUnfree: true
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
4 changes: 2 additions & 2 deletions plugins/lib/src/classes/Album.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ export class Album extends ContentBase implements MediaCollection {
return this.tidalAlbum.numberOfTracks!;
}
public tMediaItems: () => Promise<redux.MediaItem[]> = 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());
Expand Down
2 changes: 1 addition & 1 deletion plugins/lib/src/classes/ContextMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ContextMenuElem>(`[data-type="list-container__context-menu"]`, 1000);
const contextMenu = await observePromise<ContextMenuElem>(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) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type { PlaybackInfo } from "../../helpers";
import type { MetaTags } from "./MediaItem.tags";

const downloads: Record<redux.ItemId, { progress: FetchProgress; promise: Promise<void> } | 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<void> => {
if (downloads[playbackInfo.trackId] !== undefined) return downloads[playbackInfo.trackId]!.promise;
try {
Expand Down
32 changes: 17 additions & 15 deletions plugins/lib/src/classes/MediaItem/MediaItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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;
Expand All @@ -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");

Expand All @@ -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<MediaItem | undefined> = memoize(async (isrc) => {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -230,7 +232,7 @@ export class MediaItem extends ContentBase {
});

public async *isrcs(): AsyncIterable<string> {
if (this.tidalItem.contentType !== "track") return;
if (this.contentType !== "track") return;
const seen = new Set<string>();
if (this.tidalItem.isrc) {
yield this.tidalItem.isrc;
Expand Down Expand Up @@ -259,17 +261,20 @@ export class MediaItem extends ContentBase {
});

public releaseDate: () => Promise<Date | undefined> = 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;
});

/**
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand Down
12 changes: 10 additions & 2 deletions plugins/lib/src/classes/TidalApi/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -61,9 +62,16 @@ export class TidalApi {
return this.fetch<redux.Album>(`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<AlbumPage>(
`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) {
Expand Down
15 changes: 15 additions & 0 deletions plugins/lib/src/classes/TidalApi/types/AlbumPage.ts
Original file line number Diff line number Diff line change
@@ -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[];
};
}[];
}[];
};
1 change: 1 addition & 0 deletions plugins/lib/src/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export * from "./getCredentials";
export * from "./getPlaybackInfo";
export * from "./getPlaybackInfo.dasha.native";
export * from "./observable";
export * from "./parseDate";
export * from "./safeTimeout";
11 changes: 7 additions & 4 deletions plugins/lib/src/helpers/observable.ts
Original file line number Diff line number Diff line change
@@ -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<E extends Element = Element> = (elem: E) => unknown;
Expand Down Expand Up @@ -31,18 +32,20 @@ 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 = <T extends Element = Element>(selector: string, cb: ObserveCallback<T>): VoidFn => {
export const observe = <T extends Element = Element>(unloads: LunaUnloads, selector: string, cb: ObserveCallback<T>): VoidFn => {
if (observables.size === 0)
observer.observe(document.body, {
subtree: true,
childList: true,
});
const entry: ObserveEntry = [selector, cb as ObserveCallback<Element>];
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
Expand All @@ -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 = <T extends Element>(selector: string, timeoutMs: number = 1000): Promise<T | null> =>
export const observePromise = <T extends Element>(unloads: LunaUnloads, selector: string, timeoutMs: number = 1000): Promise<T | null> =>
new Promise<T | null>((res) => {
const unob = observe(selector, (elem) => {
const unob = observe(unloads, selector, (elem) => {
unob();
clearTimeout(timeout);
res(elem as T);
Expand Down
6 changes: 6 additions & 0 deletions plugins/lib/src/helpers/parseDate.ts
Original file line number Diff line number Diff line change
@@ -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;
};
4 changes: 3 additions & 1 deletion plugins/lib/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = 'TIDA<b><span style="color: #32f4ff;">Luna</span></b> <span style="color: red;">BETA</span>';
});

Expand Down
Loading