package ripsort import ( "log/slog" "regexp" "git.kapelle.org/niklas/ripsort/internal/clients/navidrome" "git.kapelle.org/niklas/ripsort/internal/clients/spotify" ) func SyncPlaylists(spotifyClientID, spotifyClientSecret, navidromeBase, navidromeUser, navidromePass string) error { spotifyClient, err := spotify.NewSpotifyClient(spotifyClientID, spotifyClientSecret) if err != nil { slog.Error("Failed to create spotify client", "err", err) return err } navidromeClient, err := navidrome.NewNavidromeClient(navidromeBase, navidromeUser, navidromePass) if err != nil { slog.Error("Failed to create navidrome client", "err", err) return err } allPlaylists, err := navidromeClient.GetPlaylists() for _, pl := range allPlaylists { // Only sync playlists that have a spotify URL in the comment spotifyPlaylistID := spotifyIDForPlaylist(&pl) if spotifyPlaylistID == "" { continue } slog.Info("Syncing playlist", "name", pl.Name) spotifyPlaylist, err := spotifyClient.GetPlaylist(spotifyPlaylistID) if err != nil { slog.Error("Failed to fetch spotify playlist", "id", spotifyPlaylistID) return err } err = syncPlaylists(navidromeClient, spotifyPlaylist, &pl) if err != nil { slog.Error("Failed to sync playlist", "name", pl.Name, "err", err) return err } } return nil } func spotifyIDForPlaylist(playlist *navidrome.NavidromePlaylist) string { var re = regexp.MustCompile(`(?m)https:\/\/open\.spotify\.com\/playlist\/([a-zA-Z0-9]*)`) matches := re.FindAllStringSubmatch(playlist.Comment, -1) if len(matches) == 0 { return "" } return matches[0][1] } func syncPlaylists(nd *navidrome.NavidromeClient, spotifyPlaylist *spotify.SpotifyPlaylist, ndPlaylist *navidrome.NavidromePlaylist) error { tracks, err := nd.GetPlaylistTracks(ndPlaylist.ID) if err != nil { return err } songsToAdd := []string{} for _, spotifySong := range spotifyPlaylist.Items.Items { if ndPlaylistContainsSong(tracks, spotifySong.Item.ID) { slog.Debug("Track already in playlist", "id", spotifySong.Item.ID) continue } songs, err := nd.GetSongBySpotifyID(spotifySong.Item.ID) if err != nil { return err } if len(songs) == 0 { slog.Warn("No song found", "spotifyid", spotifySong.Item.ID, "title", spotifySong.Item.Name, "artists", spotifySong.Item.Artists) // Missing song // TODO: handle continue } if len(songs) != 1 { slog.Warn("Found multiple songs for spotifyid", "spotifyid", spotifySong.Item.ID) } songsToAdd = append(songsToAdd, songs[0].ID) } if len(songsToAdd) == 0 { return nil } slog.Info("Adding songs to playlist", "count", len(songsToAdd)) err = nd.AddTracks(ndPlaylist.ID, songsToAdd) if err != nil { slog.Error("Failed to add songs", "err", err) } return nil } func ndPlaylistContainsSong(tracks []navidrome.NavidromePlaylistTrack, spotifyID string) bool { for _, song := range tracks { tag := song.CustomTags["spotifyid"] if tag == nil || len(tag) == 0 { continue } if tag[0] == spotifyID { return true } } return false }