mirror of
https://github.com/jeffvli/feishin.git
synced 2026-07-21 01:46:32 +02:00
Compare commits
4 Commits
dbe46e03a4
...
ff426bda6d
| Author | SHA1 | Date | |
|---|---|---|---|
| ff426bda6d | |||
| 0664b0ad02 | |||
| 61cc87e0b7 | |||
| 36624350f6 |
@@ -531,12 +531,7 @@ export const JellyfinController: InternalControllerEndpoint = {
|
|||||||
const albumIdSet = new Set([query.id]);
|
const albumIdSet = new Set([query.id]);
|
||||||
const songs = songsRes.body.Items.filter((item) => albumIdSet.has(item.AlbumId!));
|
const songs = songsRes.body.Items.filter((item) => albumIdSet.has(item.AlbumId!));
|
||||||
|
|
||||||
return jfNormalize.album(
|
return jfNormalize.album({ ...res.body, Songs: songs }, apiClientProps.server);
|
||||||
{ ...res.body, Songs: songs },
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
getAlbumList: async (args) => {
|
getAlbumList: async (args) => {
|
||||||
const { apiClientProps, query } = args;
|
const { apiClientProps, query } = args;
|
||||||
@@ -630,14 +625,7 @@ export const JellyfinController: InternalControllerEndpoint = {
|
|||||||
throw new Error('Failed to get album radio songs');
|
throw new Error('Failed to get album radio songs');
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.body.Items.map((song) =>
|
return res.body.Items.map((song) => jfNormalize.song(song, apiClientProps.server));
|
||||||
jfNormalize.song(
|
|
||||||
song,
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
getArtistList: async (args) => {
|
getArtistList: async (args) => {
|
||||||
const { apiClientProps, query } = args;
|
const { apiClientProps, query } = args;
|
||||||
@@ -693,14 +681,7 @@ export const JellyfinController: InternalControllerEndpoint = {
|
|||||||
throw new Error('Failed to get artist radio songs');
|
throw new Error('Failed to get artist radio songs');
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.body.Items.map((song) =>
|
return res.body.Items.map((song) => jfNormalize.song(song, apiClientProps.server));
|
||||||
jfNormalize.song(
|
|
||||||
song,
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
getDownloadUrl: (args) => {
|
getDownloadUrl: (args) => {
|
||||||
const { apiClientProps, query } = args;
|
const { apiClientProps, query } = args;
|
||||||
@@ -870,8 +851,6 @@ export const JellyfinController: InternalControllerEndpoint = {
|
|||||||
jfNormalize.song(
|
jfNormalize.song(
|
||||||
item as unknown as z.infer<typeof jfType._response.song>,
|
item as unknown as z.infer<typeof jfType._response.song>,
|
||||||
apiClientProps.server,
|
apiClientProps.server,
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1053,7 +1032,7 @@ export const JellyfinController: InternalControllerEndpoint = {
|
|||||||
Fields: JF_FIELDS.PLAYLIST_LIST,
|
Fields: JF_FIELDS.PLAYLIST_LIST,
|
||||||
IncludeItemTypes: 'Playlist',
|
IncludeItemTypes: 'Playlist',
|
||||||
Limit: query.limit,
|
Limit: query.limit,
|
||||||
MediaTypes: 'Audio',
|
MediaTypes: 'Audio, Unknown',
|
||||||
Recursive: true,
|
Recursive: true,
|
||||||
SearchTerm: query.searchTerm,
|
SearchTerm: query.searchTerm,
|
||||||
SortBy: playlistListSortMap.jellyfin[query.sortBy],
|
SortBy: playlistListSortMap.jellyfin[query.sortBy],
|
||||||
@@ -1100,14 +1079,7 @@ export const JellyfinController: InternalControllerEndpoint = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: res.body.Items.map((item) =>
|
items: res.body.Items.map((item) => jfNormalize.song(item, apiClientProps.server)),
|
||||||
jfNormalize.song(
|
|
||||||
item,
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
totalRecordCount: res.body.TotalRecordCount,
|
totalRecordCount: res.body.TotalRecordCount,
|
||||||
};
|
};
|
||||||
@@ -1160,14 +1132,7 @@ export const JellyfinController: InternalControllerEndpoint = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: res.body.Items.map((item) =>
|
items: res.body.Items.map((item) => jfNormalize.song(item, apiClientProps.server)),
|
||||||
jfNormalize.song(
|
|
||||||
item,
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
totalRecordCount: res.body.Items.length || 0,
|
totalRecordCount: res.body.Items.length || 0,
|
||||||
};
|
};
|
||||||
@@ -1219,14 +1184,7 @@ export const JellyfinController: InternalControllerEndpoint = {
|
|||||||
if (res.status === 200 && res.body.Items.length) {
|
if (res.status === 200 && res.body.Items.length) {
|
||||||
const results = res.body.Items.reduce<Song[]>((acc, song) => {
|
const results = res.body.Items.reduce<Song[]>((acc, song) => {
|
||||||
if (song.Id !== query.songId) {
|
if (song.Id !== query.songId) {
|
||||||
acc.push(
|
acc.push(jfNormalize.song(song, apiClientProps.server));
|
||||||
jfNormalize.song(
|
|
||||||
song,
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
@@ -1255,14 +1213,7 @@ export const JellyfinController: InternalControllerEndpoint = {
|
|||||||
|
|
||||||
return mix.body.Items.reduce<Song[]>((acc, song) => {
|
return mix.body.Items.reduce<Song[]>((acc, song) => {
|
||||||
if (song.Id !== query.songId) {
|
if (song.Id !== query.songId) {
|
||||||
acc.push(
|
acc.push(jfNormalize.song(song, apiClientProps.server));
|
||||||
jfNormalize.song(
|
|
||||||
song,
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
@@ -1282,12 +1233,7 @@ export const JellyfinController: InternalControllerEndpoint = {
|
|||||||
throw new Error('Failed to get song detail');
|
throw new Error('Failed to get song detail');
|
||||||
}
|
}
|
||||||
|
|
||||||
return jfNormalize.song(
|
return jfNormalize.song(res.body, apiClientProps.server);
|
||||||
res.body,
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
getSongList: async (args) => {
|
getSongList: async (args) => {
|
||||||
const { apiClientProps, query } = args;
|
const { apiClientProps, query } = args;
|
||||||
@@ -1399,14 +1345,7 @@ export const JellyfinController: InternalControllerEndpoint = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: items.map((item) =>
|
items: items.map((item) => jfNormalize.song(item, apiClientProps.server)),
|
||||||
jfNormalize.song(
|
|
||||||
item,
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
startIndex: query.startIndex,
|
startIndex: query.startIndex,
|
||||||
totalRecordCount,
|
totalRecordCount,
|
||||||
};
|
};
|
||||||
@@ -1538,14 +1477,7 @@ export const JellyfinController: InternalControllerEndpoint = {
|
|||||||
throw new Error('Failed to get top song list');
|
throw new Error('Failed to get top song list');
|
||||||
}
|
}
|
||||||
|
|
||||||
const items = res.body.Items.map((item) =>
|
const items = res.body.Items.map((item) => jfNormalize.song(item, apiClientProps.server));
|
||||||
jfNormalize.song(
|
|
||||||
item,
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (type === 'personal') {
|
if (type === 'personal') {
|
||||||
const sorted = orderBy(
|
const sorted = orderBy(
|
||||||
@@ -1647,12 +1579,7 @@ export const JellyfinController: InternalControllerEndpoint = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const existingSongs = existingSongsRes.body.Items.map((item) =>
|
const existingSongs = existingSongsRes.body.Items.map((item) =>
|
||||||
jfNormalize.song(
|
jfNormalize.song(item, apiClientProps.server),
|
||||||
item,
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// 2. Get playlist detail to get the name
|
// 2. Get playlist detail to get the name
|
||||||
@@ -1903,14 +1830,7 @@ export const JellyfinController: InternalControllerEndpoint = {
|
|||||||
jfNormalize.albumArtist(item, apiClientProps.server),
|
jfNormalize.albumArtist(item, apiClientProps.server),
|
||||||
),
|
),
|
||||||
albums: albums.map((item) => jfNormalize.album(item, apiClientProps.server)),
|
albums: albums.map((item) => jfNormalize.album(item, apiClientProps.server)),
|
||||||
songs: songs.map((item) =>
|
songs: songs.map((item) => jfNormalize.song(item, apiClientProps.server)),
|
||||||
jfNormalize.song(
|
|
||||||
item,
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
setPlaylistSongs: async (args) => {
|
setPlaylistSongs: async (args) => {
|
||||||
|
|||||||
@@ -367,7 +367,7 @@ export const NavidromeController: InternalControllerEndpoint = {
|
|||||||
query: { ...query, limit: 1, startIndex: 0 },
|
query: { ...query, limit: 1, startIndex: 0 },
|
||||||
}).then((result) => result!.totalRecordCount!),
|
}).then((result) => result!.totalRecordCount!),
|
||||||
getAlbumDetail: async (args) => {
|
getAlbumDetail: async (args) => {
|
||||||
const { apiClientProps, context, query } = args;
|
const { apiClientProps, query } = args;
|
||||||
|
|
||||||
const albumRes = await ndApiClient(apiClientProps).getAlbumDetail({
|
const albumRes = await ndApiClient(apiClientProps).getAlbumDetail({
|
||||||
params: {
|
params: {
|
||||||
@@ -393,8 +393,6 @@ export const NavidromeController: InternalControllerEndpoint = {
|
|||||||
return ndNormalize.album(
|
return ndNormalize.album(
|
||||||
{ ...albumRes.body.data, songs: songsData.body.data },
|
{ ...albumRes.body.data, songs: songsData.body.data },
|
||||||
apiClientProps.server,
|
apiClientProps.server,
|
||||||
context?.pathReplace,
|
|
||||||
context?.pathReplaceWith,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
getAlbumInfo: async (args) => {
|
getAlbumInfo: async (args) => {
|
||||||
@@ -418,7 +416,7 @@ export const NavidromeController: InternalControllerEndpoint = {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
getAlbumList: async (args) => {
|
getAlbumList: async (args) => {
|
||||||
const { apiClientProps, context, query } = args;
|
const { apiClientProps, query } = args;
|
||||||
|
|
||||||
const genres = hasFeature(apiClientProps.server, ServerFeature.BFR)
|
const genres = hasFeature(apiClientProps.server, ServerFeature.BFR)
|
||||||
? query.genreIds
|
? query.genreIds
|
||||||
@@ -453,14 +451,7 @@ export const NavidromeController: InternalControllerEndpoint = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: res.body.data.map((album) =>
|
items: res.body.data.map((album) => ndNormalize.album(album, apiClientProps.server)),
|
||||||
ndNormalize.album(
|
|
||||||
album,
|
|
||||||
apiClientProps.server,
|
|
||||||
context?.pathReplace,
|
|
||||||
context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
startIndex: query?.startIndex || 0,
|
startIndex: query?.startIndex || 0,
|
||||||
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
|
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
|
||||||
};
|
};
|
||||||
@@ -493,12 +484,7 @@ export const NavidromeController: InternalControllerEndpoint = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return res.body.similarSongs.song.map((song) =>
|
return res.body.similarSongs.song.map((song) =>
|
||||||
ssNormalize.song(
|
ssNormalize.song(song, apiClientProps.server),
|
||||||
song,
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
getArtistList: async (args) => {
|
getArtistList: async (args) => {
|
||||||
@@ -568,12 +554,7 @@ export const NavidromeController: InternalControllerEndpoint = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return res.body.similarSongs2.song.map((song) =>
|
return res.body.similarSongs2.song.map((song) =>
|
||||||
ssNormalize.song(
|
ssNormalize.song(song, apiClientProps.server),
|
||||||
song,
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
getDownloadUrl: SubsonicController.getDownloadUrl,
|
getDownloadUrl: SubsonicController.getDownloadUrl,
|
||||||
@@ -723,14 +704,7 @@ export const NavidromeController: InternalControllerEndpoint = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: res.body.data.map((item) =>
|
items: res.body.data.map((item) => ndNormalize.song(item, apiClientProps.server)),
|
||||||
ndNormalize.song(
|
|
||||||
item,
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
|
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
|
||||||
};
|
};
|
||||||
@@ -747,14 +721,7 @@ export const NavidromeController: InternalControllerEndpoint = {
|
|||||||
|
|
||||||
const { changedBy, current, items = [], position, updatedAt } = res.body.data; // if there is no queue saved, items is undefined
|
const { changedBy, current, items = [], position, updatedAt } = res.body.data; // if there is no queue saved, items is undefined
|
||||||
|
|
||||||
const entries = items.map((song) =>
|
const entries = items.map((song) => ndNormalize.song(song, apiClientProps.server));
|
||||||
ndNormalize.song(
|
|
||||||
song,
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
changed: updatedAt,
|
changed: updatedAt,
|
||||||
@@ -830,14 +797,7 @@ export const NavidromeController: InternalControllerEndpoint = {
|
|||||||
return (
|
return (
|
||||||
(res.body.similarSongs?.song || [])
|
(res.body.similarSongs?.song || [])
|
||||||
.filter((song) => song.id !== query.songId)
|
.filter((song) => song.id !== query.songId)
|
||||||
.map((song) =>
|
.map((song) => ssNormalize.song(song, apiClientProps.server)) || []
|
||||||
ssNormalize.song(
|
|
||||||
song,
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
) || []
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
getSongDetail: async (args) => {
|
getSongDetail: async (args) => {
|
||||||
@@ -853,12 +813,7 @@ export const NavidromeController: InternalControllerEndpoint = {
|
|||||||
throw new Error('Failed to get song detail');
|
throw new Error('Failed to get song detail');
|
||||||
}
|
}
|
||||||
|
|
||||||
return ndNormalize.song(
|
return ndNormalize.song(res.body.data, apiClientProps.server);
|
||||||
res.body.data,
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
getSongList: async (args) => {
|
getSongList: async (args) => {
|
||||||
const { apiClientProps, query } = args;
|
const { apiClientProps, query } = args;
|
||||||
@@ -898,14 +853,7 @@ export const NavidromeController: InternalControllerEndpoint = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: res.body.data.map((song) =>
|
items: res.body.data.map((song) => ndNormalize.song(song, apiClientProps.server)),
|
||||||
ndNormalize.song(
|
|
||||||
song,
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
|
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -1022,12 +970,7 @@ export const NavidromeController: InternalControllerEndpoint = {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
items: (res.body.topSongs?.song || []).map((song) =>
|
items: (res.body.topSongs?.song || []).map((song) =>
|
||||||
ssNormalize.song(
|
ssNormalize.song(song, apiClientProps.server),
|
||||||
song,
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
totalRecordCount: res.body.topSongs?.song?.length || 0,
|
totalRecordCount: res.body.topSongs?.song?.length || 0,
|
||||||
@@ -1036,7 +979,6 @@ export const NavidromeController: InternalControllerEndpoint = {
|
|||||||
|
|
||||||
const res = await NavidromeController.getSongList({
|
const res = await NavidromeController.getSongList({
|
||||||
apiClientProps,
|
apiClientProps,
|
||||||
context: args.context,
|
|
||||||
query: {
|
query: {
|
||||||
artistIds: [query.artistId],
|
artistIds: [query.artistId],
|
||||||
sortBy: SongListSort.PLAY_COUNT,
|
sortBy: SongListSort.PLAY_COUNT,
|
||||||
@@ -1138,12 +1080,7 @@ export const NavidromeController: InternalControllerEndpoint = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const existingSongs = existingSongsRes.body.data.map((item) =>
|
const existingSongs = existingSongsRes.body.data.map((item) =>
|
||||||
ndNormalize.song(
|
ndNormalize.song(item, apiClientProps.server),
|
||||||
item,
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// 2. Get playlist detail to get the name
|
// 2. Get playlist detail to get the name
|
||||||
|
|||||||
@@ -482,14 +482,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...ssNormalize.albumArtist(artist, apiClientProps.server),
|
...ssNormalize.albumArtist(artist, apiClientProps.server),
|
||||||
albums: artist.album?.map((album) =>
|
albums: artist.album?.map((album) => ssNormalize.album(album, apiClientProps.server)),
|
||||||
ssNormalize.album(
|
|
||||||
album,
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
similarArtists: null,
|
similarArtists: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -564,7 +557,6 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
getAlbumArtistListCount: (args) =>
|
getAlbumArtistListCount: (args) =>
|
||||||
SubsonicController.getAlbumArtistList({
|
SubsonicController.getAlbumArtistList({
|
||||||
...args,
|
...args,
|
||||||
context: args.context,
|
|
||||||
query: { ...args.query, startIndex: 0 },
|
query: { ...args.query, startIndex: 0 },
|
||||||
}).then((res) => res!.totalRecordCount!),
|
}).then((res) => res!.totalRecordCount!),
|
||||||
getAlbumDetail: async (args) => {
|
getAlbumDetail: async (args) => {
|
||||||
@@ -580,12 +572,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
throw new Error('Failed to get album detail');
|
throw new Error('Failed to get album detail');
|
||||||
}
|
}
|
||||||
|
|
||||||
return ssNormalize.album(
|
return ssNormalize.album(res.body.album, apiClientProps.server);
|
||||||
res.body.album,
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
getAlbumList: async (args) => {
|
getAlbumList: async (args) => {
|
||||||
const { apiClientProps, query } = args;
|
const { apiClientProps, query } = args;
|
||||||
@@ -610,12 +597,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
|
|
||||||
const results =
|
const results =
|
||||||
res.body.searchResult3?.album?.map((album) =>
|
res.body.searchResult3?.album?.map((album) =>
|
||||||
ssNormalize.album(
|
ssNormalize.album(album, apiClientProps.server),
|
||||||
album,
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
) || [];
|
) || [];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -650,14 +632,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
return artist.body.artist.album ?? [];
|
return artist.body.artist.album ?? [];
|
||||||
});
|
});
|
||||||
|
|
||||||
const items = albums.map((album) =>
|
const items = albums.map((album) => ssNormalize.album(album, apiClientProps.server));
|
||||||
ssNormalize.album(
|
|
||||||
album,
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: sortAlbumList(items, query.sortBy, query.sortOrder),
|
items: sortAlbumList(items, query.sortBy, query.sortOrder),
|
||||||
@@ -679,12 +654,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
|
|
||||||
const allResults =
|
const allResults =
|
||||||
res.body.starred?.album?.map((album) =>
|
res.body.starred?.album?.map((album) =>
|
||||||
ssNormalize.album(
|
ssNormalize.album(album, apiClientProps.server),
|
||||||
album,
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
) || [];
|
) || [];
|
||||||
|
|
||||||
return sortAndPaginate(allResults, {
|
return sortAndPaginate(allResults, {
|
||||||
@@ -749,12 +719,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
return {
|
return {
|
||||||
items:
|
items:
|
||||||
res.body.albumList2.album?.map((album) =>
|
res.body.albumList2.album?.map((album) =>
|
||||||
ssNormalize.album(
|
ssNormalize.album(album, apiClientProps.server),
|
||||||
album,
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
) || [],
|
) || [],
|
||||||
startIndex: query.startIndex,
|
startIndex: query.startIndex,
|
||||||
totalRecordCount: null,
|
totalRecordCount: null,
|
||||||
@@ -905,7 +870,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
return totalRecordCount;
|
return totalRecordCount;
|
||||||
},
|
},
|
||||||
getAlbumRadio: async (args) => {
|
getAlbumRadio: async (args) => {
|
||||||
const { apiClientProps, context, query } = args;
|
const { apiClientProps, query } = args;
|
||||||
|
|
||||||
const res = await ssApiClient(apiClientProps).getSimilarSongs({
|
const res = await ssApiClient(apiClientProps).getSimilarSongs({
|
||||||
query: {
|
query: {
|
||||||
@@ -923,12 +888,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return res.body.similarSongs.song.map((song) =>
|
return res.body.similarSongs.song.map((song) =>
|
||||||
ssNormalize.song(
|
ssNormalize.song(song, apiClientProps.server),
|
||||||
song,
|
|
||||||
apiClientProps.server,
|
|
||||||
context?.pathReplace,
|
|
||||||
context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
getArtistList: async (args) => {
|
getArtistList: async (args) => {
|
||||||
@@ -974,11 +934,10 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
getArtistListCount: async (args) =>
|
getArtistListCount: async (args) =>
|
||||||
SubsonicController.getArtistList({
|
SubsonicController.getArtistList({
|
||||||
...args,
|
...args,
|
||||||
context: args.context,
|
|
||||||
query: { ...args.query, startIndex: 0 },
|
query: { ...args.query, startIndex: 0 },
|
||||||
}).then((res) => res!.totalRecordCount!),
|
}).then((res) => res!.totalRecordCount!),
|
||||||
getArtistRadio: async (args) => {
|
getArtistRadio: async (args) => {
|
||||||
const { apiClientProps, context, query } = args;
|
const { apiClientProps, query } = args;
|
||||||
|
|
||||||
const res = await ssApiClient(apiClientProps).getSimilarSongs2({
|
const res = await ssApiClient(apiClientProps).getSimilarSongs2({
|
||||||
query: {
|
query: {
|
||||||
@@ -996,12 +955,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return res.body.similarSongs2.song.map((song) =>
|
return res.body.similarSongs2.song.map((song) =>
|
||||||
ssNormalize.song(
|
ssNormalize.song(song, apiClientProps.server),
|
||||||
song,
|
|
||||||
apiClientProps.server,
|
|
||||||
context?.pathReplace,
|
|
||||||
context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
getDownloadUrl: (args) => {
|
getDownloadUrl: (args) => {
|
||||||
@@ -1015,7 +969,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
'&c=Feishin'
|
'&c=Feishin'
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
getFolder: async ({ apiClientProps, context, query }) => {
|
getFolder: async ({ apiClientProps, query }) => {
|
||||||
const sortOrder = (query.sortOrder?.toLowerCase() ?? 'asc') as 'asc' | 'desc';
|
const sortOrder = (query.sortOrder?.toLowerCase() ?? 'asc') as 'asc' | 'desc';
|
||||||
|
|
||||||
const isRootFolderId = query.id === '0';
|
const isRootFolderId = query.id === '0';
|
||||||
@@ -1048,14 +1002,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let folders = items.map((item) =>
|
let folders = items.map((item) => ssNormalize.folder(item, apiClientProps.server));
|
||||||
ssNormalize.folder(
|
|
||||||
item,
|
|
||||||
apiClientProps.server,
|
|
||||||
context?.pathReplace,
|
|
||||||
context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
folders = orderBy(folders, [(v) => v.name.toLowerCase()], [sortOrder]);
|
folders = orderBy(folders, [(v) => v.name.toLowerCase()], [sortOrder]);
|
||||||
|
|
||||||
@@ -1083,12 +1030,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
throw new Error('Failed to get folder');
|
throw new Error('Failed to get folder');
|
||||||
}
|
}
|
||||||
|
|
||||||
const folder = ssNormalize.folder(
|
const folder = ssNormalize.folder(directoryRes.body.directory, apiClientProps.server);
|
||||||
directoryRes.body.directory,
|
|
||||||
apiClientProps.server,
|
|
||||||
context?.pathReplace,
|
|
||||||
context?.pathReplaceWith,
|
|
||||||
);
|
|
||||||
|
|
||||||
let filteredFolders = folder.children?.folders || [];
|
let filteredFolders = folder.children?.folders || [];
|
||||||
let filteredSongs = folder.children?.songs || [];
|
let filteredSongs = folder.children?.songs || [];
|
||||||
@@ -1281,7 +1223,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
|
|
||||||
return results.length;
|
return results.length;
|
||||||
},
|
},
|
||||||
getPlaylistSongList: async ({ apiClientProps, context, query }) => {
|
getPlaylistSongList: async ({ apiClientProps, query }) => {
|
||||||
const res = await ssApiClient(apiClientProps).getPlaylist({
|
const res = await ssApiClient(apiClientProps).getPlaylist({
|
||||||
query: {
|
query: {
|
||||||
id: query.id,
|
id: query.id,
|
||||||
@@ -1294,13 +1236,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
|
|
||||||
const items =
|
const items =
|
||||||
res.body.playlist.entry?.map((song, index) =>
|
res.body.playlist.entry?.map((song, index) =>
|
||||||
ssNormalize.song(
|
ssNormalize.song(song, apiClientProps.server, index),
|
||||||
song,
|
|
||||||
apiClientProps.server,
|
|
||||||
context?.pathReplace,
|
|
||||||
context?.pathReplaceWith,
|
|
||||||
index,
|
|
||||||
),
|
|
||||||
) || [];
|
) || [];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -1309,7 +1245,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
totalRecordCount: items.length,
|
totalRecordCount: items.length,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getPlayQueue: async ({ apiClientProps, context }) => {
|
getPlayQueue: async ({ apiClientProps }) => {
|
||||||
if (hasFeature(apiClientProps.server, ServerFeature.SERVER_PLAY_QUEUE)) {
|
if (hasFeature(apiClientProps.server, ServerFeature.SERVER_PLAY_QUEUE)) {
|
||||||
const res = await ssApiClient(apiClientProps).getPlayQueueByIndex();
|
const res = await ssApiClient(apiClientProps).getPlayQueueByIndex();
|
||||||
|
|
||||||
@@ -1324,15 +1260,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
changed: changed ?? '',
|
changed: changed ?? '',
|
||||||
changedBy: changedBy ?? '',
|
changedBy: changedBy ?? '',
|
||||||
currentIndex: currentIndex ?? 0,
|
currentIndex: currentIndex ?? 0,
|
||||||
entry:
|
entry: entry?.map((song) => ssNormalize.song(song, apiClientProps.server)) || [],
|
||||||
entry?.map((song) =>
|
|
||||||
ssNormalize.song(
|
|
||||||
song,
|
|
||||||
apiClientProps.server,
|
|
||||||
context?.pathReplace,
|
|
||||||
context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
) || [],
|
|
||||||
positionMs: position ?? 0,
|
positionMs: position ?? 0,
|
||||||
username: username ?? '',
|
username: username ?? '',
|
||||||
};
|
};
|
||||||
@@ -1349,22 +1277,14 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
changed,
|
changed,
|
||||||
changedBy,
|
changedBy,
|
||||||
currentIndex: current ? entry.findIndex((item) => item.id === current) : 0,
|
currentIndex: current ? entry.findIndex((item) => item.id === current) : 0,
|
||||||
entry:
|
entry: entry?.map((song) => ssNormalize.song(song, apiClientProps.server)) || [],
|
||||||
entry?.map((song) =>
|
|
||||||
ssNormalize.song(
|
|
||||||
song,
|
|
||||||
apiClientProps.server,
|
|
||||||
context?.pathReplace,
|
|
||||||
context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
) || [],
|
|
||||||
positionMs: position ?? 0,
|
positionMs: position ?? 0,
|
||||||
username,
|
username,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getRandomSongList: async (args) => {
|
getRandomSongList: async (args) => {
|
||||||
const { apiClientProps, context, query } = args;
|
const { apiClientProps, query } = args;
|
||||||
|
|
||||||
const res = await ssApiClient(apiClientProps).getRandomSongList({
|
const res = await ssApiClient(apiClientProps).getRandomSongList({
|
||||||
query: {
|
query: {
|
||||||
@@ -1382,12 +1302,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
|
|
||||||
const results = res.body.randomSongs?.song || [];
|
const results = res.body.randomSongs?.song || [];
|
||||||
const normalizedResults = results.map((song) =>
|
const normalizedResults = results.map((song) =>
|
||||||
ssNormalize.song(
|
ssNormalize.song(song, apiClientProps.server),
|
||||||
song,
|
|
||||||
apiClientProps.server,
|
|
||||||
context?.pathReplace,
|
|
||||||
context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -1473,7 +1388,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
return { features, id: apiClientProps.server?.id, version: ping.body.serverVersion };
|
return { features, id: apiClientProps.server?.id, version: ping.body.serverVersion };
|
||||||
},
|
},
|
||||||
getSimilarSongs: async (args) => {
|
getSimilarSongs: async (args) => {
|
||||||
const { apiClientProps, context, query } = args;
|
const { apiClientProps, query } = args;
|
||||||
|
|
||||||
const res = await ssApiClient(apiClientProps).getSimilarSongs({
|
const res = await ssApiClient(apiClientProps).getSimilarSongs({
|
||||||
query: {
|
query: {
|
||||||
@@ -1492,21 +1407,14 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
|
|
||||||
return res.body.similarSongs.song.reduce<Song[]>((acc, song) => {
|
return res.body.similarSongs.song.reduce<Song[]>((acc, song) => {
|
||||||
if (song.id !== query.songId) {
|
if (song.id !== query.songId) {
|
||||||
acc.push(
|
acc.push(ssNormalize.song(song, apiClientProps.server));
|
||||||
ssNormalize.song(
|
|
||||||
song,
|
|
||||||
apiClientProps.server,
|
|
||||||
context?.pathReplace,
|
|
||||||
context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, []);
|
}, []);
|
||||||
},
|
},
|
||||||
getSongDetail: async (args) => {
|
getSongDetail: async (args) => {
|
||||||
const { apiClientProps, context, query } = args;
|
const { apiClientProps, query } = args;
|
||||||
|
|
||||||
const res = await ssApiClient(apiClientProps).getSong({
|
const res = await ssApiClient(apiClientProps).getSong({
|
||||||
query: {
|
query: {
|
||||||
@@ -1518,14 +1426,9 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
throw new Error('Failed to get song detail');
|
throw new Error('Failed to get song detail');
|
||||||
}
|
}
|
||||||
|
|
||||||
return ssNormalize.song(
|
return ssNormalize.song(res.body.song, apiClientProps.server);
|
||||||
res.body.song,
|
|
||||||
apiClientProps.server,
|
|
||||||
context?.pathReplace,
|
|
||||||
context?.pathReplaceWith,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
getSongList: async ({ apiClientProps, context, query }) => {
|
getSongList: async ({ apiClientProps, query }) => {
|
||||||
const fromAlbumPromises: Promise<ServerInferResponses<typeof contract.getAlbum>>[] = [];
|
const fromAlbumPromises: Promise<ServerInferResponses<typeof contract.getAlbum>>[] = [];
|
||||||
const artistDetailPromises: Promise<ServerInferResponses<typeof contract.getArtist>>[] = [];
|
const artistDetailPromises: Promise<ServerInferResponses<typeof contract.getArtist>>[] = [];
|
||||||
|
|
||||||
@@ -1550,12 +1453,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
return {
|
return {
|
||||||
items:
|
items:
|
||||||
res.body.searchResult3?.song?.map((song) =>
|
res.body.searchResult3?.song?.map((song) =>
|
||||||
ssNormalize.song(
|
ssNormalize.song(song, apiClientProps.server),
|
||||||
song,
|
|
||||||
apiClientProps.server,
|
|
||||||
context?.pathReplace,
|
|
||||||
context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
) || [],
|
) || [],
|
||||||
startIndex: query.startIndex,
|
startIndex: query.startIndex,
|
||||||
totalRecordCount: null,
|
totalRecordCount: null,
|
||||||
@@ -1579,15 +1477,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
const results = res.body.songsByGenre?.song || [];
|
const results = res.body.songsByGenre?.song || [];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items:
|
items: results.map((song) => ssNormalize.song(song, apiClientProps.server)) || [],
|
||||||
results.map((song) =>
|
|
||||||
ssNormalize.song(
|
|
||||||
song,
|
|
||||||
apiClientProps.server,
|
|
||||||
context?.pathReplace,
|
|
||||||
context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
) || [],
|
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
totalRecordCount: null,
|
totalRecordCount: null,
|
||||||
};
|
};
|
||||||
@@ -1606,12 +1496,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
|
|
||||||
let allResults =
|
let allResults =
|
||||||
(res.body.starred?.song || []).map((song) =>
|
(res.body.starred?.song || []).map((song) =>
|
||||||
ssNormalize.song(
|
ssNormalize.song(song, apiClientProps.server),
|
||||||
song,
|
|
||||||
apiClientProps.server,
|
|
||||||
context?.pathReplace,
|
|
||||||
context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
) || [];
|
) || [];
|
||||||
|
|
||||||
const filterArtistIds = query.albumArtistIds || query.artistIds;
|
const filterArtistIds = query.albumArtistIds || query.artistIds;
|
||||||
@@ -1696,15 +1581,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items:
|
items: results.map((song) => ssNormalize.song(song, apiClientProps.server)) || [],
|
||||||
results.map((song) =>
|
|
||||||
ssNormalize.song(
|
|
||||||
song,
|
|
||||||
apiClientProps.server,
|
|
||||||
context?.pathReplace,
|
|
||||||
context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
) || [],
|
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
totalRecordCount: results.length,
|
totalRecordCount: results.length,
|
||||||
};
|
};
|
||||||
@@ -1730,12 +1607,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
return {
|
return {
|
||||||
items:
|
items:
|
||||||
res.body.searchResult3?.song?.map((song) =>
|
res.body.searchResult3?.song?.map((song) =>
|
||||||
ssNormalize.song(
|
ssNormalize.song(song, apiClientProps.server),
|
||||||
song,
|
|
||||||
apiClientProps.server,
|
|
||||||
context?.pathReplace,
|
|
||||||
context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
) || [],
|
) || [],
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
totalRecordCount: null,
|
totalRecordCount: null,
|
||||||
@@ -2103,7 +1975,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
getTopSongs: async (args) => {
|
getTopSongs: async (args) => {
|
||||||
const { apiClientProps, context, query } = args;
|
const { apiClientProps, query } = args;
|
||||||
|
|
||||||
const type = query.type === 'personal' ? 'personal' : 'community';
|
const type = query.type === 'personal' ? 'personal' : 'community';
|
||||||
|
|
||||||
@@ -2121,12 +1993,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
items: (res.body.topSongs?.song || []).map((song) =>
|
items: (res.body.topSongs?.song || []).map((song) =>
|
||||||
ssNormalize.song(
|
ssNormalize.song(song, apiClientProps.server),
|
||||||
song,
|
|
||||||
apiClientProps.server,
|
|
||||||
context?.pathReplace,
|
|
||||||
context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
totalRecordCount: res.body.topSongs?.song?.length || 0,
|
totalRecordCount: res.body.topSongs?.song?.length || 0,
|
||||||
@@ -2135,7 +2002,6 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
|
|
||||||
const res = await SubsonicController.getSongList({
|
const res = await SubsonicController.getSongList({
|
||||||
apiClientProps,
|
apiClientProps,
|
||||||
context,
|
|
||||||
query: {
|
query: {
|
||||||
artistIds: [query.artistId],
|
artistIds: [query.artistId],
|
||||||
sortBy: SongListSort.PLAY_COUNT,
|
sortBy: SongListSort.PLAY_COUNT,
|
||||||
@@ -2190,7 +2056,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
replacePlaylist: async (args) => {
|
replacePlaylist: async (args) => {
|
||||||
const { apiClientProps, body, context, query } = args;
|
const { apiClientProps, body, query } = args;
|
||||||
|
|
||||||
// 1. Fetch existing songs from the playlist
|
// 1. Fetch existing songs from the playlist
|
||||||
const existingSongsRes = await ssApiClient(apiClientProps).getPlaylist({
|
const existingSongsRes = await ssApiClient(apiClientProps).getPlaylist({
|
||||||
@@ -2205,12 +2071,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
|
|
||||||
const existingSongs =
|
const existingSongs =
|
||||||
existingSongsRes.body.playlist.entry?.map((song) =>
|
existingSongsRes.body.playlist.entry?.map((song) =>
|
||||||
ssNormalize.song(
|
ssNormalize.song(song, apiClientProps.server),
|
||||||
song,
|
|
||||||
apiClientProps.server,
|
|
||||||
context?.pathReplace,
|
|
||||||
context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
) || [];
|
) || [];
|
||||||
|
|
||||||
// 2. Get playlist detail to get the name
|
// 2. Get playlist detail to get the name
|
||||||
@@ -2388,7 +2249,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
search: async (args) => {
|
search: async (args) => {
|
||||||
const { apiClientProps, context, query } = args;
|
const { apiClientProps, query } = args;
|
||||||
|
|
||||||
const res = await ssApiClient(apiClientProps).search3({
|
const res = await ssApiClient(apiClientProps).search3({
|
||||||
query: {
|
query: {
|
||||||
@@ -2412,20 +2273,10 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
ssNormalize.albumArtist(artist, apiClientProps.server),
|
ssNormalize.albumArtist(artist, apiClientProps.server),
|
||||||
),
|
),
|
||||||
albums: (res.body.searchResult3?.album || []).map((album) =>
|
albums: (res.body.searchResult3?.album || []).map((album) =>
|
||||||
ssNormalize.album(
|
ssNormalize.album(album, apiClientProps.server),
|
||||||
album,
|
|
||||||
apiClientProps.server,
|
|
||||||
args.context?.pathReplace,
|
|
||||||
args.context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
songs: (res.body.searchResult3?.song || []).map((song) =>
|
songs: (res.body.searchResult3?.song || []).map((song) =>
|
||||||
ssNormalize.song(
|
ssNormalize.song(song, apiClientProps.server),
|
||||||
song,
|
|
||||||
apiClientProps.server,
|
|
||||||
context?.pathReplace,
|
|
||||||
context?.pathReplaceWith,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
import { ItemDetailListCellProps } from './types';
|
import { ItemDetailListCellProps } from './types';
|
||||||
|
|
||||||
export const PathColumn = ({ song }: ItemDetailListCellProps) => song.path ?? <> </>;
|
import { resolveSongPath } from '/@/renderer/utils/resolve-song-path';
|
||||||
|
|
||||||
|
export const PathColumn = ({ song }: ItemDetailListCellProps) =>
|
||||||
|
resolveSongPath(song.path) ?? <> </>;
|
||||||
|
|||||||
@@ -4,15 +4,17 @@ import {
|
|||||||
ItemTableListInnerColumn,
|
ItemTableListInnerColumn,
|
||||||
TableColumnTextContainer,
|
TableColumnTextContainer,
|
||||||
} from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
|
} from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
|
||||||
|
import { resolveSongPath } from '/@/renderer/utils/resolve-song-path';
|
||||||
|
|
||||||
export const PathColumn = (props: ItemTableListInnerColumn) => {
|
export const PathColumn = (props: ItemTableListInnerColumn) => {
|
||||||
const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
|
const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
|
||||||
const row: string | undefined = (rowItem as any)?.[props.columns[props.columnIndex].id];
|
const row: string | undefined = (rowItem as any)?.[props.columns[props.columnIndex].id];
|
||||||
|
const resolvedPath = typeof row === 'string' ? resolveSongPath(row) : null;
|
||||||
|
|
||||||
if (typeof row === 'string' && row) {
|
if (resolvedPath) {
|
||||||
return (
|
return (
|
||||||
<TableColumnTextContainer {...props}>
|
<TableColumnTextContainer {...props}>
|
||||||
<span>{row}</span>
|
<span>{resolvedPath}</span>
|
||||||
</TableColumnTextContainer>
|
</TableColumnTextContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { RefObject, useEffect, useLayoutEffect } from 'react';
|
||||||
|
import { useLocation, useNavigationType } from 'react-router';
|
||||||
|
|
||||||
|
import { useScrollStore } from '/@/renderer/store/scroll.store';
|
||||||
|
|
||||||
|
interface UseNativeScrollPersistProps {
|
||||||
|
enabled: boolean;
|
||||||
|
scrollRef: RefObject<HTMLDivElement | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// OverlayScrollbars initializes on the NativeScrollArea container and moves the
|
||||||
|
// content into a viewport child element; that child is what actually scrolls,
|
||||||
|
// so scrollTop must be read from and written to it rather than the container
|
||||||
|
// the ref points at.
|
||||||
|
const getScrollNode = (scrollRef: RefObject<HTMLDivElement | null>): HTMLElement | null => {
|
||||||
|
const node = scrollRef.current?.children[0];
|
||||||
|
return node instanceof HTMLElement ? node : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Persists vertical scroll offset for a NativeScrollArea, keyed by react-router
|
||||||
|
// location.key. Restores the saved offset only on POP navigation; PUSH/REPLACE
|
||||||
|
// continue to start at the top.
|
||||||
|
export const useNativeScrollPersist = ({ enabled, scrollRef }: UseNativeScrollPersistProps) => {
|
||||||
|
const location = useLocation();
|
||||||
|
const navigationType = useNavigationType();
|
||||||
|
const setOffset = useScrollStore((s) => s.setOffset);
|
||||||
|
const getOffset = useScrollStore((s) => s.getOffset);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const saved = getOffset(location.key);
|
||||||
|
if (!enabled || navigationType !== 'POP' || typeof saved !== 'number') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyOffset = () => {
|
||||||
|
const node = getScrollNode(scrollRef);
|
||||||
|
if (node) {
|
||||||
|
node.scrollTop = saved;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
applyOffset();
|
||||||
|
const raf = requestAnimationFrame(applyOffset);
|
||||||
|
return () => cancelAnimationFrame(raf);
|
||||||
|
}, [enabled, getOffset, location.key, navigationType, scrollRef]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const node = getScrollNode(scrollRef);
|
||||||
|
if (!enabled || !node) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleScroll = () => {
|
||||||
|
setOffset(location.key, node.scrollTop);
|
||||||
|
};
|
||||||
|
|
||||||
|
node.addEventListener('scroll', handleScroll, { passive: true });
|
||||||
|
return () => {
|
||||||
|
node.removeEventListener('scroll', handleScroll);
|
||||||
|
};
|
||||||
|
}, [enabled, location.key, scrollRef, setOffset]);
|
||||||
|
};
|
||||||
@@ -4,6 +4,7 @@ import { useParams } from 'react-router';
|
|||||||
|
|
||||||
import { useItemImageUrl } from '/@/renderer/components/item-image/item-image';
|
import { useItemImageUrl } from '/@/renderer/components/item-image/item-image';
|
||||||
import { NativeScrollArea } from '/@/renderer/components/native-scroll-area/native-scroll-area';
|
import { NativeScrollArea } from '/@/renderer/components/native-scroll-area/native-scroll-area';
|
||||||
|
import { useNativeScrollPersist } from '/@/renderer/components/native-scroll-area/use-native-scroll-persist';
|
||||||
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
|
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
|
||||||
import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
||||||
import { AlbumArtistDetailContent } from '/@/renderer/features/artists/components/album-artist-detail-content';
|
import { AlbumArtistDetailContent } from '/@/renderer/features/artists/components/album-artist-detail-content';
|
||||||
@@ -28,6 +29,8 @@ const AlbumArtistDetailRouteContent = () => {
|
|||||||
const serverId = useCurrentServerId();
|
const serverId = useCurrentServerId();
|
||||||
const { artistBackground, artistBackgroundBlur } = useArtistBackground();
|
const { artistBackground, artistBackgroundBlur } = useArtistBackground();
|
||||||
|
|
||||||
|
useNativeScrollPersist({ enabled: true, scrollRef: scrollAreaRef });
|
||||||
|
|
||||||
const { albumArtistId, artistId } = useParams() as {
|
const { albumArtistId, artistId } = useParams() as {
|
||||||
albumArtistId?: string;
|
albumArtistId?: string;
|
||||||
artistId?: string;
|
artistId?: string;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import isElectron from 'is-electron';
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { resolveSongPath } from '/@/renderer/utils/resolve-song-path';
|
||||||
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||||
import { toast } from '/@/shared/components/toast/toast';
|
import { toast } from '/@/shared/components/toast/toast';
|
||||||
import { QueueSong, Song } from '/@/shared/types/domain-types';
|
import { QueueSong, Song } from '/@/shared/types/domain-types';
|
||||||
@@ -21,12 +22,13 @@ export const ShowInFileExplorerAction = ({ items }: ShowInFileExplorerActionProp
|
|||||||
}
|
}
|
||||||
|
|
||||||
const firstItem = items[0];
|
const firstItem = items[0];
|
||||||
if (!firstItem?.path) {
|
const resolvedPath = resolveSongPath(firstItem?.path);
|
||||||
|
if (!resolvedPath) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await utils.openItem(firstItem.path);
|
await utils.openItem(resolvedPath);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error({
|
toast.error({
|
||||||
message: (error as Error).message,
|
message: (error as Error).message,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import isElectron from 'is-electron';
|
import isElectron from 'is-electron';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { useResolvedSongPath } from '/@/renderer/utils/resolve-song-path';
|
||||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||||
import { CopyButton } from '/@/shared/components/copy-button/copy-button';
|
import { CopyButton } from '/@/shared/components/copy-button/copy-button';
|
||||||
import { Group } from '/@/shared/components/group/group';
|
import { Group } from '/@/shared/components/group/group';
|
||||||
@@ -17,12 +18,13 @@ export type SongPathProps = {
|
|||||||
|
|
||||||
export const SongPath = ({ path }: SongPathProps) => {
|
export const SongPath = ({ path }: SongPathProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const resolvedPath = useResolvedSongPath(path);
|
||||||
|
|
||||||
if (!path) return null;
|
if (!resolvedPath) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group>
|
<Group>
|
||||||
<CopyButton timeout={2000} value={path}>
|
<CopyButton timeout={2000} value={resolvedPath}>
|
||||||
{({ copied, copy }) => (
|
{({ copied, copy }) => (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
label={t(
|
label={t(
|
||||||
@@ -42,7 +44,7 @@ export const SongPath = ({ path }: SongPathProps) => {
|
|||||||
<ActionIcon
|
<ActionIcon
|
||||||
icon="externalLink"
|
icon="externalLink"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
util.openItem(path).catch((error) => {
|
util.openItem(resolvedPath).catch((error) => {
|
||||||
toast.error({
|
toast.error({
|
||||||
message: (error as Error).message,
|
message: (error as Error).message,
|
||||||
title: t('error.openError'),
|
title: t('error.openError'),
|
||||||
@@ -53,7 +55,7 @@ export const SongPath = ({ path }: SongPathProps) => {
|
|||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
<Text style={{ userSelect: 'all' }}>{path}</Text>
|
<Text style={{ userSelect: 'all' }}>{resolvedPath}</Text>
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { folderQueries } from '/@/renderer/features/folders/api/folder-api';
|
|||||||
import { PlayerFilter, useSettingsStore } from '/@/renderer/store';
|
import { PlayerFilter, useSettingsStore } from '/@/renderer/store';
|
||||||
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||||
import { logMsg } from '/@/renderer/utils/logger-message';
|
import { logMsg } from '/@/renderer/utils/logger-message';
|
||||||
|
import { resolveSongPath } from '/@/renderer/utils/resolve-song-path';
|
||||||
import { sortSongList } from '/@/shared/api/utils';
|
import { sortSongList } from '/@/shared/api/utils';
|
||||||
import {
|
import {
|
||||||
PlaylistSongListQuery,
|
PlaylistSongListQuery,
|
||||||
@@ -351,7 +352,7 @@ const getSongFieldValue = (song: Song, field: string): boolean | null | number |
|
|||||||
case 'note':
|
case 'note':
|
||||||
return song.comment || '';
|
return song.comment || '';
|
||||||
case 'path':
|
case 'path':
|
||||||
return song.path || '';
|
return resolveSongPath(song.path) || '';
|
||||||
case 'playCount':
|
case 'playCount':
|
||||||
return song.playCount;
|
return song.playCount;
|
||||||
case 'rating':
|
case 'rating':
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
|
|
||||||
import { songsQueries } from '/@/renderer/features/songs/api/songs-api';
|
import { songsQueries } from '/@/renderer/features/songs/api/songs-api';
|
||||||
import { useCurrentServerId, useGeneralSettings, useSettingsStoreActions } from '/@/renderer/store';
|
import { useCurrentServerId, useGeneralSettings, useSettingsStoreActions } from '/@/renderer/store';
|
||||||
|
import { useResolvedSongPath } from '/@/renderer/utils/resolve-song-path';
|
||||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||||
import { Code } from '/@/shared/components/code/code';
|
import { Code } from '/@/shared/components/code/code';
|
||||||
import { Group } from '/@/shared/components/group/group';
|
import { Group } from '/@/shared/components/group/group';
|
||||||
@@ -27,6 +28,7 @@ export const PathSettings = memo(() => {
|
|||||||
|
|
||||||
const { pathReplace, pathReplaceWith } = useGeneralSettings();
|
const { pathReplace, pathReplaceWith } = useGeneralSettings();
|
||||||
const { setSettings } = useSettingsStoreActions();
|
const { setSettings } = useSettingsStoreActions();
|
||||||
|
const resolvedPreviewPath = useResolvedSongPath(randomSong.data?.items[0]?.path);
|
||||||
|
|
||||||
const [localPathReplace, setLocalPathReplace] = useState(pathReplace);
|
const [localPathReplace, setLocalPathReplace] = useState(pathReplace);
|
||||||
const [localPathReplaceWith, setLocalPathReplaceWith] = useState(pathReplaceWith);
|
const [localPathReplaceWith, setLocalPathReplaceWith] = useState(pathReplaceWith);
|
||||||
@@ -45,8 +47,6 @@ export const PathSettings = memo(() => {
|
|||||||
pathReplace: value,
|
pathReplace: value,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
randomSong.refetch();
|
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
const debouncedSetPathReplaceWith = useDebouncedCallback((value: string) => {
|
const debouncedSetPathReplaceWith = useDebouncedCallback((value: string) => {
|
||||||
@@ -55,8 +55,6 @@ export const PathSettings = memo(() => {
|
|||||||
pathReplaceWith: value,
|
pathReplaceWith: value,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
randomSong.refetch();
|
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -73,7 +71,7 @@ export const PathSettings = memo(() => {
|
|||||||
</Group>
|
</Group>
|
||||||
<Code>
|
<Code>
|
||||||
<Text isMuted size="md">
|
<Text isMuted size="md">
|
||||||
{randomSong.data?.items[0]?.path || ''}
|
{resolvedPreviewPath || ''}
|
||||||
</Text>
|
</Text>
|
||||||
</Code>
|
</Code>
|
||||||
<Group grow>
|
<Group grow>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export * from './get-header-color';
|
|||||||
export * from './normalize-server-url';
|
export * from './normalize-server-url';
|
||||||
export * from './parse-search-params';
|
export * from './parse-search-params';
|
||||||
export * from './random-string';
|
export * from './random-string';
|
||||||
|
export * from './resolve-song-path';
|
||||||
export * from './rgb-to-rgba';
|
export * from './rgb-to-rgba';
|
||||||
export * from './sentence-case';
|
export * from './sentence-case';
|
||||||
export * from './set-local-storage-setttings';
|
export * from './set-local-storage-setttings';
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import { usePathReplace, useSettingsStore } from '/@/renderer/store/settings.store';
|
||||||
|
import { replacePathPrefix } from '/@/shared/api/utils';
|
||||||
|
|
||||||
|
export const resolveSongPath = (path: null | string | undefined): null | string => {
|
||||||
|
if (!path) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { pathReplace, pathReplaceWith } = useSettingsStore.getState().general;
|
||||||
|
|
||||||
|
return replacePathPrefix(path, pathReplace, pathReplaceWith);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useResolvedSongPath = (path: null | string | undefined): null | string => {
|
||||||
|
const { pathReplace, pathReplaceWith } = usePathReplace();
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
if (!path) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return replacePathPrefix(path, pathReplace, pathReplaceWith);
|
||||||
|
}, [path, pathReplace, pathReplaceWith]);
|
||||||
|
};
|
||||||
@@ -2,7 +2,6 @@ import { z } from 'zod';
|
|||||||
|
|
||||||
import { jfType } from '/@/shared/api/jellyfin/jellyfin-types';
|
import { jfType } from '/@/shared/api/jellyfin/jellyfin-types';
|
||||||
import { coerceYear, parsePartialIsoDateFromApi } from '/@/shared/api/partial-iso-date';
|
import { coerceYear, parsePartialIsoDateFromApi } from '/@/shared/api/partial-iso-date';
|
||||||
import { replacePathPrefix } from '/@/shared/api/utils';
|
|
||||||
import {
|
import {
|
||||||
Album,
|
Album,
|
||||||
AlbumArtist,
|
AlbumArtist,
|
||||||
@@ -156,8 +155,6 @@ const jellyfinPremiereFields = (item: {
|
|||||||
const normalizeSong = (
|
const normalizeSong = (
|
||||||
item: z.infer<typeof jfType._response.song>,
|
item: z.infer<typeof jfType._response.song>,
|
||||||
server: null | ServerListItem,
|
server: null | ServerListItem,
|
||||||
pathReplace?: string,
|
|
||||||
pathReplaceWith?: string,
|
|
||||||
): Song => {
|
): Song => {
|
||||||
let bitDepth: null | number = null;
|
let bitDepth: null | number = null;
|
||||||
let bitRate = 0;
|
let bitRate = 0;
|
||||||
@@ -257,7 +254,7 @@ const normalizeSong = (
|
|||||||
mbzTrackId: item.ProviderIds?.MusicBrainzTrack || null,
|
mbzTrackId: item.ProviderIds?.MusicBrainzTrack || null,
|
||||||
name: item.Name,
|
name: item.Name,
|
||||||
participants,
|
participants,
|
||||||
path: replacePathPrefix(path || '', pathReplace, pathReplaceWith),
|
path: path || '',
|
||||||
peak: null,
|
peak: null,
|
||||||
playCount: (item.UserData && item.UserData.PlayCount) || 0,
|
playCount: (item.UserData && item.UserData.PlayCount) || 0,
|
||||||
playlistItemId: item.PlaylistItemId,
|
playlistItemId: item.PlaylistItemId,
|
||||||
@@ -278,8 +275,6 @@ const normalizeSong = (
|
|||||||
const normalizeAlbum = (
|
const normalizeAlbum = (
|
||||||
item: z.infer<typeof jfType._response.album>,
|
item: z.infer<typeof jfType._response.album>,
|
||||||
server: null | ServerListItem,
|
server: null | ServerListItem,
|
||||||
pathReplace?: string,
|
|
||||||
pathReplaceWith?: string,
|
|
||||||
): Album => {
|
): Album => {
|
||||||
const { originalYear, releaseDate, releaseYear } = jellyfinPremiereFields(item);
|
const { originalYear, releaseDate, releaseYear } = jellyfinPremiereFields(item);
|
||||||
|
|
||||||
@@ -342,7 +337,7 @@ const normalizeAlbum = (
|
|||||||
releaseYear,
|
releaseYear,
|
||||||
size: null,
|
size: null,
|
||||||
songCount: item?.ChildCount || null,
|
songCount: item?.ChildCount || null,
|
||||||
songs: item.Songs?.map((song) => normalizeSong(song, server, pathReplace, pathReplaceWith)),
|
songs: item.Songs?.map((song) => normalizeSong(song, server)),
|
||||||
sortName: item.SortName || item.Name,
|
sortName: item.SortName || item.Name,
|
||||||
tags: getTags(item),
|
tags: getTags(item),
|
||||||
updatedAt: item?.DateLastMediaAdded || item.DateCreated,
|
updatedAt: item?.DateLastMediaAdded || item.DateCreated,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import z from 'zod';
|
|||||||
import { ndType } from '/@/shared/api/navidrome/navidrome-types';
|
import { ndType } from '/@/shared/api/navidrome/navidrome-types';
|
||||||
import { coerceYear, parsePartialIsoDate } from '/@/shared/api/partial-iso-date';
|
import { coerceYear, parsePartialIsoDate } from '/@/shared/api/partial-iso-date';
|
||||||
import { ssType } from '/@/shared/api/subsonic/subsonic-types';
|
import { ssType } from '/@/shared/api/subsonic/subsonic-types';
|
||||||
import { replacePathPrefix } from '/@/shared/api/utils';
|
|
||||||
import {
|
import {
|
||||||
Album,
|
Album,
|
||||||
AlbumArtist,
|
AlbumArtist,
|
||||||
@@ -199,8 +198,6 @@ const getArtists = (
|
|||||||
const normalizeSong = (
|
const normalizeSong = (
|
||||||
item: z.infer<typeof ndType._response.playlistSong> | z.infer<typeof ndType._response.song>,
|
item: z.infer<typeof ndType._response.playlistSong> | z.infer<typeof ndType._response.song>,
|
||||||
server?: null | ServerListItem,
|
server?: null | ServerListItem,
|
||||||
pathReplace?: string,
|
|
||||||
pathReplaceWith?: string,
|
|
||||||
): Song => {
|
): Song => {
|
||||||
let id;
|
let id;
|
||||||
let playlistItemId;
|
let playlistItemId;
|
||||||
@@ -270,7 +267,7 @@ const normalizeSong = (
|
|||||||
name: item.title,
|
name: item.title,
|
||||||
// Thankfully, Windows is merciful and allows a mix of separators. So, we can use the
|
// Thankfully, Windows is merciful and allows a mix of separators. So, we can use the
|
||||||
// POSIX separator here instead
|
// POSIX separator here instead
|
||||||
path: item.path ? replacePathPrefix(item.path, pathReplace, pathReplaceWith) : null,
|
path: item.path ? `${item.libraryPath}/${item.path}` : null,
|
||||||
peak:
|
peak:
|
||||||
item.rgAlbumPeak || item.rgTrackPeak
|
item.rgAlbumPeak || item.rgTrackPeak
|
||||||
? { album: item.rgAlbumPeak, track: item.rgTrackPeak }
|
? { album: item.rgAlbumPeak, track: item.rgTrackPeak }
|
||||||
@@ -337,8 +334,6 @@ const normalizeAlbum = (
|
|||||||
songs?: z.infer<typeof ndType._response.songList>;
|
songs?: z.infer<typeof ndType._response.songList>;
|
||||||
},
|
},
|
||||||
server?: null | ServerListItem,
|
server?: null | ServerListItem,
|
||||||
pathReplace?: string,
|
|
||||||
pathReplaceWith?: string,
|
|
||||||
): Album => {
|
): Album => {
|
||||||
const releaseDate = normalizeNavidromeReleaseDate(item);
|
const releaseDate = normalizeNavidromeReleaseDate(item);
|
||||||
const originalDate = normalizeNavidromeOriginalDate(item);
|
const originalDate = normalizeNavidromeOriginalDate(item);
|
||||||
@@ -386,9 +381,7 @@ const normalizeAlbum = (
|
|||||||
releaseYear: releaseDate.year > 0 ? releaseDate.year : null,
|
releaseYear: releaseDate.year > 0 ? releaseDate.year : null,
|
||||||
size: item.size,
|
size: item.size,
|
||||||
songCount: item.songCount,
|
songCount: item.songCount,
|
||||||
songs: item.songs
|
songs: item.songs ? item.songs.map((song) => normalizeSong(song, server)) : undefined,
|
||||||
? item.songs.map((song) => normalizeSong(song, server, pathReplace, pathReplaceWith))
|
|
||||||
: undefined,
|
|
||||||
sortName: item.orderAlbumName,
|
sortName: item.orderAlbumName,
|
||||||
tags: item.tags || null,
|
tags: item.tags || null,
|
||||||
updatedAt: item.updatedAt,
|
updatedAt: item.updatedAt,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { z } from 'zod';
|
|||||||
|
|
||||||
import { coerceYear, parsePartialIsoDate } from '/@/shared/api/partial-iso-date';
|
import { coerceYear, parsePartialIsoDate } from '/@/shared/api/partial-iso-date';
|
||||||
import { ssType } from '/@/shared/api/subsonic/subsonic-types';
|
import { ssType } from '/@/shared/api/subsonic/subsonic-types';
|
||||||
import { replacePathPrefix } from '/@/shared/api/utils';
|
|
||||||
import {
|
import {
|
||||||
Album,
|
Album,
|
||||||
AlbumArtist,
|
AlbumArtist,
|
||||||
@@ -163,8 +162,6 @@ const subsonicReleaseFields = (item: {
|
|||||||
const normalizeSong = (
|
const normalizeSong = (
|
||||||
item: z.infer<typeof ssType._response.song>,
|
item: z.infer<typeof ssType._response.song>,
|
||||||
server?: null | ServerListItemWithCredential,
|
server?: null | ServerListItemWithCredential,
|
||||||
pathReplace?: string,
|
|
||||||
pathReplaceWith?: string,
|
|
||||||
playlistIndex?: number,
|
playlistIndex?: number,
|
||||||
discTitleMap?: Map<number, string>,
|
discTitleMap?: Map<number, string>,
|
||||||
): Song => {
|
): Song => {
|
||||||
@@ -221,7 +218,7 @@ const normalizeSong = (
|
|||||||
mbzTrackId: null,
|
mbzTrackId: null,
|
||||||
name: item.title,
|
name: item.title,
|
||||||
participants,
|
participants,
|
||||||
path: replacePathPrefix(item.path || '', pathReplace, pathReplaceWith),
|
path: item.path || '',
|
||||||
peak:
|
peak:
|
||||||
item.replayGain && (item.replayGain.albumPeak || item.replayGain.trackPeak)
|
item.replayGain && (item.replayGain.albumPeak || item.replayGain.trackPeak)
|
||||||
? {
|
? {
|
||||||
@@ -305,8 +302,6 @@ const getReleaseType = (
|
|||||||
const normalizeAlbum = (
|
const normalizeAlbum = (
|
||||||
item: z.infer<typeof ssType._response.album> | z.infer<typeof ssType._response.albumListEntry>,
|
item: z.infer<typeof ssType._response.album> | z.infer<typeof ssType._response.albumListEntry>,
|
||||||
server?: null | ServerListItemWithCredential,
|
server?: null | ServerListItemWithCredential,
|
||||||
pathReplace?: string,
|
|
||||||
pathReplaceWith?: string,
|
|
||||||
): Album => {
|
): Album => {
|
||||||
const discTitleMap = new Map<number, string>();
|
const discTitleMap = new Map<number, string>();
|
||||||
|
|
||||||
@@ -354,7 +349,7 @@ const normalizeAlbum = (
|
|||||||
songCount: item.songCount,
|
songCount: item.songCount,
|
||||||
songs:
|
songs:
|
||||||
(item as z.infer<typeof ssType._response.album>).song?.map((song) =>
|
(item as z.infer<typeof ssType._response.album>).song?.map((song) =>
|
||||||
normalizeSong(song, server, pathReplace, pathReplaceWith, undefined, discTitleMap),
|
normalizeSong(song, server, undefined, discTitleMap),
|
||||||
) || [],
|
) || [],
|
||||||
sortName: item.title,
|
sortName: item.title,
|
||||||
tags: null,
|
tags: null,
|
||||||
@@ -410,8 +405,6 @@ const normalizeGenre = (
|
|||||||
const normalizeFolder = (
|
const normalizeFolder = (
|
||||||
item: z.infer<typeof ssType._response.directory>,
|
item: z.infer<typeof ssType._response.directory>,
|
||||||
server?: null | ServerListItemWithCredential,
|
server?: null | ServerListItemWithCredential,
|
||||||
pathReplace?: string,
|
|
||||||
pathReplaceWith?: string,
|
|
||||||
): Folder => {
|
): Folder => {
|
||||||
const results = item.child?.reduce(
|
const results = item.child?.reduce(
|
||||||
(acc: { folders: Folder[]; songs: Song[] }, item) => {
|
(acc: { folders: Folder[]; songs: Song[] }, item) => {
|
||||||
@@ -421,7 +414,7 @@ const normalizeFolder = (
|
|||||||
const folder = normalizeFolder(item, server);
|
const folder = normalizeFolder(item, server);
|
||||||
acc.folders.push(folder);
|
acc.folders.push(folder);
|
||||||
} else {
|
} else {
|
||||||
const song = normalizeSong(item, server, pathReplace, pathReplaceWith);
|
const song = normalizeSong(item, server);
|
||||||
acc.songs.push(song);
|
acc.songs.push(song);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -414,10 +414,8 @@ export type Song = {
|
|||||||
userRating: null | number;
|
userRating: null | number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ApiContext = {
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||||
pathReplace?: string;
|
type ApiContext = {};
|
||||||
pathReplaceWith?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type BaseEndpointArgs = {
|
type BaseEndpointArgs = {
|
||||||
apiClientProps: {
|
apiClientProps: {
|
||||||
|
|||||||
Reference in New Issue
Block a user