From 25857d7e5ac7228c2d74d502fceb4457c1b15df2 Mon Sep 17 00:00:00 2001 From: simojenki Date: Sat, 5 Feb 2022 10:39:20 +1100 Subject: [PATCH] Extract ND into own class --- src/music_service.ts | 6 +++ src/subsonic.ts | 66 ++------------------------------ src/subsonic/generic.ts | 84 +++++++++++++++++++++++++++++++++++------ 3 files changed, 82 insertions(+), 74 deletions(-) diff --git a/src/music_service.ts b/src/music_service.ts index 8827ad0..e925e72 100644 --- a/src/music_service.ts +++ b/src/music_service.ts @@ -15,7 +15,13 @@ export class AuthFailure extends Error { } }; +export type IdName = { + id: string; + name: string; +}; + export type ArtistSummary = { + // todo: why can this be undefined? id: string | undefined; name: string; image: BUrn | undefined; diff --git a/src/subsonic.ts b/src/subsonic.ts index 1ea4765..86eaf8c 100644 --- a/src/subsonic.ts +++ b/src/subsonic.ts @@ -4,8 +4,6 @@ import { Md5 } from "ts-md5/dist/md5"; import { Credentials, MusicService, - Result, - ArtistQuery, MusicLibrary, Track, AuthFailure, @@ -19,8 +17,7 @@ import randomstring from "randomstring"; import { b64Encode, b64Decode } from "./b64"; import { axiosImageFetcher, ImageFetcher } from "./images"; import { asURLSearchParams } from "./utils"; -import { artistImageURN, SubsonicGenericMusicLibrary } from "./subsonic/generic"; - +import { artistImageURN, NaivdromeMusicLibrary, SubsonicGenericMusicLibrary } from "./subsonic/generic"; export const t = (password: string, s: string) => Md5.hashStr(`${password}${s}`); @@ -96,6 +93,7 @@ export type SubsonicCredentials = Credentials & { export const asToken = (credentials: SubsonicCredentials) => b64Encode(JSON.stringify(credentials)); + export const parseToken = (token: string): SubsonicCredentials => JSON.parse(b64Decode(token)); @@ -216,66 +214,10 @@ export class Subsonic implements MusicService { private libraryFor = ( credentials: SubsonicCredentials ): Promise => { - const genericSubsonic: SubsonicMusicLibrary = - new SubsonicGenericMusicLibrary(this, credentials); - if (credentials.type == "navidrome") { - return Promise.resolve({ - ...genericSubsonic, - flavour: () => "navidrome", - bearerToken: (credentials: Credentials) => - pipe( - TE.tryCatch( - () => - axios.post( - `${this.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, - }; - } - - 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"), - })); - }, - }); + return Promise.resolve(new NaivdromeMusicLibrary(this, credentials)); } else { - return Promise.resolve(genericSubsonic); + return Promise.resolve(new SubsonicGenericMusicLibrary(this, credentials)); } }; } diff --git a/src/subsonic/generic.ts b/src/subsonic/generic.ts index 8ba8e18..810d536 100644 --- a/src/subsonic/generic.ts +++ b/src/subsonic/generic.ts @@ -3,13 +3,16 @@ import * as A from "fp-ts/Array"; import { pipe } from "fp-ts/lib/function"; import { ordString } from "fp-ts/lib/Ord"; import { inject } from 'underscore'; +import _ from "underscore"; import logger from "../logger"; import { b64Decode, b64Encode } from "../b64"; import { assertSystem, BUrn } from "../burn"; -import { Album, AlbumQuery, AlbumQueryType, AlbumSummary, Artist, ArtistQuery, Credentials, Genre, Rating, Result, slice2, Sortable, Track } from "../music_service"; -import Subsonic, { DODGY_IMAGE_NAME, SubsonicCredentials, SubsonicMusicLibrary, SubsonicResponse, USER_AGENT } from "../subsonic"; +import { Album, AlbumQuery, AlbumQueryType, AlbumSummary, Artist, ArtistQuery, ArtistSummary, AuthFailure, Credentials, Genre, IdName, Rating, Result, slice2, Sortable, Track } from "../music_service"; +import Subsonic, { artistSummaryFromNDArtist, DODGY_IMAGE_NAME, NDArtist, SubsonicCredentials, SubsonicMusicLibrary, SubsonicResponse, USER_AGENT } from "../subsonic"; +import axios from "axios"; +import { asURLSearchParams } from "../utils"; type album = { @@ -81,14 +84,6 @@ type artistInfo = images & { similarArtist: artist[]; }; -type IdName = { - id: string; - name: string; -}; - -type ArtistSummary = IdName & { - image: BUrn | undefined; -}; export type song = { id: string; @@ -272,9 +267,11 @@ export class SubsonicGenericMusicLibrary implements SubsonicMusicLibrary { flavour = () => "subsonic"; - bearerToken = (_: Credentials) => TE.right(undefined); + bearerToken = (_: Credentials): TE.TaskEither => TE.right(undefined); - artists = (q: ArtistQuery): Promise> => + artists = async ( + q: ArtistQuery + ): Promise> => this.getArtists() .then(slice2(q)) .then(([page, total]) => ({ @@ -725,4 +722,67 @@ export class SubsonicGenericMusicLibrary implements SubsonicMusicLibrary { results: albums.slice(0, q._count), total: albums.length == 500 ? total : (q._index || 0) + albums.length, })); +}; + +export class NaivdromeMusicLibrary extends SubsonicGenericMusicLibrary { + + constructor(subsonic: Subsonic, credentials: SubsonicCredentials) { + super(subsonic, credentials); + } + + flavour = () => "navidrome"; + + bearerToken = (credentials: Credentials): TE.TaskEither => + pipe( + TE.tryCatch( + () => + axios.post( + `${this.subsonic.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, + }; + } + + 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