mirror of
https://github.com/wkulhanek/bonob.git
synced 2025-12-22 01:43:29 +01:00
scoll indices based on ND sort name for artists
This commit is contained in:
@@ -65,8 +65,8 @@ export type Track = {
|
||||
};
|
||||
|
||||
export type Paging = {
|
||||
_index: number;
|
||||
_count: number;
|
||||
_index: number | undefined;
|
||||
_count: number | undefined;
|
||||
};
|
||||
|
||||
export type Result<T> = {
|
||||
@@ -74,9 +74,10 @@ export type Result<T> = {
|
||||
total: number;
|
||||
};
|
||||
|
||||
export function slice2<T>({ _index, _count }: Paging) {
|
||||
export function slice2<T>({ _index, _count }: Partial<Paging> = {}) {
|
||||
const i = _index || 0;
|
||||
return (things: T[]): [T[], number] => [
|
||||
things.slice(_index, _index + _count),
|
||||
_count ? things.slice(i, i + _count) : things.slice(i),
|
||||
things.length,
|
||||
];
|
||||
}
|
||||
@@ -138,6 +139,10 @@ export type Playlist = PlaylistSummary & {
|
||||
entries: Track[]
|
||||
}
|
||||
|
||||
export type Sortable = {
|
||||
sortName: string
|
||||
}
|
||||
|
||||
export const range = (size: number) => [...Array(size).keys()];
|
||||
|
||||
export const asArtistAlbumPairs = (artists: Artist[]): [Artist, Album][] =>
|
||||
@@ -152,7 +157,7 @@ export interface MusicService {
|
||||
}
|
||||
|
||||
export interface MusicLibrary {
|
||||
artists(q: ArtistQuery): Promise<Result<ArtistSummary>>;
|
||||
artists(q: ArtistQuery): Promise<Result<ArtistSummary & Sortable>>;
|
||||
artist(id: string): Promise<Artist>;
|
||||
albums(q: AlbumQuery): Promise<Result<AlbumSummary>>;
|
||||
album(id: string): Promise<Album>;
|
||||
|
||||
11
src/smapi.ts
11
src/smapi.ts
@@ -19,6 +19,7 @@ import {
|
||||
Playlist,
|
||||
Rating,
|
||||
slice2,
|
||||
Sortable,
|
||||
Track,
|
||||
} from "./music_service";
|
||||
import { APITokens } from "./api_tokens";
|
||||
@@ -366,7 +367,7 @@ export const artist = (bonobUrl: URLBuilder, artist: ArtistSummary) => ({
|
||||
albumArtURI: defaultArtistArtURI(bonobUrl, artist).href(),
|
||||
});
|
||||
|
||||
export const scrollIndicesFrom = (artists: ArtistSummary[]) => {
|
||||
export const scrollIndicesFrom = (things: Sortable[]) => {
|
||||
const indicies: Record<string, number | undefined> = {
|
||||
"A":undefined,
|
||||
"B":undefined,
|
||||
@@ -395,9 +396,9 @@ export const scrollIndicesFrom = (artists: ArtistSummary[]) => {
|
||||
"Y":undefined,
|
||||
"Z":undefined,
|
||||
}
|
||||
const sortedNames = artists.map(artist => artist.name.toUpperCase()).sort();
|
||||
for(var i = 0; i < sortedNames.length; i++) {
|
||||
const char = sortedNames[i]![0]!;
|
||||
const upperNames = things.map(thing => thing.sortName.toUpperCase());
|
||||
for(var i = 0; i < upperNames.length; i++) {
|
||||
const char = upperNames[i]![0]!;
|
||||
if(Object.keys(indicies).includes(char) && indicies[char] == undefined) {
|
||||
indicies[char] = i;
|
||||
}
|
||||
@@ -1002,7 +1003,7 @@ function bindSmapiSoapServiceToExpress(
|
||||
switch(id) {
|
||||
case "artists": {
|
||||
return login(soapyHeaders?.credentials)
|
||||
.then(({ musicLibrary }) => musicLibrary.artists({ _index: 0, _count: 999999999 }))
|
||||
.then(({ musicLibrary }) => musicLibrary.artists({ _index: 0, _count: undefined }))
|
||||
.then((artists) => ({
|
||||
getScrollIndicesResult: scrollIndicesFrom(artists.results)
|
||||
}))
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
AlbumQueryType,
|
||||
Artist,
|
||||
AuthFailure,
|
||||
Sortable,
|
||||
} from "./music_service";
|
||||
import sharp from "sharp";
|
||||
import _ from "underscore";
|
||||
@@ -230,6 +231,13 @@ export function isError(
|
||||
return (subsonicResponse as SubsonicError).error !== undefined;
|
||||
}
|
||||
|
||||
export type NDArtist = {
|
||||
id: string;
|
||||
name: string;
|
||||
orderArtistName: string | undefined;
|
||||
largeImageUrl: string | undefined;
|
||||
};
|
||||
|
||||
type IdName = {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -243,6 +251,18 @@ const coverArtURN = (coverArt: string | undefined): BUrn | undefined =>
|
||||
O.getOrElseW(() => undefined)
|
||||
);
|
||||
|
||||
export const artistSummaryFromNDArtist = (
|
||||
artist: NDArtist
|
||||
): ArtistSummary & Sortable => ({
|
||||
id: artist.id,
|
||||
name: artist.name,
|
||||
sortName: artist.orderArtistName || artist.name,
|
||||
image: artistImageURN({
|
||||
artistId: artist.id,
|
||||
artistImageURL: artist.largeImageUrl,
|
||||
}),
|
||||
});
|
||||
|
||||
export const artistImageURN = (
|
||||
spec: Partial<{
|
||||
artistId: string | undefined;
|
||||
@@ -394,7 +414,7 @@ const AlbumQueryTypeToSubsonicType: Record<AlbumQueryType, string> = {
|
||||
const artistIsInLibrary = (artistId: string | undefined) =>
|
||||
artistId != undefined && artistId != "-1";
|
||||
|
||||
type SubsonicCredentials = Credentials & {
|
||||
export type SubsonicCredentials = Credentials & {
|
||||
type: string;
|
||||
bearer: string | undefined;
|
||||
};
|
||||
@@ -481,7 +501,7 @@ export class Subsonic implements MusicService {
|
||||
TE.chain(({ type }) =>
|
||||
pipe(
|
||||
TE.tryCatch(
|
||||
() => this.libraryFor({ ...credentials, type }),
|
||||
() => this.libraryFor({ ...credentials, type, bearer: undefined }),
|
||||
() => new AuthFailure("Failed to get library")
|
||||
),
|
||||
TE.map((library) => ({ type, library }))
|
||||
@@ -664,7 +684,7 @@ export class Subsonic implements MusicService {
|
||||
.then(this.toAlbumSummary),
|
||||
]).then(([total, albums]) => ({
|
||||
results: albums.slice(0, q._count),
|
||||
total: albums.length == 500 ? total : q._index + albums.length,
|
||||
total: albums.length == 500 ? total : (q._index || 0) + albums.length,
|
||||
}));
|
||||
|
||||
// getStarred2 = (credentials: Credentials): Promise<{ albums: Album[] }> =>
|
||||
@@ -677,14 +697,14 @@ export class Subsonic implements MusicService {
|
||||
login = async (token: string) => this.libraryFor(parseToken(token));
|
||||
|
||||
private libraryFor = (
|
||||
credentials: Credentials & { type: string }
|
||||
credentials: SubsonicCredentials
|
||||
): Promise<SubsonicMusicLibrary> => {
|
||||
const subsonic = this;
|
||||
|
||||
const genericSubsonic: SubsonicMusicLibrary = {
|
||||
flavour: () => "subsonic",
|
||||
bearerToken: (_: Credentials) => TE.right(undefined),
|
||||
artists: (q: ArtistQuery): Promise<Result<ArtistSummary>> =>
|
||||
artists: (q: ArtistQuery): Promise<Result<ArtistSummary & Sortable>> =>
|
||||
subsonic
|
||||
.getArtists(credentials)
|
||||
.then(slice2(q))
|
||||
@@ -693,6 +713,7 @@ export class Subsonic implements MusicService {
|
||||
results: page.map((it) => ({
|
||||
id: it.id,
|
||||
name: it.name,
|
||||
sortName: it.name,
|
||||
image: it.image,
|
||||
})),
|
||||
})),
|
||||
@@ -970,6 +991,43 @@ export class Subsonic implements MusicService {
|
||||
),
|
||||
TE.map((it) => it.data.token as string | undefined)
|
||||
),
|
||||
artists: async (q: ArtistQuery): Promise<Result<ArtistSummary & Sortable>> =>
|
||||
{
|
||||
let params: any = {
|
||||
_sort: "name",
|
||||
_order: "ASC",
|
||||
_start: q._index || "0",
|
||||
};
|
||||
if(q._count) {
|
||||
params = {
|
||||
...params,
|
||||
_end: (q._index || 0) + q._count
|
||||
}
|
||||
}
|
||||
|
||||
return axios
|
||||
.get(`${this.url}/api/artist`, {
|
||||
params: asURLSearchParams(params),
|
||||
headers: {
|
||||
"User-Agent": USER_AGENT,
|
||||
"x-nd-authorization": `Bearer ${credentials.bearer}`,
|
||||
},
|
||||
})
|
||||
.catch((e) => {
|
||||
throw `Navidrome failed with: ${e}`;
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status != 200 && response.status != 206) {
|
||||
throw `Navidrome failed with a ${
|
||||
response.status || "no!"
|
||||
} status`;
|
||||
} else return response;
|
||||
})
|
||||
.then((it) => ({
|
||||
results: (it.data as NDArtist[]).map(artistSummaryFromNDArtist),
|
||||
total: Number.parseInt(it.headers["x-total-count"] || "0")
|
||||
}))
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve(genericSubsonic);
|
||||
|
||||
Reference in New Issue
Block a user