From 1aa7b61649452193cc762d29f25d87a94a6f993d Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Fri, 26 Jan 2024 12:19:00 +0100 Subject: [PATCH 01/52] subsonic: include subsonic_id as additional metadata --- internal/backends/subsonic/subsonic.go | 16 +++++++++------- internal/backends/subsonic/subsonic_test.go | 2 ++ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/internal/backends/subsonic/subsonic.go b/internal/backends/subsonic/subsonic.go index 5b192ab..6a59630 100644 --- a/internal/backends/subsonic/subsonic.go +++ b/internal/backends/subsonic/subsonic.go @@ -100,13 +100,15 @@ func SongAsLove(song subsonic.Child, username string) models.Love { UserName: username, Created: song.Starred, Track: models.Track{ - TrackName: song.Title, - ReleaseName: song.Album, - ArtistNames: []string{song.Artist}, - TrackNumber: song.Track, - DiscNumber: song.DiscNumber, - AdditionalInfo: map[string]any{}, - Duration: time.Duration(song.Duration * int(time.Second)), + TrackName: song.Title, + ReleaseName: song.Album, + ArtistNames: []string{song.Artist}, + TrackNumber: song.Track, + DiscNumber: song.DiscNumber, + AdditionalInfo: map[string]any{ + "subsonic_id": song.ID, + }, + Duration: time.Duration(song.Duration * int(time.Second)), }, } diff --git a/internal/backends/subsonic/subsonic_test.go b/internal/backends/subsonic/subsonic_test.go index 68641cd..c5bfe36 100644 --- a/internal/backends/subsonic/subsonic_test.go +++ b/internal/backends/subsonic/subsonic_test.go @@ -39,6 +39,7 @@ func TestFromConfig(t *testing.T) { func TestSongToLove(t *testing.T) { user := "outsidecontext" song := go_subsonic.Child{ + ID: "foo123", Starred: time.Unix(1699574369, 0), Title: "Oweynagat", Album: "Here Now, There Then", @@ -59,4 +60,5 @@ func TestSongToLove(t *testing.T) { assert.Equal(song.Track, love.Track.TrackNumber) assert.Equal(song.DiscNumber, love.Track.DiscNumber) assert.Equal([]string{song.Genre}, love.Track.Tags) + assert.Equal(song.ID, love.AdditionalInfo["subsonic_id"]) } From 3f1bebd8ed9c3e030d8de3617a1e8b2cfb94ebc2 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Mon, 29 Jan 2024 08:27:51 +0100 Subject: [PATCH 02/52] deezer: fix artist and album ID URIs Fixes #7 --- internal/backends/deezer/deezer.go | 4 ++-- internal/backends/deezer/deezer_test.go | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/backends/deezer/deezer.go b/internal/backends/deezer/deezer.go index 896b348..e840c93 100644 --- a/internal/backends/deezer/deezer.go +++ b/internal/backends/deezer/deezer.go @@ -245,8 +245,8 @@ func (t Track) AsTrack() models.Track { info["music_service"] = "deezer.com" info["origin_url"] = t.Link info["deezer_id"] = t.Link - info["deezer_album_id"] = fmt.Sprintf("https://www.deezer.com/track/%v", t.Album.Id) - info["deezer_artist_id"] = fmt.Sprintf("https://www.deezer.com/track/%v", t.Artist.Id) + info["deezer_album_id"] = fmt.Sprintf("https://www.deezer.com/album/%v", t.Album.Id) + info["deezer_artist_id"] = fmt.Sprintf("https://www.deezer.com/artist/%v", t.Artist.Id) return track } diff --git a/internal/backends/deezer/deezer_test.go b/internal/backends/deezer/deezer_test.go index ac81402..9550c0e 100644 --- a/internal/backends/deezer/deezer_test.go +++ b/internal/backends/deezer/deezer_test.go @@ -57,6 +57,8 @@ func TestListenAsListen(t *testing.T) { assert.Equal(t, "deezer.com", listen.AdditionalInfo["music_service"]) assert.Equal(t, "https://www.deezer.com/track/14631511", listen.AdditionalInfo["origin_url"]) assert.Equal(t, "https://www.deezer.com/track/14631511", listen.AdditionalInfo["deezer_id"]) + assert.Equal(t, "https://www.deezer.com/album/1346960", listen.AdditionalInfo["deezer_album_id"]) + assert.Equal(t, "https://www.deezer.com/artist/92", listen.AdditionalInfo["deezer_artist_id"]) } func TestLovedTrackAsLove(t *testing.T) { From 357932f9b077a548806fcd81652147da24e60fec Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Sun, 24 Mar 2024 16:36:53 +0100 Subject: [PATCH 03/52] Use resty response.IsSuccess() instead of checking for status code 200 --- internal/backends/deezer/client.go | 2 +- internal/backends/funkwhale/client.go | 4 ++-- internal/backends/listenbrainz/client.go | 10 +++++----- internal/backends/maloja/client.go | 4 ++-- internal/backends/spotify/client.go | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/backends/deezer/client.go b/internal/backends/deezer/client.go index eccd188..3c3b740 100644 --- a/internal/backends/deezer/client.go +++ b/internal/backends/deezer/client.go @@ -85,7 +85,7 @@ func listRequest[T Result](c Client, path string, offset int, limit int) (result } response, err := request.Get(path) - if response.StatusCode() != 200 { + if !response.IsSuccess() { err = errors.New(response.String()) } else if result.Error() != nil { err = errors.New(result.Error().Message) diff --git a/internal/backends/funkwhale/client.go b/internal/backends/funkwhale/client.go index 39071e7..8f2848b 100644 --- a/internal/backends/funkwhale/client.go +++ b/internal/backends/funkwhale/client.go @@ -66,7 +66,7 @@ func (c Client) GetHistoryListenings(user string, page int, perPage int) (result SetResult(&result). Get(path) - if response.StatusCode() != 200 { + if !response.IsSuccess() { err = errors.New(response.String()) return } @@ -84,7 +84,7 @@ func (c Client) GetFavoriteTracks(page int, perPage int) (result FavoriteTracksR SetResult(&result). Get(path) - if response.StatusCode() != 200 { + if !response.IsSuccess() { err = errors.New(response.String()) return } diff --git a/internal/backends/listenbrainz/client.go b/internal/backends/listenbrainz/client.go index 72257bf..1917bd4 100644 --- a/internal/backends/listenbrainz/client.go +++ b/internal/backends/listenbrainz/client.go @@ -74,7 +74,7 @@ func (c Client) GetListens(user string, maxTime time.Time, minTime time.Time) (r SetError(&errorResult). Get(path) - if response.StatusCode() != 200 { + if !response.IsSuccess() { err = errors.New(errorResult.Error) return } @@ -90,7 +90,7 @@ func (c Client) SubmitListens(listens ListenSubmission) (result StatusResult, er SetError(&errorResult). Post(path) - if response.StatusCode() != 200 { + if !response.IsSuccess() { err = errors.New(errorResult.Error) return } @@ -112,7 +112,7 @@ func (c Client) GetFeedback(user string, status int, offset int) (result GetFeed SetError(&errorResult). Get(path) - if response.StatusCode() != 200 { + if !response.IsSuccess() { err = errors.New(errorResult.Error) return } @@ -128,7 +128,7 @@ func (c Client) SendFeedback(feedback Feedback) (result StatusResult, err error) SetError(&errorResult). Post(path) - if response.StatusCode() != 200 { + if !response.IsSuccess() { err = errors.New(errorResult.Error) return } @@ -147,7 +147,7 @@ func (c Client) Lookup(recordingName string, artistName string) (result LookupRe SetError(&errorResult). Get(path) - if response.StatusCode() != 200 { + if !response.IsSuccess() { err = errors.New(errorResult.Error) return } diff --git a/internal/backends/maloja/client.go b/internal/backends/maloja/client.go index 3b79110..f373f93 100644 --- a/internal/backends/maloja/client.go +++ b/internal/backends/maloja/client.go @@ -58,7 +58,7 @@ func (c Client) GetScrobbles(page int, perPage int) (result GetScrobblesResult, SetResult(&result). Get(path) - if response.StatusCode() != 200 { + if !response.IsSuccess() { err = errors.New(response.String()) return } @@ -73,7 +73,7 @@ func (c Client) NewScrobble(scrobble NewScrobble) (result NewScrobbleResult, err SetResult(&result). Post(path) - if response.StatusCode() != 200 { + if !response.IsSuccess() { err = errors.New(response.String()) return } diff --git a/internal/backends/spotify/client.go b/internal/backends/spotify/client.go index 7bbcf48..1c002c0 100644 --- a/internal/backends/spotify/client.go +++ b/internal/backends/spotify/client.go @@ -79,7 +79,7 @@ func (c Client) recentlyPlayed(after *time.Time, before *time.Time, limit int) ( } response, err := request.Get(path) - if response.StatusCode() != 200 { + if !response.IsSuccess() { err = errors.New(response.String()) } return @@ -95,7 +95,7 @@ func (c Client) UserTracks(offset int, limit int) (result TracksResult, err erro SetResult(&result). Get(path) - if response.StatusCode() != 200 { + if !response.IsSuccess() { err = errors.New(response.String()) } return From bcc7bf31671c85e536265b01af96fd03ab939b4e Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Mon, 15 Apr 2024 15:47:16 +0200 Subject: [PATCH 04/52] Replaced util Min/Max functions with builtin --- internal/backends/deezer/deezer.go | 5 ++--- internal/backends/spotify/spotify.go | 3 +-- internal/similarity/similarity.go | 2 +- internal/util/util.go | 16 ---------------- internal/util/util_test.go | 12 ------------ 5 files changed, 4 insertions(+), 34 deletions(-) diff --git a/internal/backends/deezer/deezer.go b/internal/backends/deezer/deezer.go index e840c93..3131c3e 100644 --- a/internal/backends/deezer/deezer.go +++ b/internal/backends/deezer/deezer.go @@ -26,7 +26,6 @@ import ( "go.uploadedlobster.com/scotty/internal/config" "go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/models" - "go.uploadedlobster.com/scotty/internal/util" "golang.org/x/oauth2" ) @@ -106,7 +105,7 @@ out: // and continue. if offset >= result.Total { p.Total = int64(result.Total) - offset = util.Max(result.Total-perPage, 0) + offset = max(result.Total-perPage, 0) continue } @@ -175,7 +174,7 @@ out: if offset >= result.Total { p.Total = int64(result.Total) totalCount = result.Total - offset = util.Max(result.Total-perPage, 0) + offset = max(result.Total-perPage, 0) continue } diff --git a/internal/backends/spotify/spotify.go b/internal/backends/spotify/spotify.go index b9a51e2..a4e3c87 100644 --- a/internal/backends/spotify/spotify.go +++ b/internal/backends/spotify/spotify.go @@ -28,7 +28,6 @@ import ( "go.uploadedlobster.com/scotty/internal/config" "go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/models" - "go.uploadedlobster.com/scotty/internal/util" "golang.org/x/oauth2" "golang.org/x/oauth2/spotify" ) @@ -184,7 +183,7 @@ out: if offset >= result.Total { p.Total = int64(result.Total) totalCount = result.Total - offset = util.Max(result.Total-perPage, 0) + offset = max(result.Total-perPage, 0) continue } diff --git a/internal/similarity/similarity.go b/internal/similarity/similarity.go index 4c0b345..358404a 100644 --- a/internal/similarity/similarity.go +++ b/internal/similarity/similarity.go @@ -33,7 +33,7 @@ func Similarity(s1 string, s2 string) float64 { s2 = norm.NFKC.String(s2) l1 := len([]rune(s1)) l2 := len([]rune(s2)) - maxLen := util.Max(l1, l2) + maxLen := max(l1, l2) // Empty strings always compare full equal if maxLen == 0 { return 1.0 diff --git a/internal/util/util.go b/internal/util/util.go index e8663a7..9ef4e14 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -17,22 +17,6 @@ package util import "golang.org/x/exp/constraints" -func Max[T constraints.Ordered](m, n T) T { - if n > m { - return n - } else { - return m - } -} - -func Min[T constraints.Ordered](m, n T) T { - if n < m { - return n - } else { - return m - } -} - func Sum[T constraints.Integer | constraints.Float](v ...T) T { var sum T for _, i := range v { diff --git a/internal/util/util_test.go b/internal/util/util_test.go index 73ec415..077fedd 100644 --- a/internal/util/util_test.go +++ b/internal/util/util_test.go @@ -23,18 +23,6 @@ import ( "go.uploadedlobster.com/scotty/internal/util" ) -func ExampleMax() { - v := util.Max(2, 5) - fmt.Print(v) - // Output: 5 -} - -func ExampleMin() { - v := util.Min(2, 5) - fmt.Print(v) - // Output: 2 -} - func ExampleSum() { values := []float64{1.4, 2.2} sum := util.Sum(values...) From cdf20728aede22157511d73603afba5d8de6dbce Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Mon, 15 Apr 2024 15:47:55 +0200 Subject: [PATCH 05/52] Update and tidy dependencies --- go.mod | 39 +++++++++-------- go.sum | 131 ++++++++++++++++++++++++++++++++------------------------- 2 files changed, 93 insertions(+), 77 deletions(-) diff --git a/go.mod b/go.mod index c73cd44..917d4b0 100644 --- a/go.mod +++ b/go.mod @@ -8,25 +8,26 @@ require ( github.com/cli/browser v1.3.0 github.com/delucks/go-subsonic v0.0.0-20220915164742-2744002c4be5 github.com/fatih/color v1.16.0 - github.com/glebarez/sqlite v1.10.0 - github.com/go-resty/resty/v2 v2.11.0 + github.com/glebarez/sqlite v1.11.0 + github.com/go-resty/resty/v2 v2.12.0 github.com/jarcoal/httpmock v1.3.1 github.com/manifoldco/promptui v0.9.0 - github.com/pelletier/go-toml/v2 v2.1.1 + github.com/pelletier/go-toml/v2 v2.2.1 github.com/shkh/lastfm-go v0.0.0-20191215035245-89a801c244e0 github.com/spf13/cast v1.6.0 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.18.2 - github.com/stretchr/testify v1.8.4 - github.com/vbauerster/mpb/v8 v8.7.2 - golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 - golang.org/x/oauth2 v0.16.0 + github.com/stretchr/testify v1.9.0 + github.com/vbauerster/mpb/v8 v8.7.3 + golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 + golang.org/x/oauth2 v0.19.0 golang.org/x/text v0.14.0 gorm.io/datatypes v1.2.0 - gorm.io/gorm v1.25.5 + gorm.io/gorm v1.25.9 ) require ( + filippo.io/edwards25519 v1.1.0 // indirect github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/chzyer/readline v1.5.1 // indirect @@ -34,9 +35,8 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/glebarez/go-sqlite v1.22.0 // indirect - github.com/go-sql-driver/mysql v1.7.1 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/google/uuid v1.5.0 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect @@ -46,9 +46,10 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/rivo/uniseg v0.4.4 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect @@ -56,15 +57,13 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.20.0 // indirect - golang.org/x/sys v0.16.0 // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/protobuf v1.32.0 // indirect + golang.org/x/net v0.24.0 // indirect + golang.org/x/sys v0.19.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - gorm.io/driver/mysql v1.5.2 // indirect - modernc.org/libc v1.40.2 // indirect + gorm.io/driver/mysql v1.5.6 // indirect + modernc.org/libc v1.49.3 // indirect modernc.org/mathutil v1.6.0 // indirect - modernc.org/memory v1.7.2 // indirect - modernc.org/sqlite v1.28.0 // indirect + modernc.org/memory v1.8.0 // indirect + modernc.org/sqlite v1.29.6 // indirect ) diff --git a/go.sum b/go.sum index 38ff47a..1e7ee15 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/Xuanwo/go-locale v1.1.0 h1:51gUxhxl66oXAjI9uPGb2O0qwPECpriKQb2hl35mQkg= @@ -38,28 +40,23 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= -github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc= -github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA= -github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8= -github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A= +github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= +github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= +github.com/go-resty/resty/v2 v2.12.0 h1:rsVL8P90LFvkUYq/V5BTVe203WfRIU4gvcf+yfzJzGA= +github.com/go-resty/resty/v2 v2.12.0/go.mod h1:o0yGPrkS3lOe1+eFajk6kBW8ScXzwU3hD69/gt2yB/0= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= -github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -95,24 +92,26 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= -github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM= github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE= github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= -github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/pelletier/go-toml/v2 v2.2.1 h1:9TA9+T8+8CUCO2+WYnDLCgrYi9+omqKXyjDtosvtEhg= +github.com/pelletier/go-toml/v2 v2.2.1/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= -github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -141,41 +140,49 @@ github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMV github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/vbauerster/mpb/v8 v8.7.2 h1:SMJtxhNho1MV3OuFgS1DAzhANN1Ejc5Ct+0iSaIkB14= -github.com/vbauerster/mpb/v8 v8.7.2/go.mod h1:ZFnrjzspgDHoxYLGvxIruiNk73GNTPG4YHgVNpR10VY= +github.com/vbauerster/mpb/v8 v8.7.3 h1:n/mKPBav4FFWp5fH4U0lPpXfiOmCEgl5Yx/NM3tKJA0= +github.com/vbauerster/mpb/v8 v8.7.3/go.mod h1:9nFlNpDGVoTmQ4QvNjSLtwLmAFjwmq0XaAF26toHGNM= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 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.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o= -golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc= +golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= -golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= +golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -188,24 +195,23 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -213,14 +219,9 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= +golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -231,22 +232,38 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/datatypes v1.2.0 h1:5YT+eokWdIxhJgWHdrb2zYUimyk0+TaFth+7a0ybzco= gorm.io/datatypes v1.2.0/go.mod h1:o1dh0ZvjIjhH/bngTpypG6lVRJ5chTBxE09FH/71k04= -gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs= -gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8= +gorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8= +gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U= gorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A= gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU= gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI= gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0= gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig= -gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= -gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= -gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= -modernc.org/libc v1.40.2 h1:pzVHG9jwYZNWANfltHiU3HYfrzYIsX6ysRLJ93adZXA= -modernc.org/libc v1.40.2/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8= +gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +modernc.org/cc/v4 v4.20.0 h1:45Or8mQfbUqJOG9WaxvlFYOAQO0lQ5RvqBcFCXngjxk= +modernc.org/cc/v4 v4.20.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= +modernc.org/ccgo/v4 v4.16.0 h1:ofwORa6vx2FMm0916/CkZjpFPSR70VwTjUCe2Eg5BnA= +modernc.org/ccgo/v4 v4.16.0/go.mod h1:dkNyWIjFrVIZ68DTo36vHK+6/ShBn4ysU61So6PIqCI= +modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= +modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= +modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= +modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= +modernc.org/libc v1.49.3 h1:j2MRCRdwJI2ls/sGbeSk0t2bypOG/uvPZUsGQFDulqg= +modernc.org/libc v1.49.3/go.mod h1:yMZuGkn7pXbKfoT/M35gFJOAEdSKdxL0q64sF7KqCDo= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= -modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= -modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= -modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ= -modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= +modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= +modernc.org/sqlite v1.29.6 h1:0lOXGrycJPptfHDuohfYgNqoe4hu+gYuN/pKgY5XjS4= +modernc.org/sqlite v1.29.6/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= From 7175d3453d73ae8a1590ad9061e7f1256f3ed642 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Mon, 16 Sep 2024 19:00:24 +0200 Subject: [PATCH 06/52] Fix go version definition in go.mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 917d4b0..912f6b1 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module go.uploadedlobster.com/scotty -go 1.21.1 +go 1.21 require ( github.com/Xuanwo/go-locale v1.1.0 From 1c1ce224f7430349a5b386e6429c881bb4e67900 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Mon, 16 Sep 2024 19:02:14 +0200 Subject: [PATCH 07/52] Update dependencies --- go.mod | 46 ++++++++++++++++++++++++---------------------- go.sum | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 22 deletions(-) diff --git a/go.mod b/go.mod index 912f6b1..5a67610 100644 --- a/go.mod +++ b/go.mod @@ -1,29 +1,31 @@ module go.uploadedlobster.com/scotty -go 1.21 +go 1.22.0 + +toolchain go1.22.2 require ( - github.com/Xuanwo/go-locale v1.1.0 + github.com/Xuanwo/go-locale v1.1.2 github.com/agnivade/levenshtein v1.1.1 github.com/cli/browser v1.3.0 - github.com/delucks/go-subsonic v0.0.0-20220915164742-2744002c4be5 - github.com/fatih/color v1.16.0 + github.com/delucks/go-subsonic v0.0.0-20240806025900-2a743ec36238 + github.com/fatih/color v1.17.0 github.com/glebarez/sqlite v1.11.0 - github.com/go-resty/resty/v2 v2.12.0 + github.com/go-resty/resty/v2 v2.15.0 github.com/jarcoal/httpmock v1.3.1 github.com/manifoldco/promptui v0.9.0 - github.com/pelletier/go-toml/v2 v2.2.1 + github.com/pelletier/go-toml/v2 v2.2.3 github.com/shkh/lastfm-go v0.0.0-20191215035245-89a801c244e0 - github.com/spf13/cast v1.6.0 - github.com/spf13/cobra v1.8.0 - github.com/spf13/viper v1.18.2 + github.com/spf13/cast v1.7.0 + github.com/spf13/cobra v1.8.1 + github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 - github.com/vbauerster/mpb/v8 v8.7.3 - golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 - golang.org/x/oauth2 v0.19.0 - golang.org/x/text v0.14.0 - gorm.io/datatypes v1.2.0 - gorm.io/gorm v1.25.9 + github.com/vbauerster/mpb/v8 v8.8.3 + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 + golang.org/x/oauth2 v0.23.0 + golang.org/x/text v0.18.0 + gorm.io/datatypes v1.2.2 + gorm.io/gorm v1.25.12 ) require ( @@ -44,26 +46,26 @@ require ( github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/locafero v0.6.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.24.0 // indirect - golang.org/x/sys v0.19.0 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/sys v0.25.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - gorm.io/driver/mysql v1.5.6 // indirect - modernc.org/libc v1.49.3 // indirect + gorm.io/driver/mysql v1.5.7 // indirect + modernc.org/libc v1.60.1 // indirect modernc.org/mathutil v1.6.0 // indirect modernc.org/memory v1.8.0 // indirect - modernc.org/sqlite v1.29.6 // indirect + modernc.org/sqlite v1.33.1 // indirect ) diff --git a/go.sum b/go.sum index 1e7ee15..2fb0b5a 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1o github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/Xuanwo/go-locale v1.1.0 h1:51gUxhxl66oXAjI9uPGb2O0qwPECpriKQb2hl35mQkg= github.com/Xuanwo/go-locale v1.1.0/go.mod h1:UKrHoZB3FPIk9wIG2/tVSobnHgNnceGSH3Y8DY5cASs= +github.com/Xuanwo/go-locale v1.1.2 h1:6H+olvrQcyVOZ+GAC2rXu4armacTT4ZrFCA0mB24XVo= +github.com/Xuanwo/go-locale v1.1.2/go.mod h1:1JBER4QV7Ji39GJ4AvVlfvqmTUqopzxQxdg2mXYOw94= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= @@ -22,18 +24,23 @@ github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38 github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/delucks/go-subsonic v0.0.0-20220915164742-2744002c4be5 h1:RuuxidatioSKGOiBzL1mTY4X22DQD8weEbS3iRLHnAg= github.com/delucks/go-subsonic v0.0.0-20220915164742-2744002c4be5/go.mod h1:vnbEuj6Z20PLcHB4rrLQAOXGMjtULfMGhRVSFPcSdUo= +github.com/delucks/go-subsonic v0.0.0-20240806025900-2a743ec36238 h1:uejyepOdHISrJTw7P84Y7yEC0FMyv1q3KNDRxWsviKw= +github.com/delucks/go-subsonic v0.0.0-20240806025900-2a743ec36238/go.mod h1:vnbEuj6Z20PLcHB4rrLQAOXGMjtULfMGhRVSFPcSdUo= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= @@ -44,6 +51,8 @@ github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GM github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= github.com/go-resty/resty/v2 v2.12.0 h1:rsVL8P90LFvkUYq/V5BTVe203WfRIU4gvcf+yfzJzGA= github.com/go-resty/resty/v2 v2.12.0/go.mod h1:o0yGPrkS3lOe1+eFajk6kBW8ScXzwU3hD69/gt2yB/0= +github.com/go-resty/resty/v2 v2.15.0 h1:clPQLZ2x9h4yGY81IzpMPnty+xoGyFaDg0XMkCsHf90= +github.com/go-resty/resty/v2 v2.15.0/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= @@ -55,6 +64,7 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= @@ -67,8 +77,10 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= github.com/jackc/pgx/v5 v5.3.0 h1:/NQi8KHMpKWHInxXesC8yD4DhkXPrVhmnwYkjp9AmBA= github.com/jackc/pgx/v5 v5.3.0/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= +github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -92,6 +104,9 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= @@ -104,6 +119,8 @@ github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdh github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/pelletier/go-toml/v2 v2.2.1 h1:9TA9+T8+8CUCO2+WYnDLCgrYi9+omqKXyjDtosvtEhg= github.com/pelletier/go-toml/v2 v2.2.1/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -117,6 +134,8 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= +github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/shkh/lastfm-go v0.0.0-20191215035245-89a801c244e0 h1:cgqwZtnR+IQfUYDLJ3Kiy4aE+O/wExTzEIg8xwC4Qfs= @@ -131,12 +150,18 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -151,6 +176,8 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/vbauerster/mpb/v8 v8.7.3 h1:n/mKPBav4FFWp5fH4U0lPpXfiOmCEgl5Yx/NM3tKJA0= github.com/vbauerster/mpb/v8 v8.7.3/go.mod h1:9nFlNpDGVoTmQ4QvNjSLtwLmAFjwmq0XaAF26toHGNM= +github.com/vbauerster/mpb/v8 v8.8.3 h1:dTOByGoqwaTJYPubhVz3lO5O6MK553XVgUo33LdnNsQ= +github.com/vbauerster/mpb/v8 v8.8.3/go.mod h1:JfCCrtcMsJwP6ZwMn9e5LMnNyp3TVNpUWWkN+nd4EWk= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= @@ -160,12 +187,16 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc= golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -176,13 +207,18 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -199,6 +235,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -212,8 +250,11 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -221,6 +262,7 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= +golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= @@ -232,8 +274,12 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/datatypes v1.2.0 h1:5YT+eokWdIxhJgWHdrb2zYUimyk0+TaFth+7a0ybzco= gorm.io/datatypes v1.2.0/go.mod h1:o1dh0ZvjIjhH/bngTpypG6lVRJ5chTBxE09FH/71k04= +gorm.io/datatypes v1.2.2 h1:sdn7ZmG4l7JWtMDUb3L98f2Ym7CO5F8mZLlrQJMfF9g= +gorm.io/datatypes v1.2.2/go.mod h1:f4BsLcFAX67szSv8svwLRjklArSHAvHLeE3pXAS5DZI= gorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8= gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= +gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U= gorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A= gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU= @@ -243,16 +289,23 @@ gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHD gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8= gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= modernc.org/cc/v4 v4.20.0 h1:45Or8mQfbUqJOG9WaxvlFYOAQO0lQ5RvqBcFCXngjxk= modernc.org/cc/v4 v4.20.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= +modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= modernc.org/ccgo/v4 v4.16.0 h1:ofwORa6vx2FMm0916/CkZjpFPSR70VwTjUCe2Eg5BnA= modernc.org/ccgo/v4 v4.16.0/go.mod h1:dkNyWIjFrVIZ68DTo36vHK+6/ShBn4ysU61So6PIqCI= +modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= +modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M= modernc.org/libc v1.49.3 h1:j2MRCRdwJI2ls/sGbeSk0t2bypOG/uvPZUsGQFDulqg= modernc.org/libc v1.49.3/go.mod h1:yMZuGkn7pXbKfoT/M35gFJOAEdSKdxL0q64sF7KqCDo= +modernc.org/libc v1.60.1 h1:at373l8IFRTkJIkAU85BIuUoBM4T1b51ds0E1ovPG2s= +modernc.org/libc v1.60.1/go.mod h1:xJuobKuNxKH3RUatS7GjR+suWj+5c2K7bi4m/S5arOY= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= @@ -263,6 +316,8 @@ modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= modernc.org/sqlite v1.29.6 h1:0lOXGrycJPptfHDuohfYgNqoe4hu+gYuN/pKgY5XjS4= modernc.org/sqlite v1.29.6/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U= +modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM= +modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= From 04eddfda33cc6f0b87dc0fcea43d5c4f50923ddc Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Mon, 16 Sep 2024 19:07:07 +0200 Subject: [PATCH 08/52] Release 0.4.1 --- CHANGES.md | 6 ++++++ internal/version/version.go | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index ff7bcbd..d3ee1d7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # Scotty Changelog +## 0.4.1 - 2024-09-16 +- Subsonic: include `subsonic_id` as additional metadata +- Deezer: fix artist and album ID URIs (#7) +- Fix installation issues due to wrong go version format in `go.mod` + + ## 0.4.0 - 2024-01-21 - JSPF: implement append mode - scrobberlog: append mode is enabled by default diff --git a/internal/version/version.go b/internal/version/version.go index 4c1b077..3f02fe2 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -17,7 +17,7 @@ package version const ( AppName = "scotty" - AppVersion = "0.4.0" + AppVersion = "0.4.1" ) func UserAgent() string { From 8fff19ceaca8b2298a934bbdaee17b91c6e9adfb Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Thu, 3 Apr 2025 14:56:39 +0200 Subject: [PATCH 09/52] Use MBID type from go.uploadedlobster.com/mbtypes --- go.mod | 5 +- go.sum | 151 +++--------------- internal/backends/funkwhale/funkwhale.go | 7 +- internal/backends/funkwhale/funkwhale_test.go | 16 +- internal/backends/lastfm/lastfm.go | 15 +- .../backends/listenbrainz/listenbrainz.go | 11 +- .../listenbrainz/listenbrainz_test.go | 20 +-- internal/backends/scrobblerlog/parser.go | 3 +- internal/backends/scrobblerlog/parser_test.go | 9 +- internal/models/models.go | 17 +- internal/models/models_test.go | 11 +- internal/similarity/similarity_test.go | 5 +- 12 files changed, 82 insertions(+), 188 deletions(-) diff --git a/go.mod b/go.mod index 5a67610..81d9deb 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module go.uploadedlobster.com/scotty -go 1.22.0 +go 1.23.0 -toolchain go1.22.2 +toolchain go1.23.8 require ( github.com/Xuanwo/go-locale v1.1.2 @@ -21,6 +21,7 @@ require ( github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 github.com/vbauerster/mpb/v8 v8.8.3 + go.uploadedlobster.com/mbtypes v0.2.0 golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 golang.org/x/oauth2 v0.23.0 golang.org/x/text v0.18.0 diff --git a/go.sum b/go.sum index 2fb0b5a..c2b9efe 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,6 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= -github.com/Xuanwo/go-locale v1.1.0 h1:51gUxhxl66oXAjI9uPGb2O0qwPECpriKQb2hl35mQkg= -github.com/Xuanwo/go-locale v1.1.0/go.mod h1:UKrHoZB3FPIk9wIG2/tVSobnHgNnceGSH3Y8DY5cASs= github.com/Xuanwo/go-locale v1.1.2 h1:6H+olvrQcyVOZ+GAC2rXu4armacTT4ZrFCA0mB24XVo= github.com/Xuanwo/go-locale v1.1.2/go.mod h1:1JBER4QV7Ji39GJ4AvVlfvqmTUqopzxQxdg2mXYOw94= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= @@ -23,22 +21,16 @@ github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/delucks/go-subsonic v0.0.0-20220915164742-2744002c4be5 h1:RuuxidatioSKGOiBzL1mTY4X22DQD8weEbS3iRLHnAg= -github.com/delucks/go-subsonic v0.0.0-20220915164742-2744002c4be5/go.mod h1:vnbEuj6Z20PLcHB4rrLQAOXGMjtULfMGhRVSFPcSdUo= github.com/delucks/go-subsonic v0.0.0-20240806025900-2a743ec36238 h1:uejyepOdHISrJTw7P84Y7yEC0FMyv1q3KNDRxWsviKw= github.com/delucks/go-subsonic v0.0.0-20240806025900-2a743ec36238/go.mod h1:vnbEuj6Z20PLcHB4rrLQAOXGMjtULfMGhRVSFPcSdUo= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -49,8 +41,6 @@ github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= -github.com/go-resty/resty/v2 v2.12.0 h1:rsVL8P90LFvkUYq/V5BTVe203WfRIU4gvcf+yfzJzGA= -github.com/go-resty/resty/v2 v2.12.0/go.mod h1:o0yGPrkS3lOe1+eFajk6kBW8ScXzwU3hD69/gt2yB/0= github.com/go-resty/resty/v2 v2.15.0 h1:clPQLZ2x9h4yGY81IzpMPnty+xoGyFaDg0XMkCsHf90= github.com/go-resty/resty/v2 v2.15.0/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= @@ -60,35 +50,30 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0kt github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= -github.com/jackc/pgx/v5 v5.3.0 h1:/NQi8KHMpKWHInxXesC8yD4DhkXPrVhmnwYkjp9AmBA= -github.com/jackc/pgx/v5 v5.3.0/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= +github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -102,13 +87,10 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= -github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= -github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM= github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE= @@ -117,11 +99,8 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= -github.com/pelletier/go-toml/v2 v2.2.1 h1:9TA9+T8+8CUCO2+WYnDLCgrYi9+omqKXyjDtosvtEhg= -github.com/pelletier/go-toml/v2 v2.2.1/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= @@ -132,152 +111,67 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= -github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/shkh/lastfm-go v0.0.0-20191215035245-89a801c244e0 h1:cgqwZtnR+IQfUYDLJ3Kiy4aE+O/wExTzEIg8xwC4Qfs= github.com/shkh/lastfm-go v0.0.0-20191215035245-89a801c244e0/go.mod h1:n3nudMl178cEvD44PaopxH9jhJaQzthSxUzLO5iKMy4= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.7 h1:I6tZjLXD2Q1kjvNbIzB1wvQBsXmKXiVrhpRE8ZjP5jY= -github.com/smartystreets/goconvey v1.6.7/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= -github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/vbauerster/mpb/v8 v8.7.3 h1:n/mKPBav4FFWp5fH4U0lPpXfiOmCEgl5Yx/NM3tKJA0= -github.com/vbauerster/mpb/v8 v8.7.3/go.mod h1:9nFlNpDGVoTmQ4QvNjSLtwLmAFjwmq0XaAF26toHGNM= github.com/vbauerster/mpb/v8 v8.8.3 h1:dTOByGoqwaTJYPubhVz3lO5O6MK553XVgUo33LdnNsQ= github.com/vbauerster/mpb/v8 v8.8.3/go.mod h1:JfCCrtcMsJwP6ZwMn9e5LMnNyp3TVNpUWWkN+nd4EWk= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -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.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +go.uploadedlobster.com/mbtypes v0.2.0 h1:sV9WM5fY8KnnKwKKVRaU3JBEKP4zD7aUX0XQfNZvEdo= +go.uploadedlobster.com/mbtypes v0.2.0/go.mod h1:/ZpwXc8oRpDa7EWeGI9xEY+nGhMIVHhTruikZWD4Krg= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc= -golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= -golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= -golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= -golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/datatypes v1.2.0 h1:5YT+eokWdIxhJgWHdrb2zYUimyk0+TaFth+7a0ybzco= -gorm.io/datatypes v1.2.0/go.mod h1:o1dh0ZvjIjhH/bngTpypG6lVRJ5chTBxE09FH/71k04= gorm.io/datatypes v1.2.2 h1:sdn7ZmG4l7JWtMDUb3L98f2Ym7CO5F8mZLlrQJMfF9g= gorm.io/datatypes v1.2.2/go.mod h1:f4BsLcFAX67szSv8svwLRjklArSHAvHLeE3pXAS5DZI= -gorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8= -gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U= @@ -287,23 +181,16 @@ gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2e gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0= gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig= gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= -gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8= -gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= -modernc.org/cc/v4 v4.20.0 h1:45Or8mQfbUqJOG9WaxvlFYOAQO0lQ5RvqBcFCXngjxk= -modernc.org/cc/v4 v4.20.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= -modernc.org/ccgo/v4 v4.16.0 h1:ofwORa6vx2FMm0916/CkZjpFPSR70VwTjUCe2Eg5BnA= -modernc.org/ccgo/v4 v4.16.0/go.mod h1:dkNyWIjFrVIZ68DTo36vHK+6/ShBn4ysU61So6PIqCI= +modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4= +modernc.org/ccgo/v4 v4.21.0/go.mod h1:h6kt6H/A2+ew/3MW/p6KEoQmrq/i3pr0J/SiwiaF/g0= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= -modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= -modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M= -modernc.org/libc v1.49.3 h1:j2MRCRdwJI2ls/sGbeSk0t2bypOG/uvPZUsGQFDulqg= -modernc.org/libc v1.49.3/go.mod h1:yMZuGkn7pXbKfoT/M35gFJOAEdSKdxL0q64sF7KqCDo= +modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= modernc.org/libc v1.60.1 h1:at373l8IFRTkJIkAU85BIuUoBM4T1b51ds0E1ovPG2s= modernc.org/libc v1.60.1/go.mod h1:xJuobKuNxKH3RUatS7GjR+suWj+5c2K7bi4m/S5arOY= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= @@ -314,8 +201,6 @@ modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= -modernc.org/sqlite v1.29.6 h1:0lOXGrycJPptfHDuohfYgNqoe4hu+gYuN/pKgY5XjS4= -modernc.org/sqlite v1.29.6/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U= modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM= modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= diff --git a/internal/backends/funkwhale/funkwhale.go b/internal/backends/funkwhale/funkwhale.go index 05017d3..39f8c59 100644 --- a/internal/backends/funkwhale/funkwhale.go +++ b/internal/backends/funkwhale/funkwhale.go @@ -20,6 +20,7 @@ import ( "sort" "time" + "go.uploadedlobster.com/mbtypes" "go.uploadedlobster.com/scotty/internal/config" "go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/models" @@ -188,7 +189,7 @@ func (f FavoriteTrack) AsLove() models.Love { } func (t Track) AsTrack() models.Track { - recordingMbid := models.MBID(t.RecordingMbid) + recordingMbid := mbtypes.MBID(t.RecordingMbid) track := models.Track{ TrackName: t.Title, ReleaseName: t.Album.Title, @@ -196,8 +197,8 @@ func (t Track) AsTrack() models.Track { TrackNumber: t.Position, DiscNumber: t.DiscNumber, RecordingMbid: recordingMbid, - ReleaseMbid: models.MBID(t.Album.ReleaseMbid), - ArtistMbids: []models.MBID{models.MBID(t.Artist.ArtistMbid)}, + ReleaseMbid: mbtypes.MBID(t.Album.ReleaseMbid), + ArtistMbids: []mbtypes.MBID{mbtypes.MBID(t.Artist.ArtistMbid)}, Tags: t.Tags, AdditionalInfo: map[string]any{ "media_player": FunkwhaleClientName, diff --git a/internal/backends/funkwhale/funkwhale_test.go b/internal/backends/funkwhale/funkwhale_test.go index 12b2b48..915a966 100644 --- a/internal/backends/funkwhale/funkwhale_test.go +++ b/internal/backends/funkwhale/funkwhale_test.go @@ -23,9 +23,9 @@ import ( "github.com/spf13/viper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.uploadedlobster.com/mbtypes" "go.uploadedlobster.com/scotty/internal/backends/funkwhale" "go.uploadedlobster.com/scotty/internal/config" - "go.uploadedlobster.com/scotty/internal/models" ) func TestFromConfig(t *testing.T) { @@ -75,9 +75,9 @@ func TestFunkwhaleListeningAsListen(t *testing.T) { assert.Equal(fwListen.Track.DiscNumber, listen.Track.DiscNumber) assert.Equal(fwListen.Track.Tags, listen.Track.Tags) // assert.Equal(backends.FunkwhaleClientName, listen.AdditionalInfo["disc_number"]) - assert.Equal(models.MBID(fwListen.Track.RecordingMbid), listen.RecordingMbid) - assert.Equal(models.MBID(fwListen.Track.Album.ReleaseMbid), listen.ReleaseMbid) - assert.Equal(models.MBID(fwListen.Track.Artist.ArtistMbid), listen.ArtistMbids[0]) + assert.Equal(mbtypes.MBID(fwListen.Track.RecordingMbid), listen.RecordingMbid) + assert.Equal(mbtypes.MBID(fwListen.Track.Album.ReleaseMbid), listen.ReleaseMbid) + assert.Equal(mbtypes.MBID(fwListen.Track.Artist.ArtistMbid), listen.ArtistMbids[0]) assert.Equal(funkwhale.FunkwhaleClientName, listen.AdditionalInfo["media_player"]) } @@ -119,10 +119,10 @@ func TestFunkwhaleFavoriteTrackAsLove(t *testing.T) { assert.Equal(favorite.Track.Position, love.Track.TrackNumber) assert.Equal(favorite.Track.DiscNumber, love.Track.DiscNumber) assert.Equal(favorite.Track.Tags, love.Track.Tags) - assert.Equal(models.MBID(favorite.Track.RecordingMbid), love.RecordingMbid) - assert.Equal(models.MBID(favorite.Track.RecordingMbid), love.Track.RecordingMbid) - assert.Equal(models.MBID(favorite.Track.Album.ReleaseMbid), love.ReleaseMbid) + assert.Equal(mbtypes.MBID(favorite.Track.RecordingMbid), love.RecordingMbid) + assert.Equal(mbtypes.MBID(favorite.Track.RecordingMbid), love.Track.RecordingMbid) + assert.Equal(mbtypes.MBID(favorite.Track.Album.ReleaseMbid), love.ReleaseMbid) require.Len(t, love.Track.ArtistMbids, 1) - assert.Equal(models.MBID(favorite.Track.Artist.ArtistMbid), love.ArtistMbids[0]) + assert.Equal(mbtypes.MBID(favorite.Track.Artist.ArtistMbid), love.ArtistMbids[0]) assert.Equal(funkwhale.FunkwhaleClientName, love.AdditionalInfo["media_player"]) } diff --git a/internal/backends/lastfm/lastfm.go b/internal/backends/lastfm/lastfm.go index d2df067..8063832 100644 --- a/internal/backends/lastfm/lastfm.go +++ b/internal/backends/lastfm/lastfm.go @@ -23,6 +23,7 @@ import ( "time" "github.com/shkh/lastfm-go/lastfm" + "go.uploadedlobster.com/mbtypes" "go.uploadedlobster.com/scotty/internal/auth" "go.uploadedlobster.com/scotty/internal/config" "go.uploadedlobster.com/scotty/internal/i18n" @@ -140,16 +141,16 @@ out: TrackName: scrobble.Name, ArtistNames: []string{}, ReleaseName: scrobble.Album.Name, - RecordingMbid: models.MBID(scrobble.Mbid), - ArtistMbids: []models.MBID{}, - ReleaseMbid: models.MBID(scrobble.Album.Mbid), + RecordingMbid: mbtypes.MBID(scrobble.Mbid), + ArtistMbids: []mbtypes.MBID{}, + ReleaseMbid: mbtypes.MBID(scrobble.Album.Mbid), }, } if scrobble.Artist.Name != "" { listen.Track.ArtistNames = []string{scrobble.Artist.Name} } if scrobble.Artist.Mbid != "" { - listen.Track.ArtistMbids = []models.MBID{models.MBID(scrobble.Artist.Mbid)} + listen.Track.ArtistMbids = []mbtypes.MBID{mbtypes.MBID(scrobble.Artist.Mbid)} } listens = append(listens, listen) } else { @@ -294,12 +295,12 @@ out: love := models.Love{ Created: time.Unix(timestamp, 0), UserName: result.User, - RecordingMbid: models.MBID(track.Mbid), + RecordingMbid: mbtypes.MBID(track.Mbid), Track: models.Track{ TrackName: track.Name, ArtistNames: []string{track.Artist.Name}, - RecordingMbid: models.MBID(track.Mbid), - ArtistMbids: []models.MBID{models.MBID(track.Artist.Mbid)}, + RecordingMbid: mbtypes.MBID(track.Mbid), + ArtistMbids: []mbtypes.MBID{mbtypes.MBID(track.Artist.Mbid)}, AdditionalInfo: models.AdditionalInfo{ "lastfm_url": track.Url, }, diff --git a/internal/backends/listenbrainz/listenbrainz.go b/internal/backends/listenbrainz/listenbrainz.go index ebeb64c..08ffdbc 100644 --- a/internal/backends/listenbrainz/listenbrainz.go +++ b/internal/backends/listenbrainz/listenbrainz.go @@ -21,6 +21,7 @@ import ( "sort" "time" + "go.uploadedlobster.com/mbtypes" "go.uploadedlobster.com/scotty/internal/config" "go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/models" @@ -323,7 +324,7 @@ func (lbListen Listen) AsListen() models.Listen { } func (f Feedback) AsLove() models.Love { - recordingMbid := models.MBID(f.RecordingMbid) + recordingMbid := mbtypes.MBID(f.RecordingMbid) track := f.TrackMetadata if track == nil { track = &Track{} @@ -350,16 +351,16 @@ func (t Track) AsTrack() models.Track { Duration: t.Duration(), TrackNumber: t.TrackNumber(), DiscNumber: t.DiscNumber(), - RecordingMbid: models.MBID(t.RecordingMbid()), - ReleaseMbid: models.MBID(t.ReleaseMbid()), - ReleaseGroupMbid: models.MBID(t.ReleaseGroupMbid()), + RecordingMbid: mbtypes.MBID(t.RecordingMbid()), + ReleaseMbid: mbtypes.MBID(t.ReleaseMbid()), + ReleaseGroupMbid: mbtypes.MBID(t.ReleaseGroupMbid()), ISRC: t.ISRC(), AdditionalInfo: t.AdditionalInfo, } if t.MbidMapping != nil && len(track.ArtistMbids) == 0 { for _, artistMbid := range t.MbidMapping.ArtistMbids { - track.ArtistMbids = append(track.ArtistMbids, models.MBID(artistMbid)) + track.ArtistMbids = append(track.ArtistMbids, mbtypes.MBID(artistMbid)) } } diff --git a/internal/backends/listenbrainz/listenbrainz_test.go b/internal/backends/listenbrainz/listenbrainz_test.go index f67280e..dc2acdc 100644 --- a/internal/backends/listenbrainz/listenbrainz_test.go +++ b/internal/backends/listenbrainz/listenbrainz_test.go @@ -23,9 +23,9 @@ import ( "github.com/spf13/viper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.uploadedlobster.com/mbtypes" "go.uploadedlobster.com/scotty/internal/backends/listenbrainz" "go.uploadedlobster.com/scotty/internal/config" - "go.uploadedlobster.com/scotty/internal/models" ) func TestFromConfig(t *testing.T) { @@ -65,9 +65,9 @@ func TestListenBrainzListenAsListen(t *testing.T) { assert.Equal(t, []string{lbListen.TrackMetadata.ArtistName}, listen.ArtistNames) assert.Equal(t, 5, listen.TrackNumber) assert.Equal(t, 1, listen.DiscNumber) - assert.Equal(t, models.MBID("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"), listen.RecordingMbid) - assert.Equal(t, models.MBID("d7f22677-9803-4d21-ba42-081b633a6f68"), listen.ReleaseMbid) - assert.Equal(t, models.MBID("80aca1ee-aa51-41be-9f75-024710d92ff4"), listen.ReleaseGroupMbid) + assert.Equal(t, mbtypes.MBID("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"), listen.RecordingMbid) + assert.Equal(t, mbtypes.MBID("d7f22677-9803-4d21-ba42-081b633a6f68"), listen.ReleaseMbid) + assert.Equal(t, mbtypes.MBID("80aca1ee-aa51-41be-9f75-024710d92ff4"), listen.ReleaseGroupMbid) assert.Equal(t, "DES561620801", listen.ISRC) assert.Equal(t, lbListen.TrackMetadata.AdditionalInfo["foo"], listen.AdditionalInfo["foo"]) } @@ -99,11 +99,11 @@ func TestListenBrainzFeedbackAsLove(t *testing.T) { assert.Equal(feedback.TrackMetadata.TrackName, love.TrackName) assert.Equal(feedback.TrackMetadata.ReleaseName, love.ReleaseName) assert.Equal([]string{feedback.TrackMetadata.ArtistName}, love.ArtistNames) - assert.Equal(models.MBID(recordingMbid), love.RecordingMbid) - assert.Equal(models.MBID(recordingMbid), love.Track.RecordingMbid) - assert.Equal(models.MBID(releaseMbid), love.Track.ReleaseMbid) + assert.Equal(mbtypes.MBID(recordingMbid), love.RecordingMbid) + assert.Equal(mbtypes.MBID(recordingMbid), love.Track.RecordingMbid) + assert.Equal(mbtypes.MBID(releaseMbid), love.Track.ReleaseMbid) require.Len(t, love.Track.ArtistMbids, 1) - assert.Equal(models.MBID(artistMbid), love.Track.ArtistMbids[0]) + assert.Equal(mbtypes.MBID(artistMbid), love.Track.ArtistMbids[0]) } func TestListenBrainzPartialFeedbackAsLove(t *testing.T) { @@ -116,7 +116,7 @@ func TestListenBrainzPartialFeedbackAsLove(t *testing.T) { love := feedback.AsLove() assert := assert.New(t) assert.Equal(time.Unix(1699859066, 0).Unix(), love.Created.Unix()) - assert.Equal(models.MBID(recordingMbid), love.RecordingMbid) - assert.Equal(models.MBID(recordingMbid), love.Track.RecordingMbid) + assert.Equal(mbtypes.MBID(recordingMbid), love.RecordingMbid) + assert.Equal(mbtypes.MBID(recordingMbid), love.Track.RecordingMbid) assert.Empty(love.Track.TrackName) } diff --git a/internal/backends/scrobblerlog/parser.go b/internal/backends/scrobblerlog/parser.go index af891ac..ce2d897 100644 --- a/internal/backends/scrobblerlog/parser.go +++ b/internal/backends/scrobblerlog/parser.go @@ -30,6 +30,7 @@ import ( "strings" "time" + "go.uploadedlobster.com/mbtypes" "go.uploadedlobster.com/scotty/internal/models" ) @@ -203,7 +204,7 @@ func rowToListen(row []string, client string) (models.Listen, error) { } if len(row) > 7 { - listen.Track.RecordingMbid = models.MBID(row[7]) + listen.Track.RecordingMbid = mbtypes.MBID(row[7]) } return listen, nil diff --git a/internal/backends/scrobblerlog/parser_test.go b/internal/backends/scrobblerlog/parser_test.go index 51d15c7..583ec49 100644 --- a/internal/backends/scrobblerlog/parser_test.go +++ b/internal/backends/scrobblerlog/parser_test.go @@ -30,6 +30,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.uploadedlobster.com/mbtypes" "go.uploadedlobster.com/scotty/internal/backends/scrobblerlog" "go.uploadedlobster.com/scotty/internal/models" ) @@ -60,10 +61,10 @@ func TestParser(t *testing.T) { assert.Equal(time.Duration(306*time.Second), listen1.Duration) assert.Equal("L", listen1.AdditionalInfo["rockbox_rating"]) assert.Equal(time.Unix(1260342084, 0), listen1.ListenedAt) - assert.Equal(models.MBID(""), listen1.RecordingMbid) + assert.Equal(mbtypes.MBID(""), listen1.RecordingMbid) listen4 := result.Listens[3] assert.Equal("S", listen4.AdditionalInfo["rockbox_rating"]) - assert.Equal(models.MBID("385ba9e9-626d-4750-a607-58e541dca78e"), listen4.RecordingMbid) + assert.Equal(mbtypes.MBID("385ba9e9-626d-4750-a607-58e541dca78e"), listen4.RecordingMbid) } func TestParserExcludeSkipped(t *testing.T) { @@ -74,7 +75,7 @@ func TestParserExcludeSkipped(t *testing.T) { assert.Len(result.Listens, 4) listen4 := result.Listens[3] assert.Equal("L", listen4.AdditionalInfo["rockbox_rating"]) - assert.Equal(models.MBID("1262beaf-19f8-4534-b9ed-7eef9ca8e83f"), listen4.RecordingMbid) + assert.Equal(mbtypes.MBID("1262beaf-19f8-4534-b9ed-7eef9ca8e83f"), listen4.RecordingMbid) } func TestWrite(t *testing.T) { @@ -93,7 +94,7 @@ func TestWrite(t *testing.T) { TrackName: "Reign", TrackNumber: 1, Duration: 271 * time.Second, - RecordingMbid: models.MBID("b59cf4e7-caee-4019-a844-79d2c58d4dff"), + RecordingMbid: mbtypes.MBID("b59cf4e7-caee-4019-a844-79d2c58d4dff"), AdditionalInfo: models.AdditionalInfo{"rockbox_rating": "L"}, }, }, diff --git a/internal/models/models.go b/internal/models/models.go index a225344..a8a6570 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -24,9 +24,10 @@ package models import ( "strings" "time" + + "go.uploadedlobster.com/mbtypes" ) -type MBID string type Entity string const ( @@ -44,11 +45,11 @@ type Track struct { DiscNumber int Duration time.Duration ISRC string - RecordingMbid MBID - ReleaseMbid MBID - ReleaseGroupMbid MBID - ArtistMbids []MBID - WorkMbids []MBID + RecordingMbid mbtypes.MBID + ReleaseMbid mbtypes.MBID + ReleaseGroupMbid mbtypes.MBID + ArtistMbids []mbtypes.MBID + WorkMbids []mbtypes.MBID Tags []string AdditionalInfo AdditionalInfo } @@ -110,8 +111,8 @@ type Love struct { Track Created time.Time UserName string - RecordingMbid MBID - RecordingMsid MBID + RecordingMbid mbtypes.MBID + RecordingMsid mbtypes.MBID } type ListensList []Listen diff --git a/internal/models/models_test.go b/internal/models/models_test.go index cd1f207..5e010bc 100644 --- a/internal/models/models_test.go +++ b/internal/models/models_test.go @@ -28,6 +28,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.uploadedlobster.com/mbtypes" "go.uploadedlobster.com/scotty/internal/models" ) @@ -44,11 +45,11 @@ func TestTrackArtistName(t *testing.T) { func TestTrackFillAdditionalInfo(t *testing.T) { track := models.Track{ - RecordingMbid: models.MBID("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"), - ReleaseGroupMbid: models.MBID("80aca1ee-aa51-41be-9f75-024710d92ff4"), - ReleaseMbid: models.MBID("aa1ea1ac-7ec4-4542-a494-105afbfe547d"), - ArtistMbids: []models.MBID{"24412926-c7bd-48e8-afad-8a285b42e131"}, - WorkMbids: []models.MBID{"c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"}, + RecordingMbid: mbtypes.MBID("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"), + ReleaseGroupMbid: mbtypes.MBID("80aca1ee-aa51-41be-9f75-024710d92ff4"), + ReleaseMbid: mbtypes.MBID("aa1ea1ac-7ec4-4542-a494-105afbfe547d"), + ArtistMbids: []mbtypes.MBID{"24412926-c7bd-48e8-afad-8a285b42e131"}, + WorkMbids: []mbtypes.MBID{"c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"}, TrackNumber: 5, DiscNumber: 1, Duration: time.Duration(413787 * time.Millisecond), diff --git a/internal/similarity/similarity_test.go b/internal/similarity/similarity_test.go index f1e92a5..89fa645 100644 --- a/internal/similarity/similarity_test.go +++ b/internal/similarity/similarity_test.go @@ -20,6 +20,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "go.uploadedlobster.com/mbtypes" "go.uploadedlobster.com/scotty/internal/models" "go.uploadedlobster.com/scotty/internal/similarity" ) @@ -74,13 +75,13 @@ func TestCompareTracksSameMBID(t *testing.T) { t1 := models.Track{ ArtistNames: []string{"Paradise Lost"}, TrackName: "Forever After", - RecordingMbid: models.MBID("2886d15c-09b0-43c6-af56-932f70dde164"), + RecordingMbid: mbtypes.MBID("2886d15c-09b0-43c6-af56-932f70dde164"), } t2 := models.Track{ ArtistNames: []string{"Paradise Lost"}, TrackName: "Forever Failure (radio edit)", ReleaseName: "Draconian Times", - RecordingMbid: models.MBID("2886d15c-09b0-43c6-af56-932f70dde164"), + RecordingMbid: mbtypes.MBID("2886d15c-09b0-43c6-af56-932f70dde164"), } assert.Equal(t, 1.0, similarity.CompareTracks(t1, t2)) } From ad1644672c1d2669e6a7d617e7c3cd45d432af28 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Thu, 3 Apr 2025 15:00:45 +0200 Subject: [PATCH 10/52] Write acronym MBID all uppercase --- internal/backends/dump/dump.go | 4 +- internal/backends/funkwhale/funkwhale.go | 10 ++--- internal/backends/funkwhale/funkwhale_test.go | 28 ++++++------- internal/backends/funkwhale/models.go | 6 +-- internal/backends/jspf/jspf.go | 22 +++++----- internal/backends/lastfm/lastfm.go | 18 ++++---- internal/backends/listenbrainz/client_test.go | 6 +-- .../backends/listenbrainz/listenbrainz.go | 42 +++++++++---------- .../listenbrainz/listenbrainz_test.go | 40 +++++++++--------- internal/backends/listenbrainz/models.go | 34 +++++++-------- internal/backends/listenbrainz/models_test.go | 14 +++---- internal/backends/scrobblerlog/parser.go | 8 ++-- internal/backends/scrobblerlog/parser_test.go | 8 ++-- internal/models/models.go | 32 +++++++------- internal/models/models_test.go | 20 ++++----- internal/similarity/similarity.go | 2 +- internal/similarity/similarity_test.go | 4 +- 17 files changed, 149 insertions(+), 149 deletions(-) diff --git a/internal/backends/dump/dump.go b/internal/backends/dump/dump.go index eb342f2..728a774 100644 --- a/internal/backends/dump/dump.go +++ b/internal/backends/dump/dump.go @@ -41,7 +41,7 @@ func (b *DumpBackend) ImportListens(export models.ListensResult, importResult mo importResult.UpdateTimestamp(listen.ListenedAt) importResult.ImportCount += 1 msg := fmt.Sprintf("🎶 %v: \"%v\" by %v (%v)", - listen.ListenedAt, listen.TrackName, listen.ArtistName(), listen.RecordingMbid) + listen.ListenedAt, listen.TrackName, listen.ArtistName(), listen.RecordingMBID) importResult.Log(models.Info, msg) progress <- models.Progress{}.FromImportResult(importResult) } @@ -54,7 +54,7 @@ func (b *DumpBackend) ImportLoves(export models.LovesResult, importResult models importResult.UpdateTimestamp(love.Created) importResult.ImportCount += 1 msg := fmt.Sprintf("❤️ %v: \"%v\" by %v (%v)", - love.Created, love.TrackName, love.ArtistName(), love.RecordingMbid) + love.Created, love.TrackName, love.ArtistName(), love.RecordingMBID) importResult.Log(models.Info, msg) progress <- models.Progress{}.FromImportResult(importResult) } diff --git a/internal/backends/funkwhale/funkwhale.go b/internal/backends/funkwhale/funkwhale.go index 39f8c59..88bb72c 100644 --- a/internal/backends/funkwhale/funkwhale.go +++ b/internal/backends/funkwhale/funkwhale.go @@ -176,7 +176,7 @@ func (f FavoriteTrack) AsLove() models.Love { track := f.Track.AsTrack() love := models.Love{ UserName: f.User.UserName, - RecordingMbid: track.RecordingMbid, + RecordingMBID: track.RecordingMBID, Track: track, } @@ -189,16 +189,16 @@ func (f FavoriteTrack) AsLove() models.Love { } func (t Track) AsTrack() models.Track { - recordingMbid := mbtypes.MBID(t.RecordingMbid) + recordingMBID := mbtypes.MBID(t.RecordingMBID) track := models.Track{ TrackName: t.Title, ReleaseName: t.Album.Title, ArtistNames: []string{t.Artist.Name}, TrackNumber: t.Position, DiscNumber: t.DiscNumber, - RecordingMbid: recordingMbid, - ReleaseMbid: mbtypes.MBID(t.Album.ReleaseMbid), - ArtistMbids: []mbtypes.MBID{mbtypes.MBID(t.Artist.ArtistMbid)}, + RecordingMBID: recordingMBID, + ReleaseMBID: mbtypes.MBID(t.Album.ReleaseMBID), + ArtistMBIDs: []mbtypes.MBID{mbtypes.MBID(t.Artist.ArtistMBID)}, Tags: t.Tags, AdditionalInfo: map[string]any{ "media_player": FunkwhaleClientName, diff --git a/internal/backends/funkwhale/funkwhale_test.go b/internal/backends/funkwhale/funkwhale_test.go index 915a966..6961c8e 100644 --- a/internal/backends/funkwhale/funkwhale_test.go +++ b/internal/backends/funkwhale/funkwhale_test.go @@ -44,17 +44,17 @@ func TestFunkwhaleListeningAsListen(t *testing.T) { }, Track: funkwhale.Track{ Title: "Oweynagat", - RecordingMbid: "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12", + RecordingMBID: "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12", Position: 5, DiscNumber: 1, Tags: []string{"foo", "bar"}, Artist: funkwhale.Artist{ Name: "Dool", - ArtistMbid: "24412926-c7bd-48e8-afad-8a285b42e131", + ArtistMBID: "24412926-c7bd-48e8-afad-8a285b42e131", }, Album: funkwhale.Album{ Title: "Here Now, There Then", - ReleaseMbid: "d7f22677-9803-4d21-ba42-081b633a6f68", + ReleaseMBID: "d7f22677-9803-4d21-ba42-081b633a6f68", }, Uploads: []funkwhale.Upload{ { @@ -75,9 +75,9 @@ func TestFunkwhaleListeningAsListen(t *testing.T) { assert.Equal(fwListen.Track.DiscNumber, listen.Track.DiscNumber) assert.Equal(fwListen.Track.Tags, listen.Track.Tags) // assert.Equal(backends.FunkwhaleClientName, listen.AdditionalInfo["disc_number"]) - assert.Equal(mbtypes.MBID(fwListen.Track.RecordingMbid), listen.RecordingMbid) - assert.Equal(mbtypes.MBID(fwListen.Track.Album.ReleaseMbid), listen.ReleaseMbid) - assert.Equal(mbtypes.MBID(fwListen.Track.Artist.ArtistMbid), listen.ArtistMbids[0]) + assert.Equal(mbtypes.MBID(fwListen.Track.RecordingMBID), listen.RecordingMBID) + assert.Equal(mbtypes.MBID(fwListen.Track.Album.ReleaseMBID), listen.ReleaseMBID) + assert.Equal(mbtypes.MBID(fwListen.Track.Artist.ArtistMBID), listen.ArtistMBIDs[0]) assert.Equal(funkwhale.FunkwhaleClientName, listen.AdditionalInfo["media_player"]) } @@ -89,17 +89,17 @@ func TestFunkwhaleFavoriteTrackAsLove(t *testing.T) { }, Track: funkwhale.Track{ Title: "Oweynagat", - RecordingMbid: "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12", + RecordingMBID: "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12", Position: 5, DiscNumber: 1, Tags: []string{"foo", "bar"}, Artist: funkwhale.Artist{ Name: "Dool", - ArtistMbid: "24412926-c7bd-48e8-afad-8a285b42e131", + ArtistMBID: "24412926-c7bd-48e8-afad-8a285b42e131", }, Album: funkwhale.Album{ Title: "Here Now, There Then", - ReleaseMbid: "d7f22677-9803-4d21-ba42-081b633a6f68", + ReleaseMBID: "d7f22677-9803-4d21-ba42-081b633a6f68", }, Uploads: []funkwhale.Upload{ { @@ -119,10 +119,10 @@ func TestFunkwhaleFavoriteTrackAsLove(t *testing.T) { assert.Equal(favorite.Track.Position, love.Track.TrackNumber) assert.Equal(favorite.Track.DiscNumber, love.Track.DiscNumber) assert.Equal(favorite.Track.Tags, love.Track.Tags) - assert.Equal(mbtypes.MBID(favorite.Track.RecordingMbid), love.RecordingMbid) - assert.Equal(mbtypes.MBID(favorite.Track.RecordingMbid), love.Track.RecordingMbid) - assert.Equal(mbtypes.MBID(favorite.Track.Album.ReleaseMbid), love.ReleaseMbid) - require.Len(t, love.Track.ArtistMbids, 1) - assert.Equal(mbtypes.MBID(favorite.Track.Artist.ArtistMbid), love.ArtistMbids[0]) + assert.Equal(mbtypes.MBID(favorite.Track.RecordingMBID), love.RecordingMBID) + assert.Equal(mbtypes.MBID(favorite.Track.RecordingMBID), love.Track.RecordingMBID) + assert.Equal(mbtypes.MBID(favorite.Track.Album.ReleaseMBID), love.ReleaseMBID) + require.Len(t, love.Track.ArtistMBIDs, 1) + assert.Equal(mbtypes.MBID(favorite.Track.Artist.ArtistMBID), love.ArtistMBIDs[0]) assert.Equal(funkwhale.FunkwhaleClientName, love.AdditionalInfo["media_player"]) } diff --git a/internal/backends/funkwhale/models.go b/internal/backends/funkwhale/models.go index 6e0349e..86b66bc 100644 --- a/internal/backends/funkwhale/models.go +++ b/internal/backends/funkwhale/models.go @@ -56,7 +56,7 @@ type Track struct { Title string `json:"title"` Position int `json:"position"` DiscNumber int `json:"disc_number"` - RecordingMbid string `json:"mbid"` + RecordingMBID string `json:"mbid"` Tags []string `json:"tags"` Uploads []Upload `json:"uploads"` } @@ -64,7 +64,7 @@ type Track struct { type Artist struct { Id int `json:"int"` Name string `json:"name"` - ArtistMbid string `json:"mbid"` + ArtistMBID string `json:"mbid"` } type Album struct { @@ -73,7 +73,7 @@ type Album struct { AlbumArtist Artist `json:"artist"` ReleaseDate string `json:"release_date"` TrackCount int `json:"track_count"` - ReleaseMbid string `json:"mbid"` + ReleaseMBID string `json:"mbid"` } type User struct { diff --git a/internal/backends/jspf/jspf.go b/internal/backends/jspf/jspf.go index 17046e7..bfa3892 100644 --- a/internal/backends/jspf/jspf.go +++ b/internal/backends/jspf/jspf.go @@ -118,8 +118,8 @@ func listenAsTrack(l models.Listen) jspf.Track { extension.AddedBy = l.UserName track.Extension[jspf.MusicBrainzTrackExtensionId] = extension - if l.RecordingMbid != "" { - track.Identifier = append(track.Identifier, "https://musicbrainz.org/recording/"+string(l.RecordingMbid)) + if l.RecordingMBID != "" { + track.Identifier = append(track.Identifier, "https://musicbrainz.org/recording/"+string(l.RecordingMBID)) } return track @@ -133,12 +133,12 @@ func loveAsTrack(l models.Love) jspf.Track { extension.AddedBy = l.UserName track.Extension[jspf.MusicBrainzTrackExtensionId] = extension - recordingMbid := l.Track.RecordingMbid - if l.RecordingMbid != "" { - recordingMbid = l.RecordingMbid + recordingMBID := l.Track.RecordingMBID + if l.RecordingMBID != "" { + recordingMBID = l.RecordingMBID } - if recordingMbid != "" { - track.Identifier = append(track.Identifier, "https://musicbrainz.org/recording/"+string(recordingMbid)) + if recordingMBID != "" { + track.Identifier = append(track.Identifier, "https://musicbrainz.org/recording/"+string(recordingMBID)) } return track @@ -159,15 +159,15 @@ func trackAsTrack(t models.Track) jspf.Track { func makeMusicBrainzExtension(t models.Track) jspf.MusicBrainzTrackExtension { extension := jspf.MusicBrainzTrackExtension{ AdditionalMetadata: t.AdditionalInfo, - ArtistIdentifiers: make([]string, len(t.ArtistMbids)), + ArtistIdentifiers: make([]string, len(t.ArtistMBIDs)), } - for i, mbid := range t.ArtistMbids { + for i, mbid := range t.ArtistMBIDs { extension.ArtistIdentifiers[i] = "https://musicbrainz.org/artist/" + string(mbid) } - if t.ReleaseMbid != "" { - extension.ReleaseIdentifier = "https://musicbrainz.org/release/" + string(t.ReleaseMbid) + if t.ReleaseMBID != "" { + extension.ReleaseIdentifier = "https://musicbrainz.org/release/" + string(t.ReleaseMBID) } // The tracknumber tag would be redundant diff --git a/internal/backends/lastfm/lastfm.go b/internal/backends/lastfm/lastfm.go index 8063832..ba660de 100644 --- a/internal/backends/lastfm/lastfm.go +++ b/internal/backends/lastfm/lastfm.go @@ -141,16 +141,16 @@ out: TrackName: scrobble.Name, ArtistNames: []string{}, ReleaseName: scrobble.Album.Name, - RecordingMbid: mbtypes.MBID(scrobble.Mbid), - ArtistMbids: []mbtypes.MBID{}, - ReleaseMbid: mbtypes.MBID(scrobble.Album.Mbid), + RecordingMBID: mbtypes.MBID(scrobble.Mbid), + ArtistMBIDs: []mbtypes.MBID{}, + ReleaseMBID: mbtypes.MBID(scrobble.Album.Mbid), }, } if scrobble.Artist.Name != "" { listen.Track.ArtistNames = []string{scrobble.Artist.Name} } if scrobble.Artist.Mbid != "" { - listen.Track.ArtistMbids = []mbtypes.MBID{mbtypes.MBID(scrobble.Artist.Mbid)} + listen.Track.ArtistMBIDs = []mbtypes.MBID{mbtypes.MBID(scrobble.Artist.Mbid)} } listens = append(listens, listen) } else { @@ -204,8 +204,8 @@ func (b *LastfmApiBackend) ImportListens(export models.ListensResult, importResu if l.TrackNumber > 0 { trackNumbers = append(trackNumbers, strconv.Itoa(l.TrackNumber)) } - if l.RecordingMbid != "" { - mbids = append(mbids, string(l.RecordingMbid)) + if l.RecordingMBID != "" { + mbids = append(mbids, string(l.RecordingMBID)) } // if l.ReleaseArtist != "" { // albumArtists = append(albums, l.ReleaseArtist) @@ -295,12 +295,12 @@ out: love := models.Love{ Created: time.Unix(timestamp, 0), UserName: result.User, - RecordingMbid: mbtypes.MBID(track.Mbid), + RecordingMBID: mbtypes.MBID(track.Mbid), Track: models.Track{ TrackName: track.Name, ArtistNames: []string{track.Artist.Name}, - RecordingMbid: mbtypes.MBID(track.Mbid), - ArtistMbids: []mbtypes.MBID{mbtypes.MBID(track.Artist.Mbid)}, + RecordingMBID: mbtypes.MBID(track.Mbid), + ArtistMBIDs: []mbtypes.MBID{mbtypes.MBID(track.Artist.Mbid)}, AdditionalInfo: models.AdditionalInfo{ "lastfm_url": track.Url, }, diff --git a/internal/backends/listenbrainz/client_test.go b/internal/backends/listenbrainz/client_test.go index cc36f1d..0fbbdfd 100644 --- a/internal/backends/listenbrainz/client_test.go +++ b/internal/backends/listenbrainz/client_test.go @@ -114,7 +114,7 @@ func TestGetFeedback(t *testing.T) { assert.Equal(302, result.TotalCount) assert.Equal(3, result.Offset) require.Len(t, result.Feedback, 2) - assert.Equal("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12", result.Feedback[0].RecordingMbid) + assert.Equal("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12", result.Feedback[0].RecordingMBID) } func TestSendFeedback(t *testing.T) { @@ -131,7 +131,7 @@ func TestSendFeedback(t *testing.T) { httpmock.RegisterResponder("POST", url, responder) feedback := listenbrainz.Feedback{ - RecordingMbid: "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12", + RecordingMBID: "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12", Score: 1, } result, err := client.SendFeedback(feedback) @@ -154,7 +154,7 @@ func TestLookup(t *testing.T) { assert := assert.New(t) assert.Equal("Say Just Words", result.RecordingName) assert.Equal("Paradise Lost", result.ArtistCreditName) - assert.Equal("569436a1-234a-44bc-a370-8f4d252bef21", result.RecordingMbid) + assert.Equal("569436a1-234a-44bc-a370-8f4d252bef21", result.RecordingMBID) } func setupHttpMock(t *testing.T, client *http.Client, url string, testDataPath string) { diff --git a/internal/backends/listenbrainz/listenbrainz.go b/internal/backends/listenbrainz/listenbrainz.go index 08ffdbc..f18a29d 100644 --- a/internal/backends/listenbrainz/listenbrainz.go +++ b/internal/backends/listenbrainz/listenbrainz.go @@ -33,7 +33,7 @@ type ListenBrainzApiBackend struct { client Client username string checkDuplicates bool - existingMbids map[string]bool + existingMBIDs map[string]bool } func (b *ListenBrainzApiBackend) Name() string { return "listenbrainz" } @@ -148,7 +148,7 @@ func (b *ListenBrainzApiBackend) ImportListens(export models.ListensResult, impo } else if isDupe { count -= 1 msg := i18n.Tr("Ignored duplicate listen %v: \"%v\" by %v (%v)", - l.ListenedAt, l.TrackName, l.ArtistName(), l.RecordingMbid) + l.ListenedAt, l.TrackName, l.ArtistName(), l.RecordingMBID) importResult.Log(models.Info, msg) continue } @@ -230,7 +230,7 @@ out: } func (b *ListenBrainzApiBackend) ImportLoves(export models.LovesResult, importResult models.ImportResult, progress chan models.Progress) (models.ImportResult, error) { - if len(b.existingMbids) == 0 { + if len(b.existingMBIDs) == 0 { existingLovesChan := make(chan models.LovesResult) go b.ExportLoves(time.Unix(0, 0), existingLovesChan, progress) existingLoves := <-existingLovesChan @@ -239,30 +239,30 @@ func (b *ListenBrainzApiBackend) ImportLoves(export models.LovesResult, importRe } // TODO: Store MBIDs directly - b.existingMbids = make(map[string]bool, len(existingLoves.Items)) + b.existingMBIDs = make(map[string]bool, len(existingLoves.Items)) for _, love := range existingLoves.Items { - b.existingMbids[string(love.RecordingMbid)] = true + b.existingMBIDs[string(love.RecordingMBID)] = true } } for _, love := range export.Items { - recordingMbid := string(love.RecordingMbid) + recordingMBID := string(love.RecordingMBID) - if recordingMbid == "" { + if recordingMBID == "" { lookup, err := b.client.Lookup(love.TrackName, love.ArtistName()) if err == nil { - recordingMbid = lookup.RecordingMbid + recordingMBID = lookup.RecordingMBID } } - if recordingMbid != "" { + if recordingMBID != "" { ok := false errMsg := "" - if b.existingMbids[recordingMbid] { + if b.existingMBIDs[recordingMBID] { ok = true } else { resp, err := b.client.SendFeedback(Feedback{ - RecordingMbid: recordingMbid, + RecordingMBID: recordingMBID, Score: 1, }) ok = err == nil && resp.Status == "ok" @@ -324,20 +324,20 @@ func (lbListen Listen) AsListen() models.Listen { } func (f Feedback) AsLove() models.Love { - recordingMbid := mbtypes.MBID(f.RecordingMbid) + recordingMBID := mbtypes.MBID(f.RecordingMBID) track := f.TrackMetadata if track == nil { track = &Track{} } love := models.Love{ UserName: f.UserName, - RecordingMbid: recordingMbid, + RecordingMBID: recordingMBID, Created: time.Unix(f.Created, 0), Track: track.AsTrack(), } - if love.Track.RecordingMbid == "" { - love.Track.RecordingMbid = love.RecordingMbid + if love.Track.RecordingMBID == "" { + love.Track.RecordingMBID = love.RecordingMBID } return love @@ -351,16 +351,16 @@ func (t Track) AsTrack() models.Track { Duration: t.Duration(), TrackNumber: t.TrackNumber(), DiscNumber: t.DiscNumber(), - RecordingMbid: mbtypes.MBID(t.RecordingMbid()), - ReleaseMbid: mbtypes.MBID(t.ReleaseMbid()), - ReleaseGroupMbid: mbtypes.MBID(t.ReleaseGroupMbid()), + RecordingMBID: mbtypes.MBID(t.RecordingMBID()), + ReleaseMBID: mbtypes.MBID(t.ReleaseMBID()), + ReleaseGroupMBID: mbtypes.MBID(t.ReleaseGroupMBID()), ISRC: t.ISRC(), AdditionalInfo: t.AdditionalInfo, } - if t.MbidMapping != nil && len(track.ArtistMbids) == 0 { - for _, artistMbid := range t.MbidMapping.ArtistMbids { - track.ArtistMbids = append(track.ArtistMbids, mbtypes.MBID(artistMbid)) + if t.MBIDMapping != nil && len(track.ArtistMBIDs) == 0 { + for _, artistMBID := range t.MBIDMapping.ArtistMBIDs { + track.ArtistMBIDs = append(track.ArtistMBIDs, mbtypes.MBID(artistMBID)) } } diff --git a/internal/backends/listenbrainz/listenbrainz_test.go b/internal/backends/listenbrainz/listenbrainz_test.go index dc2acdc..d6d577f 100644 --- a/internal/backends/listenbrainz/listenbrainz_test.go +++ b/internal/backends/listenbrainz/listenbrainz_test.go @@ -65,30 +65,30 @@ func TestListenBrainzListenAsListen(t *testing.T) { assert.Equal(t, []string{lbListen.TrackMetadata.ArtistName}, listen.ArtistNames) assert.Equal(t, 5, listen.TrackNumber) assert.Equal(t, 1, listen.DiscNumber) - assert.Equal(t, mbtypes.MBID("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"), listen.RecordingMbid) - assert.Equal(t, mbtypes.MBID("d7f22677-9803-4d21-ba42-081b633a6f68"), listen.ReleaseMbid) - assert.Equal(t, mbtypes.MBID("80aca1ee-aa51-41be-9f75-024710d92ff4"), listen.ReleaseGroupMbid) + assert.Equal(t, mbtypes.MBID("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"), listen.RecordingMBID) + assert.Equal(t, mbtypes.MBID("d7f22677-9803-4d21-ba42-081b633a6f68"), listen.ReleaseMBID) + assert.Equal(t, mbtypes.MBID("80aca1ee-aa51-41be-9f75-024710d92ff4"), listen.ReleaseGroupMBID) assert.Equal(t, "DES561620801", listen.ISRC) assert.Equal(t, lbListen.TrackMetadata.AdditionalInfo["foo"], listen.AdditionalInfo["foo"]) } func TestListenBrainzFeedbackAsLove(t *testing.T) { - recordingMbid := "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12" - releaseMbid := "d7f22677-9803-4d21-ba42-081b633a6f68" - artistMbid := "d7f22677-9803-4d21-ba42-081b633a6f68" + recordingMBID := "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12" + releaseMBID := "d7f22677-9803-4d21-ba42-081b633a6f68" + artistMBID := "d7f22677-9803-4d21-ba42-081b633a6f68" feedback := listenbrainz.Feedback{ Created: 1699859066, - RecordingMbid: recordingMbid, + RecordingMBID: recordingMBID, Score: 1, UserName: "ousidecontext", TrackMetadata: &listenbrainz.Track{ TrackName: "Oweynagat", ArtistName: "Dool", ReleaseName: "Here Now, There Then", - MbidMapping: &listenbrainz.MbidMapping{ - RecordingMbid: recordingMbid, - ReleaseMbid: releaseMbid, - ArtistMbids: []string{artistMbid}, + MBIDMapping: &listenbrainz.MBIDMapping{ + RecordingMBID: recordingMBID, + ReleaseMBID: releaseMBID, + ArtistMBIDs: []string{artistMBID}, }, }, } @@ -99,24 +99,24 @@ func TestListenBrainzFeedbackAsLove(t *testing.T) { assert.Equal(feedback.TrackMetadata.TrackName, love.TrackName) assert.Equal(feedback.TrackMetadata.ReleaseName, love.ReleaseName) assert.Equal([]string{feedback.TrackMetadata.ArtistName}, love.ArtistNames) - assert.Equal(mbtypes.MBID(recordingMbid), love.RecordingMbid) - assert.Equal(mbtypes.MBID(recordingMbid), love.Track.RecordingMbid) - assert.Equal(mbtypes.MBID(releaseMbid), love.Track.ReleaseMbid) - require.Len(t, love.Track.ArtistMbids, 1) - assert.Equal(mbtypes.MBID(artistMbid), love.Track.ArtistMbids[0]) + assert.Equal(mbtypes.MBID(recordingMBID), love.RecordingMBID) + assert.Equal(mbtypes.MBID(recordingMBID), love.Track.RecordingMBID) + assert.Equal(mbtypes.MBID(releaseMBID), love.Track.ReleaseMBID) + require.Len(t, love.Track.ArtistMBIDs, 1) + assert.Equal(mbtypes.MBID(artistMBID), love.Track.ArtistMBIDs[0]) } func TestListenBrainzPartialFeedbackAsLove(t *testing.T) { - recordingMbid := "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12" + recordingMBID := "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12" feedback := listenbrainz.Feedback{ Created: 1699859066, - RecordingMbid: recordingMbid, + RecordingMBID: recordingMBID, Score: 1, } love := feedback.AsLove() assert := assert.New(t) assert.Equal(time.Unix(1699859066, 0).Unix(), love.Created.Unix()) - assert.Equal(mbtypes.MBID(recordingMbid), love.RecordingMbid) - assert.Equal(mbtypes.MBID(recordingMbid), love.Track.RecordingMbid) + assert.Equal(mbtypes.MBID(recordingMBID), love.RecordingMBID) + assert.Equal(mbtypes.MBID(recordingMBID), love.Track.RecordingMBID) assert.Empty(love.Track.TrackName) } diff --git a/internal/backends/listenbrainz/models.go b/internal/backends/listenbrainz/models.go index c1552c7..e86ce9d 100644 --- a/internal/backends/listenbrainz/models.go +++ b/internal/backends/listenbrainz/models.go @@ -66,20 +66,20 @@ type Track struct { ArtistName string `json:"artist_name,omitempty"` ReleaseName string `json:"release_name,omitempty"` AdditionalInfo map[string]any `json:"additional_info,omitempty"` - MbidMapping *MbidMapping `json:"mbid_mapping,omitempty"` + MBIDMapping *MBIDMapping `json:"mbid_mapping,omitempty"` } -type MbidMapping struct { +type MBIDMapping struct { RecordingName string `json:"recording_name,omitempty"` - RecordingMbid string `json:"recording_mbid,omitempty"` - ReleaseMbid string `json:"release_mbid,omitempty"` - ArtistMbids []string `json:"artist_mbids,omitempty"` + RecordingMBID string `json:"recording_mbid,omitempty"` + ReleaseMBID string `json:"release_mbid,omitempty"` + ArtistMBIDs []string `json:"artist_mbids,omitempty"` Artists []Artist `json:"artists,omitempty"` } type Artist struct { ArtistCreditName string `json:"artist_credit_name,omitempty"` - ArtistMbid string `json:"artist_mbid,omitempty"` + ArtistMBID string `json:"artist_mbid,omitempty"` JoinPhrase string `json:"join_phrase,omitempty"` } @@ -92,7 +92,7 @@ type GetFeedbackResult struct { type Feedback struct { Created int64 `json:"created,omitempty"` - RecordingMbid string `json:"recording_mbid,omitempty"` + RecordingMBID string `json:"recording_mbid,omitempty"` RecordingMsid string `json:"recording_msid,omitempty"` Score int `json:"score,omitempty"` TrackMetadata *Track `json:"track_metadata,omitempty"` @@ -103,9 +103,9 @@ type LookupResult struct { ArtistCreditName string `json:"artist_credit_name"` ReleaseName string `json:"release_name"` RecordingName string `json:"recording_name"` - RecordingMbid string `json:"recording_mbid"` - ReleaseMbid string `json:"release_mbid"` - ArtistMbids []string `json:"artist_mbids"` + RecordingMBID string `json:"recording_mbid"` + ReleaseMBID string `json:"release_mbid"` + ArtistMBIDs []string `json:"artist_mbids"` } type StatusResult struct { @@ -162,25 +162,25 @@ func (t Track) ISRC() string { return tryGetValueOrEmpty[string](t.AdditionalInfo, "isrc") } -func (t Track) RecordingMbid() string { +func (t Track) RecordingMBID() string { mbid := tryGetValueOrEmpty[string](t.AdditionalInfo, "recording_mbid") - if mbid == "" && t.MbidMapping != nil { - return t.MbidMapping.RecordingMbid + if mbid == "" && t.MBIDMapping != nil { + return t.MBIDMapping.RecordingMBID } else { return mbid } } -func (t Track) ReleaseMbid() string { +func (t Track) ReleaseMBID() string { mbid := tryGetValueOrEmpty[string](t.AdditionalInfo, "release_mbid") - if mbid == "" && t.MbidMapping != nil { - return t.MbidMapping.ReleaseMbid + if mbid == "" && t.MBIDMapping != nil { + return t.MBIDMapping.ReleaseMBID } else { return mbid } } -func (t Track) ReleaseGroupMbid() string { +func (t Track) ReleaseGroupMBID() string { return tryGetValueOrEmpty[string](t.AdditionalInfo, "release_group_mbid") } diff --git a/internal/backends/listenbrainz/models_test.go b/internal/backends/listenbrainz/models_test.go index 845690d..55e5267 100644 --- a/internal/backends/listenbrainz/models_test.go +++ b/internal/backends/listenbrainz/models_test.go @@ -140,40 +140,40 @@ func TestTrackIsrc(t *testing.T) { assert.Equal(t, expected, track.ISRC()) } -func TestTrackRecordingMbid(t *testing.T) { +func TestTrackRecordingMBID(t *testing.T) { expected := "e02cc1c3-93fd-4e24-8b77-325060de920b" track := listenbrainz.Track{ AdditionalInfo: map[string]any{ "recording_mbid": expected, }, } - assert.Equal(t, expected, track.RecordingMbid()) + assert.Equal(t, expected, track.RecordingMBID()) } -func TestTrackReleaseMbid(t *testing.T) { +func TestTrackReleaseMBID(t *testing.T) { expected := "e02cc1c3-93fd-4e24-8b77-325060de920b" track := listenbrainz.Track{ AdditionalInfo: map[string]any{ "release_mbid": expected, }, } - assert.Equal(t, expected, track.ReleaseMbid()) + assert.Equal(t, expected, track.ReleaseMBID()) } -func TestReleaseGroupMbid(t *testing.T) { +func TestReleaseGroupMBID(t *testing.T) { expected := "e02cc1c3-93fd-4e24-8b77-325060de920b" track := listenbrainz.Track{ AdditionalInfo: map[string]any{ "release_group_mbid": expected, }, } - assert.Equal(t, expected, track.ReleaseGroupMbid()) + assert.Equal(t, expected, track.ReleaseGroupMBID()) } func TestMarshalPartialFeedback(t *testing.T) { feedback := listenbrainz.Feedback{ Created: 1699859066, - RecordingMbid: "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12", + RecordingMBID: "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12", } b, err := json.Marshal(feedback) require.NoError(t, err) diff --git a/internal/backends/scrobblerlog/parser.go b/internal/backends/scrobblerlog/parser.go index ce2d897..1ef08f7 100644 --- a/internal/backends/scrobblerlog/parser.go +++ b/internal/backends/scrobblerlog/parser.go @@ -58,7 +58,7 @@ func Parse(data io.Reader, includeSkipped bool) (ScrobblerLog, error) { for { // A row is: - // artistName releaseName trackName trackNumber duration rating timestamp recordingMbid + // artistName releaseName trackName trackNumber duration rating timestamp recordingMBID row, err := tsvReader.Read() if err == io.EOF { break @@ -101,7 +101,7 @@ func Write(data io.Writer, listens models.ListensList) (lastTimestamp time.Time, } // A row is: - // artistName releaseName trackName trackNumber duration rating timestamp recordingMbid + // artistName releaseName trackName trackNumber duration rating timestamp recordingMBID rating, ok := listen.AdditionalInfo["rockbox_rating"].(string) if !ok || rating == "" { rating = "L" @@ -114,7 +114,7 @@ func Write(data io.Writer, listens models.ListensList) (lastTimestamp time.Time, strconv.Itoa(int(listen.Duration.Seconds())), rating, strconv.Itoa(int(listen.ListenedAt.Unix())), - string(listen.RecordingMbid), + string(listen.RecordingMBID), }) } @@ -204,7 +204,7 @@ func rowToListen(row []string, client string) (models.Listen, error) { } if len(row) > 7 { - listen.Track.RecordingMbid = mbtypes.MBID(row[7]) + listen.Track.RecordingMBID = mbtypes.MBID(row[7]) } return listen, nil diff --git a/internal/backends/scrobblerlog/parser_test.go b/internal/backends/scrobblerlog/parser_test.go index 583ec49..480481f 100644 --- a/internal/backends/scrobblerlog/parser_test.go +++ b/internal/backends/scrobblerlog/parser_test.go @@ -61,10 +61,10 @@ func TestParser(t *testing.T) { assert.Equal(time.Duration(306*time.Second), listen1.Duration) assert.Equal("L", listen1.AdditionalInfo["rockbox_rating"]) assert.Equal(time.Unix(1260342084, 0), listen1.ListenedAt) - assert.Equal(mbtypes.MBID(""), listen1.RecordingMbid) + assert.Equal(mbtypes.MBID(""), listen1.RecordingMBID) listen4 := result.Listens[3] assert.Equal("S", listen4.AdditionalInfo["rockbox_rating"]) - assert.Equal(mbtypes.MBID("385ba9e9-626d-4750-a607-58e541dca78e"), listen4.RecordingMbid) + assert.Equal(mbtypes.MBID("385ba9e9-626d-4750-a607-58e541dca78e"), listen4.RecordingMBID) } func TestParserExcludeSkipped(t *testing.T) { @@ -75,7 +75,7 @@ func TestParserExcludeSkipped(t *testing.T) { assert.Len(result.Listens, 4) listen4 := result.Listens[3] assert.Equal("L", listen4.AdditionalInfo["rockbox_rating"]) - assert.Equal(mbtypes.MBID("1262beaf-19f8-4534-b9ed-7eef9ca8e83f"), listen4.RecordingMbid) + assert.Equal(mbtypes.MBID("1262beaf-19f8-4534-b9ed-7eef9ca8e83f"), listen4.RecordingMBID) } func TestWrite(t *testing.T) { @@ -94,7 +94,7 @@ func TestWrite(t *testing.T) { TrackName: "Reign", TrackNumber: 1, Duration: 271 * time.Second, - RecordingMbid: mbtypes.MBID("b59cf4e7-caee-4019-a844-79d2c58d4dff"), + RecordingMBID: mbtypes.MBID("b59cf4e7-caee-4019-a844-79d2c58d4dff"), AdditionalInfo: models.AdditionalInfo{"rockbox_rating": "L"}, }, }, diff --git a/internal/models/models.go b/internal/models/models.go index a8a6570..39ae236 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -45,11 +45,11 @@ type Track struct { DiscNumber int Duration time.Duration ISRC string - RecordingMbid mbtypes.MBID - ReleaseMbid mbtypes.MBID - ReleaseGroupMbid mbtypes.MBID - ArtistMbids []mbtypes.MBID - WorkMbids []mbtypes.MBID + RecordingMBID mbtypes.MBID + ReleaseMBID mbtypes.MBID + ReleaseGroupMBID mbtypes.MBID + ArtistMBIDs []mbtypes.MBID + WorkMBIDs []mbtypes.MBID Tags []string AdditionalInfo AdditionalInfo } @@ -63,20 +63,20 @@ func (t *Track) FillAdditionalInfo() { if t.AdditionalInfo == nil { t.AdditionalInfo = make(AdditionalInfo, 5) } - if t.RecordingMbid != "" { - t.AdditionalInfo["recording_mbid"] = t.RecordingMbid + if t.RecordingMBID != "" { + t.AdditionalInfo["recording_mbid"] = t.RecordingMBID } - if t.ReleaseGroupMbid != "" { - t.AdditionalInfo["release_group_mbid"] = t.ReleaseGroupMbid + if t.ReleaseGroupMBID != "" { + t.AdditionalInfo["release_group_mbid"] = t.ReleaseGroupMBID } - if t.ReleaseMbid != "" { - t.AdditionalInfo["release_mbid"] = t.ReleaseMbid + if t.ReleaseMBID != "" { + t.AdditionalInfo["release_mbid"] = t.ReleaseMBID } - if len(t.ArtistMbids) > 0 { - t.AdditionalInfo["artist_mbids"] = t.ArtistMbids + if len(t.ArtistMBIDs) > 0 { + t.AdditionalInfo["artist_mbids"] = t.ArtistMBIDs } - if len(t.WorkMbids) > 0 { - t.AdditionalInfo["work_mbids"] = t.WorkMbids + if len(t.WorkMBIDs) > 0 { + t.AdditionalInfo["work_mbids"] = t.WorkMBIDs } if t.ISRC != "" { t.AdditionalInfo["isrc"] = t.ISRC @@ -111,7 +111,7 @@ type Love struct { Track Created time.Time UserName string - RecordingMbid mbtypes.MBID + RecordingMBID mbtypes.MBID RecordingMsid mbtypes.MBID } diff --git a/internal/models/models_test.go b/internal/models/models_test.go index 5e010bc..0deaefa 100644 --- a/internal/models/models_test.go +++ b/internal/models/models_test.go @@ -45,11 +45,11 @@ func TestTrackArtistName(t *testing.T) { func TestTrackFillAdditionalInfo(t *testing.T) { track := models.Track{ - RecordingMbid: mbtypes.MBID("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"), - ReleaseGroupMbid: mbtypes.MBID("80aca1ee-aa51-41be-9f75-024710d92ff4"), - ReleaseMbid: mbtypes.MBID("aa1ea1ac-7ec4-4542-a494-105afbfe547d"), - ArtistMbids: []mbtypes.MBID{"24412926-c7bd-48e8-afad-8a285b42e131"}, - WorkMbids: []mbtypes.MBID{"c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"}, + RecordingMBID: mbtypes.MBID("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"), + ReleaseGroupMBID: mbtypes.MBID("80aca1ee-aa51-41be-9f75-024710d92ff4"), + ReleaseMBID: mbtypes.MBID("aa1ea1ac-7ec4-4542-a494-105afbfe547d"), + ArtistMBIDs: []mbtypes.MBID{"24412926-c7bd-48e8-afad-8a285b42e131"}, + WorkMBIDs: []mbtypes.MBID{"c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"}, TrackNumber: 5, DiscNumber: 1, Duration: time.Duration(413787 * time.Millisecond), @@ -59,11 +59,11 @@ func TestTrackFillAdditionalInfo(t *testing.T) { track.FillAdditionalInfo() i := track.AdditionalInfo assert := assert.New(t) - assert.Equal(track.RecordingMbid, i["recording_mbid"]) - assert.Equal(track.ReleaseGroupMbid, i["release_group_mbid"]) - assert.Equal(track.ReleaseMbid, i["release_mbid"]) - assert.Equal(track.ArtistMbids, i["artist_mbids"]) - assert.Equal(track.WorkMbids, i["work_mbids"]) + assert.Equal(track.RecordingMBID, i["recording_mbid"]) + assert.Equal(track.ReleaseGroupMBID, i["release_group_mbid"]) + assert.Equal(track.ReleaseMBID, i["release_mbid"]) + assert.Equal(track.ArtistMBIDs, i["artist_mbids"]) + assert.Equal(track.WorkMBIDs, i["work_mbids"]) assert.Equal(track.TrackNumber, i["tracknumber"]) assert.Equal(track.DiscNumber, i["discnumber"]) assert.Equal(track.Duration.Milliseconds(), i["duration_ms"]) diff --git a/internal/similarity/similarity.go b/internal/similarity/similarity.go index 358404a..3fb27c4 100644 --- a/internal/similarity/similarity.go +++ b/internal/similarity/similarity.go @@ -63,7 +63,7 @@ func NormalizeTitle(s string) string { // Compare two tracks for similarity. func CompareTracks(t1 models.Track, t2 models.Track) float64 { // Identical recording MBID always compares 100% - if t1.RecordingMbid == t2.RecordingMbid && t1.RecordingMbid != "" { + if t1.RecordingMBID == t2.RecordingMBID && t1.RecordingMBID != "" { return 1.0 } diff --git a/internal/similarity/similarity_test.go b/internal/similarity/similarity_test.go index 89fa645..c43e1d7 100644 --- a/internal/similarity/similarity_test.go +++ b/internal/similarity/similarity_test.go @@ -75,13 +75,13 @@ func TestCompareTracksSameMBID(t *testing.T) { t1 := models.Track{ ArtistNames: []string{"Paradise Lost"}, TrackName: "Forever After", - RecordingMbid: mbtypes.MBID("2886d15c-09b0-43c6-af56-932f70dde164"), + RecordingMBID: mbtypes.MBID("2886d15c-09b0-43c6-af56-932f70dde164"), } t2 := models.Track{ ArtistNames: []string{"Paradise Lost"}, TrackName: "Forever Failure (radio edit)", ReleaseName: "Draconian Times", - RecordingMbid: mbtypes.MBID("2886d15c-09b0-43c6-af56-932f70dde164"), + RecordingMBID: mbtypes.MBID("2886d15c-09b0-43c6-af56-932f70dde164"), } assert.Equal(t, 1.0, similarity.CompareTracks(t1, t2)) } From 13eb8342abfa2ed02953d3a929ee30425b6ec6de Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Thu, 3 Apr 2025 15:08:02 +0200 Subject: [PATCH 11/52] Use mbtypes.ISRC type --- internal/backends/listenbrainz/listenbrainz_test.go | 2 +- internal/backends/listenbrainz/models.go | 5 +++-- internal/backends/listenbrainz/models_test.go | 5 +++-- internal/backends/spotify/models.go | 8 +++++--- internal/backends/spotify/spotify_test.go | 3 ++- internal/models/models.go | 2 +- internal/models/models_test.go | 2 +- 7 files changed, 16 insertions(+), 11 deletions(-) diff --git a/internal/backends/listenbrainz/listenbrainz_test.go b/internal/backends/listenbrainz/listenbrainz_test.go index d6d577f..ddeac01 100644 --- a/internal/backends/listenbrainz/listenbrainz_test.go +++ b/internal/backends/listenbrainz/listenbrainz_test.go @@ -68,7 +68,7 @@ func TestListenBrainzListenAsListen(t *testing.T) { assert.Equal(t, mbtypes.MBID("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"), listen.RecordingMBID) assert.Equal(t, mbtypes.MBID("d7f22677-9803-4d21-ba42-081b633a6f68"), listen.ReleaseMBID) assert.Equal(t, mbtypes.MBID("80aca1ee-aa51-41be-9f75-024710d92ff4"), listen.ReleaseGroupMBID) - assert.Equal(t, "DES561620801", listen.ISRC) + assert.Equal(t, mbtypes.ISRC("DES561620801"), listen.ISRC) assert.Equal(t, lbListen.TrackMetadata.AdditionalInfo["foo"], listen.AdditionalInfo["foo"]) } diff --git a/internal/backends/listenbrainz/models.go b/internal/backends/listenbrainz/models.go index e86ce9d..833acd5 100644 --- a/internal/backends/listenbrainz/models.go +++ b/internal/backends/listenbrainz/models.go @@ -25,6 +25,7 @@ import ( "strconv" "time" + "go.uploadedlobster.com/mbtypes" "golang.org/x/exp/constraints" ) @@ -158,8 +159,8 @@ func (t Track) DiscNumber() int { return 0 } -func (t Track) ISRC() string { - return tryGetValueOrEmpty[string](t.AdditionalInfo, "isrc") +func (t Track) ISRC() mbtypes.ISRC { + return mbtypes.ISRC(tryGetValueOrEmpty[string](t.AdditionalInfo, "isrc")) } func (t Track) RecordingMBID() string { diff --git a/internal/backends/listenbrainz/models_test.go b/internal/backends/listenbrainz/models_test.go index 55e5267..f10f635 100644 --- a/internal/backends/listenbrainz/models_test.go +++ b/internal/backends/listenbrainz/models_test.go @@ -28,6 +28,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.uploadedlobster.com/mbtypes" "go.uploadedlobster.com/scotty/internal/backends/listenbrainz" ) @@ -131,10 +132,10 @@ func TestTrackTrackNumberString(t *testing.T) { } func TestTrackIsrc(t *testing.T) { - expected := "TCAEJ1934417" + expected := mbtypes.ISRC("TCAEJ1934417") track := listenbrainz.Track{ AdditionalInfo: map[string]any{ - "isrc": expected, + "isrc": string(expected), }, } assert.Equal(t, expected, track.ISRC()) diff --git a/internal/backends/spotify/models.go b/internal/backends/spotify/models.go index e80eccc..a22de21 100644 --- a/internal/backends/spotify/models.go +++ b/internal/backends/spotify/models.go @@ -22,6 +22,8 @@ THE SOFTWARE. package spotify +import "go.uploadedlobster.com/mbtypes" + type TracksResult struct { Href string `json:"href"` Limit int `json:"limit"` @@ -98,9 +100,9 @@ type Artist struct { } type ExternalIds struct { - ISRC string `json:"isrc"` - EAN string `json:"ean"` - UPC string `json:"upc"` + ISRC mbtypes.ISRC `json:"isrc"` + EAN string `json:"ean"` + UPC string `json:"upc"` } type ExternalUrls struct { diff --git a/internal/backends/spotify/spotify_test.go b/internal/backends/spotify/spotify_test.go index bd7ff58..1aa7e87 100644 --- a/internal/backends/spotify/spotify_test.go +++ b/internal/backends/spotify/spotify_test.go @@ -26,6 +26,7 @@ import ( "github.com/spf13/viper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.uploadedlobster.com/mbtypes" "go.uploadedlobster.com/scotty/internal/backends/spotify" "go.uploadedlobster.com/scotty/internal/config" ) @@ -59,7 +60,7 @@ func TestSpotifyListenAsListen(t *testing.T) { assert.Equal(t, []string{"Dool"}, listen.ArtistNames) assert.Equal(t, 5, listen.TrackNumber) assert.Equal(t, 1, listen.DiscNumber) - assert.Equal(t, "DES561620801", listen.ISRC) + assert.Equal(t, mbtypes.ISRC("DES561620801"), listen.ISRC) info := listen.AdditionalInfo assert.Equal(t, "spotify.com", info["music_service"]) assert.Equal(t, "https://open.spotify.com/track/2JKUgGuXK3dEvyuIJ4Yj2V", info["origin_url"]) diff --git a/internal/models/models.go b/internal/models/models.go index 39ae236..0d5abf2 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -44,7 +44,7 @@ type Track struct { TrackNumber int DiscNumber int Duration time.Duration - ISRC string + ISRC mbtypes.ISRC RecordingMBID mbtypes.MBID ReleaseMBID mbtypes.MBID ReleaseGroupMBID mbtypes.MBID diff --git a/internal/models/models_test.go b/internal/models/models_test.go index 0deaefa..5395610 100644 --- a/internal/models/models_test.go +++ b/internal/models/models_test.go @@ -53,7 +53,7 @@ func TestTrackFillAdditionalInfo(t *testing.T) { TrackNumber: 5, DiscNumber: 1, Duration: time.Duration(413787 * time.Millisecond), - ISRC: "DES561620801", + ISRC: mbtypes.ISRC("DES561620801"), Tags: []string{"rock", "psychedelic rock"}, } track.FillAdditionalInfo() From 0d9bc74bc0ba4821480b12dbff882dd34f5c08a0 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Thu, 3 Apr 2025 15:19:26 +0200 Subject: [PATCH 12/52] More conversion to mbtypes.MBID --- internal/backends/funkwhale/funkwhale.go | 7 ++- internal/backends/funkwhale/funkwhale_test.go | 15 +++--- internal/backends/funkwhale/models.go | 38 +++++++-------- internal/backends/listenbrainz/client_test.go | 5 +- .../backends/listenbrainz/listenbrainz.go | 18 ++++---- .../listenbrainz/listenbrainz_test.go | 22 ++++----- internal/backends/listenbrainz/models.go | 46 +++++++++---------- internal/backends/listenbrainz/models_test.go | 12 ++--- 8 files changed, 82 insertions(+), 81 deletions(-) diff --git a/internal/backends/funkwhale/funkwhale.go b/internal/backends/funkwhale/funkwhale.go index 88bb72c..48c3d8f 100644 --- a/internal/backends/funkwhale/funkwhale.go +++ b/internal/backends/funkwhale/funkwhale.go @@ -189,16 +189,15 @@ func (f FavoriteTrack) AsLove() models.Love { } func (t Track) AsTrack() models.Track { - recordingMBID := mbtypes.MBID(t.RecordingMBID) track := models.Track{ TrackName: t.Title, ReleaseName: t.Album.Title, ArtistNames: []string{t.Artist.Name}, TrackNumber: t.Position, DiscNumber: t.DiscNumber, - RecordingMBID: recordingMBID, - ReleaseMBID: mbtypes.MBID(t.Album.ReleaseMBID), - ArtistMBIDs: []mbtypes.MBID{mbtypes.MBID(t.Artist.ArtistMBID)}, + RecordingMBID: t.RecordingMBID, + ReleaseMBID: t.Album.ReleaseMBID, + ArtistMBIDs: []mbtypes.MBID{t.Artist.ArtistMBID}, Tags: t.Tags, AdditionalInfo: map[string]any{ "media_player": FunkwhaleClientName, diff --git a/internal/backends/funkwhale/funkwhale_test.go b/internal/backends/funkwhale/funkwhale_test.go index 6961c8e..d8654d8 100644 --- a/internal/backends/funkwhale/funkwhale_test.go +++ b/internal/backends/funkwhale/funkwhale_test.go @@ -23,7 +23,6 @@ import ( "github.com/spf13/viper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.uploadedlobster.com/mbtypes" "go.uploadedlobster.com/scotty/internal/backends/funkwhale" "go.uploadedlobster.com/scotty/internal/config" ) @@ -75,9 +74,9 @@ func TestFunkwhaleListeningAsListen(t *testing.T) { assert.Equal(fwListen.Track.DiscNumber, listen.Track.DiscNumber) assert.Equal(fwListen.Track.Tags, listen.Track.Tags) // assert.Equal(backends.FunkwhaleClientName, listen.AdditionalInfo["disc_number"]) - assert.Equal(mbtypes.MBID(fwListen.Track.RecordingMBID), listen.RecordingMBID) - assert.Equal(mbtypes.MBID(fwListen.Track.Album.ReleaseMBID), listen.ReleaseMBID) - assert.Equal(mbtypes.MBID(fwListen.Track.Artist.ArtistMBID), listen.ArtistMBIDs[0]) + assert.Equal(fwListen.Track.RecordingMBID, listen.RecordingMBID) + assert.Equal(fwListen.Track.Album.ReleaseMBID, listen.ReleaseMBID) + assert.Equal(fwListen.Track.Artist.ArtistMBID, listen.ArtistMBIDs[0]) assert.Equal(funkwhale.FunkwhaleClientName, listen.AdditionalInfo["media_player"]) } @@ -119,10 +118,10 @@ func TestFunkwhaleFavoriteTrackAsLove(t *testing.T) { assert.Equal(favorite.Track.Position, love.Track.TrackNumber) assert.Equal(favorite.Track.DiscNumber, love.Track.DiscNumber) assert.Equal(favorite.Track.Tags, love.Track.Tags) - assert.Equal(mbtypes.MBID(favorite.Track.RecordingMBID), love.RecordingMBID) - assert.Equal(mbtypes.MBID(favorite.Track.RecordingMBID), love.Track.RecordingMBID) - assert.Equal(mbtypes.MBID(favorite.Track.Album.ReleaseMBID), love.ReleaseMBID) + assert.Equal(favorite.Track.RecordingMBID, love.RecordingMBID) + assert.Equal(favorite.Track.RecordingMBID, love.Track.RecordingMBID) + assert.Equal(favorite.Track.Album.ReleaseMBID, love.ReleaseMBID) require.Len(t, love.Track.ArtistMBIDs, 1) - assert.Equal(mbtypes.MBID(favorite.Track.Artist.ArtistMBID), love.ArtistMBIDs[0]) + assert.Equal(favorite.Track.Artist.ArtistMBID, love.ArtistMBIDs[0]) assert.Equal(funkwhale.FunkwhaleClientName, love.AdditionalInfo["media_player"]) } diff --git a/internal/backends/funkwhale/models.go b/internal/backends/funkwhale/models.go index 86b66bc..10d57d0 100644 --- a/internal/backends/funkwhale/models.go +++ b/internal/backends/funkwhale/models.go @@ -21,6 +21,8 @@ THE SOFTWARE. */ package funkwhale +import "go.uploadedlobster.com/mbtypes" + type ListeningsResult struct { Count int `json:"count"` Previous string `json:"previous"` @@ -50,30 +52,30 @@ type FavoriteTrack struct { } type Track struct { - Id int `json:"int"` - Artist Artist `json:"artist"` - Album Album `json:"album"` - Title string `json:"title"` - Position int `json:"position"` - DiscNumber int `json:"disc_number"` - RecordingMBID string `json:"mbid"` - Tags []string `json:"tags"` - Uploads []Upload `json:"uploads"` + Id int `json:"int"` + Artist Artist `json:"artist"` + Album Album `json:"album"` + Title string `json:"title"` + Position int `json:"position"` + DiscNumber int `json:"disc_number"` + RecordingMBID mbtypes.MBID `json:"mbid"` + Tags []string `json:"tags"` + Uploads []Upload `json:"uploads"` } type Artist struct { - Id int `json:"int"` - Name string `json:"name"` - ArtistMBID string `json:"mbid"` + Id int `json:"int"` + Name string `json:"name"` + ArtistMBID mbtypes.MBID `json:"mbid"` } type Album struct { - Id int `json:"int"` - Title string `json:"title"` - AlbumArtist Artist `json:"artist"` - ReleaseDate string `json:"release_date"` - TrackCount int `json:"track_count"` - ReleaseMBID string `json:"mbid"` + Id int `json:"int"` + Title string `json:"title"` + AlbumArtist Artist `json:"artist"` + ReleaseDate string `json:"release_date"` + TrackCount int `json:"track_count"` + ReleaseMBID mbtypes.MBID `json:"mbid"` } type User struct { diff --git a/internal/backends/listenbrainz/client_test.go b/internal/backends/listenbrainz/client_test.go index 0fbbdfd..4e72756 100644 --- a/internal/backends/listenbrainz/client_test.go +++ b/internal/backends/listenbrainz/client_test.go @@ -29,6 +29,7 @@ import ( "github.com/jarcoal/httpmock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.uploadedlobster.com/mbtypes" "go.uploadedlobster.com/scotty/internal/backends/listenbrainz" ) @@ -114,7 +115,7 @@ func TestGetFeedback(t *testing.T) { assert.Equal(302, result.TotalCount) assert.Equal(3, result.Offset) require.Len(t, result.Feedback, 2) - assert.Equal("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12", result.Feedback[0].RecordingMBID) + assert.Equal(mbtypes.MBID("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"), result.Feedback[0].RecordingMBID) } func TestSendFeedback(t *testing.T) { @@ -154,7 +155,7 @@ func TestLookup(t *testing.T) { assert := assert.New(t) assert.Equal("Say Just Words", result.RecordingName) assert.Equal("Paradise Lost", result.ArtistCreditName) - assert.Equal("569436a1-234a-44bc-a370-8f4d252bef21", result.RecordingMBID) + assert.Equal(mbtypes.MBID("569436a1-234a-44bc-a370-8f4d252bef21"), result.RecordingMBID) } func setupHttpMock(t *testing.T, client *http.Client, url string, testDataPath string) { diff --git a/internal/backends/listenbrainz/listenbrainz.go b/internal/backends/listenbrainz/listenbrainz.go index f18a29d..97f721d 100644 --- a/internal/backends/listenbrainz/listenbrainz.go +++ b/internal/backends/listenbrainz/listenbrainz.go @@ -33,7 +33,7 @@ type ListenBrainzApiBackend struct { client Client username string checkDuplicates bool - existingMBIDs map[string]bool + existingMBIDs map[mbtypes.MBID]bool } func (b *ListenBrainzApiBackend) Name() string { return "listenbrainz" } @@ -239,14 +239,14 @@ func (b *ListenBrainzApiBackend) ImportLoves(export models.LovesResult, importRe } // TODO: Store MBIDs directly - b.existingMBIDs = make(map[string]bool, len(existingLoves.Items)) + b.existingMBIDs = make(map[mbtypes.MBID]bool, len(existingLoves.Items)) for _, love := range existingLoves.Items { - b.existingMBIDs[string(love.RecordingMBID)] = true + b.existingMBIDs[love.RecordingMBID] = true } } for _, love := range export.Items { - recordingMBID := string(love.RecordingMBID) + recordingMBID := love.RecordingMBID if recordingMBID == "" { lookup, err := b.client.Lookup(love.TrackName, love.ArtistName()) @@ -324,7 +324,7 @@ func (lbListen Listen) AsListen() models.Listen { } func (f Feedback) AsLove() models.Love { - recordingMBID := mbtypes.MBID(f.RecordingMBID) + recordingMBID := f.RecordingMBID track := f.TrackMetadata if track == nil { track = &Track{} @@ -351,16 +351,16 @@ func (t Track) AsTrack() models.Track { Duration: t.Duration(), TrackNumber: t.TrackNumber(), DiscNumber: t.DiscNumber(), - RecordingMBID: mbtypes.MBID(t.RecordingMBID()), - ReleaseMBID: mbtypes.MBID(t.ReleaseMBID()), - ReleaseGroupMBID: mbtypes.MBID(t.ReleaseGroupMBID()), + RecordingMBID: t.RecordingMBID(), + ReleaseMBID: t.ReleaseMBID(), + ReleaseGroupMBID: t.ReleaseGroupMBID(), ISRC: t.ISRC(), AdditionalInfo: t.AdditionalInfo, } if t.MBIDMapping != nil && len(track.ArtistMBIDs) == 0 { for _, artistMBID := range t.MBIDMapping.ArtistMBIDs { - track.ArtistMBIDs = append(track.ArtistMBIDs, mbtypes.MBID(artistMBID)) + track.ArtistMBIDs = append(track.ArtistMBIDs, artistMBID) } } diff --git a/internal/backends/listenbrainz/listenbrainz_test.go b/internal/backends/listenbrainz/listenbrainz_test.go index ddeac01..93428d7 100644 --- a/internal/backends/listenbrainz/listenbrainz_test.go +++ b/internal/backends/listenbrainz/listenbrainz_test.go @@ -73,9 +73,9 @@ func TestListenBrainzListenAsListen(t *testing.T) { } func TestListenBrainzFeedbackAsLove(t *testing.T) { - recordingMBID := "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12" - releaseMBID := "d7f22677-9803-4d21-ba42-081b633a6f68" - artistMBID := "d7f22677-9803-4d21-ba42-081b633a6f68" + recordingMBID := mbtypes.MBID("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12") + releaseMBID := mbtypes.MBID("d7f22677-9803-4d21-ba42-081b633a6f68") + artistMBID := mbtypes.MBID("d7f22677-9803-4d21-ba42-081b633a6f68") feedback := listenbrainz.Feedback{ Created: 1699859066, RecordingMBID: recordingMBID, @@ -88,7 +88,7 @@ func TestListenBrainzFeedbackAsLove(t *testing.T) { MBIDMapping: &listenbrainz.MBIDMapping{ RecordingMBID: recordingMBID, ReleaseMBID: releaseMBID, - ArtistMBIDs: []string{artistMBID}, + ArtistMBIDs: []mbtypes.MBID{artistMBID}, }, }, } @@ -99,15 +99,15 @@ func TestListenBrainzFeedbackAsLove(t *testing.T) { assert.Equal(feedback.TrackMetadata.TrackName, love.TrackName) assert.Equal(feedback.TrackMetadata.ReleaseName, love.ReleaseName) assert.Equal([]string{feedback.TrackMetadata.ArtistName}, love.ArtistNames) - assert.Equal(mbtypes.MBID(recordingMBID), love.RecordingMBID) - assert.Equal(mbtypes.MBID(recordingMBID), love.Track.RecordingMBID) - assert.Equal(mbtypes.MBID(releaseMBID), love.Track.ReleaseMBID) + assert.Equal(recordingMBID, love.RecordingMBID) + assert.Equal(recordingMBID, love.Track.RecordingMBID) + assert.Equal(releaseMBID, love.Track.ReleaseMBID) require.Len(t, love.Track.ArtistMBIDs, 1) - assert.Equal(mbtypes.MBID(artistMBID), love.Track.ArtistMBIDs[0]) + assert.Equal(artistMBID, love.Track.ArtistMBIDs[0]) } func TestListenBrainzPartialFeedbackAsLove(t *testing.T) { - recordingMBID := "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12" + recordingMBID := mbtypes.MBID("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12") feedback := listenbrainz.Feedback{ Created: 1699859066, RecordingMBID: recordingMBID, @@ -116,7 +116,7 @@ func TestListenBrainzPartialFeedbackAsLove(t *testing.T) { love := feedback.AsLove() assert := assert.New(t) assert.Equal(time.Unix(1699859066, 0).Unix(), love.Created.Unix()) - assert.Equal(mbtypes.MBID(recordingMBID), love.RecordingMBID) - assert.Equal(mbtypes.MBID(recordingMBID), love.Track.RecordingMBID) + assert.Equal(recordingMBID, love.RecordingMBID) + assert.Equal(recordingMBID, love.Track.RecordingMBID) assert.Empty(love.Track.TrackName) } diff --git a/internal/backends/listenbrainz/models.go b/internal/backends/listenbrainz/models.go index 833acd5..4102ee5 100644 --- a/internal/backends/listenbrainz/models.go +++ b/internal/backends/listenbrainz/models.go @@ -71,11 +71,11 @@ type Track struct { } type MBIDMapping struct { - RecordingName string `json:"recording_name,omitempty"` - RecordingMBID string `json:"recording_mbid,omitempty"` - ReleaseMBID string `json:"release_mbid,omitempty"` - ArtistMBIDs []string `json:"artist_mbids,omitempty"` - Artists []Artist `json:"artists,omitempty"` + RecordingName string `json:"recording_name,omitempty"` + RecordingMBID mbtypes.MBID `json:"recording_mbid,omitempty"` + ReleaseMBID mbtypes.MBID `json:"release_mbid,omitempty"` + ArtistMBIDs []mbtypes.MBID `json:"artist_mbids,omitempty"` + Artists []Artist `json:"artists,omitempty"` } type Artist struct { @@ -92,21 +92,21 @@ type GetFeedbackResult struct { } type Feedback struct { - Created int64 `json:"created,omitempty"` - RecordingMBID string `json:"recording_mbid,omitempty"` - RecordingMsid string `json:"recording_msid,omitempty"` - Score int `json:"score,omitempty"` - TrackMetadata *Track `json:"track_metadata,omitempty"` - UserName string `json:"user_id,omitempty"` + Created int64 `json:"created,omitempty"` + RecordingMBID mbtypes.MBID `json:"recording_mbid,omitempty"` + RecordingMsid mbtypes.MBID `json:"recording_msid,omitempty"` + Score int `json:"score,omitempty"` + TrackMetadata *Track `json:"track_metadata,omitempty"` + UserName string `json:"user_id,omitempty"` } type LookupResult struct { - ArtistCreditName string `json:"artist_credit_name"` - ReleaseName string `json:"release_name"` - RecordingName string `json:"recording_name"` - RecordingMBID string `json:"recording_mbid"` - ReleaseMBID string `json:"release_mbid"` - ArtistMBIDs []string `json:"artist_mbids"` + ArtistCreditName string `json:"artist_credit_name"` + ReleaseName string `json:"release_name"` + RecordingName string `json:"recording_name"` + RecordingMBID mbtypes.MBID `json:"recording_mbid"` + ReleaseMBID mbtypes.MBID `json:"release_mbid"` + ArtistMBIDs []mbtypes.MBID `json:"artist_mbids"` } type StatusResult struct { @@ -163,8 +163,8 @@ func (t Track) ISRC() mbtypes.ISRC { return mbtypes.ISRC(tryGetValueOrEmpty[string](t.AdditionalInfo, "isrc")) } -func (t Track) RecordingMBID() string { - mbid := tryGetValueOrEmpty[string](t.AdditionalInfo, "recording_mbid") +func (t Track) RecordingMBID() mbtypes.MBID { + mbid := mbtypes.MBID(tryGetValueOrEmpty[string](t.AdditionalInfo, "recording_mbid")) if mbid == "" && t.MBIDMapping != nil { return t.MBIDMapping.RecordingMBID } else { @@ -172,8 +172,8 @@ func (t Track) RecordingMBID() string { } } -func (t Track) ReleaseMBID() string { - mbid := tryGetValueOrEmpty[string](t.AdditionalInfo, "release_mbid") +func (t Track) ReleaseMBID() mbtypes.MBID { + mbid := mbtypes.MBID(tryGetValueOrEmpty[string](t.AdditionalInfo, "release_mbid")) if mbid == "" && t.MBIDMapping != nil { return t.MBIDMapping.ReleaseMBID } else { @@ -181,8 +181,8 @@ func (t Track) ReleaseMBID() string { } } -func (t Track) ReleaseGroupMBID() string { - return tryGetValueOrEmpty[string](t.AdditionalInfo, "release_group_mbid") +func (t Track) ReleaseGroupMBID() mbtypes.MBID { + return mbtypes.MBID(tryGetValueOrEmpty[string](t.AdditionalInfo, "release_group_mbid")) } func tryGetValueOrEmpty[T any](dict map[string]any, key string) T { diff --git a/internal/backends/listenbrainz/models_test.go b/internal/backends/listenbrainz/models_test.go index f10f635..9f5b14a 100644 --- a/internal/backends/listenbrainz/models_test.go +++ b/internal/backends/listenbrainz/models_test.go @@ -142,30 +142,30 @@ func TestTrackIsrc(t *testing.T) { } func TestTrackRecordingMBID(t *testing.T) { - expected := "e02cc1c3-93fd-4e24-8b77-325060de920b" + expected := mbtypes.MBID("e02cc1c3-93fd-4e24-8b77-325060de920b") track := listenbrainz.Track{ AdditionalInfo: map[string]any{ - "recording_mbid": expected, + "recording_mbid": string(expected), }, } assert.Equal(t, expected, track.RecordingMBID()) } func TestTrackReleaseMBID(t *testing.T) { - expected := "e02cc1c3-93fd-4e24-8b77-325060de920b" + expected := mbtypes.MBID("e02cc1c3-93fd-4e24-8b77-325060de920b") track := listenbrainz.Track{ AdditionalInfo: map[string]any{ - "release_mbid": expected, + "release_mbid": string(expected), }, } assert.Equal(t, expected, track.ReleaseMBID()) } func TestReleaseGroupMBID(t *testing.T) { - expected := "e02cc1c3-93fd-4e24-8b77-325060de920b" + expected := mbtypes.MBID("e02cc1c3-93fd-4e24-8b77-325060de920b") track := listenbrainz.Track{ AdditionalInfo: map[string]any{ - "release_group_mbid": expected, + "release_group_mbid": string(expected), }, } assert.Equal(t, expected, track.ReleaseGroupMBID()) From dc834e9b6fa4b455214626bf81783d3128b13e54 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Mon, 7 Apr 2025 08:46:46 +0200 Subject: [PATCH 13/52] update dependencies --- go.mod | 56 +++++++++---------- go.sum | 173 +++++++++++++++++++++++++++------------------------------ 2 files changed, 107 insertions(+), 122 deletions(-) diff --git a/go.mod b/go.mod index 81d9deb..051277f 100644 --- a/go.mod +++ b/go.mod @@ -5,27 +5,27 @@ go 1.23.0 toolchain go1.23.8 require ( - github.com/Xuanwo/go-locale v1.1.2 - github.com/agnivade/levenshtein v1.1.1 + github.com/Xuanwo/go-locale v1.1.3 + github.com/agnivade/levenshtein v1.2.1 github.com/cli/browser v1.3.0 github.com/delucks/go-subsonic v0.0.0-20240806025900-2a743ec36238 - github.com/fatih/color v1.17.0 + github.com/fatih/color v1.18.0 github.com/glebarez/sqlite v1.11.0 - github.com/go-resty/resty/v2 v2.15.0 + github.com/go-resty/resty/v2 v2.16.5 github.com/jarcoal/httpmock v1.3.1 github.com/manifoldco/promptui v0.9.0 github.com/pelletier/go-toml/v2 v2.2.3 github.com/shkh/lastfm-go v0.0.0-20191215035245-89a801c244e0 - github.com/spf13/cast v1.7.0 - github.com/spf13/cobra v1.8.1 - github.com/spf13/viper v1.19.0 - github.com/stretchr/testify v1.9.0 - github.com/vbauerster/mpb/v8 v8.8.3 + github.com/spf13/cast v1.7.1 + github.com/spf13/cobra v1.9.1 + github.com/spf13/viper v1.20.1 + github.com/stretchr/testify v1.10.0 + github.com/vbauerster/mpb/v8 v8.9.3 go.uploadedlobster.com/mbtypes v0.2.0 - golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 - golang.org/x/oauth2 v0.23.0 - golang.org/x/text v0.18.0 - gorm.io/datatypes v1.2.2 + golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 + golang.org/x/oauth2 v0.29.0 + golang.org/x/text v0.24.0 + gorm.io/datatypes v1.2.5 gorm.io/gorm v1.25.12 ) @@ -36,37 +36,33 @@ require ( github.com/chzyer/readline v1.5.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/glebarez/go-sqlite v1.22.0 // indirect - github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/go-sql-driver/mysql v1.9.1 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/magiconair/properties v1.8.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/sagikazarmark/locafero v0.6.0 // indirect - github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sagikazarmark/locafero v0.9.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/afero v1.14.0 // indirect + github.com/spf13/pflag v1.0.6 // indirect github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.29.0 // indirect - golang.org/x/sys v0.25.0 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/sys v0.32.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/driver/mysql v1.5.7 // indirect - modernc.org/libc v1.60.1 // indirect - modernc.org/mathutil v1.6.0 // indirect - modernc.org/memory v1.8.0 // indirect - modernc.org/sqlite v1.33.1 // indirect + modernc.org/libc v1.62.1 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.9.1 // indirect + modernc.org/sqlite v1.37.0 // indirect ) diff --git a/go.sum b/go.sum index c2b9efe..c1692a4 100644 --- a/go.sum +++ b/go.sum @@ -2,12 +2,12 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= -github.com/Xuanwo/go-locale v1.1.2 h1:6H+olvrQcyVOZ+GAC2rXu4armacTT4ZrFCA0mB24XVo= -github.com/Xuanwo/go-locale v1.1.2/go.mod h1:1JBER4QV7Ji39GJ4AvVlfvqmTUqopzxQxdg2mXYOw94= +github.com/Xuanwo/go-locale v1.1.3 h1:EWZZJJt5rqPHHbqPRH1zFCn5D7xHjjebODctA4aUO3A= +github.com/Xuanwo/go-locale v1.1.3/go.mod h1:REn+F/c+AtGSWYACBSYZgl23AP+0lfQC+SEFPN+hj30= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= -github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= -github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= +github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= +github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -21,43 +21,42 @@ github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/delucks/go-subsonic v0.0.0-20240806025900-2a743ec36238 h1:uejyepOdHISrJTw7P84Y7yEC0FMyv1q3KNDRxWsviKw= github.com/delucks/go-subsonic v0.0.0-20240806025900-2a743ec36238/go.mod h1:vnbEuj6Z20PLcHB4rrLQAOXGMjtULfMGhRVSFPcSdUo= -github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= -github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo= +github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= -github.com/go-resty/resty/v2 v2.15.0 h1:clPQLZ2x9h4yGY81IzpMPnty+xoGyFaDg0XMkCsHf90= -github.com/go-resty/resty/v2 v2.15.0/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU= +github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM= +github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= -github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI= +github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= -github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= @@ -78,13 +77,10 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= @@ -93,10 +89,8 @@ github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOj github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM= -github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE= -github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA= +github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= @@ -111,99 +105,94 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= -github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= -github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= -github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k= +github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk= github.com/shkh/lastfm-go v0.0.0-20191215035245-89a801c244e0 h1:cgqwZtnR+IQfUYDLJ3Kiy4aE+O/wExTzEIg8xwC4Qfs= github.com/shkh/lastfm-go v0.0.0-20191215035245-89a801c244e0/go.mod h1:n3nudMl178cEvD44PaopxH9jhJaQzthSxUzLO5iKMy4= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= -github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= -github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= +github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= +github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/vbauerster/mpb/v8 v8.8.3 h1:dTOByGoqwaTJYPubhVz3lO5O6MK553XVgUo33LdnNsQ= -github.com/vbauerster/mpb/v8 v8.8.3/go.mod h1:JfCCrtcMsJwP6ZwMn9e5LMnNyp3TVNpUWWkN+nd4EWk= +github.com/vbauerster/mpb/v8 v8.9.3 h1:PnMeF+sMvYv9u23l6DO6Q3+Mdj408mjLRXIzmUmU2Z8= +github.com/vbauerster/mpb/v8 v8.9.3/go.mod h1:hxS8Hz4C6ijnppDSIX6LjG8FYJSoPo9iIOcE53Zik0c= 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.2.0 h1:sV9WM5fY8KnnKwKKVRaU3JBEKP4zD7aUX0XQfNZvEdo= go.uploadedlobster.com/mbtypes v0.2.0/go.mod h1:/ZpwXc8oRpDa7EWeGI9xEY+nGhMIVHhTruikZWD4Krg= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= +golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= -golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= +golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= +golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/datatypes v1.2.2 h1:sdn7ZmG4l7JWtMDUb3L98f2Ym7CO5F8mZLlrQJMfF9g= -gorm.io/datatypes v1.2.2/go.mod h1:f4BsLcFAX67szSv8svwLRjklArSHAvHLeE3pXAS5DZI= +gorm.io/datatypes v1.2.5 h1:9UogU3jkydFVW1bIVVeoYsTpLRgwDVW3rHfJG6/Ek9I= +gorm.io/datatypes v1.2.5/go.mod h1:I5FUdlKpLb5PMqeMQhm30CQ6jXP8Rj89xkTeCSAaAD4= gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U= gorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A= gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU= gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI= -gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0= -gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig= +gorm.io/driver/sqlserver v1.5.4 h1:xA+Y1KDNspv79q43bPyjDMUgHoYHLhXYmdFcYPobg8g= +gorm.io/driver/sqlserver v1.5.4/go.mod h1:+frZ/qYmuna11zHPlh5oc2O6ZA/lS88Keb0XSH1Zh/g= gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= -modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= -modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= -modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4= -modernc.org/ccgo/v4 v4.21.0/go.mod h1:h6kt6H/A2+ew/3MW/p6KEoQmrq/i3pr0J/SiwiaF/g0= +modernc.org/cc/v4 v4.25.2 h1:T2oH7sZdGvTaie0BRNFbIYsabzCxUQg8nLqCdQ2i0ic= +modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.25.1 h1:TFSzPrAGmDsdnhT9X2UrcPMI3N/mJ9/X9ykKXwLhDsU= +modernc.org/ccgo/v4 v4.25.1/go.mod h1:njjuAYiPflywOOrm3B7kCB444ONP5pAVr8PIEoE0uDw= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= -modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M= -modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= -modernc.org/libc v1.60.1 h1:at373l8IFRTkJIkAU85BIuUoBM4T1b51ds0E1ovPG2s= -modernc.org/libc v1.60.1/go.mod h1:xJuobKuNxKH3RUatS7GjR+suWj+5c2K7bi4m/S5arOY= -modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= -modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= -modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= -modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= -modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= -modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= -modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM= -modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k= -modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= -modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/libc v1.62.1 h1:s0+fv5E3FymN8eJVmnk0llBe6rOxCu/DEU+XygRbS8s= +modernc.org/libc v1.62.1/go.mod h1:iXhATfJQLjG3NWy56a6WVU73lWOcdYVxsvwCgoPljuo= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.9.1 h1:V/Z1solwAVmMW1yttq3nDdZPJqV1rM05Ccq6KMSZ34g= +modernc.org/memory v1.9.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI= +modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= From 5f9c0f24ab63a4ad089983e3a5563ecc7e4ae0e0 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Wed, 9 Apr 2025 22:11:59 +0200 Subject: [PATCH 14/52] Updated dependencies --- go.mod | 10 +++++----- go.sum | 28 ++++++++++++++-------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/go.mod b/go.mod index 051277f..51a40fa 100644 --- a/go.mod +++ b/go.mod @@ -14,15 +14,15 @@ require ( github.com/go-resty/resty/v2 v2.16.5 github.com/jarcoal/httpmock v1.3.1 github.com/manifoldco/promptui v0.9.0 - github.com/pelletier/go-toml/v2 v2.2.3 + github.com/pelletier/go-toml/v2 v2.2.4 github.com/shkh/lastfm-go v0.0.0-20191215035245-89a801c244e0 github.com/spf13/cast v1.7.1 github.com/spf13/cobra v1.9.1 github.com/spf13/viper v1.20.1 github.com/stretchr/testify v1.10.0 github.com/vbauerster/mpb/v8 v8.9.3 - go.uploadedlobster.com/mbtypes v0.2.0 - golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 + go.uploadedlobster.com/mbtypes v0.4.0 + golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 golang.org/x/oauth2 v0.29.0 golang.org/x/text v0.24.0 gorm.io/datatypes v1.2.5 @@ -38,7 +38,7 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/glebarez/go-sqlite v1.22.0 // indirect - github.com/go-sql-driver/mysql v1.9.1 // indirect + github.com/go-sql-driver/mysql v1.9.2 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -57,7 +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 - golang.org/x/net v0.38.0 // indirect + golang.org/x/net v0.39.0 // indirect golang.org/x/sys v0.32.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/driver/mysql v1.5.7 // indirect diff --git a/go.sum b/go.sum index c1692a4..511ace4 100644 --- a/go.sum +++ b/go.sum @@ -43,8 +43,8 @@ github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7b github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM= github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI= -github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU= +github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= @@ -93,8 +93,8 @@ github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= @@ -129,16 +129,16 @@ github.com/vbauerster/mpb/v8 v8.9.3 h1:PnMeF+sMvYv9u23l6DO6Q3+Mdj408mjLRXIzmUmU2 github.com/vbauerster/mpb/v8 v8.9.3/go.mod h1:hxS8Hz4C6ijnppDSIX6LjG8FYJSoPo9iIOcE53Zik0c= 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.2.0 h1:sV9WM5fY8KnnKwKKVRaU3JBEKP4zD7aUX0XQfNZvEdo= -go.uploadedlobster.com/mbtypes v0.2.0/go.mod h1:/ZpwXc8oRpDa7EWeGI9xEY+nGhMIVHhTruikZWD4Krg= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= -golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= -golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= +go.uploadedlobster.com/mbtypes v0.4.0 h1:D5asCgHsRWufj4Yn5u0IuH2J9z1UuYImYkYIp1Z1Q7s= +go.uploadedlobster.com/mbtypes v0.4.0/go.mod h1:Bu1K1Hl77QTAE2Z7QKiW/JAp9KqYWQebkRRfG02dlZM= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= +golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= @@ -152,8 +152,8 @@ golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= -golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= +golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= +golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 329f696b553fea2ab3b14c7faaace086bdf6b03d Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Wed, 9 Apr 2025 22:30:06 +0200 Subject: [PATCH 15/52] Manage gotext as a tool with go.mod --- go.mod | 7 ++++++- internal/translations/translations.go | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 51a40fa..ee4e6f0 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module go.uploadedlobster.com/scotty go 1.23.0 -toolchain go1.23.8 +toolchain go1.24.2 require ( github.com/Xuanwo/go-locale v1.1.3 @@ -57,8 +57,11 @@ 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 + golang.org/x/mod v0.24.0 // indirect golang.org/x/net v0.39.0 // indirect + golang.org/x/sync v0.13.0 // indirect golang.org/x/sys v0.32.0 // indirect + golang.org/x/tools v0.32.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/driver/mysql v1.5.7 // indirect modernc.org/libc v1.62.1 // indirect @@ -66,3 +69,5 @@ require ( modernc.org/memory v1.9.1 // indirect modernc.org/sqlite v1.37.0 // indirect ) + +tool golang.org/x/text/cmd/gotext diff --git a/internal/translations/translations.go b/internal/translations/translations.go index c555d32..9961c41 100644 --- a/internal/translations/translations.go +++ b/internal/translations/translations.go @@ -6,4 +6,4 @@ package are published under the conditions of CC0 1.0 Universal (CC0 1.0) package translations -//go:generate gotext -srclang=en update -out=catalog.go -lang=en,de go.uploadedlobster.com/scotty +//go:generate go tool gotext -srclang=en update -out=catalog.go -lang=en,de go.uploadedlobster.com/scotty From 1ea90d2d2b0af02f66ea22902f775999fe1cf25d Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Wed, 9 Apr 2025 22:31:34 +0200 Subject: [PATCH 16/52] Update translation files --- internal/translations/catalog.go | 154 +++++++++--------- .../translations/locales/de/out.gotext.json | 26 +-- .../translations/locales/en/out.gotext.json | 12 +- 3 files changed, 98 insertions(+), 94 deletions(-) diff --git a/internal/translations/catalog.go b/internal/translations/catalog.go index 614b5b6..987612a 100644 --- a/internal/translations/catalog.go +++ b/internal/translations/catalog.go @@ -42,56 +42,56 @@ var messageKeyToIndex = map[string]int{ "\tbackend: %v": 11, "\texport: %s": 0, "\timport: %s\n": 1, - "%v: %v": 52, + "%v: %v": 47, "Aborted": 8, "Access token": 19, - "Access token received, you can use %v now.\n": 28, + "Access token received, you can use %v now.\n": 33, "Append to file": 21, - "Backend": 36, - "Check for duplicate listens on import (slower)": 45, + "Backend": 41, + "Check for duplicate listens on import (slower)": 24, "Client ID": 15, "Client secret": 16, "Delete the service configuration \"%v\"?": 7, - "Directory path": 47, - "Disable auto correction of submitted listens": 24, - "Error: OAuth state mismatch": 27, + "Directory path": 27, + "Disable auto correction of submitted listens": 25, + "Error: OAuth state mismatch": 32, "Failed reading config: %v": 2, "File path": 20, - "From timestamp: %v (%v)": 38, - "Ignore listens in incognito mode": 48, - "Ignore skipped listens": 49, - "Ignored duplicate listen %v: \"%v\" by %v (%v)": 46, - "Import failed, last reported timestamp was %v (%s)": 39, - "Import log:": 51, - "Imported %v of %v %s into %v.": 40, - "Include skipped listens": 25, - "Latest timestamp: %v (%v)": 41, - "Minimum playback duration for skipped tracks (seconds)": 50, - "No": 33, + "From timestamp: %v (%v)": 43, + "Ignore listens in incognito mode": 28, + "Ignore skipped listens": 29, + "Ignored duplicate listen %v: \"%v\" by %v (%v)": 53, + "Import failed, last reported timestamp was %v (%s)": 44, + "Import log:": 46, + "Imported %v of %v %s into %v.": 45, + "Include skipped listens": 26, + "Latest timestamp: %v (%v)": 49, + "Minimum playback duration for skipped tracks (seconds)": 30, + "No": 38, "Playlist title": 22, "Saved service %v using backend %v": 5, "Server URL": 17, - "Service": 35, + "Service": 40, "Service \"%v\" deleted\n": 9, "Service name": 3, "The backend %v requires authentication. Authenticate now?": 6, "Token received, you can close this window now.": 12, - "Transferring %s from %s to %s...": 37, + "Transferring %s from %s to %s...": 42, "Unique playlist identifier": 23, "Updated service %v using backend %v\n": 10, "User name": 18, - "Visit the URL for authorization: %v": 26, - "Yes": 32, + "Visit the URL for authorization: %v": 31, + "Yes": 37, "a service with this name already exists": 4, "backend %s does not implement %s": 13, - "done": 31, - "exporting": 29, - "importing": 30, - "invalid timestamp string \"%v\"": 53, - "key must only consist of A-Za-z0-9_-": 43, - "no configuration file defined, cannot write config": 42, - "no existing service configurations": 34, - "no service configuration \"%v\"": 44, + "done": 36, + "exporting": 34, + "importing": 35, + "invalid timestamp string \"%v\"": 48, + "key must only consist of A-Za-z0-9_-": 51, + "no configuration file defined, cannot write config": 50, + "no existing service configurations": 39, + "no service configuration \"%v\"": 52, "unknown backend \"%s\"": 14, } @@ -103,18 +103,18 @@ var deIndex = []uint32{ // 55 elements 0x000001ac, 0x000001e7, 0x00000213, 0x00000233, 0x0000023d, 0x0000024b, 0x00000256, 0x00000263, 0x00000271, 0x0000027b, 0x0000028e, 0x000002a1, - 0x000002b8, 0x000002ec, 0x0000030d, 0x00000333, - 0x0000035d, 0x0000039d, 0x000003a8, 0x000003b3, + 0x000002b8, 0x000002ed, 0x00000321, 0x00000342, + 0x00000352, 0x00000378, 0x0000039a, 0x000003d8, // Entry 20 - 3F - 0x000003ba, 0x000003bd, 0x000003c2, 0x000003eb, - 0x000003f3, 0x000003fb, 0x00000424, 0x00000442, - 0x0000047f, 0x000004aa, 0x000004cd, 0x0000051e, - 0x00000555, 0x0000057c, 0x0000057c, 0x0000057c, - 0x0000057c, 0x0000057c, 0x0000057c, 0x0000057c, - 0x0000057c, 0x0000057c, 0x0000057c, + 0x000003fe, 0x00000428, 0x00000468, 0x00000473, + 0x0000047e, 0x00000485, 0x00000488, 0x0000048d, + 0x000004b6, 0x000004be, 0x000004c6, 0x000004ef, + 0x0000050d, 0x0000054a, 0x00000575, 0x00000580, + 0x0000058d, 0x000005b1, 0x000005d4, 0x00000625, + 0x0000065c, 0x00000683, 0x00000683, } // Size: 244 bytes -const deData string = "" + // Size: 1404 bytes +const deData string = "" + // Size: 1667 bytes "\x04\x01\x09\x00\x0e\x02Export: %[1]s\x04\x01\x09\x01\x0a\x0e\x02Import:" + " %[1]s\x02Fehler beim Lesen der Konfiguration: %[1]v\x02Servicename\x02e" + "in Service mit diesem Namen existiert bereits\x02Service %[1]v mit dem B" + @@ -126,18 +126,22 @@ const deData string = "" + // Size: 1404 bytes " geschlossen werden.\x02das backend %[1]s implementiert %[2]s nicht\x02u" + "nbekanntes Backend „%[1]s“\x02Client-ID\x02Client-Secret\x02Server-URL" + "\x02Benutzername\x02Zugriffstoken\x02Dateipfad\x02An Datei anhängen\x02T" + - "itel der Playlist\x02Eindeutige Playlist-ID\x02Autokorrektur für übermit" + - "telte Titel deaktivieren\x02Übersprungene Titel einbeziehen\x02URL für A" + - "utorisierung öffnen: %[1]v\x02Fehler: OAuth-State stimmt nicht überein" + - "\x04\x00\x01\x0a;\x02Zugriffstoken erhalten, %[1]v kann jetzt verwendet " + - "werden.\x02exportiere\x02importiere\x02fertig\x02Ja\x02Nein\x02keine bes" + - "tehenden Servicekonfigurationen\x02Service\x02Backend\x02Übertrage %[1]s" + - " von %[2]s nach %[3]s...\x02Ab Zeitstempel: %[1]v (%[2]v)\x02Import fehl" + - "geschlagen, letzter Zeitstempel war %[1]v (%[2]s)\x02%[1]v von %[2]v %[3" + - "]s in %[4]v importiert.\x02Letzter Zeitstempel: %[1]v (%[2]v)\x02keine K" + - "onfigurationsdatei definiert, Konfiguration kann nicht geschrieben werde" + - "n\x02Schlüssel darf nur die Zeichen A-Za-z0-9_- beinhalten\x02keine Serv" + - "icekonfiguration „%[1]v“" + "itel der Playlist\x02Eindeutige Playlist-ID\x02Beim Import auf Listen-Du" + + "plikate prüfen (langsamer)\x02Autokorrektur für übermittelte Titel deakt" + + "ivieren\x02Übersprungene Titel einbeziehen\x02Verzeichnispfad\x02Listens" + + " im Inkognito-Modus ignorieren\x02Übersprungene Listens ignorieren\x02Mi" + + "nimale Wiedergabedauer für übersprungene Titel (Sekunden)\x02URL für Aut" + + "orisierung öffnen: %[1]v\x02Fehler: OAuth-State stimmt nicht überein\x04" + + "\x00\x01\x0a;\x02Zugriffstoken erhalten, %[1]v kann jetzt verwendet werd" + + "en.\x02exportiere\x02importiere\x02fertig\x02Ja\x02Nein\x02keine bestehe" + + "nden Servicekonfigurationen\x02Service\x02Backend\x02Übertrage %[1]s von" + + " %[2]s nach %[3]s...\x02Ab Zeitstempel: %[1]v (%[2]v)\x02Import fehlgesc" + + "hlagen, letzter Zeitstempel war %[1]v (%[2]s)\x02%[1]v von %[2]v %[3]s i" + + "n %[4]v importiert.\x02Importlog:\x02%[1]v: %[2]v\x02ungültiger Zeitstem" + + "pel „%[1]v“\x02Letzter Zeitstempel: %[1]v (%[2]v)\x02keine Konfiguration" + + "sdatei definiert, Konfiguration kann nicht geschrieben werden\x02Schlüss" + + "el darf nur die Zeichen A-Za-z0-9_- beinhalten\x02keine Servicekonfigura" + + "tion „%[1]v“" var enIndex = []uint32{ // 55 elements // Entry 0 - 1F @@ -147,15 +151,15 @@ var enIndex = []uint32{ // 55 elements 0x00000170, 0x0000019f, 0x000001c6, 0x000001de, 0x000001e8, 0x000001f6, 0x00000201, 0x0000020b, 0x00000218, 0x00000222, 0x00000231, 0x00000240, - 0x0000025b, 0x00000288, 0x000002a0, 0x000002c7, - 0x000002e3, 0x00000316, 0x00000320, 0x0000032a, + 0x0000025b, 0x0000028a, 0x000002b7, 0x000002cf, + 0x000002de, 0x000002ff, 0x00000316, 0x0000034d, // Entry 20 - 3F - 0x0000032f, 0x00000333, 0x00000336, 0x00000359, - 0x00000361, 0x00000369, 0x00000393, 0x000003b1, - 0x000003ea, 0x00000414, 0x00000434, 0x00000467, - 0x0000048c, 0x000004ad, 0x000004dc, 0x00000515, - 0x00000524, 0x00000545, 0x0000055c, 0x00000593, - 0x0000059f, 0x000005ac, 0x000005cd, + 0x00000374, 0x00000390, 0x000003c3, 0x000003cd, + 0x000003d7, 0x000003dc, 0x000003e0, 0x000003e3, + 0x00000406, 0x0000040e, 0x00000416, 0x00000440, + 0x0000045e, 0x00000497, 0x000004c1, 0x000004cd, + 0x000004da, 0x000004fb, 0x0000051b, 0x0000054e, + 0x00000573, 0x00000594, 0x000005cd, } // Size: 244 bytes const enData string = "" + // Size: 1485 bytes @@ -169,20 +173,20 @@ const enData string = "" + // Size: 1485 bytes "eceived, you can close this window now.\x02backend %[1]s does not implem" + "ent %[2]s\x02unknown backend \x22%[1]s\x22\x02Client ID\x02Client secret" + "\x02Server URL\x02User name\x02Access token\x02File path\x02Append to fi" + - "le\x02Playlist title\x02Unique playlist identifier\x02Disable auto corre" + - "ction of submitted listens\x02Include skipped listens\x02Visit the URL f" + - "or authorization: %[1]v\x02Error: OAuth state mismatch\x04\x00\x01\x0a." + - "\x02Access token received, you can use %[1]v now.\x02exporting\x02import" + - "ing\x02done\x02Yes\x02No\x02no existing service configurations\x02Servic" + - "e\x02Backend\x02Transferring %[1]s from %[2]s to %[3]s...\x02From timest" + - "amp: %[1]v (%[2]v)\x02Import failed, last reported timestamp was %[1]v (" + - "%[2]s)\x02Imported %[1]v of %[2]v %[3]s into %[4]v.\x02Latest timestamp:" + - " %[1]v (%[2]v)\x02no configuration file defined, cannot write config\x02" + - "key must only consist of A-Za-z0-9_-\x02no service configuration \x22%[1" + - "]v\x22\x02Check for duplicate listens on import (slower)\x02Ignored dupl" + - "icate listen %[1]v: \x22%[2]v\x22 by %[3]v (%[4]v)\x02Directory path\x02" + - "Ignore listens in incognito mode\x02Ignore skipped listens\x02Minimum pl" + - "ayback duration for skipped tracks (seconds)\x02Import log:\x02%[1]v: %[" + - "2]v\x02invalid timestamp string \x22%[1]v\x22" + "le\x02Playlist title\x02Unique playlist identifier\x02Check for duplicat" + + "e listens on import (slower)\x02Disable auto correction of submitted lis" + + "tens\x02Include skipped listens\x02Directory path\x02Ignore listens in i" + + "ncognito mode\x02Ignore skipped listens\x02Minimum playback duration for" + + " skipped tracks (seconds)\x02Visit the URL for authorization: %[1]v\x02E" + + "rror: OAuth state mismatch\x04\x00\x01\x0a.\x02Access token received, yo" + + "u can use %[1]v now.\x02exporting\x02importing\x02done\x02Yes\x02No\x02n" + + "o existing service configurations\x02Service\x02Backend\x02Transferring " + + "%[1]s from %[2]s to %[3]s...\x02From timestamp: %[1]v (%[2]v)\x02Import " + + "failed, last reported timestamp was %[1]v (%[2]s)\x02Imported %[1]v of %" + + "[2]v %[3]s into %[4]v.\x02Import log:\x02%[1]v: %[2]v\x02invalid timesta" + + "mp string \x22%[1]v\x22\x02Latest timestamp: %[1]v (%[2]v)\x02no configu" + + "ration file defined, cannot write config\x02key must only consist of A-Z" + + "a-z0-9_-\x02no service configuration \x22%[1]v\x22\x02Ignored duplicate " + + "listen %[1]v: \x22%[2]v\x22 by %[3]v (%[4]v)" - // Total table size 3377 bytes (3KiB); checksum: 6715024 + // Total table size 3640 bytes (3KiB); checksum: 719A868A diff --git a/internal/translations/locales/de/out.gotext.json b/internal/translations/locales/de/out.gotext.json index 3d84fe8..18333c6 100644 --- a/internal/translations/locales/de/out.gotext.json +++ b/internal/translations/locales/de/out.gotext.json @@ -258,11 +258,11 @@ { "id": "Check for duplicate listens on import (slower)", "message": "Check for duplicate listens on import (slower)", - "translation": "" + "translation": "Beim Import auf Listen-Duplikate prüfen (langsamer)" }, { - "id": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMbid})", - "message": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMbid})", + "id": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMBID})", + "message": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMBID})", "translation": "", "placeholders": [ { @@ -290,12 +290,12 @@ "expr": "l.ArtistName()" }, { - "id": "RecordingMbid", + "id": "RecordingMBID", "string": "%[4]v", - "type": "go.uploadedlobster.com/scotty/internal/models.MBID", + "type": "go.uploadedlobster.com/mbtypes.MBID", "underlyingType": "string", "argNum": 4, - "expr": "l.RecordingMbid" + "expr": "l.RecordingMBID" } ] }, @@ -312,22 +312,22 @@ { "id": "Directory path", "message": "Directory path", - "translation": "" + "translation": "Verzeichnispfad" }, { "id": "Ignore listens in incognito mode", "message": "Ignore listens in incognito mode", - "translation": "" + "translation": "Listens im Inkognito-Modus ignorieren" }, { "id": "Ignore skipped listens", "message": "Ignore skipped listens", - "translation": "" + "translation": "Übersprungene Listens ignorieren" }, { "id": "Minimum playback duration for skipped tracks (seconds)", "message": "Minimum playback duration for skipped tracks (seconds)", - "translation": "" + "translation": "Minimale Wiedergabedauer für übersprungene Titel (Sekunden)" }, { "id": "Visit the URL for authorization: {Url}", @@ -525,12 +525,12 @@ { "id": "Import log:", "message": "Import log:", - "translation": "" + "translation": "Importlog:" }, { "id": "{Type}: {Message}", "message": "{Type}: {Message}", - "translation": "", + "translation": "{Type}: {Message}", "placeholders": [ { "id": "Type", @@ -553,7 +553,7 @@ { "id": "invalid timestamp string \"{FlagValue}\"", "message": "invalid timestamp string \"{FlagValue}\"", - "translation": "", + "translation": "ungültiger Zeitstempel „{FlagValue}“", "placeholders": [ { "id": "FlagValue", diff --git a/internal/translations/locales/en/out.gotext.json b/internal/translations/locales/en/out.gotext.json index 7687276..54c6719 100644 --- a/internal/translations/locales/en/out.gotext.json +++ b/internal/translations/locales/en/out.gotext.json @@ -311,9 +311,9 @@ "fuzzy": true }, { - "id": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMbid})", - "message": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMbid})", - "translation": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMbid})", + "id": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMBID})", + "message": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMBID})", + "translation": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMBID})", "translatorComment": "Copied from source.", "placeholders": [ { @@ -341,12 +341,12 @@ "expr": "l.ArtistName()" }, { - "id": "RecordingMbid", + "id": "RecordingMBID", "string": "%[4]v", - "type": "go.uploadedlobster.com/scotty/internal/models.MBID", + "type": "go.uploadedlobster.com/mbtypes.MBID", "underlyingType": "string", "argNum": 4, - "expr": "l.RecordingMbid" + "expr": "l.RecordingMBID" } ], "fuzzy": true From 01e7569051f867b7fcf3fc3c19deb4992edb9f71 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Sun, 27 Apr 2025 13:26:47 +0200 Subject: [PATCH 17/52] Fixed progress for subsonic loves export --- internal/backends/subsonic/subsonic.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/internal/backends/subsonic/subsonic.go b/internal/backends/subsonic/subsonic.go index 6a59630..ed33214 100644 --- a/internal/backends/subsonic/subsonic.go +++ b/internal/backends/subsonic/subsonic.go @@ -78,8 +78,11 @@ func (b *SubsonicApiBackend) ExportLoves(oldestTimestamp time.Time, results chan return } - progress <- models.Progress{Elapsed: int64(len(starred.Song))}.Complete() - results <- models.LovesResult{Items: b.filterSongs(starred.Song, oldestTimestamp)} + loves := b.filterSongs(starred.Song, oldestTimestamp) + progress <- models.Progress{ + Total: int64(loves.Len()), + }.Complete() + results <- models.LovesResult{Items: loves} } func (b *SubsonicApiBackend) filterSongs(songs []*subsonic.Child, oldestTimestamp time.Time) models.LovesList { From da6c920789639c95f9111f70d6809894b2d4f2f0 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Sun, 27 Apr 2025 16:55:14 +0200 Subject: [PATCH 18/52] ListenBrainz: Fix loves import loading all existing loves Fixes import if the user had more than 1000 loves already --- .../backends/listenbrainz/listenbrainz.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/internal/backends/listenbrainz/listenbrainz.go b/internal/backends/listenbrainz/listenbrainz.go index 97f721d..e9c407b 100644 --- a/internal/backends/listenbrainz/listenbrainz.go +++ b/internal/backends/listenbrainz/listenbrainz.go @@ -233,15 +233,18 @@ func (b *ListenBrainzApiBackend) ImportLoves(export models.LovesResult, importRe if len(b.existingMBIDs) == 0 { existingLovesChan := make(chan models.LovesResult) go b.ExportLoves(time.Unix(0, 0), existingLovesChan, progress) - existingLoves := <-existingLovesChan - if existingLoves.Error != nil { - return importResult, existingLoves.Error - } // TODO: Store MBIDs directly - b.existingMBIDs = make(map[mbtypes.MBID]bool, len(existingLoves.Items)) - for _, love := range existingLoves.Items { - b.existingMBIDs[love.RecordingMBID] = true + b.existingMBIDs = make(map[mbtypes.MBID]bool, MaxItemsPerGet) + + for existingLoves := range existingLovesChan { + if existingLoves.Error != nil { + return importResult, existingLoves.Error + } + + for _, love := range existingLoves.Items { + b.existingMBIDs[love.RecordingMBID] = true + } } } @@ -268,6 +271,8 @@ func (b *ListenBrainzApiBackend) ImportLoves(export models.LovesResult, importRe ok = err == nil && resp.Status == "ok" if err != nil { errMsg = err.Error() + } else { + b.existingMBIDs[recordingMBID] = true } } From 2d66d41873fd4852718d545b876d5a09379abb7a Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Sun, 27 Apr 2025 16:57:30 +0200 Subject: [PATCH 19/52] ListenBrainz: Fix love import progress Exporting existing loves must not mark the progress as completed. --- internal/backends/listenbrainz/listenbrainz.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/internal/backends/listenbrainz/listenbrainz.go b/internal/backends/listenbrainz/listenbrainz.go index e9c407b..e3fe7cc 100644 --- a/internal/backends/listenbrainz/listenbrainz.go +++ b/internal/backends/listenbrainz/listenbrainz.go @@ -188,6 +188,10 @@ func (b *ListenBrainzApiBackend) ImportListens(export models.ListensResult, impo } func (b *ListenBrainzApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress) { + b.exportLoves(oldestTimestamp, results, progress, false) +} + +func (b *ListenBrainzApiBackend) exportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress, completeProgress bool) { offset := 0 defer close(results) loves := make(models.LovesList, 0, 2*MaxItemsPerGet) @@ -225,14 +229,16 @@ out: } sort.Sort(loves) - progress <- p.Complete() + if completeProgress { + progress <- p.Complete() + } results <- models.LovesResult{Items: loves} } func (b *ListenBrainzApiBackend) ImportLoves(export models.LovesResult, importResult models.ImportResult, progress chan models.Progress) (models.ImportResult, error) { if len(b.existingMBIDs) == 0 { existingLovesChan := make(chan models.LovesResult) - go b.ExportLoves(time.Unix(0, 0), existingLovesChan, progress) + go b.exportLoves(time.Unix(0, 0), existingLovesChan, progress, false) // TODO: Store MBIDs directly b.existingMBIDs = make(map[mbtypes.MBID]bool, MaxItemsPerGet) From bed60c7cdf1b059788d41177541192f1502aa56c Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Sun, 27 Apr 2025 17:22:29 +0200 Subject: [PATCH 20/52] Update dependencies --- go.mod | 6 +++--- go.sum | 24 ++++++++++++------------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index ee4e6f0..cab478a 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( golang.org/x/oauth2 v0.29.0 golang.org/x/text v0.24.0 gorm.io/datatypes v1.2.5 - gorm.io/gorm v1.25.12 + gorm.io/gorm v1.26.0 ) require ( @@ -64,9 +64,9 @@ require ( golang.org/x/tools v0.32.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/driver/mysql v1.5.7 // indirect - modernc.org/libc v1.62.1 // indirect + modernc.org/libc v1.64.0 // indirect modernc.org/mathutil v1.7.1 // indirect - modernc.org/memory v1.9.1 // indirect + modernc.org/memory v1.10.0 // indirect modernc.org/sqlite v1.37.0 // indirect ) diff --git a/go.sum b/go.sum index 511ace4..40290fc 100644 --- a/go.sum +++ b/go.sum @@ -170,22 +170,22 @@ gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2e gorm.io/driver/sqlserver v1.5.4 h1:xA+Y1KDNspv79q43bPyjDMUgHoYHLhXYmdFcYPobg8g= gorm.io/driver/sqlserver v1.5.4/go.mod h1:+frZ/qYmuna11zHPlh5oc2O6ZA/lS88Keb0XSH1Zh/g= gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= -gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= -gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= -modernc.org/cc/v4 v4.25.2 h1:T2oH7sZdGvTaie0BRNFbIYsabzCxUQg8nLqCdQ2i0ic= -modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= -modernc.org/ccgo/v4 v4.25.1 h1:TFSzPrAGmDsdnhT9X2UrcPMI3N/mJ9/X9ykKXwLhDsU= -modernc.org/ccgo/v4 v4.25.1/go.mod h1:njjuAYiPflywOOrm3B7kCB444ONP5pAVr8PIEoE0uDw= -modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= -modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= +gorm.io/gorm v1.26.0 h1:9lqQVPG5aNNS6AyHdRiwScAVnXHg/L/Srzx55G5fOgs= +gorm.io/gorm v1.26.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= +modernc.org/cc/v4 v4.26.0 h1:QMYvbVduUGH0rrO+5mqF/PSPPRZNpRtg2CLELy7vUpA= +modernc.org/cc/v4 v4.26.0/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.26.0 h1:gVzXaDzGeBYJ2uXTOpR8FR7OlksDOe9jxnjhIKCsiTc= +modernc.org/ccgo/v4 v4.26.0/go.mod h1:Sem8f7TFUtVXkG2fiaChQtyyfkqhJBg/zjEJBkmuAVY= +modernc.org/fileutil v1.3.1 h1:8vq5fe7jdtEvoCf3Zf9Nm0Q05sH6kGx0Op2CPx1wTC8= +modernc.org/fileutil v1.3.1/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= -modernc.org/libc v1.62.1 h1:s0+fv5E3FymN8eJVmnk0llBe6rOxCu/DEU+XygRbS8s= -modernc.org/libc v1.62.1/go.mod h1:iXhATfJQLjG3NWy56a6WVU73lWOcdYVxsvwCgoPljuo= +modernc.org/libc v1.64.0 h1:U0k8BD2d3cD3e9I8RLcZgJBHAcsJzbXx5mKGSb5pyJA= +modernc.org/libc v1.64.0/go.mod h1:7m9VzGq7APssBTydds2zBcxGREwvIGpuUBaKTXdm2Qs= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= -modernc.org/memory v1.9.1 h1:V/Z1solwAVmMW1yttq3nDdZPJqV1rM05Ccq6KMSZ34g= -modernc.org/memory v1.9.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/memory v1.10.0 h1:fzumd51yQ1DxcOxSO+S6X7+QTuVU+n8/Aj7swYjFfC4= +modernc.org/memory v1.10.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= From 910056b0a67f07a422078ebb860958d45bdf16db Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Thu, 23 Nov 2023 09:57:35 +0100 Subject: [PATCH 21/52] Subsonic: Support for some OpenSubsonic tags Mainly this makes the MusicBrainz recording ID available --- go.mod | 2 ++ go.sum | 36 ++++++++++++++++++++++++++ internal/backends/subsonic/subsonic.go | 29 ++++++++++++++------- 3 files changed, 58 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index cab478a..79382fd 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/spf13/cobra v1.9.1 github.com/spf13/viper v1.20.1 github.com/stretchr/testify v1.10.0 + github.com/supersonic-app/go-subsonic v0.0.0-20241224013245-9b2841f3711d github.com/vbauerster/mpb/v8 v8.9.3 go.uploadedlobster.com/mbtypes v0.4.0 golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 @@ -57,6 +58,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 + golang.org/x/image v0.26.0 // indirect golang.org/x/mod v0.24.0 // indirect golang.org/x/net v0.39.0 // indirect golang.org/x/sync v0.13.0 // indirect diff --git a/go.sum b/go.sum index 40290fc..715e6c7 100644 --- a/go.sum +++ b/go.sum @@ -125,35 +125,71 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/supersonic-app/go-subsonic v0.0.0-20241224013245-9b2841f3711d h1:70+Nn7yh+cfeKqqXVTdpneFqXuvrBLyP7U6GVUsjTU4= +github.com/supersonic-app/go-subsonic v0.0.0-20241224013245-9b2841f3711d/go.mod h1:D+OWPXeD9owcdcoXATv5YPBGWxxVvn5k98rt5B4wMc4= github.com/vbauerster/mpb/v8 v8.9.3 h1:PnMeF+sMvYv9u23l6DO6Q3+Mdj408mjLRXIzmUmU2Z8= github.com/vbauerster/mpb/v8 v8.9.3/go.mod h1:hxS8Hz4C6ijnppDSIX6LjG8FYJSoPo9iIOcE53Zik0c= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 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= +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= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= +golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk= +golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY= +golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/backends/subsonic/subsonic.go b/internal/backends/subsonic/subsonic.go index ed33214..59d4719 100644 --- a/internal/backends/subsonic/subsonic.go +++ b/internal/backends/subsonic/subsonic.go @@ -21,7 +21,8 @@ import ( "sort" "time" - "github.com/delucks/go-subsonic" + "github.com/supersonic-app/go-subsonic/subsonic" + "go.uploadedlobster.com/mbtypes" "go.uploadedlobster.com/scotty/internal/config" "go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/models" @@ -99,15 +100,19 @@ func (b *SubsonicApiBackend) filterSongs(songs []*subsonic.Child, oldestTimestam } func SongAsLove(song subsonic.Child, username string) models.Love { + recordingMBID := mbtypes.MBID(song.MusicBrainzID) love := models.Love{ - UserName: username, - Created: song.Starred, + UserName: username, + Created: song.Starred, + RecordingMBID: recordingMBID, Track: models.Track{ - TrackName: song.Title, - ReleaseName: song.Album, - ArtistNames: []string{song.Artist}, - TrackNumber: song.Track, - DiscNumber: song.DiscNumber, + TrackName: song.Title, + ReleaseName: song.Album, + ArtistNames: []string{song.Artist}, + TrackNumber: song.Track, + DiscNumber: song.DiscNumber, + RecordingMBID: recordingMBID, + Tags: []string{}, AdditionalInfo: map[string]any{ "subsonic_id": song.ID, }, @@ -115,7 +120,13 @@ func SongAsLove(song subsonic.Child, username string) models.Love { }, } - if song.Genre != "" { + if len(song.Genres) > 0 { + genres := make([]string, 0, len(song.Genres)) + for _, genre := range song.Genres { + genres = append(genres, genre.Name) + } + love.Track.Tags = genres + } else if song.Genre != "" { love.Track.Tags = []string{song.Genre} } From 90bf51a00bee93bc988770c1a592d313b21235ca Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Sun, 27 Apr 2025 17:54:29 +0200 Subject: [PATCH 22/52] ListenBrainz: Log missing recording MBID on love import --- internal/backends/listenbrainz/listenbrainz.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/internal/backends/listenbrainz/listenbrainz.go b/internal/backends/listenbrainz/listenbrainz.go index e3fe7cc..bd09f85 100644 --- a/internal/backends/listenbrainz/listenbrainz.go +++ b/internal/backends/listenbrainz/listenbrainz.go @@ -256,6 +256,9 @@ func (b *ListenBrainzApiBackend) ImportLoves(export models.LovesResult, importRe for _, love := range export.Items { recordingMBID := love.RecordingMBID + if recordingMBID == "" { + recordingMBID = love.Track.RecordingMBID + } if recordingMBID == "" { lookup, err := b.client.Lookup(love.TrackName, love.ArtistName()) @@ -290,6 +293,10 @@ func (b *ListenBrainzApiBackend) ImportLoves(export models.LovesResult, importRe love.TrackName, love.ArtistName(), errMsg) importResult.Log(models.Error, msg) } + } else { + msg := fmt.Sprintf("Failed import of \"%s\" by %s: no recording MBID", + love.TrackName, love.ArtistName()) + importResult.Log(models.Error, msg) } progress <- models.Progress{}.FromImportResult(importResult) From 7c0774fb8d219a11f9053d9a9fd05b90fe31c7b2 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Sun, 27 Apr 2025 18:11:58 +0200 Subject: [PATCH 23/52] ListenBrainz: Fixed loves export --- .../backends/listenbrainz/listenbrainz.go | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/internal/backends/listenbrainz/listenbrainz.go b/internal/backends/listenbrainz/listenbrainz.go index bd09f85..c05943f 100644 --- a/internal/backends/listenbrainz/listenbrainz.go +++ b/internal/backends/listenbrainz/listenbrainz.go @@ -188,10 +188,27 @@ func (b *ListenBrainzApiBackend) ImportListens(export models.ListensResult, impo } func (b *ListenBrainzApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress) { - b.exportLoves(oldestTimestamp, results, progress, false) + defer close(results) + exportChan := make(chan models.LovesResult) + p := models.Progress{} + + go b.exportLoves(time.Unix(0, 0), exportChan) + for existingLoves := range exportChan { + if existingLoves.Error != nil { + progress <- p.Complete() + results <- models.LovesResult{Error: existingLoves.Error} + } + + p.Total = int64(existingLoves.Total) + p.Elapsed += int64(existingLoves.Items.Len()) + progress <- p + results <- existingLoves + } + + progress <- p.Complete() } -func (b *ListenBrainzApiBackend) exportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress, completeProgress bool) { +func (b *ListenBrainzApiBackend) exportLoves(oldestTimestamp time.Time, results chan models.LovesResult) { offset := 0 defer close(results) loves := make(models.LovesList, 0, 2*MaxItemsPerGet) @@ -201,7 +218,6 @@ out: for { result, err := b.client.GetFeedback(b.username, 1, offset) if err != nil { - progress <- p.Complete() results <- models.LovesResult{Error: err} return } @@ -216,7 +232,6 @@ out: if love.Created.Unix() > oldestTimestamp.Unix() { loves = append(loves, love) p.Elapsed += 1 - progress <- p } else { break out } @@ -229,16 +244,16 @@ out: } sort.Sort(loves) - if completeProgress { - progress <- p.Complete() + results <- models.LovesResult{ + Total: len(loves), + Items: loves, } - results <- models.LovesResult{Items: loves} } func (b *ListenBrainzApiBackend) ImportLoves(export models.LovesResult, importResult models.ImportResult, progress chan models.Progress) (models.ImportResult, error) { if len(b.existingMBIDs) == 0 { existingLovesChan := make(chan models.LovesResult) - go b.exportLoves(time.Unix(0, 0), existingLovesChan, progress, false) + go b.exportLoves(time.Unix(0, 0), existingLovesChan) // TODO: Store MBIDs directly b.existingMBIDs = make(map[mbtypes.MBID]bool, MaxItemsPerGet) From 20c9ada6ec9fa2562884a5bbaf2b4bb8f66b0544 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Sun, 27 Apr 2025 18:14:48 +0200 Subject: [PATCH 24/52] RecordingMsid -> RecordingMSID --- internal/backends/listenbrainz/models.go | 4 ++-- internal/models/models.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/backends/listenbrainz/models.go b/internal/backends/listenbrainz/models.go index 4102ee5..ada75d3 100644 --- a/internal/backends/listenbrainz/models.go +++ b/internal/backends/listenbrainz/models.go @@ -57,7 +57,7 @@ type ListenSubmission struct { type Listen struct { InsertedAt int64 `json:"inserted_at,omitempty"` ListenedAt int64 `json:"listened_at"` - RecordingMsid string `json:"recording_msid,omitempty"` + RecordingMSID string `json:"recording_msid,omitempty"` UserName string `json:"user_name,omitempty"` TrackMetadata Track `json:"track_metadata"` } @@ -94,7 +94,7 @@ type GetFeedbackResult struct { type Feedback struct { Created int64 `json:"created,omitempty"` RecordingMBID mbtypes.MBID `json:"recording_mbid,omitempty"` - RecordingMsid mbtypes.MBID `json:"recording_msid,omitempty"` + RecordingMSID mbtypes.MBID `json:"recording_msid,omitempty"` Score int `json:"score,omitempty"` TrackMetadata *Track `json:"track_metadata,omitempty"` UserName string `json:"user_id,omitempty"` diff --git a/internal/models/models.go b/internal/models/models.go index 0d5abf2..18e3b44 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -112,7 +112,7 @@ type Love struct { Created time.Time UserName string RecordingMBID mbtypes.MBID - RecordingMsid mbtypes.MBID + RecordingMSID mbtypes.MBID } type ListensList []Listen From db78bfe457afd7ca737dddf60c383d401d2548e0 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Sun, 27 Apr 2025 18:16:27 +0200 Subject: [PATCH 25/52] Fixed subsonic test --- internal/backends/subsonic/subsonic_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/backends/subsonic/subsonic_test.go b/internal/backends/subsonic/subsonic_test.go index c5bfe36..f6508c5 100644 --- a/internal/backends/subsonic/subsonic_test.go +++ b/internal/backends/subsonic/subsonic_test.go @@ -20,9 +20,9 @@ import ( "testing" "time" - go_subsonic "github.com/delucks/go-subsonic" "github.com/spf13/viper" "github.com/stretchr/testify/assert" + go_subsonic "github.com/supersonic-app/go-subsonic/subsonic" "go.uploadedlobster.com/scotty/internal/backends/subsonic" "go.uploadedlobster.com/scotty/internal/config" ) From 9e1c2d84359a5f584b4cb2888a915f5a3124fe74 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Sun, 27 Apr 2025 18:17:19 +0200 Subject: [PATCH 26/52] Remove github.com/delucks/go-subsonic from go.mod --- go.mod | 1 - go.sum | 2 -- 2 files changed, 3 deletions(-) diff --git a/go.mod b/go.mod index 79382fd..9871720 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,6 @@ require ( github.com/Xuanwo/go-locale v1.1.3 github.com/agnivade/levenshtein v1.2.1 github.com/cli/browser v1.3.0 - github.com/delucks/go-subsonic v0.0.0-20240806025900-2a743ec36238 github.com/fatih/color v1.18.0 github.com/glebarez/sqlite v1.11.0 github.com/go-resty/resty/v2 v2.16.5 diff --git a/go.sum b/go.sum index 715e6c7..a1aa433 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,6 @@ github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEf github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/delucks/go-subsonic v0.0.0-20240806025900-2a743ec36238 h1:uejyepOdHISrJTw7P84Y7yEC0FMyv1q3KNDRxWsviKw= -github.com/delucks/go-subsonic v0.0.0-20240806025900-2a743ec36238/go.mod h1:vnbEuj6Z20PLcHB4rrLQAOXGMjtULfMGhRVSFPcSdUo= github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo= github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= From 91f78d04dd9b6873eb19e554237f6772314f7a85 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Sun, 27 Apr 2025 18:56:09 +0200 Subject: [PATCH 27/52] ListenBrainz: Handle missing loves metadata for merged recordings If a loved recording MBID got merged into another recording, the love on ListenBrainz has no metadata. Lookup the metadata directly from MusicBrainz. --- go.mod | 1 + go.sum | 2 + .../backends/listenbrainz/listenbrainz.go | 44 +++++++++++++++++++ 3 files changed, 47 insertions(+) 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), From 4a30bdf9d9aa22de5ca378dd31a41d4ceecdbe2a Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Mon, 28 Apr 2025 08:03:33 +0200 Subject: [PATCH 28/52] Update go.mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index c13d7ae..22a3154 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/supersonic-app/go-subsonic v0.0.0-20241224013245-9b2841f3711d github.com/vbauerster/mpb/v8 v8.9.3 go.uploadedlobster.com/mbtypes v0.4.0 + go.uploadedlobster.com/musicbrainzws2 v0.13.1 golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 golang.org/x/oauth2 v0.29.0 golang.org/x/text v0.24.0 @@ -57,7 +58,6 @@ 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 From 9184d2c3cfcdc4c9ff68cea8cd64698cfd3da242 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Mon, 28 Apr 2025 08:08:01 +0200 Subject: [PATCH 29/52] Update changelog for next version --- CHANGES.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index d3ee1d7..11251cd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,14 @@ # Scotty Changelog +## 0.5.0 - (not yet released) +- ListenBrainz: handle missing loves metadata in case of merged recordings +- ListenBrainz: fix loves import loading all existing loves +- ListenBrainz: fixed progress for loves import +- ListenBrainz: log missing recording MBID on love import +- Subsonic: support OpenSubsonic fields for recording MBID and genres (#5) +- Subsonic: fixed progress for loves export + + ## 0.4.1 - 2024-09-16 - Subsonic: include `subsonic_id` as additional metadata - Deezer: fix artist and album ID URIs (#7) From 69665bc28680b09601ee7db10af211452d842415 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Mon, 28 Apr 2025 08:54:17 +0200 Subject: [PATCH 30/52] scrobblerlog: consider timezone from parsed file --- internal/backends/scrobblerlog/parser.go | 42 ++++++++++++++++++++---- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/internal/backends/scrobblerlog/parser.go b/internal/backends/scrobblerlog/parser.go index 1ef08f7..eeb603b 100644 --- a/internal/backends/scrobblerlog/parser.go +++ b/internal/backends/scrobblerlog/parser.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Philipp Wolfer +Copyright © 2023-2025 Philipp Wolfer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -38,6 +38,7 @@ type ScrobblerLog struct { Timezone string Client string Listens models.ListensList + location *time.Location } func Parse(data io.Reader, includeSkipped bool) (ScrobblerLog, error) { @@ -79,8 +80,7 @@ func Parse(data io.Reader, includeSkipped bool) (ScrobblerLog, error) { continue } - client := strings.Split(result.Client, " ")[0] - listen, err := rowToListen(row, client) + listen, err := result.rowToListen(row) if err != nil { return result, err } @@ -138,14 +138,19 @@ func ReadHeader(reader *bufio.Reader, log *ScrobblerLog) error { err = fmt.Errorf("not a scrobbler log file") } + // The timezone can be set to "UTC" or "UNKNOWN", if the device writing + // the log knows the time, but not the timezone. timezone, found := strings.CutPrefix(text, "#TZ/") if found { log.Timezone = timezone + log.location = locationFromTimezone(log.Timezone) + continue } client, found := strings.CutPrefix(text, "#CLIENT/") if found { log.Client = client + continue } } @@ -171,7 +176,7 @@ func WriteHeader(writer io.Writer, log *ScrobblerLog) error { return nil } -func rowToListen(row []string, client string) (models.Listen, error) { +func (l ScrobblerLog) rowToListen(row []string) (models.Listen, error) { var listen models.Listen trackNumber, err := strconv.Atoi(row[3]) if err != nil { @@ -183,11 +188,12 @@ func rowToListen(row []string, client string) (models.Listen, error) { return listen, err } - timestamp, err := strconv.Atoi(row[6]) + timestamp, err := strconv.ParseInt(row[6], 10, 64) if err != nil { return listen, err } + client := strings.Split(l.Client, " ")[0] listen = models.Listen{ Track: models.Track{ ArtistNames: []string{row[0]}, @@ -200,7 +206,7 @@ func rowToListen(row []string, client string) (models.Listen, error) { "media_player": client, }, }, - ListenedAt: time.Unix(int64(timestamp), 0), + ListenedAt: timeFromLocalTimestamp(timestamp, l.location), } if len(row) > 7 { @@ -209,3 +215,27 @@ func rowToListen(row []string, client string) (models.Listen, error) { return listen, nil } + +// Convert the timezone string from the header to a time.Location. +// Often this is set to "UNKNOWN" in the log file, in which case it defaults +// to UTC. +func locationFromTimezone(timezone string) *time.Location { + location, err := time.LoadLocation(timezone) + if err != nil { + return time.UTC + } + return location +} + +// Convert a Unix timestamp to a time.Time object, but treat the timestamp +// as being in the given location's timezone instead of UTC. +func timeFromLocalTimestamp(timestamp int64, location *time.Location) time.Time { + t := time.Unix(timestamp, 0) + + // The time is now in UTC. Get the offset to the requested timezone. + _, offset := t.In(location).Zone() + if offset != 0 { + t = t.Add(time.Duration(offset) * time.Second) + } + return t +} From aeb3a56982d5d2f9d46c3654c880ca829df0d322 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Tue, 29 Apr 2025 08:36:34 +0200 Subject: [PATCH 31/52] Moved scrobblerlog parsing to separate package --- .../backends/scrobblerlog/scrobblerlog.go | 19 +++--- .../backends => pkg}/scrobblerlog/parser.go | 61 ++++++++++++------- .../scrobblerlog/parser_test.go | 50 +++++++-------- 3 files changed, 74 insertions(+), 56 deletions(-) rename {internal/backends => pkg}/scrobblerlog/parser.go (78%) rename {internal/backends => pkg}/scrobblerlog/parser_test.go (80%) diff --git a/internal/backends/scrobblerlog/scrobblerlog.go b/internal/backends/scrobblerlog/scrobblerlog.go index 84cae88..bb05086 100644 --- a/internal/backends/scrobblerlog/scrobblerlog.go +++ b/internal/backends/scrobblerlog/scrobblerlog.go @@ -25,6 +25,7 @@ import ( "go.uploadedlobster.com/scotty/internal/config" "go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/models" + "go.uploadedlobster.com/scotty/pkg/scrobblerlog" ) type ScrobblerLogBackend struct { @@ -32,7 +33,7 @@ type ScrobblerLogBackend struct { includeSkipped bool append bool file *os.File - log ScrobblerLog + log scrobblerlog.ScrobblerLog } func (b *ScrobblerLogBackend) Name() string { return "scrobbler-log" } @@ -58,9 +59,9 @@ func (b *ScrobblerLogBackend) FromConfig(config *config.ServiceConfig) models.Ba b.filePath = config.GetString("file-path") b.includeSkipped = config.GetBool("include-skipped", false) b.append = config.GetBool("append", true) - b.log = ScrobblerLog{ - Timezone: "UNKNOWN", - Client: "Rockbox unknown $Revision$", + b.log = scrobblerlog.ScrobblerLog{ + TZ: scrobblerlog.TZ_UTC, + Client: "Rockbox unknown $Revision$", } return b } @@ -88,7 +89,7 @@ func (b *ScrobblerLogBackend) StartImport() error { } else { // Verify existing file is a scrobbler log reader := bufio.NewReader(file) - if err = ReadHeader(reader, &b.log); err != nil { + if err = b.log.ReadHeader(reader); err != nil { file.Close() return err } @@ -99,7 +100,7 @@ func (b *ScrobblerLogBackend) StartImport() error { } if !b.append { - if err = WriteHeader(file, &b.log); err != nil { + if err = b.log.WriteHeader(file); err != nil { file.Close() return err } @@ -124,21 +125,21 @@ func (b *ScrobblerLogBackend) ExportListens(oldestTimestamp time.Time, results c defer file.Close() - log, err := Parse(file, b.includeSkipped) + err = b.log.Parse(file, b.includeSkipped) if err != nil { progress <- models.Progress{}.Complete() results <- models.ListensResult{Error: err} return } - listens := log.Listens.NewerThan(oldestTimestamp) + listens := b.log.Listens.NewerThan(oldestTimestamp) sort.Sort(listens) progress <- models.Progress{Elapsed: int64(len(listens))}.Complete() results <- models.ListensResult{Items: listens} } func (b *ScrobblerLogBackend) ImportListens(export models.ListensResult, importResult models.ImportResult, progress chan models.Progress) (models.ImportResult, error) { - lastTimestamp, err := Write(b.file, export.Items) + lastTimestamp, err := b.log.Append(b.file, export.Items) if err != nil { return importResult, err } diff --git a/internal/backends/scrobblerlog/parser.go b/pkg/scrobblerlog/parser.go similarity index 78% rename from internal/backends/scrobblerlog/parser.go rename to pkg/scrobblerlog/parser.go index eeb603b..a200d05 100644 --- a/internal/backends/scrobblerlog/parser.go +++ b/pkg/scrobblerlog/parser.go @@ -19,6 +19,12 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + +// Package to parse and writer .scrobbler.log files as written by Rockbox. +// +// See +// - https://www.rockbox.org/wiki/LastFMLog +// - https://git.rockbox.org/cgit/rockbox.git/tree/apps/plugins/lastfm_scrobbler.c package scrobblerlog import ( @@ -34,22 +40,31 @@ import ( "go.uploadedlobster.com/scotty/internal/models" ) +// TZInfo is the timezone information in the header of the scrobbler log file. +// It can be "UTC" or "UNKNOWN", if the device writing the scrobbler log file +// knows the time, but not the timezone. +type TZInfo string + +const ( + TZ_UNKNOWN TZInfo = "UNKNOWN" + TZ_UTC TZInfo = "UTC" +) + +// Represents a scrobbler log file. type ScrobblerLog struct { - Timezone string + TZ TZInfo Client string Listens models.ListensList location *time.Location } -func Parse(data io.Reader, includeSkipped bool) (ScrobblerLog, error) { - result := ScrobblerLog{ - Listens: make(models.ListensList, 0), - } +func (l *ScrobblerLog) Parse(data io.Reader, includeSkipped bool) error { + l.Listens = make(models.ListensList, 0) reader := bufio.NewReader(data) - err := ReadHeader(reader, &result) + err := l.ReadHeader(reader) if err != nil { - return result, err + return err } tsvReader := csv.NewReader(reader) @@ -64,7 +79,7 @@ func Parse(data io.Reader, includeSkipped bool) (ScrobblerLog, error) { if err == io.EOF { break } else if err != nil { - return result, err + return err } // fmt.Printf("row: %v\n", row) @@ -72,7 +87,7 @@ func Parse(data io.Reader, includeSkipped bool) (ScrobblerLog, error) { // We consider only the last field (recording MBID) optional if len(row) < 7 { line, _ := tsvReader.FieldPos(0) - return result, fmt.Errorf("invalid record in scrobblerlog line %v", line) + return fmt.Errorf("invalid record in scrobblerlog line %v", line) } rating := row[5] @@ -80,18 +95,18 @@ func Parse(data io.Reader, includeSkipped bool) (ScrobblerLog, error) { continue } - listen, err := result.rowToListen(row) + listen, err := l.rowToListen(row) if err != nil { - return result, err + return err } - result.Listens = append(result.Listens, listen) + l.Listens = append(l.Listens, listen) } - return result, nil + return nil } -func Write(data io.Writer, listens models.ListensList) (lastTimestamp time.Time, err error) { +func (l *ScrobblerLog) Append(data io.Writer, listens models.ListensList) (lastTimestamp time.Time, err error) { tsvWriter := csv.NewWriter(data) tsvWriter.Comma = '\t' @@ -122,7 +137,7 @@ func Write(data io.Writer, listens models.ListensList) (lastTimestamp time.Time, return } -func ReadHeader(reader *bufio.Reader, log *ScrobblerLog) error { +func (l *ScrobblerLog) ReadHeader(reader *bufio.Reader) error { // Skip header for i := 0; i < 3; i++ { line, _, err := reader.ReadLine() @@ -142,14 +157,14 @@ func ReadHeader(reader *bufio.Reader, log *ScrobblerLog) error { // the log knows the time, but not the timezone. timezone, found := strings.CutPrefix(text, "#TZ/") if found { - log.Timezone = timezone - log.location = locationFromTimezone(log.Timezone) + l.TZ = TZInfo(timezone) + l.location = locationFromTimezone(l.TZ) continue } client, found := strings.CutPrefix(text, "#CLIENT/") if found { - log.Client = client + l.Client = client continue } } @@ -161,11 +176,11 @@ func ReadHeader(reader *bufio.Reader, log *ScrobblerLog) error { return nil } -func WriteHeader(writer io.Writer, log *ScrobblerLog) error { +func (l *ScrobblerLog) WriteHeader(writer io.Writer) error { headers := []string{ "#AUDIOSCROBBLER/1.1\n", - "#TZ/" + log.Timezone + "\n", - "#CLIENT/" + log.Client + "\n", + "#TZ/" + string(l.TZ) + "\n", + "#CLIENT/" + l.Client + "\n", } for _, line := range headers { _, err := writer.Write([]byte(line)) @@ -219,8 +234,8 @@ func (l ScrobblerLog) rowToListen(row []string) (models.Listen, error) { // Convert the timezone string from the header to a time.Location. // Often this is set to "UNKNOWN" in the log file, in which case it defaults // to UTC. -func locationFromTimezone(timezone string) *time.Location { - location, err := time.LoadLocation(timezone) +func locationFromTimezone(timezone TZInfo) *time.Location { + location, err := time.LoadLocation(string(timezone)) if err != nil { return time.UTC } diff --git a/internal/backends/scrobblerlog/parser_test.go b/pkg/scrobblerlog/parser_test.go similarity index 80% rename from internal/backends/scrobblerlog/parser_test.go rename to pkg/scrobblerlog/parser_test.go index 480481f..b70f408 100644 --- a/internal/backends/scrobblerlog/parser_test.go +++ b/pkg/scrobblerlog/parser_test.go @@ -31,8 +31,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uploadedlobster.com/mbtypes" - "go.uploadedlobster.com/scotty/internal/backends/scrobblerlog" "go.uploadedlobster.com/scotty/internal/models" + "go.uploadedlobster.com/scotty/pkg/scrobblerlog" ) var testScrobblerLog = `#AUDIOSCROBBLER/1.1 @@ -48,9 +48,10 @@ Teeth Agency You Don't Have To Live In Pain Wolfs Jam 2 107 L 1260359404 1262bea func TestParser(t *testing.T) { assert := assert.New(t) data := bytes.NewBufferString(testScrobblerLog) - result, err := scrobblerlog.Parse(data, true) + result := scrobblerlog.ScrobblerLog{} + err := result.Parse(data, true) require.NoError(t, err) - assert.Equal("UNKNOWN", result.Timezone) + assert.Equal(scrobblerlog.TZ_UNKNOWN, result.TZ) assert.Equal("Rockbox sansaclipplus $Revision$", result.Client) assert.Len(result.Listens, 5) listen1 := result.Listens[0] @@ -70,7 +71,8 @@ func TestParser(t *testing.T) { func TestParserExcludeSkipped(t *testing.T) { assert := assert.New(t) data := bytes.NewBufferString(testScrobblerLog) - result, err := scrobblerlog.Parse(data, false) + result := scrobblerlog.ScrobblerLog{} + err := result.Parse(data, false) require.NoError(t, err) assert.Len(result.Listens, 4) listen4 := result.Listens[3] @@ -78,37 +80,37 @@ func TestParserExcludeSkipped(t *testing.T) { assert.Equal(mbtypes.MBID("1262beaf-19f8-4534-b9ed-7eef9ca8e83f"), listen4.RecordingMBID) } -func TestWrite(t *testing.T) { +func TestAppend(t *testing.T) { assert := assert.New(t) data := make([]byte, 0, 10) buffer := bytes.NewBuffer(data) log := scrobblerlog.ScrobblerLog{ - Timezone: "Unknown", - Client: "Rockbox foo $Revision$", - Listens: []models.Listen{ - { - ListenedAt: time.Unix(1699572072, 0), - Track: models.Track{ - ArtistNames: []string{"Prinzhorn Dance School"}, - ReleaseName: "Home Economics", - TrackName: "Reign", - TrackNumber: 1, - Duration: 271 * time.Second, - RecordingMBID: mbtypes.MBID("b59cf4e7-caee-4019-a844-79d2c58d4dff"), - AdditionalInfo: models.AdditionalInfo{"rockbox_rating": "L"}, - }, + TZ: scrobblerlog.TZ_UNKNOWN, + Client: "Rockbox foo $Revision$", + } + listens := []models.Listen{ + { + ListenedAt: time.Unix(1699572072, 0), + Track: models.Track{ + ArtistNames: []string{"Prinzhorn Dance School"}, + ReleaseName: "Home Economics", + TrackName: "Reign", + TrackNumber: 1, + Duration: 271 * time.Second, + RecordingMBID: mbtypes.MBID("b59cf4e7-caee-4019-a844-79d2c58d4dff"), + AdditionalInfo: models.AdditionalInfo{"rockbox_rating": "L"}, }, }, } - err := scrobblerlog.WriteHeader(buffer, &log) + err := log.WriteHeader(buffer) require.NoError(t, err) - lastTimestamp, err := scrobblerlog.Write(buffer, log.Listens) + lastTimestamp, err := log.Append(buffer, listens) require.NoError(t, err) result := buffer.String() lines := strings.Split(result, "\n") assert.Equal(5, len(lines)) assert.Equal("#AUDIOSCROBBLER/1.1", lines[0]) - assert.Equal("#TZ/Unknown", lines[1]) + assert.Equal("#TZ/UNKNOWN", lines[1]) assert.Equal("#CLIENT/Rockbox foo $Revision$", lines[2]) assert.Equal( "Prinzhorn Dance School\tHome Economics\tReign\t1\t271\tL\t1699572072\tb59cf4e7-caee-4019-a844-79d2c58d4dff", @@ -121,9 +123,9 @@ func TestReadHeader(t *testing.T) { data := bytes.NewBufferString(testScrobblerLog) reader := bufio.NewReader(data) log := scrobblerlog.ScrobblerLog{} - err := scrobblerlog.ReadHeader(reader, &log) + err := log.ReadHeader(reader) assert.NoError(t, err) - assert.Equal(t, log.Timezone, "UNKNOWN") + assert.Equal(t, log.TZ, scrobblerlog.TZ_UNKNOWN) assert.Equal(t, log.Client, "Rockbox sansaclipplus $Revision$") assert.Empty(t, log.Listens) } From aad542850a139177a8e451d1f32a976ed59db9a7 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Tue, 29 Apr 2025 09:18:57 +0200 Subject: [PATCH 32/52] scrobblerlog: Use specific Record type This makes the interface more generic and easier to reuse in other projects. --- .../backends/scrobblerlog/scrobblerlog.go | 54 ++++++++++- pkg/scrobblerlog/parser.go | 93 ++++++++++--------- pkg/scrobblerlog/parser_test.go | 61 ++++++------ 3 files changed, 132 insertions(+), 76 deletions(-) diff --git a/internal/backends/scrobblerlog/scrobblerlog.go b/internal/backends/scrobblerlog/scrobblerlog.go index bb05086..bf5afac 100644 --- a/internal/backends/scrobblerlog/scrobblerlog.go +++ b/internal/backends/scrobblerlog/scrobblerlog.go @@ -20,6 +20,7 @@ import ( "bufio" "os" "sort" + "strings" "time" "go.uploadedlobster.com/scotty/internal/config" @@ -132,14 +133,22 @@ func (b *ScrobblerLogBackend) ExportListens(oldestTimestamp time.Time, results c return } - listens := b.log.Listens.NewerThan(oldestTimestamp) - sort.Sort(listens) + listens := make(models.ListensList, 0, len(b.log.Records)) + client := strings.Split(b.log.Client, " ")[0] + for _, record := range b.log.Records { + listens = append(listens, recordToListen(record, client)) + } + sort.Sort(listens.NewerThan(oldestTimestamp)) progress <- models.Progress{Elapsed: int64(len(listens))}.Complete() results <- models.ListensResult{Items: listens} } func (b *ScrobblerLogBackend) ImportListens(export models.ListensResult, importResult models.ImportResult, progress chan models.Progress) (models.ImportResult, error) { - lastTimestamp, err := b.log.Append(b.file, export.Items) + records := make([]scrobblerlog.Record, len(export.Items)) + for i, listen := range export.Items { + records[i] = listenToRecord(listen) + } + lastTimestamp, err := b.log.Append(b.file, records) if err != nil { return importResult, err } @@ -150,3 +159,42 @@ func (b *ScrobblerLogBackend) ImportListens(export models.ListensResult, importR return importResult, nil } + +func recordToListen(record scrobblerlog.Record, client string) models.Listen { + return models.Listen{ + ListenedAt: record.Timestamp, + Track: models.Track{ + ArtistNames: []string{record.ArtistName}, + ReleaseName: record.AlbumName, + TrackName: record.TrackName, + TrackNumber: record.TrackNumber, + Duration: record.Duration, + RecordingMBID: record.MusicBrainzRecordingID, + AdditionalInfo: models.AdditionalInfo{ + "rockbox_rating": record.Rating, + "media_player": client, + }, + }, + } +} + +func listenToRecord(listen models.Listen) scrobblerlog.Record { + var rating scrobblerlog.Rating + rockboxRating, ok := listen.AdditionalInfo["rockbox_rating"].(string) + if !ok || rockboxRating == "" { + rating = scrobblerlog.RATING_LISTENED + } else { + rating = scrobblerlog.Rating(rating) + } + + return scrobblerlog.Record{ + ArtistName: listen.ArtistName(), + AlbumName: listen.ReleaseName, + TrackName: listen.TrackName, + TrackNumber: listen.TrackNumber, + Duration: listen.Duration, + Rating: rating, + Timestamp: listen.ListenedAt, + MusicBrainzRecordingID: listen.RecordingMBID, + } +} diff --git a/pkg/scrobblerlog/parser.go b/pkg/scrobblerlog/parser.go index a200d05..8f9b88a 100644 --- a/pkg/scrobblerlog/parser.go +++ b/pkg/scrobblerlog/parser.go @@ -37,7 +37,6 @@ import ( "time" "go.uploadedlobster.com/mbtypes" - "go.uploadedlobster.com/scotty/internal/models" ) // TZInfo is the timezone information in the header of the scrobbler log file. @@ -50,16 +49,36 @@ const ( TZ_UTC TZInfo = "UTC" ) +// L if listened at least 50% or S if skipped +type Rating string + +const ( + RATING_LISTENED Rating = "L" + RATING_SKIPPED Rating = "S" +) + +// A single entry of a track in the scrobbler log file. +type Record struct { + ArtistName string + AlbumName string + TrackName string + TrackNumber int + Duration time.Duration + Rating Rating + Timestamp time.Time + MusicBrainzRecordingID mbtypes.MBID +} + // Represents a scrobbler log file. type ScrobblerLog struct { TZ TZInfo Client string - Listens models.ListensList + Records []Record location *time.Location } func (l *ScrobblerLog) Parse(data io.Reader, includeSkipped bool) error { - l.Listens = make(models.ListensList, 0) + l.Records = make([]Record, 0) reader := bufio.NewReader(data) err := l.ReadHeader(reader) @@ -95,41 +114,37 @@ func (l *ScrobblerLog) Parse(data io.Reader, includeSkipped bool) error { continue } - listen, err := l.rowToListen(row) + record, err := l.rowToRecord(row) if err != nil { return err } - l.Listens = append(l.Listens, listen) + l.Records = append(l.Records, record) } return nil } -func (l *ScrobblerLog) Append(data io.Writer, listens models.ListensList) (lastTimestamp time.Time, err error) { +func (l *ScrobblerLog) Append(data io.Writer, records []Record) (lastTimestamp time.Time, err error) { tsvWriter := csv.NewWriter(data) tsvWriter.Comma = '\t' - for _, listen := range listens { - if listen.ListenedAt.Unix() > lastTimestamp.Unix() { - lastTimestamp = listen.ListenedAt + for _, record := range records { + if record.Timestamp.After(lastTimestamp) { + lastTimestamp = record.Timestamp } // A row is: // artistName releaseName trackName trackNumber duration rating timestamp recordingMBID - rating, ok := listen.AdditionalInfo["rockbox_rating"].(string) - if !ok || rating == "" { - rating = "L" - } err = tsvWriter.Write([]string{ - listen.ArtistName(), - listen.ReleaseName, - listen.TrackName, - strconv.Itoa(listen.TrackNumber), - strconv.Itoa(int(listen.Duration.Seconds())), - rating, - strconv.Itoa(int(listen.ListenedAt.Unix())), - string(listen.RecordingMBID), + record.ArtistName, + record.AlbumName, + record.TrackName, + strconv.Itoa(record.TrackNumber), + strconv.Itoa(int(record.Duration.Seconds())), + string(record.Rating), + strconv.FormatInt(record.Timestamp.Unix(), 10), + string(record.MusicBrainzRecordingID), }) } @@ -191,44 +206,38 @@ func (l *ScrobblerLog) WriteHeader(writer io.Writer) error { return nil } -func (l ScrobblerLog) rowToListen(row []string) (models.Listen, error) { - var listen models.Listen +func (l ScrobblerLog) rowToRecord(row []string) (Record, error) { + var record Record trackNumber, err := strconv.Atoi(row[3]) if err != nil { - return listen, err + return record, err } duration, err := strconv.Atoi(row[4]) if err != nil { - return listen, err + return record, err } timestamp, err := strconv.ParseInt(row[6], 10, 64) if err != nil { - return listen, err + return record, err } - client := strings.Split(l.Client, " ")[0] - listen = models.Listen{ - Track: models.Track{ - ArtistNames: []string{row[0]}, - ReleaseName: row[1], - TrackName: row[2], - TrackNumber: trackNumber, - Duration: time.Duration(duration * int(time.Second)), - AdditionalInfo: models.AdditionalInfo{ - "rockbox_rating": row[5], - "media_player": client, - }, - }, - ListenedAt: timeFromLocalTimestamp(timestamp, l.location), + record = Record{ + ArtistName: row[0], + AlbumName: row[1], + TrackName: row[2], + TrackNumber: trackNumber, + Duration: time.Duration(duration) * time.Second, + Rating: Rating(row[5]), + Timestamp: timeFromLocalTimestamp(timestamp, l.location), } if len(row) > 7 { - listen.Track.RecordingMBID = mbtypes.MBID(row[7]) + record.MusicBrainzRecordingID = mbtypes.MBID(row[7]) } - return listen, nil + return record, nil } // Convert the timezone string from the header to a time.Location. diff --git a/pkg/scrobblerlog/parser_test.go b/pkg/scrobblerlog/parser_test.go index b70f408..9b4513f 100644 --- a/pkg/scrobblerlog/parser_test.go +++ b/pkg/scrobblerlog/parser_test.go @@ -31,7 +31,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uploadedlobster.com/mbtypes" - "go.uploadedlobster.com/scotty/internal/models" "go.uploadedlobster.com/scotty/pkg/scrobblerlog" ) @@ -53,19 +52,20 @@ func TestParser(t *testing.T) { require.NoError(t, err) assert.Equal(scrobblerlog.TZ_UNKNOWN, result.TZ) assert.Equal("Rockbox sansaclipplus $Revision$", result.Client) - assert.Len(result.Listens, 5) - listen1 := result.Listens[0] - assert.Equal("Özcan Deniz", listen1.ArtistName()) - assert.Equal("Ses ve Ayrilik", listen1.ReleaseName) - assert.Equal("Sevdanin rengi (sipacik) byMrTurkey", listen1.TrackName) - assert.Equal(5, listen1.TrackNumber) - assert.Equal(time.Duration(306*time.Second), listen1.Duration) - assert.Equal("L", listen1.AdditionalInfo["rockbox_rating"]) - assert.Equal(time.Unix(1260342084, 0), listen1.ListenedAt) - assert.Equal(mbtypes.MBID(""), listen1.RecordingMBID) - listen4 := result.Listens[3] - assert.Equal("S", listen4.AdditionalInfo["rockbox_rating"]) - assert.Equal(mbtypes.MBID("385ba9e9-626d-4750-a607-58e541dca78e"), listen4.RecordingMBID) + assert.Len(result.Records, 5) + record1 := result.Records[0] + assert.Equal("Özcan Deniz", record1.ArtistName) + assert.Equal("Ses ve Ayrilik", record1.AlbumName) + assert.Equal("Sevdanin rengi (sipacik) byMrTurkey", record1.TrackName) + assert.Equal(5, record1.TrackNumber) + assert.Equal(time.Duration(306*time.Second), record1.Duration) + assert.Equal(scrobblerlog.RATING_LISTENED, record1.Rating) + assert.Equal(time.Unix(1260342084, 0), record1.Timestamp) + assert.Equal(mbtypes.MBID(""), record1.MusicBrainzRecordingID) + record4 := result.Records[3] + assert.Equal(scrobblerlog.RATING_SKIPPED, record4.Rating) + assert.Equal(mbtypes.MBID("385ba9e9-626d-4750-a607-58e541dca78e"), + record4.MusicBrainzRecordingID) } func TestParserExcludeSkipped(t *testing.T) { @@ -74,10 +74,11 @@ func TestParserExcludeSkipped(t *testing.T) { result := scrobblerlog.ScrobblerLog{} err := result.Parse(data, false) require.NoError(t, err) - assert.Len(result.Listens, 4) - listen4 := result.Listens[3] - assert.Equal("L", listen4.AdditionalInfo["rockbox_rating"]) - assert.Equal(mbtypes.MBID("1262beaf-19f8-4534-b9ed-7eef9ca8e83f"), listen4.RecordingMBID) + assert.Len(result.Records, 4) + record4 := result.Records[3] + assert.Equal(scrobblerlog.RATING_LISTENED, record4.Rating) + assert.Equal(mbtypes.MBID("1262beaf-19f8-4534-b9ed-7eef9ca8e83f"), + record4.MusicBrainzRecordingID) } func TestAppend(t *testing.T) { @@ -88,23 +89,21 @@ func TestAppend(t *testing.T) { TZ: scrobblerlog.TZ_UNKNOWN, Client: "Rockbox foo $Revision$", } - listens := []models.Listen{ + records := []scrobblerlog.Record{ { - ListenedAt: time.Unix(1699572072, 0), - Track: models.Track{ - ArtistNames: []string{"Prinzhorn Dance School"}, - ReleaseName: "Home Economics", - TrackName: "Reign", - TrackNumber: 1, - Duration: 271 * time.Second, - RecordingMBID: mbtypes.MBID("b59cf4e7-caee-4019-a844-79d2c58d4dff"), - AdditionalInfo: models.AdditionalInfo{"rockbox_rating": "L"}, - }, + ArtistName: "Prinzhorn Dance School", + AlbumName: "Home Economics", + TrackName: "Reign", + TrackNumber: 1, + Duration: 271 * time.Second, + Rating: scrobblerlog.RATING_LISTENED, + Timestamp: time.Unix(1699572072, 0), + MusicBrainzRecordingID: mbtypes.MBID("b59cf4e7-caee-4019-a844-79d2c58d4dff"), }, } err := log.WriteHeader(buffer) require.NoError(t, err) - lastTimestamp, err := log.Append(buffer, listens) + lastTimestamp, err := log.Append(buffer, records) require.NoError(t, err) result := buffer.String() lines := strings.Split(result, "\n") @@ -127,5 +126,5 @@ func TestReadHeader(t *testing.T) { assert.NoError(t, err) assert.Equal(t, log.TZ, scrobblerlog.TZ_UNKNOWN) assert.Equal(t, log.Client, "Rockbox sansaclipplus $Revision$") - assert.Empty(t, log.Listens) + assert.Empty(t, log.Records) } From 0f4b04c641c531cd0d9a2f42e30b2a32701b0bf5 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Tue, 29 Apr 2025 10:03:28 +0200 Subject: [PATCH 33/52] Renamed Backend.FromConfig to Backend.InitConfig and added error handling --- internal/backends/backends.go | 6 +++++- internal/backends/deezer/deezer.go | 4 ++-- internal/backends/deezer/deezer_test.go | 7 ++++--- internal/backends/dump/dump.go | 4 ++-- internal/backends/funkwhale/funkwhale.go | 4 ++-- internal/backends/funkwhale/funkwhale_test.go | 7 ++++--- internal/backends/jspf/jspf.go | 4 ++-- internal/backends/jspf/jspf_test.go | 7 ++++--- internal/backends/lastfm/lastfm.go | 4 ++-- internal/backends/listenbrainz/listenbrainz.go | 4 ++-- internal/backends/listenbrainz/listenbrainz_test.go | 7 ++++--- internal/backends/maloja/maloja.go | 4 ++-- internal/backends/maloja/maloja_test.go | 7 ++++--- internal/backends/scrobblerlog/scrobblerlog.go | 4 ++-- internal/backends/scrobblerlog/scrobblerlog_test.go | 7 ++++--- internal/backends/spotify/spotify.go | 4 ++-- internal/backends/spotify/spotify_test.go | 7 ++++--- internal/backends/spotifyhistory/spotifyhistory.go | 4 ++-- internal/backends/subsonic/subsonic.go | 4 ++-- internal/backends/subsonic/subsonic_test.go | 7 ++++--- internal/models/interfaces.go | 2 +- 21 files changed, 60 insertions(+), 48 deletions(-) diff --git a/internal/backends/backends.go b/internal/backends/backends.go index e4cbbc9..a9c3292 100644 --- a/internal/backends/backends.go +++ b/internal/backends/backends.go @@ -123,7 +123,11 @@ func backendWithConfig(config config.ServiceConfig) (models.Backend, error) { if err != nil { return nil, err } - return backend.FromConfig(&config), nil + err = backend.InitConfig(&config) + if err != nil { + return nil, err + } + return backend, nil } func ImplementsInterface[T interface{}](backend *models.Backend) (bool, string) { diff --git a/internal/backends/deezer/deezer.go b/internal/backends/deezer/deezer.go index 3131c3e..e7d9762 100644 --- a/internal/backends/deezer/deezer.go +++ b/internal/backends/deezer/deezer.go @@ -49,10 +49,10 @@ func (b *DeezerApiBackend) Options() []models.BackendOption { }} } -func (b *DeezerApiBackend) FromConfig(config *config.ServiceConfig) models.Backend { +func (b *DeezerApiBackend) InitConfig(config *config.ServiceConfig) error { b.clientId = config.GetString("client-id") b.clientSecret = config.GetString("client-secret") - return b + return nil } func (b *DeezerApiBackend) OAuth2Strategy(redirectUrl *url.URL) auth.OAuth2Strategy { diff --git a/internal/backends/deezer/deezer_test.go b/internal/backends/deezer/deezer_test.go index 9550c0e..19776f4 100644 --- a/internal/backends/deezer/deezer_test.go +++ b/internal/backends/deezer/deezer_test.go @@ -35,13 +35,14 @@ var ( testTrack []byte ) -func TestFromConfig(t *testing.T) { +func TestInitConfig(t *testing.T) { c := viper.New() c.Set("client-id", "someclientid") c.Set("client-secret", "someclientsecret") service := config.NewServiceConfig("test", c) - backend := (&deezer.DeezerApiBackend{}).FromConfig(&service) - assert.IsType(t, &deezer.DeezerApiBackend{}, backend) + backend := deezer.DeezerApiBackend{} + err := backend.InitConfig(&service) + assert.NoError(t, err) } func TestListenAsListen(t *testing.T) { diff --git a/internal/backends/dump/dump.go b/internal/backends/dump/dump.go index 728a774..70be12d 100644 --- a/internal/backends/dump/dump.go +++ b/internal/backends/dump/dump.go @@ -29,8 +29,8 @@ func (b *DumpBackend) Name() string { return "dump" } func (b *DumpBackend) Options() []models.BackendOption { return nil } -func (b *DumpBackend) FromConfig(config *config.ServiceConfig) models.Backend { - return b +func (b *DumpBackend) InitConfig(config *config.ServiceConfig) error { + return nil } func (b *DumpBackend) StartImport() error { return nil } diff --git a/internal/backends/funkwhale/funkwhale.go b/internal/backends/funkwhale/funkwhale.go index 48c3d8f..99bf43d 100644 --- a/internal/backends/funkwhale/funkwhale.go +++ b/internal/backends/funkwhale/funkwhale.go @@ -51,13 +51,13 @@ func (b *FunkwhaleApiBackend) Options() []models.BackendOption { }} } -func (b *FunkwhaleApiBackend) FromConfig(config *config.ServiceConfig) models.Backend { +func (b *FunkwhaleApiBackend) InitConfig(config *config.ServiceConfig) error { b.client = NewClient( config.GetString("server-url"), config.GetString("token"), ) b.username = config.GetString("username") - return b + return nil } func (b *FunkwhaleApiBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.Progress) { diff --git a/internal/backends/funkwhale/funkwhale_test.go b/internal/backends/funkwhale/funkwhale_test.go index d8654d8..93ab97b 100644 --- a/internal/backends/funkwhale/funkwhale_test.go +++ b/internal/backends/funkwhale/funkwhale_test.go @@ -27,12 +27,13 @@ import ( "go.uploadedlobster.com/scotty/internal/config" ) -func TestFromConfig(t *testing.T) { +func TestInitConfig(t *testing.T) { c := viper.New() c.Set("token", "thetoken") service := config.NewServiceConfig("test", c) - backend := (&funkwhale.FunkwhaleApiBackend{}).FromConfig(&service) - assert.IsType(t, &funkwhale.FunkwhaleApiBackend{}, backend) + backend := funkwhale.FunkwhaleApiBackend{} + err := backend.InitConfig(&service) + assert.NoError(t, err) } func TestFunkwhaleListeningAsListen(t *testing.T) { diff --git a/internal/backends/jspf/jspf.go b/internal/backends/jspf/jspf.go index bfa3892..152c810 100644 --- a/internal/backends/jspf/jspf.go +++ b/internal/backends/jspf/jspf.go @@ -60,7 +60,7 @@ func (b *JSPFBackend) Options() []models.BackendOption { }} } -func (b *JSPFBackend) FromConfig(config *config.ServiceConfig) models.Backend { +func (b *JSPFBackend) InitConfig(config *config.ServiceConfig) error { b.filePath = config.GetString("file-path") b.append = config.GetBool("append", true) b.playlist = jspf.Playlist{ @@ -75,7 +75,7 @@ func (b *JSPFBackend) FromConfig(config *config.ServiceConfig) models.Backend { }, }, } - return b + return nil } func (b *JSPFBackend) StartImport() error { diff --git a/internal/backends/jspf/jspf_test.go b/internal/backends/jspf/jspf_test.go index 31b5370..bf4f99d 100644 --- a/internal/backends/jspf/jspf_test.go +++ b/internal/backends/jspf/jspf_test.go @@ -26,13 +26,14 @@ import ( "go.uploadedlobster.com/scotty/internal/config" ) -func TestFromConfig(t *testing.T) { +func TestInitConfig(t *testing.T) { c := viper.New() c.Set("file-path", "/foo/bar.jspf") c.Set("title", "My Playlist") c.Set("username", "outsidecontext") c.Set("identifier", "http://example.com/playlist1") service := config.NewServiceConfig("test", c) - backend := (&jspf.JSPFBackend{}).FromConfig(&service) - assert.IsType(t, &jspf.JSPFBackend{}, backend) + backend := jspf.JSPFBackend{} + err := backend.InitConfig(&service) + assert.NoError(t, err) } diff --git a/internal/backends/lastfm/lastfm.go b/internal/backends/lastfm/lastfm.go index ba660de..2d4a9d5 100644 --- a/internal/backends/lastfm/lastfm.go +++ b/internal/backends/lastfm/lastfm.go @@ -61,12 +61,12 @@ func (b *LastfmApiBackend) Options() []models.BackendOption { }} } -func (b *LastfmApiBackend) FromConfig(config *config.ServiceConfig) models.Backend { +func (b *LastfmApiBackend) InitConfig(config *config.ServiceConfig) error { clientId := config.GetString("client-id") clientSecret := config.GetString("client-secret") b.client = lastfm.New(clientId, clientSecret) b.username = config.GetString("username") - return b + return nil } func (b *LastfmApiBackend) StartImport() error { return nil } diff --git a/internal/backends/listenbrainz/listenbrainz.go b/internal/backends/listenbrainz/listenbrainz.go index d13c869..49755c6 100644 --- a/internal/backends/listenbrainz/listenbrainz.go +++ b/internal/backends/listenbrainz/listenbrainz.go @@ -56,13 +56,13 @@ func (b *ListenBrainzApiBackend) Options() []models.BackendOption { }} } -func (b *ListenBrainzApiBackend) FromConfig(config *config.ServiceConfig) models.Backend { +func (b *ListenBrainzApiBackend) InitConfig(config *config.ServiceConfig) error { 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) - return b + return nil } func (b *ListenBrainzApiBackend) StartImport() error { return nil } diff --git a/internal/backends/listenbrainz/listenbrainz_test.go b/internal/backends/listenbrainz/listenbrainz_test.go index 93428d7..bf2e4d3 100644 --- a/internal/backends/listenbrainz/listenbrainz_test.go +++ b/internal/backends/listenbrainz/listenbrainz_test.go @@ -28,12 +28,13 @@ import ( "go.uploadedlobster.com/scotty/internal/config" ) -func TestFromConfig(t *testing.T) { +func TestInitConfig(t *testing.T) { c := viper.New() c.Set("token", "thetoken") service := config.NewServiceConfig("test", c) - backend := (&listenbrainz.ListenBrainzApiBackend{}).FromConfig(&service) - assert.IsType(t, &listenbrainz.ListenBrainzApiBackend{}, backend) + backend := listenbrainz.ListenBrainzApiBackend{} + err := backend.InitConfig(&service) + assert.NoError(t, err) } func TestListenBrainzListenAsListen(t *testing.T) { diff --git a/internal/backends/maloja/maloja.go b/internal/backends/maloja/maloja.go index 135bef3..e9e3348 100644 --- a/internal/backends/maloja/maloja.go +++ b/internal/backends/maloja/maloja.go @@ -51,13 +51,13 @@ func (b *MalojaApiBackend) Options() []models.BackendOption { }} } -func (b *MalojaApiBackend) FromConfig(config *config.ServiceConfig) models.Backend { +func (b *MalojaApiBackend) InitConfig(config *config.ServiceConfig) error { b.client = NewClient( config.GetString("server-url"), config.GetString("token"), ) b.nofix = config.GetBool("nofix", false) - return b + return nil } func (b *MalojaApiBackend) StartImport() error { return nil } diff --git a/internal/backends/maloja/maloja_test.go b/internal/backends/maloja/maloja_test.go index 52be58c..4a1f318 100644 --- a/internal/backends/maloja/maloja_test.go +++ b/internal/backends/maloja/maloja_test.go @@ -26,12 +26,13 @@ import ( "go.uploadedlobster.com/scotty/internal/config" ) -func TestFromConfig(t *testing.T) { +func TestInitConfig(t *testing.T) { c := viper.New() c.Set("token", "thetoken") service := config.NewServiceConfig("test", c) - backend := (&maloja.MalojaApiBackend{}).FromConfig(&service) - assert.IsType(t, &maloja.MalojaApiBackend{}, backend) + backend := maloja.MalojaApiBackend{} + err := backend.InitConfig(&service) + assert.NoError(t, err) } func TestScrobbleAsListen(t *testing.T) { diff --git a/internal/backends/scrobblerlog/scrobblerlog.go b/internal/backends/scrobblerlog/scrobblerlog.go index bf5afac..1fdfaff 100644 --- a/internal/backends/scrobblerlog/scrobblerlog.go +++ b/internal/backends/scrobblerlog/scrobblerlog.go @@ -56,7 +56,7 @@ func (b *ScrobblerLogBackend) Options() []models.BackendOption { }} } -func (b *ScrobblerLogBackend) FromConfig(config *config.ServiceConfig) models.Backend { +func (b *ScrobblerLogBackend) InitConfig(config *config.ServiceConfig) error { b.filePath = config.GetString("file-path") b.includeSkipped = config.GetBool("include-skipped", false) b.append = config.GetBool("append", true) @@ -64,7 +64,7 @@ func (b *ScrobblerLogBackend) FromConfig(config *config.ServiceConfig) models.Ba TZ: scrobblerlog.TZ_UTC, Client: "Rockbox unknown $Revision$", } - return b + return nil } func (b *ScrobblerLogBackend) StartImport() error { diff --git a/internal/backends/scrobblerlog/scrobblerlog_test.go b/internal/backends/scrobblerlog/scrobblerlog_test.go index 04e76c1..7a8ab14 100644 --- a/internal/backends/scrobblerlog/scrobblerlog_test.go +++ b/internal/backends/scrobblerlog/scrobblerlog_test.go @@ -25,10 +25,11 @@ import ( "go.uploadedlobster.com/scotty/internal/config" ) -func TestFromConfig(t *testing.T) { +func TestInitConfig(t *testing.T) { c := viper.New() c.Set("token", "thetoken") service := config.NewServiceConfig("test", c) - backend := (&scrobblerlog.ScrobblerLogBackend{}).FromConfig(&service) - assert.IsType(t, &scrobblerlog.ScrobblerLogBackend{}, backend) + backend := scrobblerlog.ScrobblerLogBackend{} + err := backend.InitConfig(&service) + assert.NoError(t, err) } diff --git a/internal/backends/spotify/spotify.go b/internal/backends/spotify/spotify.go index a4e3c87..ae2fc25 100644 --- a/internal/backends/spotify/spotify.go +++ b/internal/backends/spotify/spotify.go @@ -52,10 +52,10 @@ func (b *SpotifyApiBackend) Options() []models.BackendOption { }} } -func (b *SpotifyApiBackend) FromConfig(config *config.ServiceConfig) models.Backend { +func (b *SpotifyApiBackend) InitConfig(config *config.ServiceConfig) error { b.clientId = config.GetString("client-id") b.clientSecret = config.GetString("client-secret") - return b + return nil } func (b *SpotifyApiBackend) OAuth2Strategy(redirectUrl *url.URL) auth.OAuth2Strategy { diff --git a/internal/backends/spotify/spotify_test.go b/internal/backends/spotify/spotify_test.go index 1aa7e87..8949128 100644 --- a/internal/backends/spotify/spotify_test.go +++ b/internal/backends/spotify/spotify_test.go @@ -38,13 +38,14 @@ var ( testTrack []byte ) -func TestFromConfig(t *testing.T) { +func TestInitConfig(t *testing.T) { c := viper.New() c.Set("client-id", "someclientid") c.Set("client-secret", "someclientsecret") service := config.NewServiceConfig("test", c) - backend := (&spotify.SpotifyApiBackend{}).FromConfig(&service) - assert.IsType(t, &spotify.SpotifyApiBackend{}, backend) + backend := spotify.SpotifyApiBackend{} + err := backend.InitConfig(&service) + assert.NoError(t, err) } func TestSpotifyListenAsListen(t *testing.T) { diff --git a/internal/backends/spotifyhistory/spotifyhistory.go b/internal/backends/spotifyhistory/spotifyhistory.go index 40323a4..1c986be 100644 --- a/internal/backends/spotifyhistory/spotifyhistory.go +++ b/internal/backends/spotifyhistory/spotifyhistory.go @@ -64,12 +64,12 @@ func (b *SpotifyHistoryBackend) Options() []models.BackendOption { }} } -func (b *SpotifyHistoryBackend) FromConfig(config *config.ServiceConfig) models.Backend { +func (b *SpotifyHistoryBackend) InitConfig(config *config.ServiceConfig) error { b.dirPath = config.GetString("dir-path") b.ignoreIncognito = config.GetBool("ignore-incognito", true) b.ignoreSkipped = config.GetBool("ignore-skipped", false) b.skippedMinSeconds = config.GetInt("ignore-min-duration-seconds", 30) - return b + return nil } func (b *SpotifyHistoryBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.Progress) { diff --git a/internal/backends/subsonic/subsonic.go b/internal/backends/subsonic/subsonic.go index 59d4719..1c26bfd 100644 --- a/internal/backends/subsonic/subsonic.go +++ b/internal/backends/subsonic/subsonic.go @@ -52,7 +52,7 @@ func (b *SubsonicApiBackend) Options() []models.BackendOption { }} } -func (b *SubsonicApiBackend) FromConfig(config *config.ServiceConfig) models.Backend { +func (b *SubsonicApiBackend) InitConfig(config *config.ServiceConfig) error { b.client = subsonic.Client{ Client: &http.Client{}, BaseUrl: config.GetString("server-url"), @@ -60,7 +60,7 @@ func (b *SubsonicApiBackend) FromConfig(config *config.ServiceConfig) models.Bac ClientName: version.AppName, } b.password = config.GetString("token") - return b + return nil } func (b *SubsonicApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress) { diff --git a/internal/backends/subsonic/subsonic_test.go b/internal/backends/subsonic/subsonic_test.go index f6508c5..638c116 100644 --- a/internal/backends/subsonic/subsonic_test.go +++ b/internal/backends/subsonic/subsonic_test.go @@ -27,13 +27,14 @@ import ( "go.uploadedlobster.com/scotty/internal/config" ) -func TestFromConfig(t *testing.T) { +func TestInitConfig(t *testing.T) { c := viper.New() c.Set("server-url", "https://subsonic.example.com") c.Set("token", "thetoken") service := config.NewServiceConfig("test", c) - backend := (&subsonic.SubsonicApiBackend{}).FromConfig(&service) - assert.IsType(t, &subsonic.SubsonicApiBackend{}, backend) + backend := subsonic.SubsonicApiBackend{} + err := backend.InitConfig(&service) + assert.NoError(t, err) } func TestSongToLove(t *testing.T) { diff --git a/internal/models/interfaces.go b/internal/models/interfaces.go index cc19d8d..1c593d0 100644 --- a/internal/models/interfaces.go +++ b/internal/models/interfaces.go @@ -30,7 +30,7 @@ type Backend interface { Name() string // Initialize the backend from a config. - FromConfig(config *config.ServiceConfig) Backend + InitConfig(config *config.ServiceConfig) error // Return configuration options Options() []BackendOption From ed191d2f15131dea901964227b9c485d85497a3b Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Tue, 29 Apr 2025 10:05:40 +0200 Subject: [PATCH 34/52] scrobblerlog: Allow configuring fallback time zone Fixes #6 --- config.example.toml | 7 +++ .../backends/scrobblerlog/scrobblerlog.go | 14 ++++++ .../scrobblerlog/scrobblerlog_test.go | 10 +++++ pkg/scrobblerlog/parser.go | 44 +++++++++---------- pkg/scrobblerlog/parser_test.go | 15 +++++++ 5 files changed, 68 insertions(+), 22 deletions(-) diff --git a/config.example.toml b/config.example.toml index 6a5eb88..bdccb16 100644 --- a/config.example.toml +++ b/config.example.toml @@ -61,6 +61,13 @@ include-skipped = true # If true (default), new listens will be appended to the existing file. Set to # false to overwrite the file and create a new scrobbler log on every run. append = true +# Specify the time zone of the listens in the scrobbler log. While the log files +# are supposed to contain Unix timestamps, which are always in UTC, the player +# writing the log might not be time zone aware. This can cause the timestamps +# to be in a different time zone. Use the time-zone setting to specify a +# different time zone, e.g. "Europe/Berlin" or "America/New_York". +# The default is UTC. +time-zone = "UTC" [service.jspf] # Write listens and loves to JSPF playlist files (https://xspf.org/jspf) diff --git a/internal/backends/scrobblerlog/scrobblerlog.go b/internal/backends/scrobblerlog/scrobblerlog.go index 1fdfaff..22c8577 100644 --- a/internal/backends/scrobblerlog/scrobblerlog.go +++ b/internal/backends/scrobblerlog/scrobblerlog.go @@ -18,6 +18,7 @@ package scrobblerlog import ( "bufio" + "fmt" "os" "sort" "strings" @@ -34,6 +35,7 @@ type ScrobblerLogBackend struct { includeSkipped bool append bool file *os.File + timezone *time.Location log scrobblerlog.ScrobblerLog } @@ -53,6 +55,10 @@ func (b *ScrobblerLogBackend) Options() []models.BackendOption { Label: i18n.Tr("Append to file"), Type: models.Bool, Default: "true", + }, { + Name: "time-zone", + Label: i18n.Tr("Specify a time zone for the listen timestamps"), + Type: models.String, }} } @@ -60,6 +66,14 @@ func (b *ScrobblerLogBackend) InitConfig(config *config.ServiceConfig) error { b.filePath = config.GetString("file-path") b.includeSkipped = config.GetBool("include-skipped", false) b.append = config.GetBool("append", true) + timezone := config.GetString("time-zone") + if timezone != "" { + location, err := time.LoadLocation(timezone) + if err != nil { + return fmt.Errorf("Invalid time-zone %q: %w", timezone, err) + } + b.log.FallbackTimezone = location + } b.log = scrobblerlog.ScrobblerLog{ TZ: scrobblerlog.TZ_UTC, Client: "Rockbox unknown $Revision$", diff --git a/internal/backends/scrobblerlog/scrobblerlog_test.go b/internal/backends/scrobblerlog/scrobblerlog_test.go index 7a8ab14..962aebf 100644 --- a/internal/backends/scrobblerlog/scrobblerlog_test.go +++ b/internal/backends/scrobblerlog/scrobblerlog_test.go @@ -33,3 +33,13 @@ func TestInitConfig(t *testing.T) { err := backend.InitConfig(&service) assert.NoError(t, err) } + +func TestInitConfigInvalidTimezone(t *testing.T) { + c := viper.New() + configuredTimezone := "Invalid/Timezone" + c.Set("time-zone", configuredTimezone) + service := config.NewServiceConfig("test", c) + backend := scrobblerlog.ScrobblerLogBackend{} + err := backend.InitConfig(&service) + assert.ErrorContains(t, err, `Invalid time-zone "Invalid/Timezone"`) +} diff --git a/pkg/scrobblerlog/parser.go b/pkg/scrobblerlog/parser.go index 8f9b88a..9e33754 100644 --- a/pkg/scrobblerlog/parser.go +++ b/pkg/scrobblerlog/parser.go @@ -71,10 +71,12 @@ type Record struct { // Represents a scrobbler log file. type ScrobblerLog struct { - TZ TZInfo - Client string - Records []Record - location *time.Location + TZ TZInfo + Client string + Records []Record + // Timezone to be used for timestamps in the log file, + // if TZ is set to [TZ_UNKNOWN]. + FallbackTimezone *time.Location } func (l *ScrobblerLog) Parse(data io.Reader, includeSkipped bool) error { @@ -173,7 +175,6 @@ func (l *ScrobblerLog) ReadHeader(reader *bufio.Reader) error { timezone, found := strings.CutPrefix(text, "#TZ/") if found { l.TZ = TZInfo(timezone) - l.location = locationFromTimezone(l.TZ) continue } @@ -223,6 +224,11 @@ func (l ScrobblerLog) rowToRecord(row []string) (Record, error) { return record, err } + var timezone *time.Location = nil + if l.TZ == TZ_UNKNOWN { + timezone = l.FallbackTimezone + } + record = Record{ ArtistName: row[0], AlbumName: row[1], @@ -230,7 +236,7 @@ func (l ScrobblerLog) rowToRecord(row []string) (Record, error) { TrackNumber: trackNumber, Duration: time.Duration(duration) * time.Second, Rating: Rating(row[5]), - Timestamp: timeFromLocalTimestamp(timestamp, l.location), + Timestamp: timeFromLocalTimestamp(timestamp, timezone), } if len(row) > 7 { @@ -240,26 +246,20 @@ func (l ScrobblerLog) rowToRecord(row []string) (Record, error) { return record, nil } -// Convert the timezone string from the header to a time.Location. -// Often this is set to "UNKNOWN" in the log file, in which case it defaults -// to UTC. -func locationFromTimezone(timezone TZInfo) *time.Location { - location, err := time.LoadLocation(string(timezone)) - if err != nil { - return time.UTC - } - return location -} - -// Convert a Unix timestamp to a time.Time object, but treat the timestamp +// Convert a Unix timestamp to a [time.Time] object, but treat the timestamp // as being in the given location's timezone instead of UTC. +// If location is nil, the timestamp is returned as UTC. func timeFromLocalTimestamp(timestamp int64, location *time.Location) time.Time { t := time.Unix(timestamp, 0) - // The time is now in UTC. Get the offset to the requested timezone. - _, offset := t.In(location).Zone() - if offset != 0 { - t = t.Add(time.Duration(offset) * time.Second) + // The time is now in UTC. Get the offset to the requested timezone + // and shift the time accordingly. + if location != nil { + _, offset := t.In(location).Zone() + if offset != 0 { + t = t.Add(time.Duration(offset) * time.Second) + } } + return t } diff --git a/pkg/scrobblerlog/parser_test.go b/pkg/scrobblerlog/parser_test.go index 9b4513f..fe2f3ec 100644 --- a/pkg/scrobblerlog/parser_test.go +++ b/pkg/scrobblerlog/parser_test.go @@ -81,6 +81,21 @@ func TestParserExcludeSkipped(t *testing.T) { record4.MusicBrainzRecordingID) } +func TestParserFallbackTimezone(t *testing.T) { + assert := assert.New(t) + data := bytes.NewBufferString(testScrobblerLog) + result := scrobblerlog.ScrobblerLog{ + FallbackTimezone: time.FixedZone("UTC+2", 7200), + } + err := result.Parse(data, false) + require.NoError(t, err) + record1 := result.Records[0] + assert.Equal( + time.Unix(1260342084, 0).Add(2*time.Hour), + record1.Timestamp, + ) +} + func TestAppend(t *testing.T) { assert := assert.New(t) data := make([]byte, 0, 10) From b104c2bc428a9b1d74fc30f85e0fde212f228b72 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Tue, 29 Apr 2025 10:10:32 +0200 Subject: [PATCH 35/52] scrobblerlog: fixed listen export progress --- internal/backends/scrobblerlog/scrobblerlog.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/backends/scrobblerlog/scrobblerlog.go b/internal/backends/scrobblerlog/scrobblerlog.go index 22c8577..55c3517 100644 --- a/internal/backends/scrobblerlog/scrobblerlog.go +++ b/internal/backends/scrobblerlog/scrobblerlog.go @@ -153,7 +153,7 @@ func (b *ScrobblerLogBackend) ExportListens(oldestTimestamp time.Time, results c listens = append(listens, recordToListen(record, client)) } sort.Sort(listens.NewerThan(oldestTimestamp)) - progress <- models.Progress{Elapsed: int64(len(listens))}.Complete() + progress <- models.Progress{Total: int64(len(listens))}.Complete() results <- models.ListensResult{Items: listens} } From 159f486cdca54ff98f7e5be4ea0c7152c20854ac Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Tue, 29 Apr 2025 10:32:59 +0200 Subject: [PATCH 36/52] Upgrade musicbrainzws2 --- go.mod | 2 +- go.sum | 4 ++-- internal/backends/listenbrainz/listenbrainz.go | 6 +++++- internal/version/version.go | 1 + 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 22a3154..ef1286c 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/supersonic-app/go-subsonic v0.0.0-20241224013245-9b2841f3711d github.com/vbauerster/mpb/v8 v8.9.3 go.uploadedlobster.com/mbtypes v0.4.0 - go.uploadedlobster.com/musicbrainzws2 v0.13.1 + go.uploadedlobster.com/musicbrainzws2 v0.14.0 golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 golang.org/x/oauth2 v0.29.0 golang.org/x/text v0.24.0 diff --git a/go.sum b/go.sum index 1ee05c8..8ade87a 100644 --- a/go.sum +++ b/go.sum @@ -132,8 +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= +go.uploadedlobster.com/musicbrainzws2 v0.14.0 h1:YaEtxNwLSNT1gzFipQ4XlaThNfXjBpzzb4I6WhIeUwg= +go.uploadedlobster.com/musicbrainzws2 v0.14.0/go.mod h1:T6sYE7ZHRH3mJWT3g9jdSUPKJLZubnBjKyjMPNdkgao= 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 49755c6..d0074b1 100644 --- a/internal/backends/listenbrainz/listenbrainz.go +++ b/internal/backends/listenbrainz/listenbrainz.go @@ -58,7 +58,11 @@ func (b *ListenBrainzApiBackend) Options() []models.BackendOption { func (b *ListenBrainzApiBackend) InitConfig(config *config.ServiceConfig) error { b.client = NewClient(config.GetString("token")) - b.mbClient = *musicbrainzws2.NewClient(version.AppName, version.AppVersion) + b.mbClient = *musicbrainzws2.NewClient(musicbrainzws2.AppInfo{ + Name: version.AppName, + Version: version.AppVersion, + URL: version.AppURL, + }) b.client.MaxResults = MaxItemsPerGet b.username = config.GetString("username") b.checkDuplicates = config.GetBool("check-duplicate-listens", false) diff --git a/internal/version/version.go b/internal/version/version.go index 3f02fe2..818bec1 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -18,6 +18,7 @@ package version const ( AppName = "scotty" AppVersion = "0.4.1" + AppURL = "https://git.sr.ht/~phw/scotty/" ) func UserAgent() string { From 47486ff659bdf084cc7211c25e7f2633c9bcfc91 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Tue, 29 Apr 2025 11:05:37 +0200 Subject: [PATCH 37/52] Update weblate configuration --- .build.yml | 2 +- .weblate | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 .weblate diff --git a/.build.yml b/.build.yml index 8be4e81..32f13ec 100644 --- a/.build.yml +++ b/.build.yml @@ -5,7 +5,7 @@ packages: - hut - weblate-wlc secrets: - - 2a17e258-3e99-4093-9527-832c350d9c53 + - eafb7dc6-c02d-4b26-a960-61b968a4f454 oauth: pages.sr.ht/PAGES:RW tasks: - weblate-update: | diff --git a/.weblate b/.weblate new file mode 100644 index 0000000..9c9511e --- /dev/null +++ b/.weblate @@ -0,0 +1,3 @@ +[weblate] +url = https://translate.uploadedlobster.com/api/ +translation = scotty/app From c817480809516756aa7709d8a1221f546926cea3 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Tue, 29 Apr 2025 11:12:28 +0200 Subject: [PATCH 38/52] Updated Weblate CI secret and fixed build --- .build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.build.yml b/.build.yml index 32f13ec..d1064df 100644 --- a/.build.yml +++ b/.build.yml @@ -5,10 +5,11 @@ packages: - hut - weblate-wlc secrets: - - eafb7dc6-c02d-4b26-a960-61b968a4f454 + - 0e2ad815-6c46-4cea-878e-70fc33f71e77 oauth: pages.sr.ht/PAGES:RW tasks: - weblate-update: | + cd scotty wlc --format text pull scotty - test: | cd scotty From 597914e6db39ea6e63ad48d1bd76d3ed30f09ffd Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Tue, 29 Apr 2025 11:15:49 +0200 Subject: [PATCH 39/52] Announce new releases to Go Module Index --- .build.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.build.yml b/.build.yml index d1064df..a5d2238 100644 --- a/.build.yml +++ b/.build.yml @@ -29,5 +29,15 @@ tasks: - publish-redirect: | # Update redirect on https://go.uploadedlobster.com/scotty ./scotty/pages/publish.sh + # Skip releasing if this is not a tagged release + - only-tags: | + cd scotty + GIT_REF=$(git describe --always) + [[ "$GIT_REF" =~ ^v[0-9]+\.[0-9]+(\.[0-9]+)?$ ]] || complete-build + - announce-release: | + # Announce new release to Go Module Index + cd scotty + VERSION=$(git describe --exact-match) + curl "https://proxy.golang.org/go.uploadedlobster.com/scotty/@v/${VERSION}.info" artifacts: - scotty/dist/artifacts.tar From e135ea5fa90f46d3d17394b274870baee7510c0c Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Tue, 29 Apr 2025 11:43:42 +0200 Subject: [PATCH 40/52] Update goreleaser config file format --- .goreleaser.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 06b612a..48c88c8 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -6,7 +6,7 @@ # yaml-language-server: $schema=https://goreleaser.com/static/schema.json # vim: set ts=2 sw=2 tw=0 fo=cnqoj -version: 1 +version: 2 before: hooks: @@ -28,7 +28,7 @@ universal_binaries: - replace: true archives: - - format: tar.gz + - formats: ['tar.gz'] # this name template makes the OS and Arch compatible with the results of `uname`. name_template: >- {{ .ProjectName }}-{{ .Version }}_ @@ -42,7 +42,7 @@ archives: # use zip for windows archives format_overrides: - goos: windows - format: zip + formats: ['zip'] files: - COPYING - README.md From 82858315fa9598aff00136ab088cdc14c1e74aab Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Tue, 29 Apr 2025 11:44:04 +0200 Subject: [PATCH 41/52] Disable Linux 386 builds Compilaton fails with latest gorm --- .goreleaser.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 48c88c8..1a1e0ba 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -21,6 +21,8 @@ builds: - windows - darwin ignore: + - goos: linux + goarch: "386" - goos: windows goarch: "386" From 1516a3a9d6eb5d5c834c73c9d72fdfecdc03c379 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Tue, 29 Apr 2025 12:57:28 +0200 Subject: [PATCH 42/52] scrobblerlog: renamed setting include-skipped to ignore-skipped This makes the setting consistent with the similar setting for spotify --- config.example.toml | 10 ++++---- .../backends/scrobblerlog/scrobblerlog.go | 23 ++++++++++--------- pkg/scrobblerlog/parser.go | 11 ++++----- pkg/scrobblerlog/parser_test.go | 6 ++--- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/config.example.toml b/config.example.toml index bdccb16..6b81bac 100644 --- a/config.example.toml +++ b/config.example.toml @@ -56,8 +56,8 @@ backend = "scrobbler-log" # The file path to the .scrobbler.log file. Relative paths are resolved against # the current working directory when running scotty. file-path = "./.scrobbler.log" -# If true, reading listens from the file also returns listens marked as "skipped" -include-skipped = true +# If true (default), ignore listens marked as skipped. +ignore-skipped = true # If true (default), new listens will be appended to the existing file. Set to # false to overwrite the file and create a new scrobbler log on every run. append = true @@ -105,9 +105,9 @@ dir-path = "./my_spotify_data_extended/Spotify Extended Streaming Histor ignore-incognito = true # If true, ignore listens marked as skipped. Default is false. ignore-skipped = false -# Only consider skipped listens with a playback duration longer than this number -# of seconds. Default is 30 seconds. If ignore-skipped is set to false this -# setting has no effect. +# Only consider skipped listens with a playback duration longer than or equal to +# this number of seconds. Default is 30 seconds. If ignore-skipped is enabled +# this setting has no effect. ignore-min-duration-seconds = 30 [service.deezer] diff --git a/internal/backends/scrobblerlog/scrobblerlog.go b/internal/backends/scrobblerlog/scrobblerlog.go index 55c3517..26d417a 100644 --- a/internal/backends/scrobblerlog/scrobblerlog.go +++ b/internal/backends/scrobblerlog/scrobblerlog.go @@ -31,12 +31,12 @@ import ( ) type ScrobblerLogBackend struct { - filePath string - includeSkipped bool - append bool - file *os.File - timezone *time.Location - log scrobblerlog.ScrobblerLog + filePath string + ignoreSkipped bool + append bool + file *os.File + timezone *time.Location + log scrobblerlog.ScrobblerLog } func (b *ScrobblerLogBackend) Name() string { return "scrobbler-log" } @@ -47,9 +47,10 @@ func (b *ScrobblerLogBackend) Options() []models.BackendOption { Label: i18n.Tr("File path"), Type: models.String, }, { - Name: "include-skipped", - Label: i18n.Tr("Include skipped listens"), - Type: models.Bool, + Name: "ignore-skipped", + Label: i18n.Tr("Ignore skipped listens"), + Type: models.Bool, + Default: "true", }, { Name: "append", Label: i18n.Tr("Append to file"), @@ -64,7 +65,7 @@ func (b *ScrobblerLogBackend) Options() []models.BackendOption { func (b *ScrobblerLogBackend) InitConfig(config *config.ServiceConfig) error { b.filePath = config.GetString("file-path") - b.includeSkipped = config.GetBool("include-skipped", false) + b.ignoreSkipped = config.GetBool("ignore-skipped", true) b.append = config.GetBool("append", true) timezone := config.GetString("time-zone") if timezone != "" { @@ -140,7 +141,7 @@ func (b *ScrobblerLogBackend) ExportListens(oldestTimestamp time.Time, results c defer file.Close() - err = b.log.Parse(file, b.includeSkipped) + err = b.log.Parse(file, b.ignoreSkipped) if err != nil { progress <- models.Progress{}.Complete() results <- models.ListensResult{Error: err} diff --git a/pkg/scrobblerlog/parser.go b/pkg/scrobblerlog/parser.go index 9e33754..892f6e8 100644 --- a/pkg/scrobblerlog/parser.go +++ b/pkg/scrobblerlog/parser.go @@ -79,7 +79,7 @@ type ScrobblerLog struct { FallbackTimezone *time.Location } -func (l *ScrobblerLog) Parse(data io.Reader, includeSkipped bool) error { +func (l *ScrobblerLog) Parse(data io.Reader, ignoreSkipped bool) error { l.Records = make([]Record, 0) reader := bufio.NewReader(data) @@ -111,16 +111,15 @@ func (l *ScrobblerLog) Parse(data io.Reader, includeSkipped bool) error { return fmt.Errorf("invalid record in scrobblerlog line %v", line) } - rating := row[5] - if !includeSkipped && rating == "S" { - continue - } - record, err := l.rowToRecord(row) if err != nil { return err } + if ignoreSkipped && record.Rating == RATING_SKIPPED { + continue + } + l.Records = append(l.Records, record) } diff --git a/pkg/scrobblerlog/parser_test.go b/pkg/scrobblerlog/parser_test.go index fe2f3ec..7fd57c3 100644 --- a/pkg/scrobblerlog/parser_test.go +++ b/pkg/scrobblerlog/parser_test.go @@ -48,7 +48,7 @@ func TestParser(t *testing.T) { assert := assert.New(t) data := bytes.NewBufferString(testScrobblerLog) result := scrobblerlog.ScrobblerLog{} - err := result.Parse(data, true) + err := result.Parse(data, false) require.NoError(t, err) assert.Equal(scrobblerlog.TZ_UNKNOWN, result.TZ) assert.Equal("Rockbox sansaclipplus $Revision$", result.Client) @@ -68,11 +68,11 @@ func TestParser(t *testing.T) { record4.MusicBrainzRecordingID) } -func TestParserExcludeSkipped(t *testing.T) { +func TestParserIgnoreSkipped(t *testing.T) { assert := assert.New(t) data := bytes.NewBufferString(testScrobblerLog) result := scrobblerlog.ScrobblerLog{} - err := result.Parse(data, false) + err := result.Parse(data, true) require.NoError(t, err) assert.Len(result.Records, 4) record4 := result.Records[3] From 39b31fc664dab411a033d2908930cb5eaa390d8b Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Tue, 29 Apr 2025 13:01:54 +0200 Subject: [PATCH 43/52] Update changelog --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 11251cd..cda0d79 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,12 @@ - ListenBrainz: log missing recording MBID on love import - Subsonic: support OpenSubsonic fields for recording MBID and genres (#5) - Subsonic: fixed progress for loves export +- scrobblerlog: add "time-zone" config option (#6). +- scrobblerlog: fixed progress for listen export +- scrobblerlog: renamed setting `include-skipped` to `ignore-skipped`. + +Note: 386 builds for Linux are not available with this release due to an +incompatibility with latest version of gorm. ## 0.4.1 - 2024-09-16 From d51c97c648883e9c29e7b7c80a2eca77186e11dc Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Tue, 29 Apr 2025 13:23:41 +0200 Subject: [PATCH 44/52] Code style: All uppercase acronyms URL, ISRC, ID, HTTP --- internal/auth/auth.go | 2 +- internal/auth/strategy.go | 12 +++--- internal/backends/deezer/auth.go | 6 +-- internal/backends/deezer/client.go | 6 +-- internal/backends/deezer/client_test.go | 6 +-- internal/backends/deezer/deezer.go | 14 +++---- internal/backends/deezer/models.go | 6 +-- internal/backends/funkwhale/client.go | 12 +++--- internal/backends/funkwhale/client_test.go | 22 +++++----- internal/backends/funkwhale/models.go | 12 +++--- internal/backends/jspf/jspf.go | 6 +-- internal/backends/lastfm/auth.go | 10 ++--- internal/backends/lastfm/lastfm.go | 8 ++-- internal/backends/listenbrainz/client.go | 14 +++---- internal/backends/listenbrainz/client_test.go | 14 +++---- internal/backends/listenbrainz/models_test.go | 2 +- internal/backends/maloja/client.go | 12 +++--- internal/backends/maloja/client_test.go | 16 ++++---- internal/backends/spotify/client.go | 8 ++-- internal/backends/spotify/client_test.go | 6 +-- internal/backends/spotify/models.go | 20 +++++----- internal/backends/spotify/spotify.go | 40 +++++++++---------- internal/backends/spotifyhistory/models.go | 4 +- internal/cli/auth.go | 10 ++--- pkg/jspf/extensions.go | 4 +- pkg/jspf/extensions_test.go | 2 +- 26 files changed, 137 insertions(+), 137 deletions(-) diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 5ba05af..84c85b6 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -27,7 +27,7 @@ type OAuth2Authenticator interface { models.Backend // Returns OAuth2 config suitable for this backend - OAuth2Strategy(redirectUrl *url.URL) OAuth2Strategy + OAuth2Strategy(redirectURL *url.URL) OAuth2Strategy // Setup the OAuth2 client OAuth2Setup(token oauth2.TokenSource) error diff --git a/internal/auth/strategy.go b/internal/auth/strategy.go index 3d03fa4..7e3c265 100644 --- a/internal/auth/strategy.go +++ b/internal/auth/strategy.go @@ -24,14 +24,14 @@ import ( type OAuth2Strategy interface { Config() oauth2.Config - AuthCodeURL(verifier string, state string) AuthUrl + AuthCodeURL(verifier string, state string) AuthURL ExchangeToken(code CodeResponse, verifier string) (*oauth2.Token, error) } -type AuthUrl struct { +type AuthURL struct { // The URL the user must visit to approve access - Url string + URL string // Random state string passed on to the callback. // Leave empty if the service does not support state. State string @@ -56,10 +56,10 @@ func (s StandardStrategy) Config() oauth2.Config { return s.conf } -func (s StandardStrategy) AuthCodeURL(verifier string, state string) AuthUrl { +func (s StandardStrategy) AuthCodeURL(verifier string, state string) AuthURL { url := s.conf.AuthCodeURL(state, oauth2.AccessTypeOffline, oauth2.S256ChallengeOption(verifier)) - return AuthUrl{ - Url: url, + return AuthURL{ + URL: url, State: state, Param: "code", } diff --git a/internal/backends/deezer/auth.go b/internal/backends/deezer/auth.go index aa30b04..0304dec 100644 --- a/internal/backends/deezer/auth.go +++ b/internal/backends/deezer/auth.go @@ -33,10 +33,10 @@ func (s deezerStrategy) Config() oauth2.Config { return s.conf } -func (s deezerStrategy) AuthCodeURL(verifier string, state string) auth.AuthUrl { +func (s deezerStrategy) AuthCodeURL(verifier string, state string) auth.AuthURL { url := s.conf.AuthCodeURL(state, oauth2.AccessTypeOffline, oauth2.S256ChallengeOption(verifier)) - return auth.AuthUrl{ - Url: url, + return auth.AuthURL{ + URL: url, State: state, Param: "code", } diff --git a/internal/backends/deezer/client.go b/internal/backends/deezer/client.go index 3c3b740..05264ae 100644 --- a/internal/backends/deezer/client.go +++ b/internal/backends/deezer/client.go @@ -36,7 +36,7 @@ const MaxItemsPerGet = 1000 const DefaultRateLimitWaitSeconds = 5 type Client struct { - HttpClient *resty.Client + HTTPClient *resty.Client token oauth2.TokenSource } @@ -47,7 +47,7 @@ func NewClient(token oauth2.TokenSource) Client { client.SetHeader("User-Agent", version.UserAgent()) client.SetRetryCount(5) return Client{ - HttpClient: client, + HTTPClient: client, token: token, } } @@ -73,7 +73,7 @@ func (c Client) setToken(req *resty.Request) error { } func listRequest[T Result](c Client, path string, offset int, limit int) (result T, err error) { - request := c.HttpClient.R(). + request := c.HTTPClient.R(). SetQueryParams(map[string]string{ "index": strconv.Itoa(offset), "limit": strconv.Itoa(limit), diff --git a/internal/backends/deezer/client_test.go b/internal/backends/deezer/client_test.go index f8240f8..c90b01a 100644 --- a/internal/backends/deezer/client_test.go +++ b/internal/backends/deezer/client_test.go @@ -44,7 +44,7 @@ func TestGetUserHistory(t *testing.T) { token := oauth2.StaticTokenSource(&oauth2.Token{}) client := deezer.NewClient(token) - setupHttpMock(t, client.HttpClient.GetClient(), + setupHTTPMock(t, client.HTTPClient.GetClient(), "https://api.deezer.com/user/me/history", "testdata/user-history.json") @@ -65,7 +65,7 @@ func TestGetUserTracks(t *testing.T) { token := oauth2.StaticTokenSource(&oauth2.Token{}) client := deezer.NewClient(token) - setupHttpMock(t, client.HttpClient.GetClient(), + setupHTTPMock(t, client.HTTPClient.GetClient(), "https://api.deezer.com/user/me/tracks", "testdata/user-tracks.json") @@ -81,7 +81,7 @@ func TestGetUserTracks(t *testing.T) { assert.Equal("Outland", track1.Track.Album.Title) } -func setupHttpMock(t *testing.T, client *http.Client, url string, testDataPath string) { +func setupHTTPMock(t *testing.T, client *http.Client, url string, testDataPath string) { httpmock.ActivateNonDefault(client) responder, err := httpmock.NewJsonResponder(200, httpmock.File(testDataPath)) diff --git a/internal/backends/deezer/deezer.go b/internal/backends/deezer/deezer.go index e7d9762..7b6e1ad 100644 --- a/internal/backends/deezer/deezer.go +++ b/internal/backends/deezer/deezer.go @@ -31,7 +31,7 @@ import ( type DeezerApiBackend struct { client Client - clientId string + clientID string clientSecret string } @@ -50,19 +50,19 @@ func (b *DeezerApiBackend) Options() []models.BackendOption { } func (b *DeezerApiBackend) InitConfig(config *config.ServiceConfig) error { - b.clientId = config.GetString("client-id") + b.clientID = config.GetString("client-id") b.clientSecret = config.GetString("client-secret") return nil } -func (b *DeezerApiBackend) OAuth2Strategy(redirectUrl *url.URL) auth.OAuth2Strategy { +func (b *DeezerApiBackend) OAuth2Strategy(redirectURL *url.URL) auth.OAuth2Strategy { conf := oauth2.Config{ - ClientID: b.clientId, + ClientID: b.clientID, ClientSecret: b.clientSecret, Scopes: []string{ "offline_access,basic_access,listening_history", }, - RedirectURL: redirectUrl.String(), + RedirectURL: redirectURL.String(), Endpoint: oauth2.Endpoint{ AuthURL: "https://connect.deezer.com/oauth/auth.php", TokenURL: "https://connect.deezer.com/oauth/access_token.php", @@ -244,8 +244,8 @@ func (t Track) AsTrack() models.Track { info["music_service"] = "deezer.com" info["origin_url"] = t.Link info["deezer_id"] = t.Link - info["deezer_album_id"] = fmt.Sprintf("https://www.deezer.com/album/%v", t.Album.Id) - info["deezer_artist_id"] = fmt.Sprintf("https://www.deezer.com/artist/%v", t.Artist.Id) + info["deezer_album_id"] = fmt.Sprintf("https://www.deezer.com/album/%v", t.Album.ID) + info["deezer_artist_id"] = fmt.Sprintf("https://www.deezer.com/artist/%v", t.Artist.ID) return track } diff --git a/internal/backends/deezer/models.go b/internal/backends/deezer/models.go index 712cd73..85b569c 100644 --- a/internal/backends/deezer/models.go +++ b/internal/backends/deezer/models.go @@ -51,7 +51,7 @@ type HistoryResult struct { } type Track struct { - Id int `json:"id"` + ID int `json:"id"` Type string `json:"type"` Link string `json:"link"` Title string `json:"title"` @@ -75,7 +75,7 @@ type LovedTrack struct { } type Album struct { - Id int `json:"id"` + ID int `json:"id"` Type string `json:"type"` Link string `json:"link"` Title string `json:"title"` @@ -83,7 +83,7 @@ type Album struct { } type Artist struct { - Id int `json:"id"` + ID int `json:"id"` Type string `json:"type"` Link string `json:"link"` Name string `json:"name"` diff --git a/internal/backends/funkwhale/client.go b/internal/backends/funkwhale/client.go index 8f2848b..c231c94 100644 --- a/internal/backends/funkwhale/client.go +++ b/internal/backends/funkwhale/client.go @@ -33,13 +33,13 @@ import ( const MaxItemsPerGet = 50 type Client struct { - HttpClient *resty.Client + HTTPClient *resty.Client token string } -func NewClient(serverUrl string, token string) Client { +func NewClient(serverURL string, token string) Client { client := resty.New() - client.SetBaseURL(serverUrl) + client.SetBaseURL(serverURL) client.SetAuthScheme("Bearer") client.SetAuthToken(token) client.SetHeader("Accept", "application/json") @@ -49,14 +49,14 @@ func NewClient(serverUrl string, token string) Client { ratelimit.EnableHTTPHeaderRateLimit(client, "Retry-After") return Client{ - HttpClient: client, + HTTPClient: client, token: token, } } func (c Client) GetHistoryListenings(user string, page int, perPage int) (result ListeningsResult, err error) { const path = "/api/v1/history/listenings" - response, err := c.HttpClient.R(). + response, err := c.HTTPClient.R(). SetQueryParams(map[string]string{ "username": user, "page": strconv.Itoa(page), @@ -75,7 +75,7 @@ func (c Client) GetHistoryListenings(user string, page int, perPage int) (result func (c Client) GetFavoriteTracks(page int, perPage int) (result FavoriteTracksResult, err error) { const path = "/api/v1/favorites/tracks" - response, err := c.HttpClient.R(). + response, err := c.HTTPClient.R(). SetQueryParams(map[string]string{ "page": strconv.Itoa(page), "page_size": strconv.Itoa(perPage), diff --git a/internal/backends/funkwhale/client_test.go b/internal/backends/funkwhale/client_test.go index 89325cd..e850a4d 100644 --- a/internal/backends/funkwhale/client_test.go +++ b/internal/backends/funkwhale/client_test.go @@ -32,20 +32,20 @@ import ( ) func TestNewClient(t *testing.T) { - serverUrl := "https://funkwhale.example.com" + serverURL := "https://funkwhale.example.com" token := "foobar123" - client := funkwhale.NewClient(serverUrl, token) - assert.Equal(t, serverUrl, client.HttpClient.BaseURL) - assert.Equal(t, token, client.HttpClient.Token) + client := funkwhale.NewClient(serverURL, token) + assert.Equal(t, serverURL, client.HTTPClient.BaseURL) + assert.Equal(t, token, client.HTTPClient.Token) } func TestGetHistoryListenings(t *testing.T) { defer httpmock.DeactivateAndReset() - serverUrl := "https://funkwhale.example.com" + serverURL := "https://funkwhale.example.com" token := "thetoken" - client := funkwhale.NewClient(serverUrl, token) - setupHttpMock(t, client.HttpClient.GetClient(), + client := funkwhale.NewClient(serverURL, token) + setupHTTPMock(t, client.HTTPClient.GetClient(), "https://funkwhale.example.com/api/v1/history/listenings", "testdata/listenings.json") @@ -67,9 +67,9 @@ func TestGetFavoriteTracks(t *testing.T) { defer httpmock.DeactivateAndReset() token := "thetoken" - serverUrl := "https://funkwhale.example.com" - client := funkwhale.NewClient(serverUrl, token) - setupHttpMock(t, client.HttpClient.GetClient(), + serverURL := "https://funkwhale.example.com" + client := funkwhale.NewClient(serverURL, token) + setupHTTPMock(t, client.HTTPClient.GetClient(), "https://funkwhale.example.com/api/v1/favorites/tracks", "testdata/favorite-tracks.json") @@ -87,7 +87,7 @@ func TestGetFavoriteTracks(t *testing.T) { assert.Equal("phw", fav1.User.UserName) } -func setupHttpMock(t *testing.T, client *http.Client, url string, testDataPath string) { +func setupHTTPMock(t *testing.T, client *http.Client, url string, testDataPath string) { httpmock.ActivateNonDefault(client) responder, err := httpmock.NewJsonResponder(200, httpmock.File(testDataPath)) diff --git a/internal/backends/funkwhale/models.go b/internal/backends/funkwhale/models.go index 10d57d0..faaae12 100644 --- a/internal/backends/funkwhale/models.go +++ b/internal/backends/funkwhale/models.go @@ -31,7 +31,7 @@ type ListeningsResult struct { } type Listening struct { - Id int `json:"int"` + ID int `json:"int"` User User `json:"user"` Track Track `json:"track"` CreationDate string `json:"creation_date"` @@ -45,14 +45,14 @@ type FavoriteTracksResult struct { } type FavoriteTrack struct { - Id int `json:"int"` + ID int `json:"int"` User User `json:"user"` Track Track `json:"track"` CreationDate string `json:"creation_date"` } type Track struct { - Id int `json:"int"` + ID int `json:"int"` Artist Artist `json:"artist"` Album Album `json:"album"` Title string `json:"title"` @@ -64,13 +64,13 @@ type Track struct { } type Artist struct { - Id int `json:"int"` + ID int `json:"int"` Name string `json:"name"` ArtistMBID mbtypes.MBID `json:"mbid"` } type Album struct { - Id int `json:"int"` + ID int `json:"int"` Title string `json:"title"` AlbumArtist Artist `json:"artist"` ReleaseDate string `json:"release_date"` @@ -79,7 +79,7 @@ type Album struct { } type User struct { - Id int `json:"int"` + ID int `json:"int"` UserName string `json:"username"` } diff --git a/internal/backends/jspf/jspf.go b/internal/backends/jspf/jspf.go index 152c810..3e6866d 100644 --- a/internal/backends/jspf/jspf.go +++ b/internal/backends/jspf/jspf.go @@ -69,7 +69,7 @@ func (b *JSPFBackend) InitConfig(config *config.ServiceConfig) error { Identifier: config.GetString("identifier"), Tracks: make([]jspf.Track, 0), Extension: map[string]any{ - jspf.MusicBrainzPlaylistExtensionId: jspf.MusicBrainzPlaylistExtension{ + jspf.MusicBrainzPlaylistExtensionID: jspf.MusicBrainzPlaylistExtension{ LastModifiedAt: time.Now(), Public: true, }, @@ -116,7 +116,7 @@ func listenAsTrack(l models.Listen) jspf.Track { extension := makeMusicBrainzExtension(l.Track) extension.AddedAt = l.ListenedAt extension.AddedBy = l.UserName - track.Extension[jspf.MusicBrainzTrackExtensionId] = extension + track.Extension[jspf.MusicBrainzTrackExtensionID] = extension if l.RecordingMBID != "" { track.Identifier = append(track.Identifier, "https://musicbrainz.org/recording/"+string(l.RecordingMBID)) @@ -131,7 +131,7 @@ func loveAsTrack(l models.Love) jspf.Track { extension := makeMusicBrainzExtension(l.Track) extension.AddedAt = l.Created extension.AddedBy = l.UserName - track.Extension[jspf.MusicBrainzTrackExtensionId] = extension + track.Extension[jspf.MusicBrainzTrackExtensionID] = extension recordingMBID := l.Track.RecordingMBID if l.RecordingMBID != "" { diff --git a/internal/backends/lastfm/auth.go b/internal/backends/lastfm/auth.go index bbc65bc..9d43e0c 100644 --- a/internal/backends/lastfm/auth.go +++ b/internal/backends/lastfm/auth.go @@ -25,21 +25,21 @@ import ( type lastfmStrategy struct { client *lastfm.Api - redirectUrl *url.URL + redirectURL *url.URL } func (s lastfmStrategy) Config() oauth2.Config { return oauth2.Config{} } -func (s lastfmStrategy) AuthCodeURL(verifier string, state string) auth.AuthUrl { +func (s lastfmStrategy) AuthCodeURL(verifier string, state string) auth.AuthURL { // Last.fm does not use OAuth2, but the provided authorization flow with // callback URL is close enough we can shoehorn it into the existing // authentication strategy. // TODO: Investigate and use callback-less flow with api.GetAuthTokenUrl(token) - url := s.client.GetAuthRequestUrl(s.redirectUrl.String()) - return auth.AuthUrl{ - Url: url, + url := s.client.GetAuthRequestUrl(s.redirectURL.String()) + return auth.AuthURL{ + URL: url, State: "", // last.fm does not use state Param: "token", } diff --git a/internal/backends/lastfm/lastfm.go b/internal/backends/lastfm/lastfm.go index 2d4a9d5..76fe9c7 100644 --- a/internal/backends/lastfm/lastfm.go +++ b/internal/backends/lastfm/lastfm.go @@ -62,9 +62,9 @@ func (b *LastfmApiBackend) Options() []models.BackendOption { } func (b *LastfmApiBackend) InitConfig(config *config.ServiceConfig) error { - clientId := config.GetString("client-id") + clientID := config.GetString("client-id") clientSecret := config.GetString("client-secret") - b.client = lastfm.New(clientId, clientSecret) + b.client = lastfm.New(clientID, clientSecret) b.username = config.GetString("username") return nil } @@ -72,10 +72,10 @@ func (b *LastfmApiBackend) InitConfig(config *config.ServiceConfig) error { func (b *LastfmApiBackend) StartImport() error { return nil } func (b *LastfmApiBackend) FinishImport() error { return nil } -func (b *LastfmApiBackend) OAuth2Strategy(redirectUrl *url.URL) auth.OAuth2Strategy { +func (b *LastfmApiBackend) OAuth2Strategy(redirectURL *url.URL) auth.OAuth2Strategy { return lastfmStrategy{ client: b.client, - redirectUrl: redirectUrl, + redirectURL: redirectURL, } } diff --git a/internal/backends/listenbrainz/client.go b/internal/backends/listenbrainz/client.go index 1917bd4..fff476c 100644 --- a/internal/backends/listenbrainz/client.go +++ b/internal/backends/listenbrainz/client.go @@ -39,7 +39,7 @@ const ( ) type Client struct { - HttpClient *resty.Client + HTTPClient *resty.Client MaxResults int } @@ -55,7 +55,7 @@ func NewClient(token string) Client { ratelimit.EnableHTTPHeaderRateLimit(client, "X-RateLimit-Reset-In") return Client{ - HttpClient: client, + HTTPClient: client, MaxResults: DefaultItemsPerGet, } } @@ -63,7 +63,7 @@ func NewClient(token string) Client { func (c Client) GetListens(user string, maxTime time.Time, minTime time.Time) (result GetListensResult, err error) { const path = "/user/{username}/listens" errorResult := ErrorResult{} - response, err := c.HttpClient.R(). + response, err := c.HTTPClient.R(). SetPathParam("username", user). SetQueryParams(map[string]string{ "max_ts": strconv.FormatInt(maxTime.Unix(), 10), @@ -84,7 +84,7 @@ func (c Client) GetListens(user string, maxTime time.Time, minTime time.Time) (r func (c Client) SubmitListens(listens ListenSubmission) (result StatusResult, err error) { const path = "/submit-listens" errorResult := ErrorResult{} - response, err := c.HttpClient.R(). + response, err := c.HTTPClient.R(). SetBody(listens). SetResult(&result). SetError(&errorResult). @@ -100,7 +100,7 @@ func (c Client) SubmitListens(listens ListenSubmission) (result StatusResult, er func (c Client) GetFeedback(user string, status int, offset int) (result GetFeedbackResult, err error) { const path = "/feedback/user/{username}/get-feedback" errorResult := ErrorResult{} - response, err := c.HttpClient.R(). + response, err := c.HTTPClient.R(). SetPathParam("username", user). SetQueryParams(map[string]string{ "status": strconv.Itoa(status), @@ -122,7 +122,7 @@ func (c Client) GetFeedback(user string, status int, offset int) (result GetFeed func (c Client) SendFeedback(feedback Feedback) (result StatusResult, err error) { const path = "/feedback/recording-feedback" errorResult := ErrorResult{} - response, err := c.HttpClient.R(). + response, err := c.HTTPClient.R(). SetBody(feedback). SetResult(&result). SetError(&errorResult). @@ -138,7 +138,7 @@ func (c Client) SendFeedback(feedback Feedback) (result StatusResult, err error) func (c Client) Lookup(recordingName string, artistName string) (result LookupResult, err error) { const path = "/metadata/lookup" errorResult := ErrorResult{} - response, err := c.HttpClient.R(). + response, err := c.HTTPClient.R(). SetQueryParams(map[string]string{ "recording_name": recordingName, "artist_name": artistName, diff --git a/internal/backends/listenbrainz/client_test.go b/internal/backends/listenbrainz/client_test.go index 4e72756..2e841ae 100644 --- a/internal/backends/listenbrainz/client_test.go +++ b/internal/backends/listenbrainz/client_test.go @@ -36,7 +36,7 @@ import ( func TestNewClient(t *testing.T) { token := "foobar123" client := listenbrainz.NewClient(token) - assert.Equal(t, token, client.HttpClient.Token) + assert.Equal(t, token, client.HTTPClient.Token) assert.Equal(t, listenbrainz.DefaultItemsPerGet, client.MaxResults) } @@ -45,7 +45,7 @@ func TestGetListens(t *testing.T) { client := listenbrainz.NewClient("thetoken") client.MaxResults = 2 - setupHttpMock(t, client.HttpClient.GetClient(), + setupHTTPMock(t, client.HTTPClient.GetClient(), "https://api.listenbrainz.org/1/user/outsidecontext/listens", "testdata/listens.json") @@ -62,7 +62,7 @@ func TestGetListens(t *testing.T) { func TestSubmitListens(t *testing.T) { client := listenbrainz.NewClient("thetoken") - httpmock.ActivateNonDefault(client.HttpClient.GetClient()) + httpmock.ActivateNonDefault(client.HTTPClient.GetClient()) responder, err := httpmock.NewJsonResponder(200, listenbrainz.StatusResult{ Status: "ok", @@ -103,7 +103,7 @@ func TestGetFeedback(t *testing.T) { client := listenbrainz.NewClient("thetoken") client.MaxResults = 2 - setupHttpMock(t, client.HttpClient.GetClient(), + setupHTTPMock(t, client.HTTPClient.GetClient(), "https://api.listenbrainz.org/1/feedback/user/outsidecontext/get-feedback", "testdata/feedback.json") @@ -120,7 +120,7 @@ func TestGetFeedback(t *testing.T) { func TestSendFeedback(t *testing.T) { client := listenbrainz.NewClient("thetoken") - httpmock.ActivateNonDefault(client.HttpClient.GetClient()) + httpmock.ActivateNonDefault(client.HTTPClient.GetClient()) responder, err := httpmock.NewJsonResponder(200, listenbrainz.StatusResult{ Status: "ok", @@ -145,7 +145,7 @@ func TestLookup(t *testing.T) { defer httpmock.DeactivateAndReset() client := listenbrainz.NewClient("thetoken") - setupHttpMock(t, client.HttpClient.GetClient(), + setupHTTPMock(t, client.HTTPClient.GetClient(), "https://api.listenbrainz.org/1/metadata/lookup", "testdata/lookup.json") @@ -158,7 +158,7 @@ func TestLookup(t *testing.T) { assert.Equal(mbtypes.MBID("569436a1-234a-44bc-a370-8f4d252bef21"), result.RecordingMBID) } -func setupHttpMock(t *testing.T, client *http.Client, url string, testDataPath string) { +func setupHTTPMock(t *testing.T, client *http.Client, url string, testDataPath string) { httpmock.ActivateNonDefault(client) responder, err := httpmock.NewJsonResponder(200, httpmock.File(testDataPath)) diff --git a/internal/backends/listenbrainz/models_test.go b/internal/backends/listenbrainz/models_test.go index 9f5b14a..02cbe98 100644 --- a/internal/backends/listenbrainz/models_test.go +++ b/internal/backends/listenbrainz/models_test.go @@ -131,7 +131,7 @@ func TestTrackTrackNumberString(t *testing.T) { assert.Equal(t, 12, track.TrackNumber()) } -func TestTrackIsrc(t *testing.T) { +func TestTrackISRC(t *testing.T) { expected := mbtypes.ISRC("TCAEJ1934417") track := listenbrainz.Track{ AdditionalInfo: map[string]any{ diff --git a/internal/backends/maloja/client.go b/internal/backends/maloja/client.go index f373f93..249819a 100644 --- a/internal/backends/maloja/client.go +++ b/internal/backends/maloja/client.go @@ -32,25 +32,25 @@ import ( const MaxItemsPerGet = 1000 type Client struct { - HttpClient *resty.Client + HTTPClient *resty.Client token string } -func NewClient(serverUrl string, token string) Client { +func NewClient(serverURL string, token string) Client { client := resty.New() - client.SetBaseURL(serverUrl) + client.SetBaseURL(serverURL) client.SetHeader("Accept", "application/json") client.SetHeader("User-Agent", version.UserAgent()) client.SetRetryCount(5) return Client{ - HttpClient: client, + HTTPClient: client, token: token, } } func (c Client) GetScrobbles(page int, perPage int) (result GetScrobblesResult, err error) { const path = "/apis/mlj_1/scrobbles" - response, err := c.HttpClient.R(). + response, err := c.HTTPClient.R(). SetQueryParams(map[string]string{ "page": strconv.Itoa(page), "perpage": strconv.Itoa(perPage), @@ -68,7 +68,7 @@ func (c Client) GetScrobbles(page int, perPage int) (result GetScrobblesResult, func (c Client) NewScrobble(scrobble NewScrobble) (result NewScrobbleResult, err error) { const path = "/apis/mlj_1/newscrobble" scrobble.Key = c.token - response, err := c.HttpClient.R(). + response, err := c.HTTPClient.R(). SetBody(scrobble). SetResult(&result). Post(path) diff --git a/internal/backends/maloja/client_test.go b/internal/backends/maloja/client_test.go index 6a07adb..54316a8 100644 --- a/internal/backends/maloja/client_test.go +++ b/internal/backends/maloja/client_test.go @@ -32,19 +32,19 @@ import ( ) func TestNewClient(t *testing.T) { - serverUrl := "https://maloja.example.com" + serverURL := "https://maloja.example.com" token := "foobar123" - client := maloja.NewClient(serverUrl, token) - assert.Equal(t, serverUrl, client.HttpClient.BaseURL) + client := maloja.NewClient(serverURL, token) + assert.Equal(t, serverURL, client.HTTPClient.BaseURL) } func TestGetScrobbles(t *testing.T) { defer httpmock.DeactivateAndReset() - serverUrl := "https://maloja.example.com" + serverURL := "https://maloja.example.com" token := "thetoken" - client := maloja.NewClient(serverUrl, token) - setupHttpMock(t, client.HttpClient.GetClient(), + client := maloja.NewClient(serverURL, token) + setupHTTPMock(t, client.HTTPClient.GetClient(), "https://maloja.example.com/apis/mlj_1/scrobbles", "testdata/scrobbles.json") @@ -60,7 +60,7 @@ func TestGetScrobbles(t *testing.T) { func TestNewScrobble(t *testing.T) { server := "https://maloja.example.com" client := maloja.NewClient(server, "thetoken") - httpmock.ActivateNonDefault(client.HttpClient.GetClient()) + httpmock.ActivateNonDefault(client.HTTPClient.GetClient()) responder, err := httpmock.NewJsonResponder(200, httpmock.File("testdata/newscrobble-result.json")) if err != nil { @@ -80,7 +80,7 @@ func TestNewScrobble(t *testing.T) { assert.Equal(t, "success", result.Status) } -func setupHttpMock(t *testing.T, client *http.Client, url string, testDataPath string) { +func setupHTTPMock(t *testing.T, client *http.Client, url string, testDataPath string) { httpmock.ActivateNonDefault(client) responder, err := httpmock.NewJsonResponder(200, httpmock.File(testDataPath)) diff --git a/internal/backends/spotify/client.go b/internal/backends/spotify/client.go index 1c002c0..ff2b0a3 100644 --- a/internal/backends/spotify/client.go +++ b/internal/backends/spotify/client.go @@ -40,7 +40,7 @@ const ( ) type Client struct { - HttpClient *resty.Client + HTTPClient *resty.Client } func NewClient(token oauth2.TokenSource) Client { @@ -55,7 +55,7 @@ func NewClient(token oauth2.TokenSource) Client { ratelimit.EnableHTTPHeaderRateLimit(client, "Retry-After") return Client{ - HttpClient: client, + HTTPClient: client, } } @@ -69,7 +69,7 @@ func (c Client) RecentlyPlayedBefore(before time.Time, limit int) (RecentlyPlaye func (c Client) recentlyPlayed(after *time.Time, before *time.Time, limit int) (result RecentlyPlayedResult, err error) { const path = "/me/player/recently-played" - request := c.HttpClient.R(). + request := c.HTTPClient.R(). SetQueryParam("limit", strconv.Itoa(limit)). SetResult(&result) if after != nil { @@ -87,7 +87,7 @@ func (c Client) recentlyPlayed(after *time.Time, before *time.Time, limit int) ( func (c Client) UserTracks(offset int, limit int) (result TracksResult, err error) { const path = "/me/tracks" - response, err := c.HttpClient.R(). + response, err := c.HTTPClient.R(). SetQueryParams(map[string]string{ "offset": strconv.Itoa(offset), "limit": strconv.Itoa(limit), diff --git a/internal/backends/spotify/client_test.go b/internal/backends/spotify/client_test.go index 7d738bf..78ff063 100644 --- a/internal/backends/spotify/client_test.go +++ b/internal/backends/spotify/client_test.go @@ -43,7 +43,7 @@ func TestRecentlyPlayedAfter(t *testing.T) { defer httpmock.DeactivateAndReset() client := spotify.NewClient(nil) - setupHttpMock(t, client.HttpClient.GetClient(), + setupHTTPMock(t, client.HTTPClient.GetClient(), "https://api.spotify.com/v1/me/player/recently-played", "testdata/recently-played.json") @@ -63,7 +63,7 @@ func TestGetUserTracks(t *testing.T) { defer httpmock.DeactivateAndReset() client := spotify.NewClient(nil) - setupHttpMock(t, client.HttpClient.GetClient(), + setupHTTPMock(t, client.HTTPClient.GetClient(), "https://api.spotify.com/v1/me/tracks", "testdata/user-tracks.json") @@ -79,7 +79,7 @@ func TestGetUserTracks(t *testing.T) { assert.Equal("Zeal & Ardor", track1.Track.Album.Name) } -func setupHttpMock(t *testing.T, client *http.Client, url string, testDataPath string) { +func setupHTTPMock(t *testing.T, client *http.Client, url string, testDataPath string) { httpmock.ActivateNonDefault(client) responder, err := httpmock.NewJsonResponder(200, httpmock.File(testDataPath)) diff --git a/internal/backends/spotify/models.go b/internal/backends/spotify/models.go index a22de21..e279e15 100644 --- a/internal/backends/spotify/models.go +++ b/internal/backends/spotify/models.go @@ -58,7 +58,7 @@ type Listen struct { } type Track struct { - Id string `json:"id"` + ID string `json:"id"` Name string `json:"name"` Href string `json:"href"` Uri string `json:"uri"` @@ -69,14 +69,14 @@ type Track struct { Explicit bool `json:"explicit"` IsLocal bool `json:"is_local"` Popularity int `json:"popularity"` - ExternalIds ExternalIds `json:"external_ids"` - ExternalUrls ExternalUrls `json:"external_urls"` + ExternalIDs ExternalIDs `json:"external_ids"` + ExternalURLs ExternalURLs `json:"external_urls"` Album Album `json:"album"` Artists []Artist `json:"artists"` } type Album struct { - Id string `json:"id"` + ID string `json:"id"` Name string `json:"name"` Href string `json:"href"` Uri string `json:"uri"` @@ -85,32 +85,32 @@ type Album struct { ReleaseDate string `json:"release_date"` ReleaseDatePrecision string `json:"release_date_precision"` AlbumType string `json:"album_type"` - ExternalUrls ExternalUrls `json:"external_urls"` + ExternalURLs ExternalURLs `json:"external_urls"` Artists []Artist `json:"artists"` Images []Image `json:"images"` } type Artist struct { - Id string `json:"id"` + ID string `json:"id"` Name string `json:"name"` Href string `json:"href"` Uri string `json:"uri"` Type string `json:"type"` - ExternalUrls ExternalUrls `json:"external_urls"` + ExternalURLs ExternalURLs `json:"external_urls"` } -type ExternalIds struct { +type ExternalIDs struct { ISRC mbtypes.ISRC `json:"isrc"` EAN string `json:"ean"` UPC string `json:"upc"` } -type ExternalUrls struct { +type ExternalURLs struct { Spotify string `json:"spotify"` } type Image struct { - Url string `json:"url"` + URL string `json:"url"` Height int `json:"height"` Width int `json:"width"` } diff --git a/internal/backends/spotify/spotify.go b/internal/backends/spotify/spotify.go index ae2fc25..1827769 100644 --- a/internal/backends/spotify/spotify.go +++ b/internal/backends/spotify/spotify.go @@ -34,7 +34,7 @@ import ( type SpotifyApiBackend struct { client Client - clientId string + clientID string clientSecret string } @@ -53,14 +53,14 @@ func (b *SpotifyApiBackend) Options() []models.BackendOption { } func (b *SpotifyApiBackend) InitConfig(config *config.ServiceConfig) error { - b.clientId = config.GetString("client-id") + b.clientID = config.GetString("client-id") b.clientSecret = config.GetString("client-secret") return nil } -func (b *SpotifyApiBackend) OAuth2Strategy(redirectUrl *url.URL) auth.OAuth2Strategy { +func (b *SpotifyApiBackend) OAuth2Strategy(redirectURL *url.URL) auth.OAuth2Strategy { conf := oauth2.Config{ - ClientID: b.clientId, + ClientID: b.clientID, ClientSecret: b.clientSecret, Scopes: []string{ "user-read-currently-playing", @@ -68,16 +68,16 @@ func (b *SpotifyApiBackend) OAuth2Strategy(redirectUrl *url.URL) auth.OAuth2Stra "user-library-read", "user-library-modify", }, - RedirectURL: redirectUrl.String(), + RedirectURL: redirectURL.String(), Endpoint: spotify.Endpoint, } return auth.NewStandardStrategy(conf) } -func (b *SpotifyApiBackend) OAuth2Config(redirectUrl *url.URL) oauth2.Config { +func (b *SpotifyApiBackend) OAuth2Config(redirectURL *url.URL) oauth2.Config { return oauth2.Config{ - ClientID: b.clientId, + ClientID: b.clientID, ClientSecret: b.clientSecret, Scopes: []string{ "user-read-currently-playing", @@ -85,7 +85,7 @@ func (b *SpotifyApiBackend) OAuth2Config(redirectUrl *url.URL) oauth2.Config { "user-library-read", "user-library-modify", }, - RedirectURL: redirectUrl.String(), + RedirectURL: redirectURL.String(), Endpoint: spotify.Endpoint, } } @@ -251,7 +251,7 @@ func (t Track) AsTrack() models.Track { Duration: time.Duration(t.DurationMs * int(time.Millisecond)), TrackNumber: t.TrackNumber, DiscNumber: t.DiscNumber, - ISRC: t.ExternalIds.ISRC, + ISRC: t.ExternalIDs.ISRC, AdditionalInfo: map[string]any{}, } @@ -264,30 +264,30 @@ func (t Track) AsTrack() models.Track { info["music_service"] = "spotify.com" } - if t.ExternalUrls.Spotify != "" { - info["origin_url"] = t.ExternalUrls.Spotify - info["spotify_id"] = t.ExternalUrls.Spotify + if t.ExternalURLs.Spotify != "" { + info["origin_url"] = t.ExternalURLs.Spotify + info["spotify_id"] = t.ExternalURLs.Spotify } - if t.Album.ExternalUrls.Spotify != "" { - info["spotify_album_id"] = t.Album.ExternalUrls.Spotify + if t.Album.ExternalURLs.Spotify != "" { + info["spotify_album_id"] = t.Album.ExternalURLs.Spotify } if len(t.Artists) > 0 { - info["spotify_artist_ids"] = extractArtistIds(t.Artists) + info["spotify_artist_ids"] = extractArtistIDs(t.Artists) } if len(t.Album.Artists) > 0 { - info["spotify_album_artist_ids"] = extractArtistIds(t.Album.Artists) + info["spotify_album_artist_ids"] = extractArtistIDs(t.Album.Artists) } return track } -func extractArtistIds(artists []Artist) []string { - artistIds := make([]string, len(artists)) +func extractArtistIDs(artists []Artist) []string { + artistIDs := make([]string, len(artists)) for i, artist := range artists { - artistIds[i] = artist.ExternalUrls.Spotify + artistIDs[i] = artist.ExternalURLs.Spotify } - return artistIds + return artistIDs } diff --git a/internal/backends/spotifyhistory/models.go b/internal/backends/spotifyhistory/models.go index fad4fb1..a2eba23 100644 --- a/internal/backends/spotifyhistory/models.go +++ b/internal/backends/spotifyhistory/models.go @@ -92,8 +92,8 @@ func (i HistoryItem) AsListen() models.Listen { PlaybackDuration: time.Duration(i.MillisecondsPlayed * int(time.Millisecond)), UserName: i.UserName, } - if trackUrl, err := formatSpotifyUri(i.SpotifyTrackUri); err != nil { - listen.AdditionalInfo["spotify_id"] = trackUrl + if trackURL, err := formatSpotifyUri(i.SpotifyTrackUri); err != nil { + listen.AdditionalInfo["spotify_id"] = trackURL } return listen } diff --git a/internal/cli/auth.go b/internal/cli/auth.go index 828651a..ff14573 100644 --- a/internal/cli/auth.go +++ b/internal/cli/auth.go @@ -43,20 +43,20 @@ func AuthenticationFlow(service config.ServiceConfig, backend auth.OAuth2Authent state := auth.RandomState() // Redirect user to consent page to ask for permission specified scopes. - authUrl := strategy.AuthCodeURL(verifier, state) + authURL := strategy.AuthCodeURL(verifier, state) // Start an HTTP server to listen for the response responseChan := make(chan auth.CodeResponse) - auth.RunOauth2CallbackServer(*redirectURL, authUrl.Param, responseChan) + auth.RunOauth2CallbackServer(*redirectURL, authURL.Param, responseChan) // Open the URL - fmt.Println(i18n.Tr("Visit the URL for authorization: %v", authUrl.Url)) - err = browser.OpenURL(authUrl.Url) + fmt.Println(i18n.Tr("Visit the URL for authorization: %v", authURL.URL)) + err = browser.OpenURL(authURL.URL) cobra.CheckErr(err) // Retrieve the code from the authentication callback code := <-responseChan - if code.State != authUrl.State { + if code.State != authURL.State { cobra.CompErrorln(i18n.Tr("Error: OAuth state mismatch")) os.Exit(1) } diff --git a/pkg/jspf/extensions.go b/pkg/jspf/extensions.go index 07cd3c1..0f521c4 100644 --- a/pkg/jspf/extensions.go +++ b/pkg/jspf/extensions.go @@ -26,9 +26,9 @@ import "time" const ( // The identifier for the MusicBrainz / ListenBrainz JSPF playlist extension - MusicBrainzPlaylistExtensionId = "https://musicbrainz.org/doc/jspf#playlist" + MusicBrainzPlaylistExtensionID = "https://musicbrainz.org/doc/jspf#playlist" // The identifier for the MusicBrainz / ListenBrainz JSPF track extension - MusicBrainzTrackExtensionId = "https://musicbrainz.org/doc/jspf#track" + MusicBrainzTrackExtensionID = "https://musicbrainz.org/doc/jspf#track" ) // MusicBrainz / ListenBrainz JSPF track extension diff --git a/pkg/jspf/extensions_test.go b/pkg/jspf/extensions_test.go index 8d8653d..883301d 100644 --- a/pkg/jspf/extensions_test.go +++ b/pkg/jspf/extensions_test.go @@ -39,7 +39,7 @@ func ExampleMusicBrainzTrackExtension() { { Title: "Oweynagat", Extension: map[string]any{ - jspf.MusicBrainzTrackExtensionId: jspf.MusicBrainzTrackExtension{ + jspf.MusicBrainzTrackExtensionID: jspf.MusicBrainzTrackExtension{ AddedAt: time.Date(2023, 11, 24, 07, 47, 50, 0, time.UTC), AddedBy: "scotty", }, From bcb183499449e3fe9572895c8b8a1734b346c7d2 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Tue, 29 Apr 2025 13:29:00 +0200 Subject: [PATCH 45/52] scrobblerlog: use camelcase for constants --- internal/backends/scrobblerlog/scrobblerlog.go | 4 ++-- pkg/scrobblerlog/parser.go | 14 +++++++------- pkg/scrobblerlog/parser_test.go | 14 +++++++------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/internal/backends/scrobblerlog/scrobblerlog.go b/internal/backends/scrobblerlog/scrobblerlog.go index 26d417a..19ed30b 100644 --- a/internal/backends/scrobblerlog/scrobblerlog.go +++ b/internal/backends/scrobblerlog/scrobblerlog.go @@ -76,7 +76,7 @@ func (b *ScrobblerLogBackend) InitConfig(config *config.ServiceConfig) error { b.log.FallbackTimezone = location } b.log = scrobblerlog.ScrobblerLog{ - TZ: scrobblerlog.TZ_UTC, + TZ: scrobblerlog.TimezoneUTC, Client: "Rockbox unknown $Revision$", } return nil @@ -197,7 +197,7 @@ func listenToRecord(listen models.Listen) scrobblerlog.Record { var rating scrobblerlog.Rating rockboxRating, ok := listen.AdditionalInfo["rockbox_rating"].(string) if !ok || rockboxRating == "" { - rating = scrobblerlog.RATING_LISTENED + rating = scrobblerlog.RatingListened } else { rating = scrobblerlog.Rating(rating) } diff --git a/pkg/scrobblerlog/parser.go b/pkg/scrobblerlog/parser.go index 892f6e8..9c6471c 100644 --- a/pkg/scrobblerlog/parser.go +++ b/pkg/scrobblerlog/parser.go @@ -45,16 +45,16 @@ import ( type TZInfo string const ( - TZ_UNKNOWN TZInfo = "UNKNOWN" - TZ_UTC TZInfo = "UTC" + TimezoneUnknown TZInfo = "UNKNOWN" + TimezoneUTC TZInfo = "UTC" ) // L if listened at least 50% or S if skipped type Rating string const ( - RATING_LISTENED Rating = "L" - RATING_SKIPPED Rating = "S" + RatingListened Rating = "L" + RatingSkipped Rating = "S" ) // A single entry of a track in the scrobbler log file. @@ -75,7 +75,7 @@ type ScrobblerLog struct { Client string Records []Record // Timezone to be used for timestamps in the log file, - // if TZ is set to [TZ_UNKNOWN]. + // if TZ is set to [TimezoneUnknown]. FallbackTimezone *time.Location } @@ -116,7 +116,7 @@ func (l *ScrobblerLog) Parse(data io.Reader, ignoreSkipped bool) error { return err } - if ignoreSkipped && record.Rating == RATING_SKIPPED { + if ignoreSkipped && record.Rating == RatingSkipped { continue } @@ -224,7 +224,7 @@ func (l ScrobblerLog) rowToRecord(row []string) (Record, error) { } var timezone *time.Location = nil - if l.TZ == TZ_UNKNOWN { + if l.TZ == TimezoneUnknown { timezone = l.FallbackTimezone } diff --git a/pkg/scrobblerlog/parser_test.go b/pkg/scrobblerlog/parser_test.go index 7fd57c3..f4527cc 100644 --- a/pkg/scrobblerlog/parser_test.go +++ b/pkg/scrobblerlog/parser_test.go @@ -50,7 +50,7 @@ func TestParser(t *testing.T) { result := scrobblerlog.ScrobblerLog{} err := result.Parse(data, false) require.NoError(t, err) - assert.Equal(scrobblerlog.TZ_UNKNOWN, result.TZ) + assert.Equal(scrobblerlog.TimezoneUnknown, result.TZ) assert.Equal("Rockbox sansaclipplus $Revision$", result.Client) assert.Len(result.Records, 5) record1 := result.Records[0] @@ -59,11 +59,11 @@ func TestParser(t *testing.T) { assert.Equal("Sevdanin rengi (sipacik) byMrTurkey", record1.TrackName) assert.Equal(5, record1.TrackNumber) assert.Equal(time.Duration(306*time.Second), record1.Duration) - assert.Equal(scrobblerlog.RATING_LISTENED, record1.Rating) + assert.Equal(scrobblerlog.RatingListened, record1.Rating) assert.Equal(time.Unix(1260342084, 0), record1.Timestamp) assert.Equal(mbtypes.MBID(""), record1.MusicBrainzRecordingID) record4 := result.Records[3] - assert.Equal(scrobblerlog.RATING_SKIPPED, record4.Rating) + assert.Equal(scrobblerlog.RatingSkipped, record4.Rating) assert.Equal(mbtypes.MBID("385ba9e9-626d-4750-a607-58e541dca78e"), record4.MusicBrainzRecordingID) } @@ -76,7 +76,7 @@ func TestParserIgnoreSkipped(t *testing.T) { require.NoError(t, err) assert.Len(result.Records, 4) record4 := result.Records[3] - assert.Equal(scrobblerlog.RATING_LISTENED, record4.Rating) + assert.Equal(scrobblerlog.RatingListened, record4.Rating) assert.Equal(mbtypes.MBID("1262beaf-19f8-4534-b9ed-7eef9ca8e83f"), record4.MusicBrainzRecordingID) } @@ -101,7 +101,7 @@ func TestAppend(t *testing.T) { data := make([]byte, 0, 10) buffer := bytes.NewBuffer(data) log := scrobblerlog.ScrobblerLog{ - TZ: scrobblerlog.TZ_UNKNOWN, + TZ: scrobblerlog.TimezoneUnknown, Client: "Rockbox foo $Revision$", } records := []scrobblerlog.Record{ @@ -111,7 +111,7 @@ func TestAppend(t *testing.T) { TrackName: "Reign", TrackNumber: 1, Duration: 271 * time.Second, - Rating: scrobblerlog.RATING_LISTENED, + Rating: scrobblerlog.RatingListened, Timestamp: time.Unix(1699572072, 0), MusicBrainzRecordingID: mbtypes.MBID("b59cf4e7-caee-4019-a844-79d2c58d4dff"), }, @@ -139,7 +139,7 @@ func TestReadHeader(t *testing.T) { log := scrobblerlog.ScrobblerLog{} err := log.ReadHeader(reader) assert.NoError(t, err) - assert.Equal(t, log.TZ, scrobblerlog.TZ_UNKNOWN) + assert.Equal(t, log.TZ, scrobblerlog.TimezoneUnknown) assert.Equal(t, log.Client, "Rockbox sansaclipplus $Revision$") assert.Empty(t, log.Records) } From dff34b249c29916848ef5b700563126bd59a3eef Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Tue, 29 Apr 2025 15:46:14 +0200 Subject: [PATCH 46/52] Updated translation files --- internal/translations/catalog.go | 123 +++++++++--------- .../translations/locales/de/out.gotext.json | 26 ++-- .../translations/locales/en/out.gotext.json | 30 ++--- 3 files changed, 89 insertions(+), 90 deletions(-) diff --git a/internal/translations/catalog.go b/internal/translations/catalog.go index 987612a..c4a7937 100644 --- a/internal/translations/catalog.go +++ b/internal/translations/catalog.go @@ -42,56 +42,56 @@ var messageKeyToIndex = map[string]int{ "\tbackend: %v": 11, "\texport: %s": 0, "\timport: %s\n": 1, - "%v: %v": 47, + "%v: %v": 45, "Aborted": 8, "Access token": 19, - "Access token received, you can use %v now.\n": 33, + "Access token received, you can use %v now.\n": 31, "Append to file": 21, - "Backend": 41, + "Backend": 39, "Check for duplicate listens on import (slower)": 24, "Client ID": 15, "Client secret": 16, "Delete the service configuration \"%v\"?": 7, "Directory path": 27, "Disable auto correction of submitted listens": 25, - "Error: OAuth state mismatch": 32, + "Error: OAuth state mismatch": 30, "Failed reading config: %v": 2, "File path": 20, - "From timestamp: %v (%v)": 43, + "From timestamp: %v (%v)": 41, "Ignore listens in incognito mode": 28, - "Ignore skipped listens": 29, - "Ignored duplicate listen %v: \"%v\" by %v (%v)": 53, - "Import failed, last reported timestamp was %v (%s)": 44, - "Import log:": 46, - "Imported %v of %v %s into %v.": 45, - "Include skipped listens": 26, - "Latest timestamp: %v (%v)": 49, - "Minimum playback duration for skipped tracks (seconds)": 30, - "No": 38, + "Ignore skipped listens": 26, + "Ignored duplicate listen %v: \"%v\" by %v (%v)": 51, + "Import failed, last reported timestamp was %v (%s)": 42, + "Import log:": 44, + "Imported %v of %v %s into %v.": 43, + "Latest timestamp: %v (%v)": 47, + "Minimum playback duration for skipped tracks (seconds)": 29, + "No": 36, "Playlist title": 22, "Saved service %v using backend %v": 5, "Server URL": 17, - "Service": 40, + "Service": 38, "Service \"%v\" deleted\n": 9, "Service name": 3, + "Specify a time zone for the listen timestamps": 52, "The backend %v requires authentication. Authenticate now?": 6, "Token received, you can close this window now.": 12, - "Transferring %s from %s to %s...": 42, + "Transferring %s from %s to %s...": 40, "Unique playlist identifier": 23, "Updated service %v using backend %v\n": 10, "User name": 18, - "Visit the URL for authorization: %v": 31, - "Yes": 37, + "Visit the URL for authorization: %v": 53, + "Yes": 35, "a service with this name already exists": 4, "backend %s does not implement %s": 13, - "done": 36, - "exporting": 34, - "importing": 35, - "invalid timestamp string \"%v\"": 48, - "key must only consist of A-Za-z0-9_-": 51, - "no configuration file defined, cannot write config": 50, - "no existing service configurations": 39, - "no service configuration \"%v\"": 52, + "done": 34, + "exporting": 32, + "importing": 33, + "invalid timestamp string \"%v\"": 46, + "key must only consist of A-Za-z0-9_-": 49, + "no configuration file defined, cannot write config": 48, + "no existing service configurations": 37, + "no service configuration \"%v\"": 50, "unknown backend \"%s\"": 14, } @@ -103,18 +103,18 @@ var deIndex = []uint32{ // 55 elements 0x000001ac, 0x000001e7, 0x00000213, 0x00000233, 0x0000023d, 0x0000024b, 0x00000256, 0x00000263, 0x00000271, 0x0000027b, 0x0000028e, 0x000002a1, - 0x000002b8, 0x000002ed, 0x00000321, 0x00000342, - 0x00000352, 0x00000378, 0x0000039a, 0x000003d8, + 0x000002b8, 0x000002ed, 0x00000321, 0x00000343, + 0x00000353, 0x00000379, 0x000003b7, 0x000003e1, // Entry 20 - 3F - 0x000003fe, 0x00000428, 0x00000468, 0x00000473, - 0x0000047e, 0x00000485, 0x00000488, 0x0000048d, - 0x000004b6, 0x000004be, 0x000004c6, 0x000004ef, - 0x0000050d, 0x0000054a, 0x00000575, 0x00000580, - 0x0000058d, 0x000005b1, 0x000005d4, 0x00000625, - 0x0000065c, 0x00000683, 0x00000683, + 0x00000421, 0x0000042c, 0x00000437, 0x0000043e, + 0x00000441, 0x00000446, 0x0000046f, 0x00000477, + 0x0000047f, 0x000004a8, 0x000004c6, 0x00000503, + 0x0000052e, 0x00000539, 0x00000546, 0x0000056a, + 0x0000058d, 0x000005de, 0x00000615, 0x0000063c, + 0x0000063c, 0x0000063c, 0x0000063c, } // Size: 244 bytes -const deData string = "" + // Size: 1667 bytes +const deData string = "" + // Size: 1596 bytes "\x04\x01\x09\x00\x0e\x02Export: %[1]s\x04\x01\x09\x01\x0a\x0e\x02Import:" + " %[1]s\x02Fehler beim Lesen der Konfiguration: %[1]v\x02Servicename\x02e" + "in Service mit diesem Namen existiert bereits\x02Service %[1]v mit dem B" + @@ -128,10 +128,9 @@ const deData string = "" + // Size: 1667 bytes "\x02Benutzername\x02Zugriffstoken\x02Dateipfad\x02An Datei anhängen\x02T" + "itel der Playlist\x02Eindeutige Playlist-ID\x02Beim Import auf Listen-Du" + "plikate prüfen (langsamer)\x02Autokorrektur für übermittelte Titel deakt" + - "ivieren\x02Übersprungene Titel einbeziehen\x02Verzeichnispfad\x02Listens" + - " im Inkognito-Modus ignorieren\x02Übersprungene Listens ignorieren\x02Mi" + - "nimale Wiedergabedauer für übersprungene Titel (Sekunden)\x02URL für Aut" + - "orisierung öffnen: %[1]v\x02Fehler: OAuth-State stimmt nicht überein\x04" + + "ivieren\x02Übersprungene Listens ignorieren\x02Verzeichnispfad\x02Listen" + + "s im Inkognito-Modus ignorieren\x02Minimale Wiedergabedauer für überspru" + + "ngene Titel (Sekunden)\x02Fehler: OAuth-State stimmt nicht überein\x04" + "\x00\x01\x0a;\x02Zugriffstoken erhalten, %[1]v kann jetzt verwendet werd" + "en.\x02exportiere\x02importiere\x02fertig\x02Ja\x02Nein\x02keine bestehe" + "nden Servicekonfigurationen\x02Service\x02Backend\x02Übertrage %[1]s von" + @@ -151,18 +150,18 @@ var enIndex = []uint32{ // 55 elements 0x00000170, 0x0000019f, 0x000001c6, 0x000001de, 0x000001e8, 0x000001f6, 0x00000201, 0x0000020b, 0x00000218, 0x00000222, 0x00000231, 0x00000240, - 0x0000025b, 0x0000028a, 0x000002b7, 0x000002cf, - 0x000002de, 0x000002ff, 0x00000316, 0x0000034d, + 0x0000025b, 0x0000028a, 0x000002b7, 0x000002ce, + 0x000002dd, 0x000002fe, 0x00000335, 0x00000351, // Entry 20 - 3F - 0x00000374, 0x00000390, 0x000003c3, 0x000003cd, - 0x000003d7, 0x000003dc, 0x000003e0, 0x000003e3, - 0x00000406, 0x0000040e, 0x00000416, 0x00000440, - 0x0000045e, 0x00000497, 0x000004c1, 0x000004cd, - 0x000004da, 0x000004fb, 0x0000051b, 0x0000054e, - 0x00000573, 0x00000594, 0x000005cd, + 0x00000384, 0x0000038e, 0x00000398, 0x0000039d, + 0x000003a1, 0x000003a4, 0x000003c7, 0x000003cf, + 0x000003d7, 0x00000401, 0x0000041f, 0x00000458, + 0x00000482, 0x0000048e, 0x0000049b, 0x000004bc, + 0x000004dc, 0x0000050f, 0x00000534, 0x00000555, + 0x0000058e, 0x000005bc, 0x000005e3, } // Size: 244 bytes -const enData string = "" + // Size: 1485 bytes +const enData string = "" + // Size: 1507 bytes "\x04\x01\x09\x00\x0e\x02export: %[1]s\x04\x01\x09\x01\x0a\x0e\x02import:" + " %[1]s\x02Failed reading config: %[1]v\x02Service name\x02a service with" + " this name already exists\x02Saved service %[1]v using backend %[2]v\x02" + @@ -175,18 +174,18 @@ const enData string = "" + // Size: 1485 bytes "\x02Server URL\x02User name\x02Access token\x02File path\x02Append to fi" + "le\x02Playlist title\x02Unique playlist identifier\x02Check for duplicat" + "e listens on import (slower)\x02Disable auto correction of submitted lis" + - "tens\x02Include skipped listens\x02Directory path\x02Ignore listens in i" + - "ncognito mode\x02Ignore skipped listens\x02Minimum playback duration for" + - " skipped tracks (seconds)\x02Visit the URL for authorization: %[1]v\x02E" + - "rror: OAuth state mismatch\x04\x00\x01\x0a.\x02Access token received, yo" + - "u can use %[1]v now.\x02exporting\x02importing\x02done\x02Yes\x02No\x02n" + - "o existing service configurations\x02Service\x02Backend\x02Transferring " + - "%[1]s from %[2]s to %[3]s...\x02From timestamp: %[1]v (%[2]v)\x02Import " + - "failed, last reported timestamp was %[1]v (%[2]s)\x02Imported %[1]v of %" + - "[2]v %[3]s into %[4]v.\x02Import log:\x02%[1]v: %[2]v\x02invalid timesta" + - "mp string \x22%[1]v\x22\x02Latest timestamp: %[1]v (%[2]v)\x02no configu" + - "ration file defined, cannot write config\x02key must only consist of A-Z" + - "a-z0-9_-\x02no service configuration \x22%[1]v\x22\x02Ignored duplicate " + - "listen %[1]v: \x22%[2]v\x22 by %[3]v (%[4]v)" + "tens\x02Ignore skipped listens\x02Directory path\x02Ignore listens in in" + + "cognito mode\x02Minimum playback duration for skipped tracks (seconds)" + + "\x02Error: OAuth state mismatch\x04\x00\x01\x0a.\x02Access token receive" + + "d, you can use %[1]v now.\x02exporting\x02importing\x02done\x02Yes\x02No" + + "\x02no existing service configurations\x02Service\x02Backend\x02Transfer" + + "ring %[1]s from %[2]s to %[3]s...\x02From timestamp: %[1]v (%[2]v)\x02Im" + + "port failed, last reported timestamp was %[1]v (%[2]s)\x02Imported %[1]v" + + " of %[2]v %[3]s into %[4]v.\x02Import log:\x02%[1]v: %[2]v\x02invalid ti" + + "mestamp string \x22%[1]v\x22\x02Latest timestamp: %[1]v (%[2]v)\x02no co" + + "nfiguration file defined, cannot write config\x02key must only consist o" + + "f A-Za-z0-9_-\x02no service configuration \x22%[1]v\x22\x02Ignored dupli" + + "cate listen %[1]v: \x22%[2]v\x22 by %[3]v (%[4]v)\x02Specify a time zone" + + " for the listen timestamps\x02Visit the URL for authorization: %[1]v" - // Total table size 3640 bytes (3KiB); checksum: 719A868A + // Total table size 3591 bytes (3KiB); checksum: 6C862242 diff --git a/internal/translations/locales/de/out.gotext.json b/internal/translations/locales/de/out.gotext.json index 18333c6..65abfdd 100644 --- a/internal/translations/locales/de/out.gotext.json +++ b/internal/translations/locales/de/out.gotext.json @@ -305,9 +305,14 @@ "translation": "Autokorrektur für übermittelte Titel deaktivieren" }, { - "id": "Include skipped listens", - "message": "Include skipped listens", - "translation": "Übersprungene Titel einbeziehen" + "id": "Ignore skipped listens", + "message": "Ignore skipped listens", + "translation": "Übersprungene Listens ignorieren" + }, + { + "id": "Specify a time zone for the listen timestamps", + "message": "Specify a time zone for the listen timestamps", + "translation": "" }, { "id": "Directory path", @@ -319,28 +324,23 @@ "message": "Ignore listens in incognito mode", "translation": "Listens im Inkognito-Modus ignorieren" }, - { - "id": "Ignore skipped listens", - "message": "Ignore skipped listens", - "translation": "Übersprungene Listens ignorieren" - }, { "id": "Minimum playback duration for skipped tracks (seconds)", "message": "Minimum playback duration for skipped tracks (seconds)", "translation": "Minimale Wiedergabedauer für übersprungene Titel (Sekunden)" }, { - "id": "Visit the URL for authorization: {Url}", - "message": "Visit the URL for authorization: {Url}", - "translation": "URL für Autorisierung öffnen: {Url}", + "id": "Visit the URL for authorization: {URL}", + "message": "Visit the URL for authorization: {URL}", + "translation": "", "placeholders": [ { - "id": "Url", + "id": "URL", "string": "%[1]v", "type": "string", "underlyingType": "string", "argNum": 1, - "expr": "authUrl.Url" + "expr": "authURL.URL" } ] }, diff --git a/internal/translations/locales/en/out.gotext.json b/internal/translations/locales/en/out.gotext.json index 54c6719..de7d4d9 100644 --- a/internal/translations/locales/en/out.gotext.json +++ b/internal/translations/locales/en/out.gotext.json @@ -359,9 +359,16 @@ "fuzzy": true }, { - "id": "Include skipped listens", - "message": "Include skipped listens", - "translation": "Include skipped listens", + "id": "Ignore skipped listens", + "message": "Ignore skipped listens", + "translation": "Ignore skipped listens", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Specify a time zone for the listen timestamps", + "message": "Specify a time zone for the listen timestamps", + "translation": "Specify a time zone for the listen timestamps", "translatorComment": "Copied from source.", "fuzzy": true }, @@ -379,13 +386,6 @@ "translatorComment": "Copied from source.", "fuzzy": true }, - { - "id": "Ignore skipped listens", - "message": "Ignore skipped listens", - "translation": "Ignore skipped listens", - "translatorComment": "Copied from source.", - "fuzzy": true - }, { "id": "Minimum playback duration for skipped tracks (seconds)", "message": "Minimum playback duration for skipped tracks (seconds)", @@ -394,18 +394,18 @@ "fuzzy": true }, { - "id": "Visit the URL for authorization: {Url}", - "message": "Visit the URL for authorization: {Url}", - "translation": "Visit the URL for authorization: {Url}", + "id": "Visit the URL for authorization: {URL}", + "message": "Visit the URL for authorization: {URL}", + "translation": "Visit the URL for authorization: {URL}", "translatorComment": "Copied from source.", "placeholders": [ { - "id": "Url", + "id": "URL", "string": "%[1]v", "type": "string", "underlyingType": "string", "argNum": 1, - "expr": "authUrl.Url" + "expr": "authURL.URL" } ], "fuzzy": true From 90e101080f707311bd539474cb2c7d2cea6920fa Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Tue, 29 Apr 2025 13:51:09 +0000 Subject: [PATCH 47/52] Translated using Weblate (German) Currently translated at 100.0% (54 of 54 strings) Co-authored-by: Philipp Wolfer Translate-URL: https://translate.uploadedlobster.com/projects/scotty/app/de/ Translation: Scotty/app --- internal/translations/locales/de/messages.gotext.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/translations/locales/de/messages.gotext.json b/internal/translations/locales/de/messages.gotext.json index afa4129..19a7a25 100644 --- a/internal/translations/locales/de/messages.gotext.json +++ b/internal/translations/locales/de/messages.gotext.json @@ -175,7 +175,7 @@ { "id": "backend {Backend} does not implement {InterfaceName}", "message": "backend {Backend} does not implement {InterfaceName}", - "translation": "das backend {Backend} implementiert {InterfaceName} nicht", + "translation": "das Backend {Backend} implementiert {InterfaceName} nicht", "placeholders": [ { "id": "Backend", @@ -413,7 +413,7 @@ { "id": "Transferring {Entity} from {SourceName} to {TargetName}...", "message": "Transferring {Entity} from {SourceName} to {TargetName}...", - "translation": "Übertrage {Entity} von {SourceName} nach {TargetName}...", + "translation": "Übertrage {Entity} von {SourceName} nach {TargetName}…", "placeholders": [ { "id": "Entity", From a5442b477e3669ca8c3dda7808d8c308d08368e9 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Tue, 29 Apr 2025 16:06:13 +0200 Subject: [PATCH 48/52] Sync translations with new strings --- internal/cli/transfer.go | 2 +- internal/translations/catalog.go | 36 ++++++------- .../locales/de/messages.gotext.json | 54 +++++++++---------- .../translations/locales/de/out.gotext.json | 8 +-- .../locales/en/messages.gotext.json | 50 ++++++++--------- .../translations/locales/en/out.gotext.json | 6 +-- 6 files changed, 78 insertions(+), 78 deletions(-) diff --git a/internal/cli/transfer.go b/internal/cli/transfer.go index 427af06..ade7ece 100644 --- a/internal/cli/transfer.go +++ b/internal/cli/transfer.go @@ -88,7 +88,7 @@ func (c *TransferCmd[E, I, R]) resolveBackends(source string, target string) err } func (c *TransferCmd[E, I, R]) Transfer(exp backends.ExportProcessor[R], imp backends.ImportProcessor[R]) error { - fmt.Println(i18n.Tr("Transferring %s from %s to %s...", c.entity, c.sourceName, c.targetName)) + fmt.Println(i18n.Tr("Transferring %s from %s to %s…", c.entity, c.sourceName, c.targetName)) // Authenticate backends, if needed config := viper.GetViper() diff --git a/internal/translations/catalog.go b/internal/translations/catalog.go index c4a7937..374179f 100644 --- a/internal/translations/catalog.go +++ b/internal/translations/catalog.go @@ -76,7 +76,7 @@ var messageKeyToIndex = map[string]int{ "Specify a time zone for the listen timestamps": 52, "The backend %v requires authentication. Authenticate now?": 6, "Token received, you can close this window now.": 12, - "Transferring %s from %s to %s...": 40, + "Transferring %s from %s to %s…": 40, "Unique playlist identifier": 23, "Updated service %v using backend %v\n": 10, "User name": 18, @@ -123,7 +123,7 @@ const deData string = "" + // Size: 1596 bytes "\x02Abgebrochen\x04\x00\x01\x0a\x1e\x02Service „%[1]v“ gelöscht\x04\x00" + "\x01\x0a1\x02Service %[1]v mit dem Backend %[2]v aktualisiert\x04\x01" + "\x09\x00\x0f\x02Backend: %[1]v\x02Token erhalten, das Fenster kann jetzt" + - " geschlossen werden.\x02das backend %[1]s implementiert %[2]s nicht\x02u" + + " geschlossen werden.\x02das Backend %[1]s implementiert %[2]s nicht\x02u" + "nbekanntes Backend „%[1]s“\x02Client-ID\x02Client-Secret\x02Server-URL" + "\x02Benutzername\x02Zugriffstoken\x02Dateipfad\x02An Datei anhängen\x02T" + "itel der Playlist\x02Eindeutige Playlist-ID\x02Beim Import auf Listen-Du" + @@ -134,13 +134,13 @@ const deData string = "" + // Size: 1596 bytes "\x00\x01\x0a;\x02Zugriffstoken erhalten, %[1]v kann jetzt verwendet werd" + "en.\x02exportiere\x02importiere\x02fertig\x02Ja\x02Nein\x02keine bestehe" + "nden Servicekonfigurationen\x02Service\x02Backend\x02Übertrage %[1]s von" + - " %[2]s nach %[3]s...\x02Ab Zeitstempel: %[1]v (%[2]v)\x02Import fehlgesc" + - "hlagen, letzter Zeitstempel war %[1]v (%[2]s)\x02%[1]v von %[2]v %[3]s i" + - "n %[4]v importiert.\x02Importlog:\x02%[1]v: %[2]v\x02ungültiger Zeitstem" + - "pel „%[1]v“\x02Letzter Zeitstempel: %[1]v (%[2]v)\x02keine Konfiguration" + - "sdatei definiert, Konfiguration kann nicht geschrieben werden\x02Schlüss" + - "el darf nur die Zeichen A-Za-z0-9_- beinhalten\x02keine Servicekonfigura" + - "tion „%[1]v“" + " %[2]s nach %[3]s…\x02Ab Zeitstempel: %[1]v (%[2]v)\x02Import fehlgeschl" + + "agen, letzter Zeitstempel war %[1]v (%[2]s)\x02%[1]v von %[2]v %[3]s in " + + "%[4]v importiert.\x02Importlog:\x02%[1]v: %[2]v\x02ungültiger Zeitstempe" + + "l „%[1]v“\x02Letzter Zeitstempel: %[1]v (%[2]v)\x02keine Konfigurationsd" + + "atei definiert, Konfiguration kann nicht geschrieben werden\x02Schlüssel" + + " darf nur die Zeichen A-Za-z0-9_- beinhalten\x02keine Servicekonfigurati" + + "on „%[1]v“" var enIndex = []uint32{ // 55 elements // Entry 0 - 1F @@ -179,13 +179,13 @@ const enData string = "" + // Size: 1507 bytes "\x02Error: OAuth state mismatch\x04\x00\x01\x0a.\x02Access token receive" + "d, you can use %[1]v now.\x02exporting\x02importing\x02done\x02Yes\x02No" + "\x02no existing service configurations\x02Service\x02Backend\x02Transfer" + - "ring %[1]s from %[2]s to %[3]s...\x02From timestamp: %[1]v (%[2]v)\x02Im" + - "port failed, last reported timestamp was %[1]v (%[2]s)\x02Imported %[1]v" + - " of %[2]v %[3]s into %[4]v.\x02Import log:\x02%[1]v: %[2]v\x02invalid ti" + - "mestamp string \x22%[1]v\x22\x02Latest timestamp: %[1]v (%[2]v)\x02no co" + - "nfiguration file defined, cannot write config\x02key must only consist o" + - "f A-Za-z0-9_-\x02no service configuration \x22%[1]v\x22\x02Ignored dupli" + - "cate listen %[1]v: \x22%[2]v\x22 by %[3]v (%[4]v)\x02Specify a time zone" + - " for the listen timestamps\x02Visit the URL for authorization: %[1]v" + "ring %[1]s from %[2]s to %[3]s…\x02From timestamp: %[1]v (%[2]v)\x02Impo" + + "rt failed, last reported timestamp was %[1]v (%[2]s)\x02Imported %[1]v o" + + "f %[2]v %[3]s into %[4]v.\x02Import log:\x02%[1]v: %[2]v\x02invalid time" + + "stamp string \x22%[1]v\x22\x02Latest timestamp: %[1]v (%[2]v)\x02no conf" + + "iguration file defined, cannot write config\x02key must only consist of " + + "A-Za-z0-9_-\x02no service configuration \x22%[1]v\x22\x02Ignored duplica" + + "te listen %[1]v: \x22%[2]v\x22 by %[3]v (%[4]v)\x02Specify a time zone f" + + "or the listen timestamps\x02Visit the URL for authorization: %[1]v" - // Total table size 3591 bytes (3KiB); checksum: 6C862242 + // Total table size 3591 bytes (3KiB); checksum: 2A4B9572 diff --git a/internal/translations/locales/de/messages.gotext.json b/internal/translations/locales/de/messages.gotext.json index 19a7a25..dfa808b 100644 --- a/internal/translations/locales/de/messages.gotext.json +++ b/internal/translations/locales/de/messages.gotext.json @@ -261,9 +261,9 @@ "translation": "Beim Import auf Listen-Duplikate prüfen (langsamer)" }, { - "id": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMbid})", - "message": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMbid})", - "translation": "Listen-Duplikat ignoriert {ListenedAt}: „{TrackName}“ von {ArtistName} ({RecordingMbid})", + "id": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMBID})", + "message": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMBID})", + "translation": "", "placeholders": [ { "id": "ListenedAt", @@ -290,12 +290,12 @@ "expr": "l.ArtistName()" }, { - "id": "RecordingMbid", + "id": "RecordingMBID", "string": "%[4]v", - "type": "go.uploadedlobster.com/scotty/internal/models.MBID", + "type": "go.uploadedlobster.com/mbtypes.MBID", "underlyingType": "string", "argNum": 4, - "expr": "l.RecordingMbid" + "expr": "l.RecordingMBID" } ] }, @@ -305,9 +305,14 @@ "translation": "Autokorrektur für übermittelte Titel deaktivieren" }, { - "id": "Include skipped listens", - "message": "Include skipped listens", - "translation": "Übersprungene Titel einbeziehen" + "id": "Ignore skipped listens", + "message": "Ignore skipped listens", + "translation": "Übersprungene Listens ignorieren" + }, + { + "id": "Specify a time zone for the listen timestamps", + "message": "Specify a time zone for the listen timestamps", + "translation": "" }, { "id": "Directory path", @@ -319,28 +324,23 @@ "message": "Ignore listens in incognito mode", "translation": "Listens im Inkognito-Modus ignorieren" }, - { - "id": "Ignore skipped listens", - "message": "Ignore skipped listens", - "translation": "Übersprungene Listens ignorieren" - }, { "id": "Minimum playback duration for skipped tracks (seconds)", "message": "Minimum playback duration for skipped tracks (seconds)", "translation": "Minimale Wiedergabedauer für übersprungene Titel (Sekunden)" }, { - "id": "Visit the URL for authorization: {Url}", - "message": "Visit the URL for authorization: {Url}", - "translation": "URL für Autorisierung öffnen: {Url}", + "id": "Visit the URL for authorization: {URL}", + "message": "Visit the URL for authorization: {URL}", + "translation": "", "placeholders": [ { - "id": "Url", + "id": "URL", "string": "%[1]v", "type": "string", "underlyingType": "string", "argNum": 1, - "expr": "authUrl.Url" + "expr": "authURL.URL" } ] }, @@ -367,23 +367,23 @@ { "id": "exporting", "message": "exporting", + "translation": "exportiere", "translatorComment": "Copied from source.", - "fuzzy": true, - "translation": "exportiere" + "fuzzy": true }, { "id": "importing", "message": "importing", + "translation": "importiere", "translatorComment": "Copied from source.", - "fuzzy": true, - "translation": "importiere" + "fuzzy": true }, { "id": "done", "message": "done", + "translation": "fertig", "translatorComment": "Copied from source.", - "fuzzy": true, - "translation": "fertig" + "fuzzy": true }, { "id": "Yes", @@ -411,8 +411,8 @@ "translation": "Backend" }, { - "id": "Transferring {Entity} from {SourceName} to {TargetName}...", - "message": "Transferring {Entity} from {SourceName} to {TargetName}...", + "id": "Transferring {Entity} from {SourceName} to {TargetName}…", + "message": "Transferring {Entity} from {SourceName} to {TargetName}…", "translation": "Übertrage {Entity} von {SourceName} nach {TargetName}…", "placeholders": [ { diff --git a/internal/translations/locales/de/out.gotext.json b/internal/translations/locales/de/out.gotext.json index 65abfdd..7a13af8 100644 --- a/internal/translations/locales/de/out.gotext.json +++ b/internal/translations/locales/de/out.gotext.json @@ -175,7 +175,7 @@ { "id": "backend {Backend} does not implement {InterfaceName}", "message": "backend {Backend} does not implement {InterfaceName}", - "translation": "das backend {Backend} implementiert {InterfaceName} nicht", + "translation": "das Backend {Backend} implementiert {InterfaceName} nicht", "placeholders": [ { "id": "Backend", @@ -411,9 +411,9 @@ "translation": "Backend" }, { - "id": "Transferring {Entity} from {SourceName} to {TargetName}...", - "message": "Transferring {Entity} from {SourceName} to {TargetName}...", - "translation": "Übertrage {Entity} von {SourceName} nach {TargetName}...", + "id": "Transferring {Entity} from {SourceName} to {TargetName}…", + "message": "Transferring {Entity} from {SourceName} to {TargetName}…", + "translation": "Übertrage {Entity} von {SourceName} nach {TargetName}…", "placeholders": [ { "id": "Entity", diff --git a/internal/translations/locales/en/messages.gotext.json b/internal/translations/locales/en/messages.gotext.json index 7687276..ed62636 100644 --- a/internal/translations/locales/en/messages.gotext.json +++ b/internal/translations/locales/en/messages.gotext.json @@ -311,9 +311,9 @@ "fuzzy": true }, { - "id": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMbid})", - "message": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMbid})", - "translation": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMbid})", + "id": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMBID})", + "message": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMBID})", + "translation": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMBID})", "translatorComment": "Copied from source.", "placeholders": [ { @@ -341,12 +341,12 @@ "expr": "l.ArtistName()" }, { - "id": "RecordingMbid", + "id": "RecordingMBID", "string": "%[4]v", - "type": "go.uploadedlobster.com/scotty/internal/models.MBID", + "type": "go.uploadedlobster.com/mbtypes.MBID", "underlyingType": "string", "argNum": 4, - "expr": "l.RecordingMbid" + "expr": "l.RecordingMBID" } ], "fuzzy": true @@ -359,9 +359,16 @@ "fuzzy": true }, { - "id": "Include skipped listens", - "message": "Include skipped listens", - "translation": "Include skipped listens", + "id": "Ignore skipped listens", + "message": "Ignore skipped listens", + "translation": "Ignore skipped listens", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Specify a time zone for the listen timestamps", + "message": "Specify a time zone for the listen timestamps", + "translation": "Specify a time zone for the listen timestamps", "translatorComment": "Copied from source.", "fuzzy": true }, @@ -379,13 +386,6 @@ "translatorComment": "Copied from source.", "fuzzy": true }, - { - "id": "Ignore skipped listens", - "message": "Ignore skipped listens", - "translation": "Ignore skipped listens", - "translatorComment": "Copied from source.", - "fuzzy": true - }, { "id": "Minimum playback duration for skipped tracks (seconds)", "message": "Minimum playback duration for skipped tracks (seconds)", @@ -394,18 +394,18 @@ "fuzzy": true }, { - "id": "Visit the URL for authorization: {Url}", - "message": "Visit the URL for authorization: {Url}", - "translation": "Visit the URL for authorization: {Url}", + "id": "Visit the URL for authorization: {URL}", + "message": "Visit the URL for authorization: {URL}", + "translation": "Visit the URL for authorization: {URL}", "translatorComment": "Copied from source.", "placeholders": [ { - "id": "Url", + "id": "URL", "string": "%[1]v", "type": "string", "underlyingType": "string", "argNum": 1, - "expr": "authUrl.Url" + "expr": "authURL.URL" } ], "fuzzy": true @@ -491,9 +491,9 @@ "fuzzy": true }, { - "id": "Transferring {Entity} from {SourceName} to {TargetName}...", - "message": "Transferring {Entity} from {SourceName} to {TargetName}...", - "translation": "Transferring {Entity} from {SourceName} to {TargetName}...", + "id": "Transferring {Entity} from {SourceName} to {TargetName}…", + "message": "Transferring {Entity} from {SourceName} to {TargetName}…", + "translation": "Transferring {Entity} from {SourceName} to {TargetName}…", "translatorComment": "Copied from source.", "placeholders": [ { @@ -714,4 +714,4 @@ "fuzzy": true } ] -} \ No newline at end of file +} diff --git a/internal/translations/locales/en/out.gotext.json b/internal/translations/locales/en/out.gotext.json index de7d4d9..eecf359 100644 --- a/internal/translations/locales/en/out.gotext.json +++ b/internal/translations/locales/en/out.gotext.json @@ -491,9 +491,9 @@ "fuzzy": true }, { - "id": "Transferring {Entity} from {SourceName} to {TargetName}...", - "message": "Transferring {Entity} from {SourceName} to {TargetName}...", - "translation": "Transferring {Entity} from {SourceName} to {TargetName}...", + "id": "Transferring {Entity} from {SourceName} to {TargetName}…", + "message": "Transferring {Entity} from {SourceName} to {TargetName}…", + "translation": "Transferring {Entity} from {SourceName} to {TargetName}…", "translatorComment": "Copied from source.", "placeholders": [ { From a6cc8d49ac278694e840d41091bab82ba056e0f3 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Tue, 29 Apr 2025 14:11:31 +0000 Subject: [PATCH 49/52] Translated using Weblate (German) Currently translated at 100.0% (54 of 54 strings) Co-authored-by: Philipp Wolfer Translate-URL: https://translate.uploadedlobster.com/projects/scotty/app/de/ Translation: Scotty/app --- .../locales/de/messages.gotext.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/internal/translations/locales/de/messages.gotext.json b/internal/translations/locales/de/messages.gotext.json index dfa808b..8cbe44a 100644 --- a/internal/translations/locales/de/messages.gotext.json +++ b/internal/translations/locales/de/messages.gotext.json @@ -263,7 +263,7 @@ { "id": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMBID})", "message": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMBID})", - "translation": "", + "translation": "Listen-Duplikat ignoriert {ListenedAt}: \"{TrackName}\" von {ArtistName} ({RecordingMBID})", "placeholders": [ { "id": "ListenedAt", @@ -312,7 +312,7 @@ { "id": "Specify a time zone for the listen timestamps", "message": "Specify a time zone for the listen timestamps", - "translation": "" + "translation": "Zeitzone für den Abspiel-Zeitstempel" }, { "id": "Directory path", @@ -332,7 +332,7 @@ { "id": "Visit the URL for authorization: {URL}", "message": "Visit the URL for authorization: {URL}", - "translation": "", + "translation": "Zur Anmeldung folgende URL aufrufen: {URL}", "placeholders": [ { "id": "URL", @@ -367,23 +367,23 @@ { "id": "exporting", "message": "exporting", - "translation": "exportiere", "translatorComment": "Copied from source.", - "fuzzy": true + "fuzzy": true, + "translation": "exportiere" }, { "id": "importing", "message": "importing", - "translation": "importiere", "translatorComment": "Copied from source.", - "fuzzy": true + "fuzzy": true, + "translation": "importiere" }, { "id": "done", "message": "done", - "translation": "fertig", "translatorComment": "Copied from source.", - "fuzzy": true + "fuzzy": true, + "translation": "fertig" }, { "id": "Yes", From 19852be68b6003c03cd45b1d80db54251954b82b Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Tue, 29 Apr 2025 16:12:42 +0200 Subject: [PATCH 50/52] Updated translations --- internal/translations/catalog.go | 152 +++++++++--------- .../translations/locales/de/out.gotext.json | 6 +- 2 files changed, 80 insertions(+), 78 deletions(-) diff --git a/internal/translations/catalog.go b/internal/translations/catalog.go index 374179f..3eb2f7e 100644 --- a/internal/translations/catalog.go +++ b/internal/translations/catalog.go @@ -42,56 +42,56 @@ var messageKeyToIndex = map[string]int{ "\tbackend: %v": 11, "\texport: %s": 0, "\timport: %s\n": 1, - "%v: %v": 45, + "%v: %v": 48, "Aborted": 8, "Access token": 19, - "Access token received, you can use %v now.\n": 31, + "Access token received, you can use %v now.\n": 34, "Append to file": 21, - "Backend": 39, + "Backend": 42, "Check for duplicate listens on import (slower)": 24, "Client ID": 15, "Client secret": 16, "Delete the service configuration \"%v\"?": 7, - "Directory path": 27, - "Disable auto correction of submitted listens": 25, - "Error: OAuth state mismatch": 30, + "Directory path": 29, + "Disable auto correction of submitted listens": 26, + "Error: OAuth state mismatch": 33, "Failed reading config: %v": 2, "File path": 20, - "From timestamp: %v (%v)": 41, - "Ignore listens in incognito mode": 28, - "Ignore skipped listens": 26, - "Ignored duplicate listen %v: \"%v\" by %v (%v)": 51, - "Import failed, last reported timestamp was %v (%s)": 42, - "Import log:": 44, - "Imported %v of %v %s into %v.": 43, - "Latest timestamp: %v (%v)": 47, - "Minimum playback duration for skipped tracks (seconds)": 29, - "No": 36, + "From timestamp: %v (%v)": 44, + "Ignore listens in incognito mode": 30, + "Ignore skipped listens": 27, + "Ignored duplicate listen %v: \"%v\" by %v (%v)": 25, + "Import failed, last reported timestamp was %v (%s)": 45, + "Import log:": 47, + "Imported %v of %v %s into %v.": 46, + "Latest timestamp: %v (%v)": 50, + "Minimum playback duration for skipped tracks (seconds)": 31, + "No": 39, "Playlist title": 22, "Saved service %v using backend %v": 5, "Server URL": 17, - "Service": 38, + "Service": 41, "Service \"%v\" deleted\n": 9, "Service name": 3, - "Specify a time zone for the listen timestamps": 52, + "Specify a time zone for the listen timestamps": 28, "The backend %v requires authentication. Authenticate now?": 6, "Token received, you can close this window now.": 12, - "Transferring %s from %s to %s…": 40, + "Transferring %s from %s to %s…": 43, "Unique playlist identifier": 23, "Updated service %v using backend %v\n": 10, "User name": 18, - "Visit the URL for authorization: %v": 53, - "Yes": 35, + "Visit the URL for authorization: %v": 32, + "Yes": 38, "a service with this name already exists": 4, "backend %s does not implement %s": 13, - "done": 34, - "exporting": 32, - "importing": 33, - "invalid timestamp string \"%v\"": 46, - "key must only consist of A-Za-z0-9_-": 49, - "no configuration file defined, cannot write config": 48, - "no existing service configurations": 37, - "no service configuration \"%v\"": 50, + "done": 37, + "exporting": 35, + "importing": 36, + "invalid timestamp string \"%v\"": 49, + "key must only consist of A-Za-z0-9_-": 52, + "no configuration file defined, cannot write config": 51, + "no existing service configurations": 40, + "no service configuration \"%v\"": 53, "unknown backend \"%s\"": 14, } @@ -103,18 +103,18 @@ var deIndex = []uint32{ // 55 elements 0x000001ac, 0x000001e7, 0x00000213, 0x00000233, 0x0000023d, 0x0000024b, 0x00000256, 0x00000263, 0x00000271, 0x0000027b, 0x0000028e, 0x000002a1, - 0x000002b8, 0x000002ed, 0x00000321, 0x00000343, - 0x00000353, 0x00000379, 0x000003b7, 0x000003e1, + 0x000002b8, 0x000002ed, 0x00000328, 0x0000035c, + 0x0000037e, 0x000003a4, 0x000003b4, 0x000003da, // Entry 20 - 3F - 0x00000421, 0x0000042c, 0x00000437, 0x0000043e, - 0x00000441, 0x00000446, 0x0000046f, 0x00000477, - 0x0000047f, 0x000004a8, 0x000004c6, 0x00000503, - 0x0000052e, 0x00000539, 0x00000546, 0x0000056a, - 0x0000058d, 0x000005de, 0x00000615, 0x0000063c, - 0x0000063c, 0x0000063c, 0x0000063c, + 0x00000418, 0x00000443, 0x0000046d, 0x000004ad, + 0x000004b8, 0x000004c3, 0x000004ca, 0x000004cd, + 0x000004d2, 0x000004fb, 0x00000503, 0x0000050b, + 0x00000534, 0x00000552, 0x0000058f, 0x000005ba, + 0x000005c5, 0x000005d2, 0x000005f6, 0x00000619, + 0x0000066a, 0x000006a1, 0x000006c8, } // Size: 244 bytes -const deData string = "" + // Size: 1596 bytes +const deData string = "" + // Size: 1736 bytes "\x04\x01\x09\x00\x0e\x02Export: %[1]s\x04\x01\x09\x01\x0a\x0e\x02Import:" + " %[1]s\x02Fehler beim Lesen der Konfiguration: %[1]v\x02Servicename\x02e" + "in Service mit diesem Namen existiert bereits\x02Service %[1]v mit dem B" + @@ -127,20 +127,22 @@ const deData string = "" + // Size: 1596 bytes "nbekanntes Backend „%[1]s“\x02Client-ID\x02Client-Secret\x02Server-URL" + "\x02Benutzername\x02Zugriffstoken\x02Dateipfad\x02An Datei anhängen\x02T" + "itel der Playlist\x02Eindeutige Playlist-ID\x02Beim Import auf Listen-Du" + - "plikate prüfen (langsamer)\x02Autokorrektur für übermittelte Titel deakt" + - "ivieren\x02Übersprungene Listens ignorieren\x02Verzeichnispfad\x02Listen" + - "s im Inkognito-Modus ignorieren\x02Minimale Wiedergabedauer für überspru" + - "ngene Titel (Sekunden)\x02Fehler: OAuth-State stimmt nicht überein\x04" + - "\x00\x01\x0a;\x02Zugriffstoken erhalten, %[1]v kann jetzt verwendet werd" + - "en.\x02exportiere\x02importiere\x02fertig\x02Ja\x02Nein\x02keine bestehe" + - "nden Servicekonfigurationen\x02Service\x02Backend\x02Übertrage %[1]s von" + - " %[2]s nach %[3]s…\x02Ab Zeitstempel: %[1]v (%[2]v)\x02Import fehlgeschl" + - "agen, letzter Zeitstempel war %[1]v (%[2]s)\x02%[1]v von %[2]v %[3]s in " + - "%[4]v importiert.\x02Importlog:\x02%[1]v: %[2]v\x02ungültiger Zeitstempe" + - "l „%[1]v“\x02Letzter Zeitstempel: %[1]v (%[2]v)\x02keine Konfigurationsd" + - "atei definiert, Konfiguration kann nicht geschrieben werden\x02Schlüssel" + - " darf nur die Zeichen A-Za-z0-9_- beinhalten\x02keine Servicekonfigurati" + - "on „%[1]v“" + "plikate prüfen (langsamer)\x02Listen-Duplikat ignoriert %[1]v: \x22%[2]v" + + "\x22 von %[3]v (%[4]v)\x02Autokorrektur für übermittelte Titel deaktivie" + + "ren\x02Übersprungene Listens ignorieren\x02Zeitzone für den Abspiel-Zeit" + + "stempel\x02Verzeichnispfad\x02Listens im Inkognito-Modus ignorieren\x02M" + + "inimale Wiedergabedauer für übersprungene Titel (Sekunden)\x02Zur Anmeld" + + "ung folgende URL aufrufen: %[1]v\x02Fehler: OAuth-State stimmt nicht übe" + + "rein\x04\x00\x01\x0a;\x02Zugriffstoken erhalten, %[1]v kann jetzt verwen" + + "det werden.\x02exportiere\x02importiere\x02fertig\x02Ja\x02Nein\x02keine" + + " bestehenden Servicekonfigurationen\x02Service\x02Backend\x02Übertrage %" + + "[1]s von %[2]s nach %[3]s…\x02Ab Zeitstempel: %[1]v (%[2]v)\x02Import fe" + + "hlgeschlagen, letzter Zeitstempel war %[1]v (%[2]s)\x02%[1]v von %[2]v %" + + "[3]s in %[4]v importiert.\x02Importlog:\x02%[1]v: %[2]v\x02ungültiger Ze" + + "itstempel „%[1]v“\x02Letzter Zeitstempel: %[1]v (%[2]v)\x02keine Konfigu" + + "rationsdatei definiert, Konfiguration kann nicht geschrieben werden\x02S" + + "chlüssel darf nur die Zeichen A-Za-z0-9_- beinhalten\x02keine Servicekon" + + "figuration „%[1]v“" var enIndex = []uint32{ // 55 elements // Entry 0 - 1F @@ -150,15 +152,15 @@ var enIndex = []uint32{ // 55 elements 0x00000170, 0x0000019f, 0x000001c6, 0x000001de, 0x000001e8, 0x000001f6, 0x00000201, 0x0000020b, 0x00000218, 0x00000222, 0x00000231, 0x00000240, - 0x0000025b, 0x0000028a, 0x000002b7, 0x000002ce, - 0x000002dd, 0x000002fe, 0x00000335, 0x00000351, + 0x0000025b, 0x0000028a, 0x000002c3, 0x000002f0, + 0x00000307, 0x00000335, 0x00000344, 0x00000365, // Entry 20 - 3F - 0x00000384, 0x0000038e, 0x00000398, 0x0000039d, - 0x000003a1, 0x000003a4, 0x000003c7, 0x000003cf, - 0x000003d7, 0x00000401, 0x0000041f, 0x00000458, - 0x00000482, 0x0000048e, 0x0000049b, 0x000004bc, - 0x000004dc, 0x0000050f, 0x00000534, 0x00000555, - 0x0000058e, 0x000005bc, 0x000005e3, + 0x0000039c, 0x000003c3, 0x000003df, 0x00000412, + 0x0000041c, 0x00000426, 0x0000042b, 0x0000042f, + 0x00000432, 0x00000455, 0x0000045d, 0x00000465, + 0x0000048f, 0x000004ad, 0x000004e6, 0x00000510, + 0x0000051c, 0x00000529, 0x0000054a, 0x0000056a, + 0x0000059d, 0x000005c2, 0x000005e3, } // Size: 244 bytes const enData string = "" + // Size: 1507 bytes @@ -173,19 +175,19 @@ const enData string = "" + // Size: 1507 bytes "ent %[2]s\x02unknown backend \x22%[1]s\x22\x02Client ID\x02Client secret" + "\x02Server URL\x02User name\x02Access token\x02File path\x02Append to fi" + "le\x02Playlist title\x02Unique playlist identifier\x02Check for duplicat" + - "e listens on import (slower)\x02Disable auto correction of submitted lis" + - "tens\x02Ignore skipped listens\x02Directory path\x02Ignore listens in in" + - "cognito mode\x02Minimum playback duration for skipped tracks (seconds)" + - "\x02Error: OAuth state mismatch\x04\x00\x01\x0a.\x02Access token receive" + - "d, you can use %[1]v now.\x02exporting\x02importing\x02done\x02Yes\x02No" + - "\x02no existing service configurations\x02Service\x02Backend\x02Transfer" + - "ring %[1]s from %[2]s to %[3]s…\x02From timestamp: %[1]v (%[2]v)\x02Impo" + - "rt failed, last reported timestamp was %[1]v (%[2]s)\x02Imported %[1]v o" + - "f %[2]v %[3]s into %[4]v.\x02Import log:\x02%[1]v: %[2]v\x02invalid time" + - "stamp string \x22%[1]v\x22\x02Latest timestamp: %[1]v (%[2]v)\x02no conf" + - "iguration file defined, cannot write config\x02key must only consist of " + - "A-Za-z0-9_-\x02no service configuration \x22%[1]v\x22\x02Ignored duplica" + - "te listen %[1]v: \x22%[2]v\x22 by %[3]v (%[4]v)\x02Specify a time zone f" + - "or the listen timestamps\x02Visit the URL for authorization: %[1]v" + "e listens on import (slower)\x02Ignored duplicate listen %[1]v: \x22%[2]" + + "v\x22 by %[3]v (%[4]v)\x02Disable auto correction of submitted listens" + + "\x02Ignore skipped listens\x02Specify a time zone for the listen timesta" + + "mps\x02Directory path\x02Ignore listens in incognito mode\x02Minimum pla" + + "yback duration for skipped tracks (seconds)\x02Visit the URL for authori" + + "zation: %[1]v\x02Error: OAuth state mismatch\x04\x00\x01\x0a.\x02Access " + + "token received, you can use %[1]v now.\x02exporting\x02importing\x02done" + + "\x02Yes\x02No\x02no existing service configurations\x02Service\x02Backen" + + "d\x02Transferring %[1]s from %[2]s to %[3]s…\x02From timestamp: %[1]v (%" + + "[2]v)\x02Import failed, last reported timestamp was %[1]v (%[2]s)\x02Imp" + + "orted %[1]v of %[2]v %[3]s into %[4]v.\x02Import log:\x02%[1]v: %[2]v" + + "\x02invalid timestamp string \x22%[1]v\x22\x02Latest timestamp: %[1]v (%" + + "[2]v)\x02no configuration file defined, cannot write config\x02key must " + + "only consist of A-Za-z0-9_-\x02no service configuration \x22%[1]v\x22" - // Total table size 3591 bytes (3KiB); checksum: 2A4B9572 + // Total table size 3731 bytes (3KiB); checksum: F7951710 diff --git a/internal/translations/locales/de/out.gotext.json b/internal/translations/locales/de/out.gotext.json index 7a13af8..680505e 100644 --- a/internal/translations/locales/de/out.gotext.json +++ b/internal/translations/locales/de/out.gotext.json @@ -263,7 +263,7 @@ { "id": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMBID})", "message": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMBID})", - "translation": "", + "translation": "Listen-Duplikat ignoriert {ListenedAt}: \"{TrackName}\" von {ArtistName} ({RecordingMBID})", "placeholders": [ { "id": "ListenedAt", @@ -312,7 +312,7 @@ { "id": "Specify a time zone for the listen timestamps", "message": "Specify a time zone for the listen timestamps", - "translation": "" + "translation": "Zeitzone für den Abspiel-Zeitstempel" }, { "id": "Directory path", @@ -332,7 +332,7 @@ { "id": "Visit the URL for authorization: {URL}", "message": "Visit the URL for authorization: {URL}", - "translation": "", + "translation": "Zur Anmeldung folgende URL aufrufen: {URL}", "placeholders": [ { "id": "URL", From 1e91b684cb547946d9f47a7830fbea0368142f12 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Tue, 29 Apr 2025 16:16:43 +0200 Subject: [PATCH 51/52] Release 0.5.0 --- CHANGES.md | 2 +- internal/version/version.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index cda0d79..7324f77 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Scotty Changelog -## 0.5.0 - (not yet released) +## 0.5.0 - 2025-04-29 - ListenBrainz: handle missing loves metadata in case of merged recordings - ListenBrainz: fix loves import loading all existing loves - ListenBrainz: fixed progress for loves import diff --git a/internal/version/version.go b/internal/version/version.go index 818bec1..07d1569 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -1,5 +1,5 @@ /* -Copyright © 2023-2024 Philipp Wolfer +Copyright © 2023-2025 Philipp Wolfer Scotty is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -17,7 +17,7 @@ package version const ( AppName = "scotty" - AppVersion = "0.4.1" + AppVersion = "0.5.0" AppURL = "https://git.sr.ht/~phw/scotty/" ) From 0a411fe2fa90a0db8edc82316f7d2534c88c0c9f Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Tue, 29 Apr 2025 17:25:10 +0200 Subject: [PATCH 52/52] If locale detection fails fall back to English --- internal/i18n/i18n.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/i18n/i18n.go b/internal/i18n/i18n.go index cbfd516..a910ca0 100644 --- a/internal/i18n/i18n.go +++ b/internal/i18n/i18n.go @@ -16,11 +16,10 @@ Scotty. If not, see . package i18n import ( - "log" - "github.com/Xuanwo/go-locale" _ "go.uploadedlobster.com/scotty/internal/translations" + "golang.org/x/text/language" "golang.org/x/text/message" ) @@ -29,7 +28,7 @@ var localizer Localizer func init() { tag, err := locale.Detect() if err != nil { - log.Fatal(err) + tag = language.English } localizer = New(tag) }