122 lines
3.0 KiB
Go
122 lines
3.0 KiB
Go
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
|
|
}
|