diff --git a/src/app.ts b/src/app.ts index 162ecbd..b7b8304 100644 --- a/src/app.ts +++ b/src/app.ts @@ -31,6 +31,7 @@ const bonob = bonobService( const sonosSystem = sonos(config.sonos.discovery); +// todo: just pass in the customClientsForStringArray into subsonic and make it sort it out. const streamUserAgent = config.subsonic.customClientsFor ? appendMimeTypeToClientFor(config.subsonic.customClientsFor.split(",")) : DEFAULT; diff --git a/src/subsonic/index.ts b/src/subsonic/index.ts index 4954ab2..5ab2f45 100644 --- a/src/subsonic/index.ts +++ b/src/subsonic/index.ts @@ -15,7 +15,7 @@ import { import { b64Encode, b64Decode } from "../b64"; import { axiosImageFetcher, ImageFetcher } from "../images"; import { asURLSearchParams } from "../utils"; -import { NaivdromeMusicLibrary, SubsonicGenericMusicLibrary } from "./generic"; +import { navidromeMusicLibrary, SubsonicGenericMusicLibrary } from "./library"; export const t = (password: string, s: string) => Md5.hashStr(`${password}${s}`); @@ -194,10 +194,11 @@ export class Subsonic implements MusicService { private libraryFor = ( credentials: SubsonicCredentials ): Promise => { + const subsonicGenericLibrary = new SubsonicGenericMusicLibrary(this, credentials); if (credentials.type == "navidrome") { - return Promise.resolve(new NaivdromeMusicLibrary(this, credentials)); + return Promise.resolve(navidromeMusicLibrary(this.url, subsonicGenericLibrary, credentials)); } else { - return Promise.resolve(new SubsonicGenericMusicLibrary(this, credentials)); + return Promise.resolve(subsonicGenericLibrary); } }; } diff --git a/src/subsonic/generic.ts b/src/subsonic/library.ts similarity index 92% rename from src/subsonic/generic.ts rename to src/subsonic/library.ts index 2526ebd..03df6fd 100644 --- a/src/subsonic/generic.ts +++ b/src/subsonic/library.ts @@ -10,7 +10,7 @@ import { b64Decode, b64Encode } from "../b64"; import { assertSystem, BUrn } from "../burn"; import { Album, AlbumQuery, AlbumQueryType, AlbumSummary, Artist, ArtistQuery, ArtistSummary, AuthFailure, Credentials, Genre, IdName, Rating, Result, slice2, Sortable, Track } from "../music_service"; -import Subsonic, { DODGY_IMAGE_NAME, SubsonicCredentials, SubsonicMusicLibrary, SubsonicResponse, USER_AGENT } from "../subsonic"; +import Subsonic, { DODGY_IMAGE_NAME, SubsonicCredentials, SubsonicMusicLibrary, SubsonicResponse, USER_AGENT } from "."; import axios from "axios"; import { asURLSearchParams } from "../utils"; import { artistSummaryFromNDArtist, NDArtist } from "./navidrome"; @@ -725,65 +725,59 @@ export class SubsonicGenericMusicLibrary implements SubsonicMusicLibrary { })); }; -export class NaivdromeMusicLibrary extends SubsonicGenericMusicLibrary { - - constructor(subsonic: Subsonic, credentials: SubsonicCredentials) { - super(subsonic, credentials); - } - - flavour = () => "navidrome"; - - bearerToken = (credentials: Credentials): TE.TaskEither => +export const navidromeMusicLibrary = (url: string, subsonicLibrary: SubsonicMusicLibrary, subsonicCredentials: SubsonicCredentials): SubsonicMusicLibrary => ({ + ...subsonicLibrary, + flavour: () => "navidrome", + bearerToken: (credentials: Credentials): TE.TaskEither => pipe( TE.tryCatch( () => axios.post( - `${this.subsonic.url}/auth/login`, + `${url}/auth/login`, _.pick(credentials, "username", "password") ), () => new AuthFailure("Failed to get bearerToken") ), TE.map((it) => it.data.token as string | undefined) - ); - - artists = async ( - q: ArtistQuery - ): Promise> => { - let params: any = { - _sort: "name", - _order: "ASC", - _start: q._index || "0", - }; - if (q._count) { - params = { - ...params, - _end: (q._index || 0) + q._count, + ), + artists: async ( + q: ArtistQuery + ): Promise> => { + let params: any = { + _sort: "name", + _order: "ASC", + _start: q._index || "0", }; - } - - const x: Promise> = axios - .get(`${this.subsonic.url}/api/artist`, { - params: asURLSearchParams(params), - headers: { - "User-Agent": USER_AGENT, - "x-nd-authorization": `Bearer ${this.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"), - })); - - return x; - } -} \ No newline at end of file + if (q._count) { + params = { + ...params, + _end: (q._index || 0) + q._count, + }; + } + + const x: Promise> = axios + .get(`${url}/api/artist`, { + params: asURLSearchParams(params), + headers: { + "User-Agent": USER_AGENT, + "x-nd-authorization": `Bearer ${subsonicCredentials.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"), + })); + + return x; + } +}) \ No newline at end of file diff --git a/src/subsonic/navidrome.ts b/src/subsonic/navidrome.ts index 7d96b4f..6f4c75b 100644 --- a/src/subsonic/navidrome.ts +++ b/src/subsonic/navidrome.ts @@ -1,5 +1,5 @@ import { ArtistSummary, Sortable } from "../music_service"; -import { artistImageURN } from "./generic"; +import { artistImageURN } from "./library"; export type NDArtist = { id: string; diff --git a/src/utils.ts b/src/utils.ts index 4070e89..3069676 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -28,4 +28,4 @@ export function takeWithRepeats(things:T[], count: number) { result.push(things[i % things.length]) } return result; -} \ No newline at end of file +} diff --git a/tests/builders.ts b/tests/builders.ts index d3695de..37e2d6a 100644 --- a/tests/builders.ts +++ b/tests/builders.ts @@ -17,7 +17,7 @@ import { } from "../src/music_service"; import { b64Encode } from "../src/b64"; -import { artistImageURN } from "../src/subsonic/generic"; +import { artistImageURN } from "../src/subsonic/library"; const randomInt = (max: number) => Math.floor(Math.random() * Math.floor(max)); const randomIpAddress = () => `127.0.${randomInt(255)}.${randomInt(255)}`; diff --git a/tests/subsonic/generic.test.ts b/tests/subsonic/generic.test.ts index 500b509..f86a365 100644 --- a/tests/subsonic/generic.test.ts +++ b/tests/subsonic/generic.test.ts @@ -12,7 +12,7 @@ jest.mock("randomstring"); import { aGenre, anAlbum, anArtist, aPlaylist, aPlaylistSummary, aSimilarArtist, aTrack, POP, ROCK } from "../builders"; import { BUrn } from "../../src/burn"; import { Album, AlbumQuery, AlbumSummary, albumToAlbumSummary, Artist, artistToArtistSummary, asArtistAlbumPairs, Playlist, PlaylistSummary, Rating, SimilarArtist, Track } from "../../src/music_service"; -import { artistImageURN, asGenre, asTrack, images, isValidImage, song, SubsonicGenericMusicLibrary } from "../../src/subsonic/generic"; +import { artistImageURN, asGenre, asTrack, images, isValidImage, song, SubsonicGenericMusicLibrary } from "../../src/subsonic/library"; import { EMPTY, error, FAILURE, subsonicOK, ok } from "../subsonic.test"; import Subsonic, { DODGY_IMAGE_NAME, t } from "../../src/subsonic"; import { asURLSearchParams } from "../../src/utils"; diff --git a/tests/subsonic/navidrome.test.ts b/tests/subsonic/navidrome.test.ts index f94d685..6aca01c 100644 --- a/tests/subsonic/navidrome.test.ts +++ b/tests/subsonic/navidrome.test.ts @@ -1,6 +1,6 @@ import { v4 as uuid } from "uuid"; import { DODGY_IMAGE_NAME } from "../../src/subsonic"; -import { artistImageURN } from "../../src/subsonic/generic"; +import { artistImageURN } from "../../src/subsonic/library"; import { artistSummaryFromNDArtist } from "../../src/subsonic/navidrome";