diff --git a/server/ctrlsubsonic/ctrl.go b/server/ctrlsubsonic/ctrl.go index 35f4b9f..a9ff25b 100644 --- a/server/ctrlsubsonic/ctrl.go +++ b/server/ctrlsubsonic/ctrl.go @@ -13,6 +13,7 @@ import ( "go.senan.xyz/gonic/server/ctrlsubsonic/params" "go.senan.xyz/gonic/server/ctrlsubsonic/spec" "go.senan.xyz/gonic/server/jukebox" + "go.senan.xyz/gonic/server/lastfm" ) type CtxKey int @@ -28,6 +29,7 @@ type Controller struct { CachePath string CoverCachePath string Jukebox *jukebox.Jukebox + Scrobblers []lastfm.Scrobbler } type metaResponse struct { diff --git a/server/ctrlsubsonic/handlers_common.go b/server/ctrlsubsonic/handlers_common.go index 187c8b8..e5805c2 100644 --- a/server/ctrlsubsonic/handlers_common.go +++ b/server/ctrlsubsonic/handlers_common.go @@ -61,14 +61,16 @@ func (c *Controller) ServeScrobble(r *http.Request) *spec.Response { StampMili: params.GetOrInt("time", int(time.Now().UnixNano()/1e6)), Submission: params.GetOrBool("submission", true), } - err = lastfm.Scrobble( - c.DB.GetSetting("lastfm_api_key"), - c.DB.GetSetting("lastfm_secret"), - user.LastFMSession, - opts, - ) - if err != nil { - return spec.NewError(0, "error when submitting: %v", err) + scrobbleErrs := []error{} + for _, scrobbler := range c.Scrobblers { + if !scrobbler.Enabled(user) { + continue + } + err = scrobbler.Scrobble(user, opts) + scrobbleErrs = append(scrobbleErrs, err) + } + if len(scrobbleErrs) != 0 { + return spec.NewError(0, "error when submitting: %v", scrobbleErrs) } return spec.NewResponse() } diff --git a/server/lastfm/lastfm.go b/server/lastfm/lastfm.go index f80b3b1..c9172ac 100644 --- a/server/lastfm/lastfm.go +++ b/server/lastfm/lastfm.go @@ -15,7 +15,8 @@ import ( ) const ( - baseURL = "https://ws.audioscrobbler.com/2.0/" + lastfmBaseURL = "https://ws.audioscrobbler.com/2.0/" + lbBaseURL = "https://api.listenbrainz.org" ) var ( @@ -42,7 +43,7 @@ func getParamSignature(params url.Values, secret string) string { } func makeRequest(method string, params url.Values) (LastFM, error) { - req, _ := http.NewRequest(method, baseURL, nil) + req, _ := http.NewRequest(method, lastfmBaseURL, nil) req.URL.RawQuery = params.Encode() resp, err := http.DefaultClient.Do(req) if err != nil { @@ -79,7 +80,18 @@ type ScrobbleOptions struct { Submission bool } -func Scrobble(apiKey, secret, session string, opts ScrobbleOptions) error { +type LastfmScrobbler struct { //nolint + DB *db.DB +} + +func (lfm *LastfmScrobbler) Scrobble(reqUser interface{}, opts ScrobbleOptions) error { + apiKey := lfm.DB.GetSetting("lastfm_api_key") + secret := lfm.DB.GetSetting("lastfm_secret") + // fetch user to get lastfm session + user := reqUser.(*db.User) + if user.LastFMSession == "" { + return fmt.Errorf("you don't have a last.fm session: %w", ErrLastFM) + } params := url.Values{} if opts.Submission { params.Add("method", "track.Scrobble") @@ -89,7 +101,7 @@ func Scrobble(apiKey, secret, session string, opts ScrobbleOptions) error { params.Add("method", "track.updateNowPlaying") } params.Add("api_key", apiKey) - params.Add("sk", session) + params.Add("sk", user.LastFMSession) params.Add("artist", opts.Track.TagTrackArtist) params.Add("track", opts.Track.TagTitle) params.Add("trackNumber", strconv.Itoa(opts.Track.TagTrackNumber)) diff --git a/server/lastfm/models.go b/server/lastfm/models.go index 08c2bc3..6279689 100644 --- a/server/lastfm/models.go +++ b/server/lastfm/models.go @@ -4,6 +4,10 @@ import ( "encoding/xml" ) +type Scrobbler interface { + Scrobble(interface{}, ScrobbleOptions) error +} + type LastFM struct { XMLName xml.Name `xml:"lfm"` Status string `xml:"status,attr"` diff --git a/server/server.go b/server/server.go index 78000c8..8e1fff6 100644 --- a/server/server.go +++ b/server/server.go @@ -17,6 +17,7 @@ import ( "go.senan.xyz/gonic/server/ctrlsubsonic" "go.senan.xyz/gonic/server/db" "go.senan.xyz/gonic/server/jukebox" + "go.senan.xyz/gonic/server/lastfm" "go.senan.xyz/gonic/server/scanner" ) @@ -62,11 +63,16 @@ func New(opts Options) *Server { sessDB.SessionOpts.SameSite = http.SameSiteLaxMode // ctrlAdmin := ctrladmin.New(base, sessDB) + lastfmScrobbler := &lastfm.LastfmScrobbler{DB: opts.DB} + scrobblers := []lastfm.Scrobbler{ + lastfmScrobbler, + } ctrlSubsonic := &ctrlsubsonic.Controller{ Controller: base, CachePath: opts.CachePath, CoverCachePath: opts.CoverCachePath, Jukebox: jukebox, + Scrobblers: scrobblers, } setupMisc(r, base) setupAdmin(r.PathPrefix("/admin").Subrouter(), ctrlAdmin)