feishin/src/renderer/api/subsonic/subsonic-api.ts

209 lines
5.9 KiB
TypeScript
Raw Normal View History

2023-04-24 01:21:29 -07:00
import { initClient, initContract } from '@ts-rest/core';
import axios, { Method, AxiosError, isAxiosError, AxiosResponse } from 'axios';
2023-05-09 19:33:46 -07:00
import omitBy from 'lodash/omitBy';
2023-05-09 05:05:15 -07:00
import qs from 'qs';
import { z } from 'zod';
2023-04-24 01:21:29 -07:00
import { ssType } from '/@/renderer/api/subsonic/subsonic-types';
2023-04-25 16:25:26 -07:00
import { ServerListItem } from '/@/renderer/api/types';
import { toast } from '/@/renderer/components/toast/index';
import i18n from '/@/i18n/i18n';
2023-04-24 01:21:29 -07:00
const c = initContract();
export const contract = c.router({
2023-07-01 19:10:05 -07:00
authenticate: {
method: 'GET',
path: 'ping.view',
query: ssType._parameters.authenticate,
responses: {
200: ssType._response.authenticate,
},
2023-04-24 01:21:29 -07:00
},
2023-07-01 19:10:05 -07:00
createFavorite: {
method: 'GET',
path: 'star.view',
query: ssType._parameters.createFavorite,
responses: {
200: ssType._response.createFavorite,
},
2023-04-24 01:21:29 -07:00
},
2023-07-01 19:10:05 -07:00
getArtistInfo: {
method: 'GET',
path: 'getArtistInfo.view',
query: ssType._parameters.artistInfo,
responses: {
200: ssType._response.artistInfo,
},
2023-04-24 01:21:29 -07:00
},
2023-07-01 19:10:05 -07:00
getMusicFolderList: {
method: 'GET',
path: 'getMusicFolders.view',
responses: {
200: ssType._response.musicFolderList,
},
2023-04-24 01:21:29 -07:00
},
2023-07-01 19:10:05 -07:00
getRandomSongList: {
method: 'GET',
path: 'getRandomSongs.view',
query: ssType._parameters.randomSongList,
responses: {
200: ssType._response.randomSongList,
},
2023-05-21 07:30:28 -07:00
},
2023-07-01 19:10:05 -07:00
getTopSongsList: {
method: 'GET',
path: 'getTopSongs.view',
query: ssType._parameters.topSongsList,
responses: {
200: ssType._response.topSongsList,
},
2023-04-24 01:21:29 -07:00
},
2023-07-01 19:10:05 -07:00
removeFavorite: {
method: 'GET',
path: 'unstar.view',
query: ssType._parameters.removeFavorite,
responses: {
200: ssType._response.removeFavorite,
},
2023-04-24 01:21:29 -07:00
},
2023-07-01 19:10:05 -07:00
scrobble: {
method: 'GET',
path: 'scrobble.view',
query: ssType._parameters.scrobble,
responses: {
200: ssType._response.scrobble,
},
2023-04-24 01:21:29 -07:00
},
2023-07-01 19:10:05 -07:00
search3: {
method: 'GET',
path: 'search3.view',
query: ssType._parameters.search3,
responses: {
200: ssType._response.search3,
},
2023-05-19 00:14:41 -07:00
},
2023-07-01 19:10:05 -07:00
setRating: {
method: 'GET',
path: 'setRating.view',
query: ssType._parameters.setRating,
responses: {
200: ssType._response.setRating,
},
2023-04-24 01:21:29 -07:00
},
});
const axiosClient = axios.create({});
2023-05-09 05:05:15 -07:00
axiosClient.defaults.paramsSerializer = (params) => {
2023-07-01 19:10:05 -07:00
return qs.stringify(params, { arrayFormat: 'repeat' });
2023-05-09 05:05:15 -07:00
};
2023-04-24 01:21:29 -07:00
axiosClient.interceptors.response.use(
2023-07-01 19:10:05 -07:00
(response) => {
const data = response.data;
if (data['subsonic-response'].status !== 'ok') {
// Suppress code related to non-linked lastfm or spotify from Navidrome
if (data['subsonic-response'].error.code !== 0) {
toast.error({
message: data['subsonic-response'].error.message,
title: i18n.t('error.genericError', { postProcess: 'sentenceCase' }) as string,
2023-07-01 19:10:05 -07:00
});
}
}
return response;
},
(error) => {
return Promise.reject(error);
},
2023-04-24 01:21:29 -07:00
);
2023-05-09 19:33:46 -07:00
const parsePath = (fullPath: string) => {
2023-07-01 19:10:05 -07:00
const [path, params] = fullPath.split('?');
2023-05-09 19:33:46 -07:00
2023-07-01 19:10:05 -07:00
const parsedParams = qs.parse(params);
const notNilParams = omitBy(parsedParams, (value) => value === 'undefined' || value === 'null');
2023-05-09 19:33:46 -07:00
2023-07-01 19:10:05 -07:00
return {
params: notNilParams,
path,
};
2023-05-09 19:33:46 -07:00
};
2023-04-25 16:25:26 -07:00
export const ssApiClient = (args: {
2023-07-01 19:10:05 -07:00
server: ServerListItem | null;
signal?: AbortSignal;
url?: string;
2023-04-25 16:25:26 -07:00
}) => {
2023-07-01 19:10:05 -07:00
const { server, url, signal } = args;
return initClient(contract, {
api: async ({ path, method, headers, body }) => {
let baseUrl: string | undefined;
const authParams: Record<string, any> = {};
const { params, path: api } = parsePath(path);
if (server) {
baseUrl = `${server.url}/rest`;
const token = server.credential;
const params = token.split(/&?\w=/gm);
authParams.u = server.username;
if (params?.length === 4) {
authParams.s = params[2];
authParams.t = params[3];
} else if (params?.length === 3) {
authParams.p = params[2];
}
} else {
baseUrl = url;
}
try {
const result = await axiosClient.request<
z.infer<typeof ssType._response.baseResponse>
>({
data: body,
headers,
method: method as Method,
params: {
c: 'Feishin',
f: 'json',
v: '1.13.0',
...authParams,
...params,
},
signal,
url: `${baseUrl}/${api}`,
});
return {
body: result.data['subsonic-response'],
headers: result.headers as any,
status: result.status,
};
} catch (e: Error | AxiosError | any) {
console.log('CATCH ERR');
if (isAxiosError(e)) {
const error = e as AxiosError;
const response = error.response as AxiosResponse;
return {
body: response?.data,
headers: response.headers as any,
status: response?.status,
};
}
throw e;
}
},
baseHeaders: {
'Content-Type': 'application/json',
},
baseUrl: '',
});
2023-04-24 01:21:29 -07:00
};