tests passing

This commit is contained in:
simojenki
2025-02-15 06:12:29 +00:00
parent cc0dc3704d
commit 0451c3a931
9 changed files with 544 additions and 318 deletions

View File

@@ -23,7 +23,8 @@ export type ArtistSummary = {
export type SimilarArtist = ArtistSummary & { inLibrary: boolean };
export type Artist = ArtistSummary & {
// todo: maybe is should be artist.summary rather than an artist also being a summary?
export type Artist = Pick<ArtistSummary, "id" | "name" | "image"> & {
albums: AlbumSummary[];
similarArtists: SimilarArtist[]
};
@@ -34,12 +35,11 @@ export type AlbumSummary = {
year: string | undefined;
genre: Genre | undefined;
coverArt: BUrn | undefined;
artistName: string | undefined;
artistId: string | undefined;
};
export type Album = AlbumSummary & {};
export type Album = Pick<AlbumSummary, "id" | "name" | "year" | "genre" | "coverArt" | "artistName" | "artistId"> & { tracks: Track[] };
export type Genre = {
name: string;
@@ -175,7 +175,7 @@ export interface MusicLibrary {
artists(q: ArtistQuery): Promise<Result<ArtistSummary>>;
artist(id: string): Promise<Artist>;
albums(q: AlbumQuery): Promise<Result<AlbumSummary>>;
album(id: string): Promise<Album>;
album(id: string): Promise<AlbumSummary>;
tracks(albumId: string): Promise<Track[]>;
track(trackId: string): Promise<Track>;
genres(): Promise<Genre[]>;

View File

@@ -10,7 +10,6 @@ import logger from "./logger";
import { LinkCodes } from "./link_codes";
import {
Album,
AlbumQuery,
AlbumSummary,
ArtistSummary,
@@ -612,7 +611,7 @@ function bindSmapiSoapServiceToExpress(
switch (type) {
case "artist":
return musicLibrary.artist(typeId).then((artist) => {
const [page, total] = slice2<Album>(paging)(
const [page, total] = slice2<AlbumSummary>(paging)(
artist.albums
);
return {

View File

@@ -14,6 +14,7 @@ import {
AlbumQueryType,
PlaylistSummary,
Encoding,
albumToAlbumSummary,
} from "./music_library";
import sharp from "sharp";
import _ from "underscore";
@@ -24,7 +25,7 @@ import axios, { AxiosRequestConfig } from "axios";
import randomstring from "randomstring";
import { b64Encode, b64Decode } from "./b64";
import { BUrn } from "./burn";
import { artist } from "./smapi";
import { album, artist } from "./smapi";
import { URLBuilder } from "./url_builder";
export const BROWSER_HEADERS = {
@@ -159,6 +160,7 @@ export type song = {
transcodedContentType: string | undefined;
type: string | undefined;
userRating: number | undefined;
// todo: this field shouldnt be on song?
starred: string | undefined;
};
@@ -279,7 +281,7 @@ export const artistImageURN = (
};
export const asTrack = (
album: Album,
album: AlbumSummary,
song: song,
customPlayers: CustomPlayers
): Track => ({
@@ -298,7 +300,7 @@ export const asTrack = (
number: song.track || 0,
genre: maybeAsGenre(song.genre),
coverArt: coverArtURN(song.coverArt),
album,
album: album,
artist: {
id: song.artistId,
name: song.artist ? song.artist : "?",
@@ -315,7 +317,7 @@ export const asTrack = (
},
});
export const asAlbum = (album: album): Album => ({
export const asAlbumSummary = (album: album): AlbumSummary => ({
id: album.id,
name: album.name,
year: album.year,
@@ -412,7 +414,7 @@ export const asURLSearchParams = (q: any) => {
export type ImageFetcher = (url: string) => Promise<CoverArt | undefined>;
export const cachingImageFetcher =
(cacheDir: string, delegate: ImageFetcher) =>
(cacheDir: string, delegate: ImageFetcher, makeSharp = sharp) =>
async (url: string): Promise<CoverArt | undefined> => {
const filename = path.join(cacheDir, `${Md5.hashStr(url)}.png`);
return fse
@@ -421,7 +423,7 @@ export const cachingImageFetcher =
.catch(() =>
delegate(url).then((image) => {
if (image) {
return sharp(image.data)
return makeSharp(image.data)
.png()
.toBuffer()
.then((png) => {
@@ -584,19 +586,30 @@ export class Subsonic {
})),
}));
getAlbum = (credentials: Credentials, id: string): Promise<Album> =>
getAlbum = (credentials: Credentials, id: string): Promise<Album> =>
this.getJSON<GetAlbumResponse>(credentials, "/rest/getAlbum", { id })
.then((it) => it.album)
.then((album) => ({
id: album.id,
name: album.name,
year: album.year,
genre: maybeAsGenre(album.genre),
artistId: album.artistId,
artistName: album.artist,
coverArt: coverArtURN(album.coverArt),
}));
.then((album) => {
const x: AlbumSummary = {
id: album.id,
name: album.name,
year: album.year,
genre: maybeAsGenre(album.genre),
artistId: album.artistId,
artistName: album.artist,
coverArt: coverArtURN(album.coverArt)
}
return { summary: x, songs: album.song }
}).then(({ summary, songs }) => {
const x: AlbumSummary = summary
const y: Track[] = songs.map((it) => asTrack(summary, it, this.customPlayers))
return {
...x,
tracks: y
};
});
getArtist = (
credentials: Credentials,
id: string
@@ -647,7 +660,7 @@ export class Subsonic {
.then((it) => it.song)
.then((song) =>
this.getAlbum(credentials, song.albumId!).then((album) =>
asTrack(album, song, this.customPlayers)
asTrack(albumToAlbumSummary(album), song, this.customPlayers)
)
);

View File

@@ -4,7 +4,6 @@ import {
Credentials,
MusicService,
ArtistSummary,
Album,
Result,
slice2,
AlbumQuery,
@@ -15,13 +14,14 @@ import {
Artist,
AuthFailure,
AuthSuccess,
albumToAlbumSummary,
} from "./music_library";
import {
Subsonic,
CustomPlayers,
GetAlbumResponse,
asTrack,
asAlbum,
asAlbumSummary,
PingResponse,
NO_CUSTOM_PLAYERS,
asToken,
@@ -171,11 +171,13 @@ export class SubsonicMusicLibrary implements MusicLibrary {
albums = async (q: AlbumQuery): Promise<Result<AlbumSummary>> =>
this.subsonic.getAlbumList2(this.credentials, q);
album = (id: string): Promise<Album> =>
this.subsonic.getAlbum(this.credentials, id);
// todo: this should probably return an Album
album = (id: string): Promise<AlbumSummary> =>
this.subsonic.getAlbum(this.credentials, id).then(albumToAlbumSummary);
genres = () => this.subsonic.getGenres(this.credentials);
// todo: do we even need this if Album has tracks?
tracks = (albumId: string) =>
this.subsonic
.getJSON<GetAlbumResponse>(this.credentials, "/rest/getAlbum", {
@@ -184,7 +186,7 @@ export class SubsonicMusicLibrary implements MusicLibrary {
.then((it) => it.album)
.then((album) =>
(album.song || []).map((song) =>
asTrack(asAlbum(album), song, this.customPlayers)
asTrack(asAlbumSummary(album), song, this.customPlayers)
)
);
@@ -418,7 +420,7 @@ export class SubsonicMusicLibrary implements MusicLibrary {
songs.map((song) =>
this.subsonic
.getAlbum(this.credentials, song.albumId!)
.then((album) => asTrack(album, song, this.customPlayers))
.then((album) => asTrack(albumToAlbumSummary(album), song, this.customPlayers))
)
)
);
@@ -436,7 +438,7 @@ export class SubsonicMusicLibrary implements MusicLibrary {
songs.map((song) =>
this.subsonic
.getAlbum(this.credentials, song.albumId!)
.then((album) => asTrack(album, song, this.customPlayers))
.then((album) => asTrack(albumToAlbumSummary(album), song, this.customPlayers))
)
)
)