Ability to see TopRated/starred albums (#63)

This commit is contained in:
Simon J
2021-10-08 00:08:32 +11:00
committed by GitHub
parent 6116975d7a
commit cc95beb4f2
6 changed files with 196 additions and 65 deletions

View File

@@ -81,10 +81,10 @@ const translations: Record<SUPPORTED_LANG, Record<KEY, string>> = {
loginFailed: "Login failed!",
noSonosDevices: "No sonos devices",
favourites: "Favourites",
STAR: "Star track",
UNSTAR: "Un-star track",
STAR_SUCCESS: "Track starred successfully",
UNSTAR_SUCCESS: "Track un-starred successfully",
STAR: "Star",
UNSTAR: "Un-star",
STAR_SUCCESS: "Track starred",
UNSTAR_SUCCESS: "Track un-starred",
LOVE: "Love",
LOVE_SUCCESS: "Track loved"
},
@@ -122,11 +122,11 @@ const translations: Record<SUPPORTED_LANG, Record<KEY, string>> = {
loginFailed: "Inloggen mislukt!",
noSonosDevices: "Geen Sonos-apparaten",
favourites: "Favorieten",
STAR: "Ster spoor",
UNSTAR: "Track zonder ster",
STAR_SUCCESS: "Track succesvol gemarkeerd",
UNSTAR_SUCCESS: "Succes zonder ster bijhouden",
LOVE: "Liefde ",
STAR: "Ster ",
UNSTAR: "Een ster",
STAR_SUCCESS: "Nummer met ster",
UNSTAR_SUCCESS: "Track zonder ster",
LOVE: "Liefde",
LOVE_SUCCESS: "Volg geliefd"
},
};

View File

