From e8fdfb95a64e06bbaea81242e8448ca38c1bea1e Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Thu, 23 Nov 2023 22:20:40 +0100 Subject: [PATCH] ListenBrainz submit listens --- backends/listenbrainz/client.go | 25 ++++++++++++++-- backends/listenbrainz/client_test.go | 38 ++++++++++++++++++++++++ backends/listenbrainz/listenbrainz.go | 42 +++++++++++++++++++++++++++ backends/listenbrainz/models.go | 19 ++++++++++-- 4 files changed, 118 insertions(+), 6 deletions(-) diff --git a/backends/listenbrainz/client.go b/backends/listenbrainz/client.go index 6fbab00..c90a637 100644 --- a/backends/listenbrainz/client.go +++ b/backends/listenbrainz/client.go @@ -32,9 +32,12 @@ import ( const listenBrainzBaseURL = "https://api.listenbrainz.org/1/" -const DefaultItemsPerGet = 25 -const MaxItemsPerGet = 1000 -const DefaultRateLimitWaitSeconds = 5 +const ( + DefaultItemsPerGet = 25 + MaxItemsPerGet = 1000 + MaxListensPerRequest = 1000 + DefaultRateLimitWaitSeconds = 5 +) type Client struct { HttpClient *resty.Client @@ -96,6 +99,22 @@ func (c Client) GetListens(user string, maxTime time.Time, minTime time.Time) (r return } +func (c Client) SubmitListens(listens ListenSubmission) (result StatusResult, err error) { + const path = "/submit-listens" + errorResult := ErrorResult{} + response, err := c.HttpClient.R(). + SetBody(listens). + SetResult(&result). + SetError(&errorResult). + Post(path) + + if response.StatusCode() != 200 { + err = errors.New(errorResult.Error) + return + } + return +} + func (c Client) GetFeedback(user string, status int, offset int) (result GetFeedbackResult, err error) { const path = "/feedback/user/{username}/get-feedback" errorResult := ErrorResult{} diff --git a/backends/listenbrainz/client_test.go b/backends/listenbrainz/client_test.go index 69d42d3..b151a81 100644 --- a/backends/listenbrainz/client_test.go +++ b/backends/listenbrainz/client_test.go @@ -57,6 +57,44 @@ func TestGetListens(t *testing.T) { assert.Equal("Shadowplay", result.Payload.Listens[0].TrackMetadata.TrackName) } +func TestSubmitListens(t *testing.T) { + client := listenbrainz.NewClient("thetoken") + httpmock.ActivateNonDefault(client.HttpClient.GetClient()) + + responder, err := httpmock.NewJsonResponder(200, listenbrainz.StatusResult{ + Status: "ok", + }) + if err != nil { + t.Fatal(err) + } + url := "https://api.listenbrainz.org/1/submit-listens" + httpmock.RegisterResponder("POST", url, responder) + + listens := listenbrainz.ListenSubmission{ + ListenType: listenbrainz.Import, + Payload: []listenbrainz.Listen{ + { + ListenedAt: time.Now().Unix(), + TrackMetadata: listenbrainz.Track{ + TrackName: "Oweynagat", + ArtistName: "Dool", + }, + }, + { + ListenedAt: time.Now().Add(-2 * time.Minute).Unix(), + TrackMetadata: listenbrainz.Track{ + TrackName: "Say Just Words", + ArtistName: "Paradise Lost", + }, + }, + }, + } + result, err := client.SubmitListens(listens) + require.NoError(t, err) + + assert.Equal(t, "ok", result.Status) +} + func TestGetFeedback(t *testing.T) { defer httpmock.DeactivateAndReset() diff --git a/backends/listenbrainz/listenbrainz.go b/backends/listenbrainz/listenbrainz.go index 65cdcbd..efee9bf 100644 --- a/backends/listenbrainz/listenbrainz.go +++ b/backends/listenbrainz/listenbrainz.go @@ -95,6 +95,48 @@ out: results <- models.ListensResult{Listens: listens, OldestTimestamp: oldestTimestamp} } +func (b *ListenBrainzApiBackend) ImportListens(export models.ListensResult, importResult models.ImportResult, progress chan models.Progress) (models.ImportResult, error) { + total := len(export.Listens) + for i := 0; i < total; i += MaxListensPerRequest { + listens := export.Listens[i:min(i+MaxItemsPerGet, total)] + count := len(listens) + if count == 0 { + break + } + + submission := ListenSubmission{ + ListenType: Import, + Payload: make([]Listen, 0, count), + } + + for _, l := range listens { + l.FillAdditionalInfo() + listen := Listen{ + ListenedAt: l.ListenedAt.Unix(), + TrackMetadata: Track{ + TrackName: l.TrackName, + ReleaseName: l.ReleaseName, + ArtistName: l.ArtistName(), + AdditionalInfo: l.AdditionalInfo, + }, + } + listen.TrackMetadata.AdditionalInfo["submission_client"] = "Scotty" + submission.Payload = append(submission.Payload, listen) + } + + _, err := b.client.SubmitListens(submission) + if err != nil { + return importResult, err + } + + importResult.UpdateTimestamp(listens[count-1].ListenedAt) + importResult.ImportCount += count + progress <- models.Progress{}.FromImportResult(importResult) + } + + return importResult, nil +} + func (b *ListenBrainzApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress) { offset := 0 defer close(results) diff --git a/backends/listenbrainz/models.go b/backends/listenbrainz/models.go index e70d61f..a7cd32b 100644 --- a/backends/listenbrainz/models.go +++ b/backends/listenbrainz/models.go @@ -39,11 +39,24 @@ type GetListenPayload struct { Listens []Listen `json:"listens"` } +type listenType string + +const ( + PlayingNow listenType = "playing_now" + Single listenType = "single" + Import listenType = "import" +) + +type ListenSubmission struct { + ListenType listenType `json:"listen_type"` + Payload []Listen `json:"payload"` +} + type Listen struct { - InsertedAt int64 `json:"inserted_at"` + InsertedAt int64 `json:"inserted_at,omitempty"` ListenedAt int64 `json:"listened_at"` - RecordingMsid string `json:"recording_msid"` - UserName string `json:"user_name"` + RecordingMsid string `json:"recording_msid,omitempty"` + UserName string `json:"user_name,omitempty"` TrackMetadata Track `json:"track_metadata"` }