package client import ( "encoding/json" "fmt" "net/http" "net/url" "github.com/fjurmanovic/sugarr/internal/model" "github.com/fjurmanovic/sugarr/internal/utl/configs" ) type JellyfinClient struct { url string apiKey string userID string http *http.Client } func NewJellyfinClient(cfg *configs.Config) *JellyfinClient { return &JellyfinClient{ url: cfg.JellyfinURL, apiKey: cfg.JellyfinAPIKey, userID: cfg.JellyfinUserID, http: &http.Client{}, } } type jellyfinItemsResponse struct { Items []jellyfinItem `json:"Items"` } type jellyfinItem struct { Name string `json:"Name"` ProductionYear int `json:"ProductionYear"` ProviderIds map[string]string `json:"ProviderIds"` Genres []string `json:"Genres"` CommunityRating float64 `json:"CommunityRating"` UserData jellyfinUserData `json:"UserData"` } type jellyfinUserData struct { IsFavorite bool `json:"IsFavorite"` PlayCount int `json:"PlayCount"` } func (c *JellyfinClient) get(path string, params url.Values) (*jellyfinItemsResponse, error) { u := fmt.Sprintf("%s%s?%s", c.url, path, params.Encode()) req, err := http.NewRequest(http.MethodGet, u, nil) if err != nil { return nil, err } req.Header.Set("Authorization", fmt.Sprintf("MediaBrowser Token=%q", c.apiKey)) req.Header.Set("Accept", "application/json") resp, err := c.http.Do(req) if err != nil { return nil, fmt.Errorf("jellyfin request failed: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("jellyfin returned status %d for %s", resp.StatusCode, path) } var result jellyfinItemsResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, fmt.Errorf("decoding jellyfin response: %w", err) } return &result, nil } func toMovies(items []jellyfinItem) []model.Movie { movies := make([]model.Movie, 0, len(items)) for _, item := range items { movies = append(movies, model.Movie{ Name: item.Name, Year: item.ProductionYear, IMDbID: item.ProviderIds["Imdb"], TMDbID: item.ProviderIds["Tmdb"], Genres: item.Genres, CommunityRating: item.CommunityRating, IsFavorite: item.UserData.IsFavorite, PlayCount: item.UserData.PlayCount, }) } return movies } func (c *JellyfinClient) GetFavoriteMovies() ([]model.Movie, error) { params := url.Values{ "IncludeItemTypes": {"Movie"}, "Recursive": {"true"}, "IsFavorite": {"true"}, "Fields": {"ProviderIds,Genres,CommunityRating,UserData"}, } resp, err := c.get(fmt.Sprintf("/Users/%s/Items", c.userID), params) if err != nil { return nil, err } return toMovies(resp.Items), nil } func (c *JellyfinClient) GetRecentlyWatched() ([]model.Movie, error) { params := url.Values{ "IncludeItemTypes": {"Movie"}, "Recursive": {"true"}, "Filters": {"IsPlayed"}, "SortBy": {"DatePlayed"}, "SortOrder": {"Descending"}, "Limit": {"50"}, "Fields": {"ProviderIds,Genres,CommunityRating,UserData"}, } resp, err := c.get(fmt.Sprintf("/Users/%s/Items", c.userID), params) if err != nil { return nil, err } return toMovies(resp.Items), nil } func (c *JellyfinClient) GetAllMovies() ([]model.Movie, error) { params := url.Values{ "IncludeItemTypes": {"Movie"}, "Recursive": {"true"}, "Fields": {"ProviderIds"}, "Limit": {"10000"}, } resp, err := c.get(fmt.Sprintf("/Users/%s/Items", c.userID), params) if err != nil { return nil, err } return toMovies(resp.Items), nil }