From f06a2d82e9cf50ba4940f77b6980a905405fce40 Mon Sep 17 00:00:00 2001 From: Niklas Kapelle Date: Thu, 7 May 2026 16:26:03 +0200 Subject: [PATCH] minor cleanup --- cmd/ripsort.go | 1 - internal/playlist/navidrome.go | 65 ++++++++++++++++++++-------------- internal/playlist/spotify.go | 8 +++-- 3 files changed, 44 insertions(+), 30 deletions(-) diff --git a/cmd/ripsort.go b/cmd/ripsort.go index 903a765..9353836 100644 --- a/cmd/ripsort.go +++ b/cmd/ripsort.go @@ -37,7 +37,6 @@ type args struct { Sort *SortCmd `arg:"subcommand:sort"` SyncPlaylists *SyncPlaylists `arg:"subcommand:sync-playlist"` Verbose bool `arg:"-v" default:"false"` - DryRun bool `arg:"--dry-run" default:"false"` } func Run() { diff --git a/internal/playlist/navidrome.go b/internal/playlist/navidrome.go index edc6ee1..41f92d0 100644 --- a/internal/playlist/navidrome.go +++ b/internal/playlist/navidrome.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "maps" "net/http" "net/url" "strconv" @@ -69,12 +70,12 @@ type NavidromePlaylistTrack struct { MediaFileID string `json:"mediaFileId"` // the actual song ID } -type loginRequest struct { +type navidromeLoginRequest struct { Username string `json:"username"` Password string `json:"password"` } -type loginResponse struct { +type navidromeLoginResponse struct { Token string `json:"token"` Name string `json:"name"` IsAdmin bool `json:"isAdmin"` @@ -98,14 +99,14 @@ func NewNavidromeClient(baseURL, username, password string) (*NavidromeClient, e } if err := c.login(); err != nil { - return nil, err + return nil, fmt.Errorf("login: %w", err) } return c, nil } func (c *NavidromeClient) login() error { - body, _ := json.Marshal(loginRequest{Username: c.username, Password: c.password}) + body, _ := json.Marshal(navidromeLoginRequest{Username: c.username, Password: c.password}) resp, err := c.httpClient.Post( c.baseURL+"/auth/login", @@ -113,18 +114,18 @@ func (c *NavidromeClient) login() error { bytes.NewReader(body), ) if err != nil { - return fmt.Errorf("navidrome: login: %w", err) + return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return fmt.Errorf("navidrome: login: HTTP %d", resp.StatusCode) + return fmt.Errorf("status code: %d", resp.StatusCode) } - var lr loginResponse + var lr navidromeLoginResponse if err := json.NewDecoder(resp.Body).Decode(&lr); err != nil { - return fmt.Errorf("navidrome: login decode: %w", err) + return err } c.mu.Lock() @@ -134,6 +135,7 @@ func (c *NavidromeClient) login() error { return nil } +// Lists all playlists without the tracks func (c *NavidromeClient) GetPlaylists() ([]NavidromePlaylist, error) { var playlists []NavidromePlaylist @@ -144,32 +146,28 @@ func (c *NavidromeClient) GetPlaylists() ([]NavidromePlaylist, error) { return playlists, nil } +// Get a single playlist without the tracks func (c *NavidromeClient) GetPlaylist(id string) (*NavidromePlaylist, error) { var pl NavidromePlaylist if err := c.request(http.MethodGet, "/api/playlist/"+id, nil, &pl); err != nil { return nil, err } - tracks, err := c.getPlaylistTracks(id) - if err != nil { - return nil, err - } - - pl.Tracks = tracks - return &pl, nil } -func (c *NavidromeClient) getPlaylistTracks(playlistID string) ([]NavidromePlaylistTrack, error) { +// Get all tracks inside a playlist +func (c *NavidromeClient) GetPlaylistTracks(playlistID string) ([]NavidromePlaylistTrack, error) { var tracks []NavidromePlaylistTrack if err := c.getList("/api/playlist/"+playlistID+"/tracks", nil, &tracks); err != nil { return nil, err } + return tracks, nil } +// Search songs based on a custom tag named "spotifyid" func (c *NavidromeClient) GetSongBySpotifyID(spotifyID string) ([]NavidromeTrack, error) { - // https://navi.kapelle.org/api/song?_end=100&_order=ASC&_sort=title&_start=0&spotifyid=0NhcaHA0cyJyhk3g8tUsZP&missing=false params := url.Values{ "spotifyid": {spotifyID}, } @@ -182,8 +180,9 @@ func (c *NavidromeClient) GetSongBySpotifyID(spotifyID string) ([]NavidromeTrack return songs, nil } +// Add tracks to a playlist func (c *NavidromeClient) AddTracks(playlistID string, songIDs []string) error { - body := map[string]interface{}{"ids": songIDs} + body := map[string]any{"ids": songIDs} return c.request(http.MethodPost, "/api/playlist/"+playlistID+"/tracks", body, nil) } @@ -196,24 +195,25 @@ func (c *NavidromeClient) getList(path string, extra url.Values, dest any) error "_start": {strconv.Itoa(start)}, "_end": {strconv.Itoa(start + pageSize)}, } - for k, vs := range extra { - params[k] = vs - } + + maps.Copy(params, extra) httpResp, err := c.do(http.MethodGet, path+"?"+params.Encode(), nil) if err != nil { return err } + defer httpResp.Body.Close() + c.refreshToken(httpResp) if httpResp.StatusCode != http.StatusOK && httpResp.StatusCode != http.StatusPartialContent { - return fmt.Errorf("navidrome: GET %s: HTTP %d", path, httpResp.StatusCode) + return fmt.Errorf("status code: %d", httpResp.StatusCode) } var page []json.RawMessage if err := json.NewDecoder(httpResp.Body).Decode(&page); err != nil { - return fmt.Errorf("navidrome: decode %s: %w", path, err) + return fmt.Errorf("decode: %w", err) } all = append(all, page...) @@ -231,19 +231,22 @@ func (c *NavidromeClient) request(method, path string, body any, dest any) error if err != nil { return err } + defer httpResp.Body.Close() + c.refreshToken(httpResp) if httpResp.StatusCode >= 300 { raw, _ := io.ReadAll(httpResp.Body) - return fmt.Errorf("navidrome: %s %s: HTTP %d: %s", - method, path, httpResp.StatusCode, strings.TrimSpace(string(raw))) + return fmt.Errorf("status code: %d: %s", httpResp.StatusCode, strings.TrimSpace(string(raw))) } + if dest != nil && httpResp.ContentLength != 0 { if err := json.NewDecoder(httpResp.Body).Decode(dest); err != nil { - return fmt.Errorf("navidrome: decode %s: %w", path, err) + return fmt.Errorf("decode: %w", err) } } + return nil } @@ -257,6 +260,7 @@ func (c *NavidromeClient) do(method, path string, body any) (*http.Response, err if err != nil { return nil, err } + if resp.StatusCode == http.StatusUnauthorized { resp.Body.Close() if err := c.login(); err != nil { @@ -264,6 +268,7 @@ func (c *NavidromeClient) do(method, path string, body any) (*http.Response, err } return c.doOnce(method, path, bodyBytes) } + return resp, nil } @@ -272,16 +277,20 @@ func (c *NavidromeClient) doOnce(method, path string, bodyBytes []byte) (*http.R if bodyBytes != nil { bodyReader = bytes.NewReader(bodyBytes) } + req, err := http.NewRequest(method, c.baseURL+path, bodyReader) if err != nil { - return nil, fmt.Errorf("navidrome: build request: %w", err) + return nil, fmt.Errorf("build request: %w", err) } + if bodyBytes != nil { req.Header.Set("Content-Type", "application/json") } + c.mu.Lock() req.Header.Set("X-ND-Authorization", "Bearer "+c.token) c.mu.Unlock() + return c.httpClient.Do(req) } @@ -289,10 +298,12 @@ func marshalBody(body any) ([]byte, error) { if body == nil { return nil, nil } + b, err := json.Marshal(body) if err != nil { return nil, fmt.Errorf("navidrome: marshal body: %w", err) } + return b, nil } diff --git a/internal/playlist/spotify.go b/internal/playlist/spotify.go index 2cba23d..015994b 100644 --- a/internal/playlist/spotify.go +++ b/internal/playlist/spotify.go @@ -90,11 +90,12 @@ func (c *SpotifyClient) GetPlaylist(playlistID string) (*SpotifyPlaylist, error) if err != nil { return nil, err } + defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("API error (%d): %s", resp.StatusCode, body) + return nil, fmt.Errorf("status code: %d: %s", resp.StatusCode, body) } var playlist SpotifyPlaylist @@ -115,6 +116,7 @@ func (c *SpotifyClient) renewToken() error { if err != nil { return err } + req.Header.Set("Authorization", "Basic "+creds) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") @@ -122,11 +124,12 @@ func (c *SpotifyClient) renewToken() error { if err != nil { return err } + defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) if resp.StatusCode != http.StatusOK { - return fmt.Errorf("auth failed (%d): %s", resp.StatusCode, body) + return fmt.Errorf("auth failed: %d: %s", resp.StatusCode, body) } var token spotifyTokenResponse @@ -158,5 +161,6 @@ func (c *SpotifyClient) doOnce(req *http.Request) (*http.Response, error) { c.mu.Lock() req.Header.Set("Authorization", "Bearer "+c.token) c.mu.Unlock() + return c.httpClient.Do(req) }