From a06ae2e18e28dd1ae2a033a3fa84d0b4f4363107 Mon Sep 17 00:00:00 2001 From: simojenki Date: Wed, 21 Apr 2021 14:35:21 +1000 Subject: [PATCH] Linking Album->Artist so that artist name shows on albumLists --- src/music_service.ts | 5 +++ src/navidrome.ts | 10 ++++++ src/server.ts | 2 +- src/smapi.ts | 80 +++++++++++++++++++++++------------------ src/sonos.ts | 2 +- tests/builders.ts | 48 ++++++++++++++++++------- tests/navidrome.test.ts | 32 +++++++---------- tests/smapi.test.ts | 48 ++++++++++++++++++++----- 8 files changed, 151 insertions(+), 76 deletions(-) diff --git a/src/music_service.ts b/src/music_service.ts index 62813cb..a76fdec 100644 --- a/src/music_service.ts +++ b/src/music_service.ts @@ -50,6 +50,9 @@ export type AlbumSummary = { name: string; year: string | undefined; genre: Genre | undefined; + + artistName: string; + artistId: string; }; export type Album = AlbumSummary & {}; @@ -111,6 +114,8 @@ export const albumToAlbumSummary = (it: Album): AlbumSummary => ({ name: it.name, year: it.year, genre: it.genre, + artistName: it.artistName, + artistId: it.artistId, }); export type StreamingHeader = "content-type" | "content-length" | "content-range" | "accept-ranges"; diff --git a/src/navidrome.ts b/src/navidrome.ts index 3068a7d..e95185d 100644 --- a/src/navidrome.ts +++ b/src/navidrome.ts @@ -69,6 +69,8 @@ export type album = { _genre: string | undefined; _year: string | undefined; _coverArt: string | undefined; + _artist: string; + _artistId: string; }; export type artistSummary = { @@ -215,6 +217,8 @@ const asAlbum = (album: album) => ({ name: album._name, year: album._year, genre: maybeAsGenre(album._genre), + artistId: album._artistId, + artistName: album._artist }); export const asGenre = (genreName: string) => ({ @@ -361,6 +365,8 @@ export class Navidrome implements MusicService { name: album._name, year: album._year, genre: maybeAsGenre(album._genre), + artistId: album._artistId, + artistName: album._artist })); getArtist = ( @@ -379,6 +385,8 @@ export class Navidrome implements MusicService { name: album._name, year: album._year, genre: maybeAsGenre(album._genre), + artistId: it._id, + artistName: it._name, })), })); @@ -422,6 +430,8 @@ export class Navidrome implements MusicService { name: album._name, year: album._year, genre: maybeAsGenre(album._genre), + artistId: album._artistId, + artistName: album._artist })); search3 = (credentials: Credentials, q: any) => diff --git a/src/server.ts b/src/server.ts index 3ced1fe..d7150eb 100644 --- a/src/server.ts +++ b/src/server.ts @@ -135,7 +135,7 @@ function server( - + `); }); diff --git a/src/smapi.ts b/src/smapi.ts index c3318dc..d25edd1 100644 --- a/src/smapi.ts +++ b/src/smapi.ts @@ -188,30 +188,15 @@ class SonosSoap { } } +export type ContainerType = "container" | "search" | "albumList"; + export type Container = { - itemType: "container" | "search"; + itemType: ContainerType; id: string; title: string; + displayType: string | undefined }; -const container = ({ - id, - title, -}: { - id: string; - title: string; -}): Container => ({ - itemType: "container", - id, - title, -}); - -const search = ({ id, title }: { id: string; title: string }): Container => ({ - itemType: "search", - id, - title, -}); - const genre = (genre: Genre) => ({ itemType: "container", id: `genre:${genre.id}`, @@ -239,6 +224,8 @@ export const album = ( ) => ({ itemType: "album", id: `album:${album.id}`, + artist: album.artistName, + artistId: album.artistId, title: album.name, albumArtURI: defaultAlbumArtURI(webAddress, accessToken, album), canPlay: true, @@ -262,10 +249,10 @@ export const track = ( albumArtURI: defaultAlbumArtURI(webAddress, accessToken, track.album), artist: track.artist.name, artistId: track.artist.id, - duration: `${track.duration}`, + duration: track.duration, genre: track.album.genre?.name, genreId: track.album.genre?.id, - trackNumber: `${track.number}`, + trackNumber: track.number, }, }); @@ -490,23 +477,46 @@ function bindSmapiSoapServiceToExpress( case "root": return getMetadataResult({ mediaCollection: [ - container({ id: "artists", title: "Artists" }), - container({ id: "albums", title: "Albums" }), - container({ id: "genres", title: "Genres" }), - container({ id: "randomAlbums", title: "Random" }), - container({ id: "starredAlbums", title: "Starred" }), - container({ + { + itemType: "container", + id: "artists", + title: "Artists", + }, + { + itemType: "albumList", + id: "albums", + title: "Albums", + }, + { + itemType: "container", + id: "genres", + title: "Genres", + }, + { + itemType: "albumList", + id: "randomAlbums", + title: "Random", + }, + { + itemType: "albumList", + id: "starredAlbums", + title: "Starred", + }, + { + itemType: "albumList", id: "recentlyAdded", title: "Recently Added", - }), - container({ + }, + { + itemType: "albumList", id: "recentlyPlayed", title: "Recently Played", - }), - container({ + }, + { + itemType: "albumList", id: "mostPlayed", title: "Most Played", - }), + }, ], index: 0, total: 8, @@ -514,9 +524,9 @@ function bindSmapiSoapServiceToExpress( case "search": return getMetadataResult({ mediaCollection: [ - search({ id: "artists", title: "Artists" }), - search({ id: "albums", title: "Albums" }), - search({ id: "tracks", title: "Tracks" }), + { itemType: "search", id: "artists", title: "Artists" }, + { itemType: "search", id: "albums", title: "Albums" }, + { itemType: "search", id: "tracks", title: "Tracks" }, ], index: 0, total: 3, diff --git a/src/sonos.ts b/src/sonos.ts index d055bc1..7003c1a 100644 --- a/src/sonos.ts +++ b/src/sonos.ts @@ -7,7 +7,7 @@ import logger from "./logger"; import { SOAP_PATH, STRINGS_ROUTE, PRESENTATION_MAP_ROUTE } from "./smapi"; import qs from "querystring" -export const PRESENTATION_AND_STRINGS_VERSION = "12"; +export const PRESENTATION_AND_STRINGS_VERSION = "15"; export type Capability = | "search" diff --git a/tests/builders.ts b/tests/builders.ts index 68323b5..cdd98a8 100644 --- a/tests/builders.ts +++ b/tests/builders.ts @@ -4,6 +4,7 @@ import { Credentials } from "../src/smapi"; import { Service, Device } from "../src/sonos"; import { Album, Artist, Track, albumToAlbumSummary, artistToArtistSummary } from "../src/music_service"; +import randomString from "../src/random_string"; const randomInt = (max: number) => Math.floor(Math.random() * Math.floor(max)); const randomIpAddress = () => `127.0.${randomInt(255)}.${randomInt(255)}`; @@ -70,7 +71,7 @@ export function someCredentials(token: string): Credentials { export function anArtist(fields: Partial = {}): Artist { const id = uuid(); - return { + const artist = { id, name: `Artist ${id}`, albums: [anAlbum(), anAlbum(), anAlbum()], @@ -85,6 +86,11 @@ export function anArtist(fields: Partial = {}): Artist { ], ...fields, }; + artist.albums.forEach(album => { + album.artistId = artist.id; + album.artistName = artist.name; + }) + return artist; } export const HIP_HOP = { id: "genre_hip_hop", name: "Hip-Hop" }; @@ -123,25 +129,33 @@ export function anAlbum(fields: Partial = {}): Album { name: `Album ${id}`, genre: randomGenre(), year: `19${randomInt(99)}`, + artistId: `Artist ${uuid()}`, + artistName: `Artist ${randomString()}`, ...fields, }; } +export const BLONDIE_ID = uuid(); +export const BLONDIE_NAME = "Blondie"; export const BLONDIE: Artist = { - id: uuid(), - name: "Blondie", + id: BLONDIE_ID, + name: BLONDIE_NAME, albums: [ { id: uuid(), name: "Blondie", year: "1976", genre: NEW_WAVE, + artistId: BLONDIE_ID, + artistName: BLONDIE_NAME }, { id: uuid(), name: "Parallel Lines", year: "1978", genre: POP_ROCK, + artistId: BLONDIE_ID, + artistName: BLONDIE_NAME }, ], image: { @@ -152,13 +166,15 @@ export const BLONDIE: Artist = { similarArtists: [], }; +export const BOB_MARLEY_ID = uuid(); +export const BOB_MARLEY_NAME = "Bob Marley"; export const BOB_MARLEY: Artist = { - id: uuid(), - name: "Bob Marley", + id: BOB_MARLEY_ID, + name: BOB_MARLEY_NAME, albums: [ - { id: uuid(), name: "Burin'", year: "1973", genre: REGGAE }, - { id: uuid(), name: "Exodus", year: "1977", genre: REGGAE }, - { id: uuid(), name: "Kaya", year: "1978", genre: SKA }, + { id: uuid(), name: "Burin'", year: "1973", genre: REGGAE, artistId: BOB_MARLEY_ID, artistName: BOB_MARLEY_NAME }, + { id: uuid(), name: "Exodus", year: "1977", genre: REGGAE, artistId: BOB_MARLEY_ID, artistName: BOB_MARLEY_NAME }, + { id: uuid(), name: "Kaya", year: "1978", genre: SKA, artistId: BOB_MARLEY_ID, artistName: BOB_MARLEY_NAME }, ], image: { small: "http://localhost/BOB_MARLEY/sml", @@ -168,9 +184,11 @@ export const BOB_MARLEY: Artist = { similarArtists: [], }; +export const MADONNA_ID = uuid(); +export const MADONNA_NAME = "Madonna"; export const MADONNA: Artist = { - id: uuid(), - name: "Madonna", + id: MADONNA_ID, + name: MADONNA_NAME, albums: [], image: { small: "http://localhost/MADONNA/sml", @@ -180,21 +198,27 @@ export const MADONNA: Artist = { similarArtists: [], }; +export const METALLICA_ID = uuid(); +export const METALLICA_NAME = "Metallica"; export const METALLICA: Artist = { - id: uuid(), - name: "Metallica", + id: METALLICA_ID, + name: METALLICA_NAME, albums: [ { id: uuid(), name: "Ride the Lightening", year: "1984", genre: METAL, + artistId: METALLICA_ID, + artistName: METALLICA_NAME, }, { id: uuid(), name: "Master of Puppets", year: "1986", genre: METAL, + artistId: METALLICA_ID, + artistName: METALLICA_NAME, }, ], image: { diff --git a/tests/navidrome.test.ts b/tests/navidrome.test.ts index cf7eb29..8de93ea 100644 --- a/tests/navidrome.test.ts +++ b/tests/navidrome.test.ts @@ -1374,26 +1374,24 @@ describe("Navidrome", () => { const tripHop = asGenre("Trip-Hop"); const album = anAlbum({ id: "album1", name: "Burnin", genre: hipHop }); - const albumSummary = albumToAlbumSummary(album); const artist = anArtist({ id: "artist1", name: "Bob Marley", albums: [album], }); - const artistSummary = artistToArtistSummary(artist); const tracks = [ - aTrack({ artist: artistSummary, album: albumSummary, genre: hipHop }), - aTrack({ artist: artistSummary, album: albumSummary, genre: hipHop }), + aTrack({ artist: artistToArtistSummary(artist), album: albumToAlbumSummary(album), genre: hipHop }), + aTrack({ artist: artistToArtistSummary(artist), album: albumToAlbumSummary(album), genre: hipHop }), aTrack({ - artist: artistSummary, - album: albumSummary, + artist: artistToArtistSummary(artist), + album: albumToAlbumSummary(album), genre: tripHop, }), aTrack({ - artist: artistSummary, - album: albumSummary, + artist: artistToArtistSummary(artist), + album: albumToAlbumSummary(album), genre: tripHop, }), ]; @@ -1433,19 +1431,17 @@ describe("Navidrome", () => { name: "Burnin", genre: flipFlop, }); - const albumSummary = albumToAlbumSummary(album); const artist = anArtist({ id: "artist1", name: "Bob Marley", albums: [album], }); - const artistSummary = artistToArtistSummary(artist); const tracks = [ aTrack({ - artist: artistSummary, - album: albumSummary, + artist: artistToArtistSummary(artist), + album: albumToAlbumSummary(album), genre: flipFlop, }), ]; @@ -1520,18 +1516,16 @@ describe("Navidrome", () => { const pop = asGenre("Pop"); const album = anAlbum({ id: "album1", name: "Burnin", genre: pop }); - const albumSummary = albumToAlbumSummary(album); const artist = anArtist({ id: "artist1", name: "Bob Marley", albums: [album], }); - const artistSummary = artistToArtistSummary(artist); const track = aTrack({ - artist: artistSummary, - album: albumSummary, + artist: artistToArtistSummary(artist), + album: albumToAlbumSummary(album), genre: pop, }); @@ -2730,11 +2724,11 @@ describe("Navidrome", () => { describe("searchAlbums", () => { describe("when there is 1 search results", () => { it("should return true", async () => { - const artist = anArtist({ name: "#1" }); const album = anAlbum({ name: "foo woo", genre: { id: "pop", name: "pop" }, }); + const artist = anArtist({ name: "#1", albums:[album] }); mockGET .mockImplementationOnce(() => Promise.resolve(ok(PING_OK))) @@ -2765,17 +2759,17 @@ describe("Navidrome", () => { describe("when there are many search results", () => { it("should return true", async () => { - const artist1 = anArtist({ name: "artist1" }); const album1 = anAlbum({ name: "album1", genre: { id: "pop", name: "pop" }, }); + const artist1 = anArtist({ name: "artist1", albums: [album1] }); - const artist2 = anArtist({ name: "artist2" }); const album2 = anAlbum({ name: "album2", genre: { id: "pop", name: "pop" }, }); + const artist2 = anArtist({ name: "artist2", albums: [album2] }); mockGET .mockImplementationOnce(() => Promise.resolve(ok(PING_OK))) diff --git a/tests/smapi.test.ts b/tests/smapi.test.ts index e088d61..d18a96a 100644 --- a/tests/smapi.test.ts +++ b/tests/smapi.test.ts @@ -241,6 +241,8 @@ describe("album", () => { title: someAlbum.name, albumArtURI: defaultAlbumArtURI(webAddress, accessToken, someAlbum), canPlay: true, + artist: someAlbum.artistName, + artistId: someAlbum.artistId }); }); }); @@ -633,7 +635,7 @@ describe("api", () => { musicLibrary.searchTracks.mockResolvedValue([track1, track2]); }); - it.only("should return the tracks", async () => { + it("should return the tracks", async () => { const term = "whoopie"; const result = await ws.searchAsync({ @@ -642,7 +644,15 @@ describe("api", () => { }); expect(result[0]).toEqual( searchResult({ - mediaCollection: tracks.map((it) => track(rootUrl, accessToken, it)), + mediaCollection: tracks.map((it) => { + const t = track(rootUrl, accessToken, it) as any; + t.trackMetadata = { + ...t.trackMetadata, + duration: `${t.trackMetadata.duration}`, + trackNumber: `${t.trackMetadata.trackNumber}`, + } + return t; + }), index: 0, total: 2, }) @@ -722,30 +732,30 @@ describe("api", () => { getMetadataResult({ mediaCollection: [ { itemType: "container", id: "artists", title: "Artists" }, - { itemType: "container", id: "albums", title: "Albums" }, + { itemType: "albumList", id: "albums", title: "Albums" }, { itemType: "container", id: "genres", title: "Genres" }, { - itemType: "container", + itemType: "albumList", id: "randomAlbums", title: "Random", }, { - itemType: "container", + itemType: "albumList", id: "starredAlbums", title: "Starred", }, { - itemType: "container", + itemType: "albumList", id: "recentlyAdded", title: "Recently Added", }, { - itemType: "container", + itemType: "albumList", id: "recentlyPlayed", title: "Recently Played", }, { - itemType: "container", + itemType: "albumList", id: "mostPlayed", title: "Most Played", }, @@ -853,6 +863,8 @@ describe("api", () => { title: it.name, albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it), canPlay: true, + artistId: it.artistId, + artist: it.artistName })), index: 0, total: artistWithManyAlbums.albums.length, @@ -884,6 +896,8 @@ describe("api", () => { title: it.name, albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it), canPlay: true, + artistId: it.artistId, + artist: it.artistName })), index: 2, total: artistWithManyAlbums.albums.length, @@ -1137,6 +1151,8 @@ describe("api", () => { title: it.name, albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it), canPlay: true, + artistId: it.artistId, + artist: it.artistName })), index: 0, total: 6, @@ -1180,6 +1196,8 @@ describe("api", () => { title: it.name, albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it), canPlay: true, + artistId: it.artistId, + artist: it.artistName })), index: 0, total: 6, @@ -1223,6 +1241,8 @@ describe("api", () => { title: it.name, albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it), canPlay: true, + artistId: it.artistId, + artist: it.artistName })), index: 0, total: 6, @@ -1266,6 +1286,8 @@ describe("api", () => { title: it.name, albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it), canPlay: true, + artistId: it.artistId, + artist: it.artistName })), index: 0, total: 6, @@ -1309,6 +1331,8 @@ describe("api", () => { title: it.name, albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it), canPlay: true, + artistId: it.artistId, + artist: it.artistName })), index: 0, total: 6, @@ -1350,6 +1374,8 @@ describe("api", () => { title: it.name, albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it), canPlay: true, + artistId: it.artistId, + artist: it.artistName })), index: 0, total: 6, @@ -1391,6 +1417,8 @@ describe("api", () => { title: it.name, albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it), canPlay: true, + artistId: it.artistId, + artist: it.artistName })), index: 2, total: 6, @@ -1430,6 +1458,8 @@ describe("api", () => { title: it.name, albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it), canPlay: true, + artistId: it.artistId, + artist: it.artistName })), index: 0, total: 4, @@ -1472,6 +1502,8 @@ describe("api", () => { title: it.name, albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it), canPlay: true, + artistId: it.artistId, + artist: it.artistName })), index: 0, total: 4,