Merge a088970cd1900f16b132fdfff97e70f7b8b401cf into a45adbbb2eb5a620173b5c1710017368abfb6be6

This commit is contained in:
Timo 2026-10-18 01:18:21 +02:00 committed by GitHub
commit 98baa17e2e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 63 additions and 2 deletions

View File

@ -388,7 +388,7 @@ func (l *lastfmAgent) Scrobble(ctx context.Context, userId string, s scrobbler.S
return errors.Join(err, scrobbler.ErrNotAuthorized)
}
if s.Duration <= 30 {
if s.Duration < float32(conf.Server.LastFM.MinScrobbleDuration.Seconds()) {
log.Debug(ctx, "Skipping Last.fm scrobble for short song", "track", s.Title, "duration", s.Duration)
return nil
}

View File

@ -481,6 +481,28 @@ var _ = Describe("lastfmAgent", func() {
Expect(httpClient.SavedRequest).To(BeNil())
})
It("skips songs according to the configured minimum duration", func() {
conf.Server.LastFM.MinScrobbleDuration = 60 * time.Second
track.Duration = 59
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString("{}")), StatusCode: 200}
err := agent.Scrobble(ctx, "user-1", scrobbler.Scrobble{MediaFile: *track, TimeStamp: time.Now()})
Expect(err).ToNot(HaveOccurred())
Expect(httpClient.SavedRequest).To(BeNil())
})
It("does not skip short songs when the minimum duration is disabled", func() {
conf.Server.LastFM.MinScrobbleDuration = 0
track.Duration = 29
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString("{}")), StatusCode: 200}
err := agent.Scrobble(ctx, "user-1", scrobbler.Scrobble{MediaFile: *track, TimeStamp: time.Now()})
Expect(err).ToNot(HaveOccurred())
Expect(httpClient.SavedRequest).ToNot(BeNil())
})
It("returns ErrNotAuthorized if user is not linked", func() {
err := agent.Scrobble(ctx, "user-2", scrobbler.Scrobble{MediaFile: *track, TimeStamp: time.Now()})
Expect(err).To(MatchError(scrobbler.ErrNotAuthorized))

View File

@ -182,7 +182,11 @@ func (c *client) scrobble(ctx context.Context, sessionKey string, info ScrobbleI
params.Add("album", info.album)
params.Add("trackNumber", strconv.Itoa(info.trackNumber))
params.Add("mbid", info.mbid)
params.Add("duration", strconv.Itoa(info.duration))
if info.duration > 30 {
params.Add("duration", strconv.Itoa(info.duration))
} else {
log.Debug(ctx, "LastFM: scrobble duration is less than 30 seconds, duration will not be transmitted", "duration", info.duration)
}
params.Add("albumArtist", info.albumArtist)
params.Add("sk", sessionKey)
resp, err := c.makeRequest(ctx, http.MethodPost, params, true)

View File

@ -210,6 +210,7 @@ type lastfmOptions struct {
Secret string //nolint:gosec
Language string
ScrobbleFirstArtistOnly bool
MinScrobbleDuration time.Duration
// Computed values
Languages []string `conf:"-"` // Computed from Language, split by comma
@ -515,6 +516,12 @@ func Load(noConfigDump bool) {
Server.UICoverArtSize = newValue
}
// Check for negative values in durations and set to default (TODO: more than just LastFM.MinScrobbleduration)
if Server.LastFM.MinScrobbleDuration < 0 {
log.Warn("MinScrobbleDuration must be non-negative, setting to default", "value", Server.LastFM.MinScrobbleDuration, "newValue", consts.DefaultMinScrobbleDuration)
Server.LastFM.MinScrobbleDuration = consts.DefaultMinScrobbleDuration
}
if Server.Scanner.Extractor != consts.DefaultScannerExtractor {
log.Warn("Invalid Scanner.Extractor, using default", "value", Server.Scanner.Extractor, "default", consts.DefaultScannerExtractor)
Server.Scanner.Extractor = consts.DefaultScannerExtractor
@ -1087,6 +1094,7 @@ func setViperDefaults() {
viper.SetDefault("lastfm.apikey", "")
viper.SetDefault("lastfm.secret", "")
viper.SetDefault("lastfm.scrobblefirstartistonly", false)
viper.SetDefault("lastfm.minscrobbleduration", consts.DefaultMinScrobbleDuration)
viper.SetDefault("deezer.enabled", true)
viper.SetDefault("deezer.language", consts.DefaultInfoLanguage)
viper.SetDefault("listenbrainz.enabled", true)

View File

@ -402,6 +402,31 @@ var _ = Describe("Configuration", func() {
})
})
Describe("MinScrobbleDuration", func() {
It("defaults to 30 seconds", func() {
conf.Load(true)
Expect(conf.Server.LastFM.MinScrobbleDuration).To(Equal(consts.DefaultMinScrobbleDuration))
})
It("sets negative values to default", func() {
viper.SetDefault("lastfm.minscrobbleduration", "-10s")
conf.Load(true)
Expect(conf.Server.LastFM.MinScrobbleDuration).To(Equal(consts.DefaultMinScrobbleDuration))
})
It("disables with zero value", func() {
viper.SetDefault("lastfm.minscrobbleduration", "0s")
conf.Load(true)
Expect(conf.Server.LastFM.MinScrobbleDuration).To(Equal(time.Duration(0)))
})
It("preserves positive values", func() {
viper.SetDefault("lastfm.minscrobbleduration", "45s")
conf.Load(true)
Expect(conf.Server.LastFM.MinScrobbleDuration).To(Equal(45 * time.Second))
})
})
Describe("Scanner.Extractor", func() {
BeforeEach(func() {
viper.Reset()

View File

@ -83,6 +83,8 @@ const (
DefaultInfoLanguage = "en"
DefaultMinScrobbleDuration = 30 * time.Second
ArtistInfoTimeToLive = 24 * time.Hour
AlbumInfoTimeToLive = 7 * 24 * time.Hour
UpdateLastAccessFrequency = time.Minute