diff --git a/src/app.d.ts b/src/app.d.ts index eeedf6f..11bbd18 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -45,6 +45,7 @@ declare global { interface Connection { getRecommendations: () => Promise getConnectionInfo: () => Promise + search: (searchTerm: string) => Promise } // These Schemas should only contain general info data that is necessary for data fetching purposes. // They are NOT meant to be stores for large amounts of data, i.e. Don't include the data for every single song the Playlist type. diff --git a/src/lib/server/jellyfin.ts b/src/lib/server/jellyfin.ts index 6d3e6cc..85d7830 100644 --- a/src/lib/server/jellyfin.ts +++ b/src/lib/server/jellyfin.ts @@ -5,12 +5,16 @@ export class Jellyfin implements Connection { private serverUrl: string private accessToken: string + private readonly BASEHEADERS: Headers + constructor(id: string, userId: string, jellyfinUserId: string, serverUrl: string, accessToken: string) { this.id = id this.userId = userId this.jfUserId = jellyfinUserId this.serverUrl = serverUrl this.accessToken = accessToken + + this.BASEHEADERS = new Headers({ Authorization: `MediaBrowser Token="${this.accessToken}"` }) } // const audoSearchParams = new URLSearchParams({ @@ -23,13 +27,11 @@ export class Jellyfin implements Connection { // }) public getConnectionInfo = async (): Promise> => { - const reqHeaders = new Headers({ Authorization: `MediaBrowser Token="${this.accessToken}"` }) - const userUrl = new URL(`Users/${this.jfUserId}`, this.serverUrl).href const systemUrl = new URL('System/Info', this.serverUrl).href - const userResponse = await fetch(userUrl, { headers: reqHeaders }) - const systemResponse = await fetch(systemUrl, { headers: reqHeaders }) + const userResponse = await fetch(userUrl, { headers: this.BASEHEADERS }) + const systemResponse = await fetch(systemUrl, { headers: this.BASEHEADERS }) const userData: JellyfinAPI.User = await userResponse.json() const systemData: JellyfinAPI.System = await systemResponse.json() @@ -51,7 +53,7 @@ export class Jellyfin implements Connection { } public getRecommendations = async (): Promise => { - const mostPlayedSongsSearchParams = new URLSearchParams({ + const searchParams = new URLSearchParams({ SortBy: 'PlayCount', SortOrder: 'Descending', IncludeItemTypes: 'Audio', @@ -59,15 +61,34 @@ export class Jellyfin implements Connection { limit: '10', }) - const mostPlayedSongsURL = new URL(`/Users/${this.jfUserId}/Items?${mostPlayedSongsSearchParams.toString()}`, this.serverUrl).href - const requestHeaders = new Headers({ Authorization: `MediaBrowser Token="${this.accessToken}"` }) + const mostPlayedSongsURL = new URL(`/Users/${this.jfUserId}/Items?${searchParams.toString()}`, this.serverUrl).toString() - const mostPlayedResponse = await fetch(mostPlayedSongsURL, { headers: requestHeaders }) + 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)) } + public search = async (searchTerm: string): Promise => { + const searchParams = new URLSearchParams({ + userId: this.jfUserId, + searchTerm, + includeItemTypes: 'Audio,MusicAlbum,MusicArtist', + }) + + const searchURL = new URL(`Search/Hints?${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[] + } + + 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 => { const thumbnail = song.ImageTags?.Primary ? new URL(`Items/${song.Id}/Images/Primary`, this.serverUrl).href @@ -196,4 +217,26 @@ declare namespace JellyfinAPI { interface Artist extends JellyfinAPI.MediaItem { Type: 'MusicArtist' } + + type SearchHint = { + Id: string + 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' + } | { + Type: 'MusicAlbum' + RunTimeTicks: number + ProductionYear?: number + AlbumArtist: string + }) }