@@ -107,7 +107,7 @@ export const asResult = <T>([results, total]: [T[], number]) => ({
export type ArtistQuery = Paging;
export type AlbumQueryType = 'alphabeticalByArtist' | 'alphabeticalByName' | 'byGenre' | 'random' | 'recent' | 'frequent' | 'newest' | 'starred';
export type AlbumQueryType = 'alphabeticalByArtist' | 'alphabeticalByName' | 'byGenre' | 'random' | 'recentlyPlayed' | 'mostPlayed' | 'recentlyAdded' | 'favourited' | 'starred';
export type AlbumQuery = Paging & {
type: AlbumQueryType;

View File

@@ -81,10 +81,11 @@ export type GetDeviceAuthTokenResult = {
};
};
export const ratingAsInt = (rating: Rating): number => rating.stars * 10 + (rating.love ? 1 : 0) + 100;
export const ratingAsInt = (rating: Rating): number =>
rating.stars * 10 + (rating.love ? 1 : 0) + 100;
export const ratingFromInt = (value: number): Rating => {
const x = value - 100;
return { love: (x % 10 == 1), stars: Math.floor(x / 10) }
return { love: x % 10 == 1, stars: Math.floor(x / 10) };
};
export type MediaCollection = {
@@ -309,7 +310,7 @@ export const track = (bonobUrl: URLBuilder, track: Track) => ({
},
dynamic: {
property: [{ name: "rating", value: `${ratingAsInt(track.rating)}` }],
}
},
});
export const artist = (bonobUrl: URLBuilder, artist: ArtistSummary) => ({
@@ -426,10 +427,7 @@ function bindSmapiSoapServiceToExpress(
.then(splitId(id))
.then(async ({ musicLibrary, accessToken, typeId }) =>
musicLibrary.track(typeId!).then((it) => ({
getMediaMetadataResult: track(
urlWithToken(accessToken),
it
),
getMediaMetadataResult: track(urlWithToken(accessToken), it),
}))
),
search: async (
@@ -516,10 +514,7 @@ function bindSmapiSoapServiceToExpress(
case "track":
return musicLibrary.track(typeId).then((it) => ({
getExtendedMetadataResult: {
mediaMetadata: track(
urlWithToken(accessToken),
it
),
mediaMetadata: track(urlWithToken(accessToken), it),
},
}));
case "album":
@@ -606,12 +601,12 @@ function bindSmapiSoapServiceToExpress(
albumArtURI: iconArtURI(bonobUrl, "heart").href(),
itemType: "albumList",
},
// {
// id: "topRatedAlbums",
// title: lang("topRated"),
// albumArtURI: iconArtURI(bonobUrl, "star").href(),
// itemType: "albumList",
// },
{
id: "starredAlbums",
title: lang("topRated"),
albumArtURI: iconArtURI(bonobUrl, "star").href(),
itemType: "albumList",
},
{
id: "playlists",
title: lang("playlists"),
@@ -710,23 +705,28 @@ function bindSmapiSoapServiceToExpress(
...paging,
});
case "favouriteAlbums":
return albums({
type: "favourited",
...paging,
});
case "starredAlbums":
return albums({
type: "starred",
...paging,
});
case "recentlyAdded":
return albums({
type: "newest",
type: "recentlyAdded",
...paging,
});
case "recentlyPlayed":
return albums({
type: "recent",
type: "recentlyPlayed",
...paging,
});
case "mostPlayed":
return albums({
type: "frequent",
type: "mostPlayed",
...paging,
});
case "genres":

View File

@@ -20,6 +20,7 @@ import {
Track,
CoverArt,
Rating,
AlbumQueryType,
} from "./music_service";
import sharp from "sharp";
import _ from "underscore";
@@ -204,6 +205,7 @@ export type GetSongResponse = {
export type GetStarredResponse = {
starred2: {
song: song[];
album: album[];
};
};
@@ -261,11 +263,14 @@ export const asTrack = (album: Album, song: song): Track => ({
},
rating: {
love: song.starred != undefined,
stars: (song.userRating && song.userRating <= 5 && song.userRating >= 0 ? song.userRating : 0),
stars:
song.userRating && song.userRating <= 5 && song.userRating >= 0
? song.userRating
: 0,
},
});
const asAlbum = (album: album) => ({
const asAlbum = (album: album): Album => ({
id: album.id,
name: album.name,
year: album.year,
@@ -350,6 +355,18 @@ export const axiosImageFetcher = (url: string): Promise<CoverArt | undefined> =>
}))
.catch(() => undefined);
const AlbumQueryTypeToSubsonicType: Record<AlbumQueryType, string> = {
alphabeticalByArtist: "alphabeticalByArtist",
alphabeticalByName: "alphabeticalByName",
byGenre: "byGenre",
random: "random",
recentlyPlayed: "recent",
mostPlayed: "frequent",
recentlyAdded: "newest",
favourited: "starred",
starred: "highest",
};
export class Subsonic implements MusicService {
url: string;
encryption: Encryption;
@@ -536,6 +553,31 @@ export class Subsonic implements MusicService {
songs: it.searchResult3.song || [],
}));
getAlbumList2 = (credentials: Credentials, q: AlbumQuery) =>
Promise.all([
this.getArtists(credentials).then((it) =>
_.inject(it, (total, artist) => total + artist.albumCount, 0)
),
this.getJSON<GetAlbumListResponse>(credentials, "/rest/getAlbumList2", {
type: AlbumQueryTypeToSubsonicType[q.type],
...(q.genre ? { genre: b64Decode(q.genre) } : {}),
size: 500,
offset: q._index,
})
.then((response) => response.albumList2.album || [])
.then(this.toAlbumSummary),
]).then(([total, albums]) => ({
results: albums.slice(0, q._count),
total: albums.length == 500 ? total : q._index + albums.length,
}));
// getStarred2 = (credentials: Credentials): Promise<{ albums: Album[] }> =>
// this.getJSON<GetStarredResponse>(credentials, "/rest/getStarred2")
// .then((it) => it.starred2)
// .then((it) => ({
// albums: it.album.map(asAlbum),
// }));
async login(token: string) {
const subsonic = this;
const credentials: Credentials = this.parseToken(token);
@@ -552,25 +594,7 @@ export class Subsonic implements MusicService {
artist: async (id: string): Promise<Artist> =>
subsonic.getArtistWithInfo(credentials, id),
albums: async (q: AlbumQuery): Promise<Result<AlbumSummary>> =>
Promise.all([
subsonic
.getArtists(credentials)
.then((it) =>
_.inject(it, (total, artist) => total + artist.albumCount, 0)
),
subsonic
.getJSON<GetAlbumListResponse>(credentials, "/rest/getAlbumList2", {
type: q.type,
...(q.genre ? { genre: b64Decode(q.genre) } : {}),
size: 500,
offset: q._index,
})
.then((response) => response.albumList2.album || [])
.then(subsonic.toAlbumSummary),
]).then(([total, albums]) => ({
results: albums.slice(0, q._count),
total: albums.length == 500 ? total : q._index + albums.length,
})),
subsonic.getAlbumList2(credentials, q),
album: (id: string): Promise<Album> => subsonic.getAlbum(credentials, id),
genres: () =>
subsonic