Extract ND into own class

This commit is contained in:
simojenki
2022-02-05 10:39:20 +11:00
parent 50cb5b2550
commit 25857d7e5a
3 changed files with 82 additions and 74 deletions

View File

@@ -15,7 +15,13 @@ export class AuthFailure extends Error {
} }
}; };
export type IdName = {
id: string;
name: string;
};
export type ArtistSummary = { export type ArtistSummary = {
// todo: why can this be undefined?
id: string | undefined; id: string | undefined;
name: string; name: string;
image: BUrn | undefined; image: BUrn | undefined;

View File

@@ -4,8 +4,6 @@ import { Md5 } from "ts-md5/dist/md5";
import { import {
Credentials, Credentials,
MusicService, MusicService,
Result,
ArtistQuery,
MusicLibrary, MusicLibrary,
Track, Track,
AuthFailure, AuthFailure,
@@ -19,8 +17,7 @@ import randomstring from "randomstring";
import { b64Encode, b64Decode } from "./b64"; import { b64Encode, b64Decode } from "./b64";
import { axiosImageFetcher, ImageFetcher } from "./images"; import { axiosImageFetcher, ImageFetcher } from "./images";
import { asURLSearchParams } from "./utils"; import { asURLSearchParams } from "./utils";
import { artistImageURN, SubsonicGenericMusicLibrary } from "./subsonic/generic"; import { artistImageURN, NaivdromeMusicLibrary, SubsonicGenericMusicLibrary } from "./subsonic/generic";
export const t = (password: string, s: string) => export const t = (password: string, s: string) =>
Md5.hashStr(`${password}${s}`); Md5.hashStr(`${password}${s}`);
@@ -96,6 +93,7 @@ export type SubsonicCredentials = Credentials & {
export const asToken = (credentials: SubsonicCredentials) => export const asToken = (credentials: SubsonicCredentials) =>
b64Encode(JSON.stringify(credentials)); b64Encode(JSON.stringify(credentials));
export const parseToken = (token: string): SubsonicCredentials => export const parseToken = (token: string): SubsonicCredentials =>
JSON.parse(b64Decode(token)); JSON.parse(b64Decode(token));
@@ -216,66 +214,10 @@ export class Subsonic implements MusicService {
private libraryFor = ( private libraryFor = (
credentials: SubsonicCredentials credentials: SubsonicCredentials
): Promise<SubsonicMusicLibrary> => { ): Promise<SubsonicMusicLibrary> => {
const genericSubsonic: SubsonicMusicLibrary =
new SubsonicGenericMusicLibrary(this, credentials);
if (credentials.type == "navidrome") { if (credentials.type == "navidrome") {
return Promise.resolve({ return Promise.resolve(new NaivdromeMusicLibrary(this, credentials));
...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<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 { } else {
return Promise.resolve(genericSubsonic); return Promise.resolve(new SubsonicGenericMusicLibrary(this, credentials));
} }
}; };
} }

View File

@@ -3,13 +3,16 @@ import * as A from "fp-ts/Array";
import { pipe } from "fp-ts/lib/function"; import { pipe } from "fp-ts/lib/function";
import { ordString } from "fp-ts/lib/Ord"; import { ordString } from "fp-ts/lib/Ord";
import { inject } from 'underscore'; import { inject } from 'underscore';
import _ from "underscore";
import logger from "../logger"; import logger from "../logger";
import { b64Decode, b64Encode } from "../b64"; import { b64Decode, b64Encode } from "../b64";
import { assertSystem, BUrn } from "../burn"; import { assertSystem, BUrn } from "../burn";
import { Album, AlbumQuery, AlbumQueryType, AlbumSummary, Artist, ArtistQuery, Credentials, Genre, Rating, Result, slice2, Sortable, Track } from "../music_service"; 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, { artistSummaryFromNDArtist, DODGY_IMAGE_NAME, NDArtist, SubsonicCredentials, SubsonicMusicLibrary, SubsonicResponse, USER_AGENT } from "../subsonic";
import axios from "axios";
import { asURLSearchParams } from "../utils";
type album = { type album = {
@@ -81,14 +84,6 @@ type artistInfo = images & {
similarArtist: artist[]; similarArtist: artist[];
}; };
type IdName = {
id: string;
name: string;
};
type ArtistSummary = IdName & {
image: BUrn | undefined;
};
export type song = { export type song = {
id: string; id: string;
@@ -272,9 +267,11 @@ export class SubsonicGenericMusicLibrary implements SubsonicMusicLibrary {
flavour = () => "subsonic"; flavour = () => "subsonic";
bearerToken = (_: Credentials) => TE.right(undefined); bearerToken = (_: Credentials): TE.TaskEither<Error, string | undefined> => TE.right(undefined);
artists = (q: ArtistQuery): Promise<Result<ArtistSummary & Sortable>> => artists = async (
q: ArtistQuery
): Promise<Result<ArtistSummary & Sortable>> =>
this.getArtists() this.getArtists()
.then(slice2(q)) .then(slice2(q))
.then(([page, total]) => ({ .then(([page, total]) => ({
@@ -725,4 +722,67 @@ export class SubsonicGenericMusicLibrary implements SubsonicMusicLibrary {
results: albums.slice(0, q._count), results: albums.slice(0, q._count),
total: albums.length == 500 ? total : (q._index || 0) + albums.length, 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<Error, string | undefined> =>
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<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,
};
}
const x: Promise<Result<ArtistSummary & Sortable>> = 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;
}
} }