Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sample.env
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ SYSTEM_PASSWORD=
API_KEY=
# Name of the music library in your system (emby, jellyfin, plex)
LIBRARY_NAME=
# Mark playlist as public (subsonic)
# Mark playlist as public (subsonic, jellyfin)
# PUBLIC_PLAYLIST=false

# === Downloader Configuration ===
Expand Down
3 changes: 3 additions & 0 deletions src/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ func (c *Client) systemSetup() error {
if c.Cfg.Creds.APIKey == "" {
return fmt.Errorf("Jellyfin API_KEY is required")
}
if c.Cfg.Creds.User == "" {
return fmt.Errorf("Jellyfin SYSTEM_USERNAME is required")
}
if err := c.API.AddHeader(); err != nil {
return err
}
Expand Down
37 changes: 34 additions & 3 deletions src/client/jellyfin.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ type JFPlaylist struct {
ID string `json:"Id"`
}

type JFUser struct {
ID string `json:"Id"`
Name string `json:"Name"`
}

type Jellyfin struct {
LibraryID string
HttpClient *util.HttpClient
Expand Down Expand Up @@ -197,15 +202,21 @@ func (c *Jellyfin) CreatePlaylist(tracks []*models.Track) error {
if err != nil {
return fmt.Errorf("failed to marshal track IDs: %s", err.Error())
}
userID, err := c.ResolveUserID()
if err != nil {
return err
}
isPublic := c.Cfg.Subsonic.PublicPlaylist

queryParams := "/Playlists"
payload := fmt.Appendf(nil, `
{
"Name": "%s",
"Ids": %s,
"MediaType": "Audio",
"UserId": "%s"
}`, c.Cfg.PlaylistName, songs, c.Cfg.Creds.APIKey)
"UserId": "%s",
"IsPublic": %t
}`, c.Cfg.PlaylistName, songs, userID, isPublic)

body, err := c.HttpClient.MakeRequest("POST", c.Cfg.URL+queryParams, bytes.NewReader(payload), c.Cfg.Creds.Headers)
if err != nil {
Expand All @@ -220,16 +231,18 @@ func (c *Jellyfin) CreatePlaylist(tracks []*models.Track) error {
}

func (c *Jellyfin) UpdatePlaylist() error {
isPublic := c.Cfg.Subsonic.PublicPlaylist
queryParams := fmt.Sprintf("/Items/%s", c.Cfg.PlaylistID)
payload := fmt.Appendf(nil, `
{
"Id":"%s",
"Name":"%s",
"Overview":"%s",
"IsPublic":%t,
"Genres":[],
"Tags":[],
"ProviderIds":{}
}`, c.Cfg.PlaylistID, c.Cfg.PlaylistName, c.Cfg.PlaylistDescr) // the additional fields have to be added, otherwise JF returns code 400
}`, c.Cfg.PlaylistID, c.Cfg.PlaylistName, c.Cfg.PlaylistDescr, isPublic) // the additional fields have to be added, otherwise JF returns code 400

if _, err := c.HttpClient.MakeRequest("POST", c.Cfg.URL+queryParams, bytes.NewBuffer(payload), c.Cfg.Creds.Headers); err != nil {
return err
Expand Down Expand Up @@ -259,3 +272,21 @@ func formatJFSongs(tracks []*models.Track) ([]byte, error) { // marshal track ID
}
return songs, nil
}

func (c *Jellyfin) ResolveUserID() (string, error) {
body, err := c.HttpClient.MakeRequest("GET", c.Cfg.URL+"/Users", nil, c.Cfg.Creds.Headers)
if err != nil {
return "", err
}
var users []JFUser
if err = util.ParseResp(body, &users); err != nil {
return "", err
}
for _, user := range users {
if strings.EqualFold(user.Name, c.Cfg.Creds.User) {
return user.ID, nil
}
}

return "", fmt.Errorf("failed to find Jellyfin user %q", c.Cfg.Creds.User)
}