mirror of
https://github.com/wkulhanek/bonob.git
synced 2025-12-21 17:33:29 +01:00
Compare commits
2 Commits
feature/v6
...
feature/ye
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d86b536aa | ||
|
|
e6a291be40 |
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@@ -40,10 +40,10 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
-
|
-
|
||||||
name: Set up QEMU
|
name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v2
|
||||||
-
|
-
|
||||||
name: Set up Docker Buildx
|
name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v2
|
||||||
-
|
-
|
||||||
name: Docker meta
|
name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
@@ -69,12 +69,10 @@ jobs:
|
|||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
-
|
-
|
||||||
name: Push image
|
name: Push image
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm/v7,linux/arm64
|
platforms: linux/amd64,linux/arm/v7,linux/arm64
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
# linux/arm/v6,
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,14 +9,14 @@ Support for Subsonic API clones (tested against Navidrome and Gonic).
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Integrates with Subsonic API clones (Navidrome, Gonic)
|
- Integrates with Subsonic API clones (Navidrome, Gonic)
|
||||||
- Browse by Artist, Albums, Random, Favourites, Top Rated, Playlist, Genres, Years, Recently Added Albums, Recently Played Albums, Most Played Albums
|
- Browse by Artist, Albums, Random, Favourites, Top Rated, Playlist, Genres, Recently Added Albums, Recently Played Albums, Most Played Albums
|
||||||
- Artist & Album Art
|
- Artist & Album Art
|
||||||
- View Related Artists via Artist -> '...' -> Menu -> Related Arists
|
- View Related Artists via Artist -> '...' -> Menu -> Related Arists
|
||||||
- Now playing & Track Scrobbling
|
- Now playing & Track Scrobbling
|
||||||
- Search by Album, Artist, Track
|
- Search by Album, Artist, Track
|
||||||
- Playlist editing through sonos app.
|
- Playlist editing through sonos app.
|
||||||
- Marking of songs as favourites and with ratings through the sonos app.
|
- Marking of songs as favourites and with ratings through the sonos app.
|
||||||
- Localization (only en-US, da-DK, nl-NL & fr-FR supported currently, require translations for other languages). [Sonos localization and supported languages](https://docs.sonos.com/docs/localization)
|
- Localization (only en-US, da-DK & nl-NL supported currently, require translations for other languages). [Sonos localization and supported languages](https://docs.sonos.com/docs/localization)
|
||||||
- Auto discovery of sonos devices
|
- Auto discovery of sonos devices
|
||||||
- Discovery of sonos devices using seed IP address
|
- Discovery of sonos devices using seed IP address
|
||||||
- Auto registration with sonos on start
|
- Auto registration with sonos on start
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ export type KEY =
|
|||||||
| "loginFailed"
|
| "loginFailed"
|
||||||
| "noSonosDevices"
|
| "noSonosDevices"
|
||||||
| "favourites"
|
| "favourites"
|
||||||
| "years"
|
|
||||||
| "LOVE"
|
| "LOVE"
|
||||||
| "LOVE_SUCCESS"
|
| "LOVE_SUCCESS"
|
||||||
| "STAR"
|
| "STAR"
|
||||||
@@ -84,7 +83,6 @@ const translations: Record<SUPPORTED_LANG, Record<KEY, string>> = {
|
|||||||
loginFailed: "Login failed!",
|
loginFailed: "Login failed!",
|
||||||
noSonosDevices: "No sonos devices",
|
noSonosDevices: "No sonos devices",
|
||||||
favourites: "Favourites",
|
favourites: "Favourites",
|
||||||
years: "Years",
|
|
||||||
STAR: "Star",
|
STAR: "Star",
|
||||||
UNSTAR: "Un-star",
|
UNSTAR: "Un-star",
|
||||||
STAR_SUCCESS: "Track starred",
|
STAR_SUCCESS: "Track starred",
|
||||||
@@ -127,7 +125,6 @@ const translations: Record<SUPPORTED_LANG, Record<KEY, string>> = {
|
|||||||
loginFailed: "Log på fejlede!",
|
loginFailed: "Log på fejlede!",
|
||||||
noSonosDevices: "Ingen Sonos enheder",
|
noSonosDevices: "Ingen Sonos enheder",
|
||||||
favourites: "Favoritter",
|
favourites: "Favoritter",
|
||||||
years: "Flere år",
|
|
||||||
STAR: "Tilføj stjerne",
|
STAR: "Tilføj stjerne",
|
||||||
UNSTAR: "Fjern stjerne",
|
UNSTAR: "Fjern stjerne",
|
||||||
STAR_SUCCESS: "Stjerne tilføjet",
|
STAR_SUCCESS: "Stjerne tilføjet",
|
||||||
@@ -170,7 +167,6 @@ const translations: Record<SUPPORTED_LANG, Record<KEY, string>> = {
|
|||||||
loginFailed: "La connexion a échoué !",
|
loginFailed: "La connexion a échoué !",
|
||||||
noSonosDevices: "Aucun appareil Sonos",
|
noSonosDevices: "Aucun appareil Sonos",
|
||||||
favourites: "Favoris",
|
favourites: "Favoris",
|
||||||
years: "Années",
|
|
||||||
STAR: "Suivre",
|
STAR: "Suivre",
|
||||||
UNSTAR: "Ne plus suivre",
|
UNSTAR: "Ne plus suivre",
|
||||||
STAR_SUCCESS: "Piste suivie",
|
STAR_SUCCESS: "Piste suivie",
|
||||||
@@ -213,7 +209,6 @@ const translations: Record<SUPPORTED_LANG, Record<KEY, string>> = {
|
|||||||
loginFailed: "Inloggen mislukt!",
|
loginFailed: "Inloggen mislukt!",
|
||||||
noSonosDevices: "Geen Sonos-apparaten",
|
noSonosDevices: "Geen Sonos-apparaten",
|
||||||
favourites: "Favorieten",
|
favourites: "Favorieten",
|
||||||
years: "Jaren",
|
|
||||||
STAR: "Ster ",
|
STAR: "Ster ",
|
||||||
UNSTAR: "Een ster",
|
UNSTAR: "Een ster",
|
||||||
STAR_SUCCESS: "Nummer met ster",
|
STAR_SUCCESS: "Nummer met ster",
|
||||||
|
|||||||
@@ -46,10 +46,6 @@ export type Genre = {
|
|||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Year = {
|
|
||||||
year: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Rating = {
|
export type Rating = {
|
||||||
love: boolean;
|
love: boolean;
|
||||||
stars: number;
|
stars: number;
|
||||||
@@ -104,13 +100,11 @@ export const asResult = <T>([results, total]: [T[], number]) => ({
|
|||||||
|
|
||||||
export type ArtistQuery = Paging;
|
export type ArtistQuery = Paging;
|
||||||
|
|
||||||
export type AlbumQueryType = 'alphabeticalByArtist' | 'alphabeticalByName' | 'byGenre' | 'byYear' | 'random' | 'recentlyPlayed' | 'mostPlayed' | 'recentlyAdded' | 'favourited' | 'starred';
|
export type AlbumQueryType = 'alphabeticalByArtist' | 'alphabeticalByName' | 'byGenre' | 'random' | 'recentlyPlayed' | 'mostPlayed' | 'recentlyAdded' | 'favourited' | 'starred';
|
||||||
|
|
||||||
export type AlbumQuery = Paging & {
|
export type AlbumQuery = Paging & {
|
||||||
type: AlbumQueryType;
|
type: AlbumQueryType;
|
||||||
genre?: string;
|
genre?: string;
|
||||||
fromYear?: string;
|
|
||||||
toYear?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const artistToArtistSummary = (it: Artist): ArtistSummary => ({
|
export const artistToArtistSummary = (it: Artist): ArtistSummary => ({
|
||||||
@@ -179,7 +173,6 @@ export interface MusicLibrary {
|
|||||||
tracks(albumId: string): Promise<Track[]>;
|
tracks(albumId: string): Promise<Track[]>;
|
||||||
track(trackId: string): Promise<Track>;
|
track(trackId: string): Promise<Track>;
|
||||||
genres(): Promise<Genre[]>;
|
genres(): Promise<Genre[]>;
|
||||||
years(): Promise<Year[]>;
|
|
||||||
stream({
|
stream({
|
||||||
trackId,
|
trackId,
|
||||||
range,
|
range,
|
||||||
|
|||||||
36
src/smapi.ts
36
src/smapi.ts
@@ -15,7 +15,6 @@ import {
|
|||||||
AlbumSummary,
|
AlbumSummary,
|
||||||
ArtistSummary,
|
ArtistSummary,
|
||||||
Genre,
|
Genre,
|
||||||
Year,
|
|
||||||
MusicService,
|
MusicService,
|
||||||
Playlist,
|
Playlist,
|
||||||
RadioStation,
|
RadioStation,
|
||||||
@@ -245,19 +244,12 @@ export type Container = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const genre = (bonobUrl: URLBuilder, genre: Genre) => ({
|
const genre = (bonobUrl: URLBuilder, genre: Genre) => ({
|
||||||
itemType: "albumList",
|
itemType: "container",
|
||||||
id: `genre:${genre.id}`,
|
id: `genre:${genre.id}`,
|
||||||
title: genre.name,
|
title: genre.name,
|
||||||
albumArtURI: iconArtURI(bonobUrl, iconForGenre(genre.name)).href(),
|
albumArtURI: iconArtURI(bonobUrl, iconForGenre(genre.name)).href(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const year = (bonobUrl: URLBuilder, year: Year) => ({
|
|
||||||
itemType: "albumList",
|
|
||||||
id: `year:${year.year}`,
|
|
||||||
title: year.year,
|
|
||||||
albumArtURI: iconArtURI(bonobUrl, "music").href(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const playlist = (bonobUrl: URLBuilder, playlist: Playlist) => ({
|
const playlist = (bonobUrl: URLBuilder, playlist: Playlist) => ({
|
||||||
itemType: "playlist",
|
itemType: "playlist",
|
||||||
id: `playlist:${playlist.id}`,
|
id: `playlist:${playlist.id}`,
|
||||||
@@ -748,12 +740,6 @@ function bindSmapiSoapServiceToExpress(
|
|||||||
albumArtURI: iconArtURI(bonobUrl, "genres").href(),
|
albumArtURI: iconArtURI(bonobUrl, "genres").href(),
|
||||||
itemType: "container",
|
itemType: "container",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "years",
|
|
||||||
title: lang("years"),
|
|
||||||
albumArtURI: iconArtURI(bonobUrl, "music").href(),
|
|
||||||
itemType: "container",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "recentlyAdded",
|
id: "recentlyAdded",
|
||||||
title: lang("recentlyAdded"),
|
title: lang("recentlyAdded"),
|
||||||
@@ -831,13 +817,6 @@ function bindSmapiSoapServiceToExpress(
|
|||||||
genre: typeId,
|
genre: typeId,
|
||||||
...paging,
|
...paging,
|
||||||
});
|
});
|
||||||
case "year":
|
|
||||||
return albums({
|
|
||||||
type: "byYear",
|
|
||||||
fromYear: typeId,
|
|
||||||
toYear: typeId,
|
|
||||||
...paging,
|
|
||||||
});
|
|
||||||
case "randomAlbums":
|
case "randomAlbums":
|
||||||
return albums({
|
return albums({
|
||||||
type: "random",
|
type: "random",
|
||||||
@@ -881,19 +860,6 @@ function bindSmapiSoapServiceToExpress(
|
|||||||
total,
|
total,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
case "years":
|
|
||||||
return musicLibrary
|
|
||||||
.years()
|
|
||||||
.then(slice2(paging))
|
|
||||||
.then(([page, total]) =>
|
|
||||||
getMetadataResult({
|
|
||||||
mediaCollection: page.map((it) =>
|
|
||||||
year(bonobUrl, it)
|
|
||||||
),
|
|
||||||
index: paging._index,
|
|
||||||
total,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
case "genres":
|
case "genres":
|
||||||
return musicLibrary
|
return musicLibrary
|
||||||
.genres()
|
.genres()
|
||||||
|
|||||||
@@ -346,10 +346,6 @@ const maybeAsGenre = (genreName: string | undefined): Genre | undefined =>
|
|||||||
O.getOrElseW(() => undefined)
|
O.getOrElseW(() => undefined)
|
||||||
);
|
);
|
||||||
|
|
||||||
export const asYear = (year: string) => ({
|
|
||||||
year: year,
|
|
||||||
});
|
|
||||||
|
|
||||||
export interface CustomPlayers {
|
export interface CustomPlayers {
|
||||||
encodingFor({ mimeType }: { mimeType: string }): O.Option<Encoding>
|
encodingFor({ mimeType }: { mimeType: string }): O.Option<Encoding>
|
||||||
}
|
}
|
||||||
@@ -450,7 +446,6 @@ const AlbumQueryTypeToSubsonicType: Record<AlbumQueryType, string> = {
|
|||||||
alphabeticalByArtist: "alphabeticalByArtist",
|
alphabeticalByArtist: "alphabeticalByArtist",
|
||||||
alphabeticalByName: "alphabeticalByName",
|
alphabeticalByName: "alphabeticalByName",
|
||||||
byGenre: "byGenre",
|
byGenre: "byGenre",
|
||||||
byYear: "byYear",
|
|
||||||
random: "random",
|
random: "random",
|
||||||
recentlyPlayed: "recent",
|
recentlyPlayed: "recent",
|
||||||
mostPlayed: "frequent",
|
mostPlayed: "frequent",
|
||||||
@@ -725,8 +720,6 @@ export class Subsonic implements MusicService {
|
|||||||
this.getJSON<GetAlbumListResponse>(credentials, "/rest/getAlbumList2", {
|
this.getJSON<GetAlbumListResponse>(credentials, "/rest/getAlbumList2", {
|
||||||
type: AlbumQueryTypeToSubsonicType[q.type],
|
type: AlbumQueryTypeToSubsonicType[q.type],
|
||||||
...(q.genre ? { genre: b64Decode(q.genre) } : {}),
|
...(q.genre ? { genre: b64Decode(q.genre) } : {}),
|
||||||
...(q.fromYear ? { fromYear: q.fromYear} : {}),
|
|
||||||
...(q.toYear ? { toYear: q.toYear} : {}),
|
|
||||||
size: 500,
|
size: 500,
|
||||||
offset: q._index,
|
offset: q._index,
|
||||||
})
|
})
|
||||||
@@ -1044,24 +1037,7 @@ export class Subsonic implements MusicService {
|
|||||||
.then(it =>
|
.then(it =>
|
||||||
it.find(station => station.id === id)!
|
it.find(station => station.id === id)!
|
||||||
),
|
),
|
||||||
years: async () => {
|
|
||||||
const q: AlbumQuery = {
|
|
||||||
_index: 0,
|
|
||||||
_count: 100000, // FIXME: better than this ?
|
|
||||||
type: "alphabeticalByArtist",
|
|
||||||
};
|
|
||||||
const years = subsonic.getAlbumList2(credentials, q)
|
|
||||||
.then(({ results }) =>
|
|
||||||
results.map((album) => album.year || "?")
|
|
||||||
.filter((item, i, ar) => ar.indexOf(item) === i)
|
|
||||||
.sort()
|
|
||||||
.map((year) => ({
|
|
||||||
...asYear(year)
|
|
||||||
}))
|
|
||||||
.reverse()
|
|
||||||
);
|
|
||||||
return years;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (credentials.type == "navidrome") {
|
if (credentials.type == "navidrome") {
|
||||||
|
|||||||
@@ -166,12 +166,6 @@ export const SAMPLE_GENRES = [
|
|||||||
];
|
];
|
||||||
export const randomGenre = () => SAMPLE_GENRES[randomInt(SAMPLE_GENRES.length)];
|
export const randomGenre = () => SAMPLE_GENRES[randomInt(SAMPLE_GENRES.length)];
|
||||||
|
|
||||||
export const aYear = (year: string) => ({ id: year, year });
|
|
||||||
|
|
||||||
export const Y2024 = aYear("2024");
|
|
||||||
export const Y2023 = aYear("2023");
|
|
||||||
export const Y1969 = aYear("1969");
|
|
||||||
|
|
||||||
export function aTrack(fields: Partial<Track> = {}): Track {
|
export function aTrack(fields: Partial<Track> = {}): Track {
|
||||||
const id = uuid();
|
const id = uuid();
|
||||||
const artist = anArtist();
|
const artist = anArtist();
|
||||||
|
|||||||
@@ -163,7 +163,6 @@ export class InMemoryMusicService implements MusicService {
|
|||||||
topSongs: async (_: string) => Promise.resolve([]),
|
topSongs: async (_: string) => Promise.resolve([]),
|
||||||
radioStations: async () => Promise.resolve([]),
|
radioStations: async () => Promise.resolve([]),
|
||||||
radioStation: async (_: string) => Promise.reject("Unsupported operation"),
|
radioStation: async (_: string) => Promise.reject("Unsupported operation"),
|
||||||
years: async () => Promise.resolve([]),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,9 +39,6 @@ import {
|
|||||||
ROCK,
|
ROCK,
|
||||||
TRIP_HOP,
|
TRIP_HOP,
|
||||||
PUNK,
|
PUNK,
|
||||||
Y2024,
|
|
||||||
Y2023,
|
|
||||||
Y1969,
|
|
||||||
aPlaylist,
|
aPlaylist,
|
||||||
aRadioStation,
|
aRadioStation,
|
||||||
} from "./builders";
|
} from "./builders";
|
||||||
@@ -578,8 +575,6 @@ describe("wsdl api", () => {
|
|||||||
artists: jest.fn(),
|
artists: jest.fn(),
|
||||||
artist: jest.fn(),
|
artist: jest.fn(),
|
||||||
genres: jest.fn(),
|
genres: jest.fn(),
|
||||||
years: jest.fn(),
|
|
||||||
year: jest.fn(),
|
|
||||||
playlists: jest.fn(),
|
playlists: jest.fn(),
|
||||||
playlist: jest.fn(),
|
playlist: jest.fn(),
|
||||||
album: jest.fn(),
|
album: jest.fn(),
|
||||||
@@ -1158,12 +1153,6 @@ describe("wsdl api", () => {
|
|||||||
albumArtURI: iconArtURI(bonobUrl, "genres").href(),
|
albumArtURI: iconArtURI(bonobUrl, "genres").href(),
|
||||||
itemType: "container",
|
itemType: "container",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "years",
|
|
||||||
title: "Years",
|
|
||||||
albumArtURI: iconArtURI(bonobUrl, "music").href(),
|
|
||||||
itemType: "container",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "recentlyAdded",
|
id: "recentlyAdded",
|
||||||
title: "Recently added",
|
title: "Recently added",
|
||||||
@@ -1258,12 +1247,6 @@ describe("wsdl api", () => {
|
|||||||
albumArtURI: iconArtURI(bonobUrl, "genres").href(),
|
albumArtURI: iconArtURI(bonobUrl, "genres").href(),
|
||||||
itemType: "container",
|
itemType: "container",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "years",
|
|
||||||
title: "Jaren",
|
|
||||||
albumArtURI: iconArtURI(bonobUrl, "music").href(),
|
|
||||||
itemType: "container",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "recentlyAdded",
|
id: "recentlyAdded",
|
||||||
title: "Onlangs toegevoegd",
|
title: "Onlangs toegevoegd",
|
||||||
@@ -1341,7 +1324,7 @@ describe("wsdl api", () => {
|
|||||||
expect(result[0]).toEqual(
|
expect(result[0]).toEqual(
|
||||||
getMetadataResult({
|
getMetadataResult({
|
||||||
mediaCollection: expectedGenres.map((genre) => ({
|
mediaCollection: expectedGenres.map((genre) => ({
|
||||||
itemType: "albumList",
|
itemType: "container",
|
||||||
id: `genre:${genre.id}`,
|
id: `genre:${genre.id}`,
|
||||||
title: genre.name,
|
title: genre.name,
|
||||||
albumArtURI: iconArtURI(
|
albumArtURI: iconArtURI(
|
||||||
@@ -1366,7 +1349,7 @@ describe("wsdl api", () => {
|
|||||||
expect(result[0]).toEqual(
|
expect(result[0]).toEqual(
|
||||||
getMetadataResult({
|
getMetadataResult({
|
||||||
mediaCollection: [PUNK, ROCK].map((genre) => ({
|
mediaCollection: [PUNK, ROCK].map((genre) => ({
|
||||||
itemType: "albumList",
|
itemType: "container",
|
||||||
id: `genre:${genre.id}`,
|
id: `genre:${genre.id}`,
|
||||||
title: genre.name,
|
title: genre.name,
|
||||||
albumArtURI: iconArtURI(
|
albumArtURI: iconArtURI(
|
||||||
@@ -1382,64 +1365,6 @@ describe("wsdl api", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("asking for a year", () => {
|
|
||||||
const expectedYears = [Y1969, Y2023, Y2024];
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
musicLibrary.years.mockResolvedValue(expectedYears);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("asking for all years", () => {
|
|
||||||
it("should return a collection of years", async () => {
|
|
||||||
const result = await ws.getMetadataAsync({
|
|
||||||
id: `years`,
|
|
||||||
index: 0,
|
|
||||||
count: 100,
|
|
||||||
});
|
|
||||||
expect(result[0]).toEqual(
|
|
||||||
getMetadataResult({
|
|
||||||
mediaCollection: expectedYears.map((year) => ({
|
|
||||||
itemType: "albumList",
|
|
||||||
id: `year:${year.id}`,
|
|
||||||
title: year.year,
|
|
||||||
albumArtURI: iconArtURI(
|
|
||||||
bonobUrl,
|
|
||||||
"music",
|
|
||||||
).href(),
|
|
||||||
})),
|
|
||||||
index: 0,
|
|
||||||
total: expectedYears.length,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("asking for a page of years", () => {
|
|
||||||
it("should return just that page", async () => {
|
|
||||||
const result = await ws.getMetadataAsync({
|
|
||||||
id: `years`,
|
|
||||||
index: 1,
|
|
||||||
count: 2,
|
|
||||||
});
|
|
||||||
expect(result[0]).toEqual(
|
|
||||||
getMetadataResult({
|
|
||||||
mediaCollection: [Y2023, Y2024].map((year) => ({
|
|
||||||
itemType: "albumList",
|
|
||||||
id: `year:${year.id}`,
|
|
||||||
title: year.year,
|
|
||||||
albumArtURI: iconArtURI(
|
|
||||||
bonobUrl,
|
|
||||||
"music"
|
|
||||||
).href(),
|
|
||||||
})),
|
|
||||||
index: 1,
|
|
||||||
total: expectedYears.length,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("asking for playlists", () => {
|
describe("asking for playlists", () => {
|
||||||
const playlist1 = aPlaylist({ id: "1", name: "pl1", entries: []});
|
const playlist1 = aPlaylist({ id: "1", name: "pl1", entries: []});
|
||||||
const playlist2 = aPlaylist({ id: "2", name: "pl2", entries: []});
|
const playlist2 = aPlaylist({ id: "2", name: "pl2", entries: []});
|
||||||
|
|||||||
8
web/icons/bob.svg
Normal file
8
web/icons/bob.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||||
|
<style>
|
||||||
|
.txt {
|
||||||
|
font: bold 13px helvetica;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<text y="20%" class="txt" textLength="80%" lengthAdjust="spacingAndGlyphs">80s</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 226 B |
Reference in New Issue
Block a user