From 48f60e27242d9390ffd1b81245783155bbc1d169 Mon Sep 17 00:00:00 2001 From: Eclypsed Date: Mon, 25 Mar 2024 19:40:47 -0400 Subject: [PATCH] Removed db from tracking --- .gitignore | 1 + src/lib/server/jellyfin.ts | 164 +++++++++++++++++++++---------------- src/lib/server/lazuli.db | Bin 32768 -> 0 bytes 3 files changed, 94 insertions(+), 71 deletions(-) delete mode 100644 src/lib/server/lazuli.db diff --git a/.gitignore b/.gitignore index 6635cf5..85c923b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ node_modules !.env.example vite.config.js.timestamp-* vite.config.ts.timestamp-* +*.db \ No newline at end of file diff --git a/src/lib/server/jellyfin.ts b/src/lib/server/jellyfin.ts index 85d7830..42c1d79 100644 --- a/src/lib/server/jellyfin.ts +++ b/src/lib/server/jellyfin.ts @@ -66,43 +66,49 @@ export class Jellyfin implements Connection { const mostPlayedResponse = await fetch(mostPlayedSongsURL, { headers: this.BASEHEADERS }) const mostPlayedData = await mostPlayedResponse.json() - return Array.from(mostPlayedData.Items as JellyfinAPI.Song[], (song) => this.songFactory(song)) + return Array.from(mostPlayedData.Items as JellyfinAPI.Song[], (song) => this.parseSong(song)) } public search = async (searchTerm: string): Promise => { const searchParams = new URLSearchParams({ - userId: this.jfUserId, searchTerm, - includeItemTypes: 'Audio,MusicAlbum,MusicArtist', + includeItemTypes: 'Audio,MusicAlbum', // Potentially add MusicArtist + recursive: 'true', }) - const searchURL = new URL(`Search/Hints?${searchParams.toString()}`, this.serverUrl).toString() + const searchURL = new URL(`Users/${this.jfUserId}/Items?${searchParams.toString()}`, this.serverUrl).toString() const searchResponse = await fetch(searchURL, { headers: this.BASEHEADERS }) if (!searchResponse.ok) throw new JellyfinFetchError('Failed to search Jellyfin', searchResponse.status, searchURL) - const searchResults = (await searchResponse.json()).SearchHints as JellyfinAPI.SearchHint[] + const searchResults = (await searchResponse.json()).Items as (JellyfinAPI.Song | JellyfinAPI.Album)[] // JellyfinAPI.Artist + + const parsedResults: MediaItem[] = [] + searchResults.forEach((result) => { + switch (result.Type) { + case 'Audio': + parsedResults.push(this.parseSong(result)) + break + case 'MusicAlbum': + parsedResults.push(this.parseAlbum(result)) + break + } + }) + return parsedResults } - private mediaItemFactory = (mediaItem: JellyfinAPI.MediaItem): MediaItem => { - const thumbnail = mediaItem.ImageTags?.Primary - ? new URL(`Items/${mediaItem.Id}/Images/Primary`, this.serverUrl).toString() - : mediaItem. - - } - - private songFactory = (song: JellyfinAPI.Song): Song => { + private parseSong = (song: JellyfinAPI.Song): Song => { const thumbnail = song.ImageTags?.Primary ? new URL(`Items/${song.Id}/Images/Primary`, this.serverUrl).href : song.AlbumPrimaryImageTag ? new URL(`Items/${song.AlbumId}/Images/Primary`, this.serverUrl).href : undefined - const artists = song.ArtistItems + const artists: Song['artists'] = song.ArtistItems ? Array.from(song.ArtistItems, (artist) => { return { id: artist.Id, name: artist.Name } }) - : [] + : undefined - // Add Album details + const album: Song['album'] = song.AlbumId && song.Album ? { id: song.AlbumId, name: song.Album } : undefined return { connection: { @@ -112,10 +118,35 @@ export class Jellyfin implements Connection { type: 'song', id: song.Id, name: song.Name, - duration: Math.floor(song.RunTimeTicks / 10000), + duration: ticksToSeconds(song.RunTimeTicks), thumbnail, artists, - releaseDate: String(song.ProductionYear), + album, + releaseDate: song.ProductionYear?.toString(), + } + } + + private parseAlbum = (album: JellyfinAPI.Album): Album => { + const thumbnail = album.ImageTags?.Primary ? new URL(`Items/${album.Id}/Images/Primary`, this.serverUrl).toString() : undefined + + const artists: Album['artists'] = album.AlbumArtists + ? Array.from(album.AlbumArtists, (artist) => { + return { id: artist.Id, name: artist.Name } + }) + : undefined + + return { + connection: { + id: this.id, + type: 'jellyfin', + }, + type: 'album', + id: album.Id, + name: album.Name, + duration: ticksToSeconds(album.RunTimeTicks), + thumbnail, + artists, + releaseDate: album.ProductionYear?.toString(), } } @@ -142,6 +173,8 @@ export class Jellyfin implements Connection { } } +const ticksToSeconds = (ticks: number): number => Math.floor(ticks / 10000) + export class JellyfinFetchError extends Error { public httpCode: number public url: string @@ -168,75 +201,64 @@ declare namespace JellyfinAPI { ServerName: string } - interface MediaItem { + type Song = { Name: string Id: string - Type: 'Audio' | 'MusicAlbum' | 'Playlist' | 'MusicArtist' - ImageTags?: { - Primary?: string - } - } - - interface Song extends JellyfinAPI.MediaItem { - RunTimeTicks: number - ProductionYear: number Type: 'Audio' - ArtistItems: { + RunTimeTicks: number + ProductionYear?: number + ArtistItems?: { Name: string Id: string }[] Album?: string AlbumId?: string AlbumPrimaryImageTag?: string - AlbumArtists: { + AlbumArtists?: { Name: string Id: string }[] + ImageTags?: { + Primary?: string + } } - interface Album extends JellyfinAPI.MediaItem { - RunTimeTicks: number - ProductionYear: number - Type: 'MusicAlbum' - ArtistItems: { - Name: string - Id: string - }[] - AlbumArtists: { - Name: string - Id: string - }[] - } - - interface Playlist extends JellyfinAPI.MediaItem { - RunTimeTicks: number - Type: 'Playlist' - ChildCount: number - } - - interface Artist extends JellyfinAPI.MediaItem { - Type: 'MusicArtist' - } - - type SearchHint = { - Id: string + type Album = { Name: string - PrimaryImageTag?: string - } & ({ - Type: 'Audio' - RunTimeTicks: number - ProductionYear?: number - AlbumId: string // When no album exists, the id defaults to: "00000000000000000000000000000000" - Album?: string - Artists: { - [key: number]: string - }[] - } | { - Type: 'MusicArtist' - } | { + Id: string Type: 'MusicAlbum' RunTimeTicks: number ProductionYear?: number - AlbumArtist: string - }) + ArtistItems?: { + Name: string + Id: string + }[] + AlbumArtists?: { + Name: string + Id: string + }[] + ImageTags?: { + Primary?: string + } + } + + type Playlist = { + Name: string + Id: string + Type: 'Playlist' + RunTimeTicks: number + ChildCount: number + ImageTags?: { + Primary?: string + } + } + + type Artist = { + Name: string + Id: string + Type: 'MusicArtist' + ImageTags?: { + Primary?: string + } + } } diff --git a/src/lib/server/lazuli.db b/src/lib/server/lazuli.db deleted file mode 100644 index ed585bfd92765948411b59bc30fb78b48f22d4e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeI)TW{J{8~|{WG|+ZUnY0I9*4Coc8l;-o_#SQ29BS?XVsHp~LB1To+zht4rOHFv zYTE0*!k#Dflk9!(`wD&7;|x(yP@652wyBr@5r$*?VE*U#KSDaTp>kNTJcKuGr>}W9 zjeUs)0@(LBj$v5PUvB%$W!dyAw=P%w*T9wfb=4pipZvHT{sRl$qp4?H~XGAOHd&00JNY0w4eaAB@1uuY;k;qep?)Ay3nLsNB=$JE zWT`6SRcXH<urmrDqd6z zh4qF5t&i}keC+Rg?|u`xhGPvq%N{J7t-sr!0`=YZY)_RGIh!sn;@Y_gC59_=1z zQm){tADz2nnXy&OF)9>{$d3Y;H89c49rgSpH?*;5FV`C@jRv)<-hTPbtx)9gH3P!Szud}^k z(x9%a1kb+>gd&kh;AL&;yG+{hX=BawBAKg9E(YsA8KuhsxEj-B3|EWULsh(cJcmmrul2%~x^6Vw=IpcaiIQy^Ok%SianfZTx*xYT0x#mtW59xBW^W{1fIs zpnw1ffB*=900@8p2!H?xfB*=900{hF0{y_2`rv$vGdNa221y9KVG<0DOhRV`ir_Sn zWI0CANKGqRM%(Ub?vsS}gd(5FHQIFz$vYKwbC{YA;toACOWA@bu<_I3QEr;8=TrPB zJFgF<(Gf-S!%}sA*Y8&#{2S&!pnw1ffB*=900@8p2!H?xfB*=900?|!0#7&9J2yFp za54XXi-q5QWF3TXK>!3m00ck)1V8`;KmY_l00ck)1pZRsi_HgjF7F*!-2cC^{SXU3 z^q-)B00@8p2!H?xfB*=900@ArBKl+3GG0W-%l|is?nTukUm~Qok`uW7TuTuprZ^eYr>*GO zi>PK8$aSlWAJY3R=UPIHYouhdi6Tw71Uhw9DRYv@I`&lc+{4q_g-D0C7h^D$E^awUr?vlDLeoSDoj9Xf9hO6*YT zrzSc6w3qH>^)xE!nMzmB<-AsZZjU-?O37sBo#Hg_&bmF@NRKngVKFg!Zgm`8*UJ+v z*{bKIicyi2gG%Z!lRi!ySSDhdOi>=YV9%4lybb~^y;*eEA*9? zN!E_dWIj(%lb)2|nOU<#4&0%Ae5|(RN~+uMDz5**Xg0K*d34VYgQ6*Bv55THc_ZNG z|G~L95;c?IcoPwtNz(*lBAO7{gh&{M$e6lL(+thtB>x@M>&=_i;7aOwnqv&aiV4Bs z7+oZFmgiXHA59YkhGc0!y0OzMIabSBIiX`J48>bj1?#H|*kRtc)SZOwq zXpkbwHFc5UXkF8JlOg@XHhGqzO*VSAxcDT>ppp|0kSa=o$!s00@8p2!H?xfB*=900@8p2>1ej0;y>~hX4Qo