diff --git a/go.mod b/go.mod index 9871720..c13d7ae 100644 --- a/go.mod +++ b/go.mod @@ -57,6 +57,7 @@ require ( github.com/spf13/pflag v1.0.6 // indirect github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect + go.uploadedlobster.com/musicbrainzws2 v0.13.1 // indirect golang.org/x/image v0.26.0 // indirect golang.org/x/mod v0.24.0 // indirect golang.org/x/net v0.39.0 // indirect diff --git a/go.sum b/go.sum index a1aa433..1ee05c8 100644 --- a/go.sum +++ b/go.sum @@ -132,6 +132,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uploadedlobster.com/mbtypes v0.4.0 h1:D5asCgHsRWufj4Yn5u0IuH2J9z1UuYImYkYIp1Z1Q7s= go.uploadedlobster.com/mbtypes v0.4.0/go.mod h1:Bu1K1Hl77QTAE2Z7QKiW/JAp9KqYWQebkRRfG02dlZM= +go.uploadedlobster.com/musicbrainzws2 v0.13.1 h1:34GKI7l9eTCyh9ozNOHmlwAAUTDK9WVRsFZK5trxcwQ= +go.uploadedlobster.com/musicbrainzws2 v0.13.1/go.mod h1:TVln70Fzp/++fw0/jCP1xXwgilVwDkzTwRbV8GwUYLA= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= diff --git a/internal/backends/listenbrainz/listenbrainz.go b/internal/backends/listenbrainz/listenbrainz.go index c05943f..d13c869 100644 --- a/internal/backends/listenbrainz/listenbrainz.go +++ b/internal/backends/listenbrainz/listenbrainz.go @@ -22,6 +22,7 @@ import ( "time" "go.uploadedlobster.com/mbtypes" + "go.uploadedlobster.com/musicbrainzws2" "go.uploadedlobster.com/scotty/internal/config" "go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/models" @@ -31,6 +32,7 @@ import ( type ListenBrainzApiBackend struct { client Client + mbClient musicbrainzws2.Client username string checkDuplicates bool existingMBIDs map[mbtypes.MBID]bool @@ -56,6 +58,7 @@ func (b *ListenBrainzApiBackend) Options() []models.BackendOption { func (b *ListenBrainzApiBackend) FromConfig(config *config.ServiceConfig) models.Backend { b.client = NewClient(config.GetString("token")) + b.mbClient = *musicbrainzws2.NewClient(version.AppName, version.AppVersion) b.client.MaxResults = MaxItemsPerGet b.username = config.GetString("username") b.checkDuplicates = config.GetBool("check-duplicate-listens", false) @@ -228,6 +231,16 @@ out: } for _, feedback := range result.Feedback { + // Missing track metadata indicates that the recording MBID is no + // longer available and might have been merged. Try fetching details + // from MusicBrainz. + if feedback.TrackMetadata == nil { + track, err := b.lookupRecording(feedback.RecordingMBID) + if err == nil { + feedback.TrackMetadata = track + } + } + love := feedback.AsLove() if love.Created.Unix() > oldestTimestamp.Unix() { loves = append(loves, love) @@ -265,6 +278,12 @@ func (b *ListenBrainzApiBackend) ImportLoves(export models.LovesResult, importRe for _, love := range existingLoves.Items { b.existingMBIDs[love.RecordingMBID] = true + // In case the loved MBID got merged the track MBID represents the + // actual recording MBID. + if love.Track.RecordingMBID != "" && + love.Track.RecordingMBID != love.RecordingMBID { + b.existingMBIDs[love.Track.RecordingMBID] = true + } } } } @@ -347,6 +366,31 @@ func (b *ListenBrainzApiBackend) checkDuplicateListen(listen models.Listen) (boo return false, nil } +func (b *ListenBrainzApiBackend) lookupRecording(mbid mbtypes.MBID) (*Track, error) { + filter := musicbrainzws2.IncludesFilter{ + Includes: []string{"artist-credits"}, + } + recording, err := b.mbClient.LookupRecording(mbid, filter) + if err != nil { + return nil, err + } + + artistMBIDs := make([]mbtypes.MBID, 0, len(recording.ArtistCredit)) + for _, artist := range recording.ArtistCredit { + artistMBIDs = append(artistMBIDs, artist.Artist.ID) + } + track := Track{ + TrackName: recording.Title, + ArtistName: recording.ArtistCredit.String(), + MBIDMapping: &MBIDMapping{ + // In case of redirects this MBID differs from the looked up MBID + RecordingMBID: recording.ID, + ArtistMBIDs: artistMBIDs, + }, + } + return &track, nil +} + func (lbListen Listen) AsListen() models.Listen { listen := models.Listen{ ListenedAt: time.Unix(lbListen.ListenedAt, 0),