lastfm: loves import

This commit is contained in:
Philipp Wolfer 2023-11-26 12:56:27 +01:00
parent 1249238d3a
commit 406e150987
No known key found for this signature in database
GPG key ID: 8FDF744D4919943B
5 changed files with 57 additions and 6 deletions

View file

@ -44,7 +44,7 @@ deezer | ✓ | | ✓ | -
dump | | ✓ | | ✓ dump | | ✓ | | ✓
funkwhale | ✓ | | ✓ | - funkwhale | ✓ | | ✓ | -
jspf | - | ✓ | - | ✓ jspf | - | ✓ | - | ✓
lastfm | - | - | - | - lastfm | - | - | ✓ | ✓
listenbrainz | ✓ | ✓ | ✓ | ✓ listenbrainz | ✓ | ✓ | ✓ | ✓
maloja | ✓ | ✓ | | maloja | ✓ | ✓ | |
scrobbler-log | ✓ | ✓ | | scrobbler-log | ✓ | ✓ | |

View file

@ -49,7 +49,10 @@ func Authenticate(service string, backend models.Backend, db storage.Database, c
} }
conf := authenticator.OAuth2Strategy(redirectURL).Config() conf := authenticator.OAuth2Strategy(redirectURL).Config()
tokenSource := auth.NewDatabaseTokenSource(db, service, &conf, token) tokenSource := auth.NewDatabaseTokenSource(db, service, &conf, token)
authenticator.OAuth2Setup(tokenSource) err = authenticator.OAuth2Setup(tokenSource)
if err != nil {
return needAuth, err
}
} }
return needAuth, nil return needAuth, nil
} }

View file

@ -24,12 +24,15 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go.uploadedlobster.com/scotty/internal/backends" "go.uploadedlobster.com/scotty/internal/backends"
"go.uploadedlobster.com/scotty/internal/backends/deezer"
"go.uploadedlobster.com/scotty/internal/backends/dump" "go.uploadedlobster.com/scotty/internal/backends/dump"
"go.uploadedlobster.com/scotty/internal/backends/funkwhale" "go.uploadedlobster.com/scotty/internal/backends/funkwhale"
"go.uploadedlobster.com/scotty/internal/backends/jspf" "go.uploadedlobster.com/scotty/internal/backends/jspf"
"go.uploadedlobster.com/scotty/internal/backends/lastfm"
"go.uploadedlobster.com/scotty/internal/backends/listenbrainz" "go.uploadedlobster.com/scotty/internal/backends/listenbrainz"
"go.uploadedlobster.com/scotty/internal/backends/maloja" "go.uploadedlobster.com/scotty/internal/backends/maloja"
"go.uploadedlobster.com/scotty/internal/backends/scrobblerlog" "go.uploadedlobster.com/scotty/internal/backends/scrobblerlog"
"go.uploadedlobster.com/scotty/internal/backends/spotify"
"go.uploadedlobster.com/scotty/internal/backends/subsonic" "go.uploadedlobster.com/scotty/internal/backends/subsonic"
"go.uploadedlobster.com/scotty/internal/models" "go.uploadedlobster.com/scotty/internal/models"
) )
@ -73,6 +76,10 @@ func TestGetBackends(t *testing.T) {
} }
func TestImplementsInterfaces(t *testing.T) { func TestImplementsInterfaces(t *testing.T) {
expectInterface[models.ListensExport](t, &deezer.DeezerApiBackend{})
expectInterface[models.LovesExport](t, &deezer.DeezerApiBackend{})
// expectInterface[models.LovesImport](t, &deezer.DeezerApiBackend{})
expectInterface[models.ListensImport](t, &dump.DumpBackend{}) expectInterface[models.ListensImport](t, &dump.DumpBackend{})
expectInterface[models.LovesImport](t, &dump.DumpBackend{}) expectInterface[models.LovesImport](t, &dump.DumpBackend{})
@ -82,18 +89,27 @@ func TestImplementsInterfaces(t *testing.T) {
// expectInterface[models.LovesImport](t, &funkwhale.FunkwhaleApiBackend{}) // expectInterface[models.LovesImport](t, &funkwhale.FunkwhaleApiBackend{})
// expectInterface[models.ListensExport](t, &jspf.JSPFBackend{}) // expectInterface[models.ListensExport](t, &jspf.JSPFBackend{})
// expectInterface[models.ListensImport](t, &jspf.JSPFBackend{}) expectInterface[models.ListensImport](t, &jspf.JSPFBackend{})
// expectInterface[models.LovesExport](t, &jspf.JSPFBackend{}) // expectInterface[models.LovesExport](t, &jspf.JSPFBackend{})
expectInterface[models.LovesImport](t, &jspf.JSPFBackend{}) expectInterface[models.LovesImport](t, &jspf.JSPFBackend{})
// expectInterface[models.ListensExport](t, &lastfm.LastfmApiBackend{})
// expectInterface[models.ListensImport](t, &lastfm.LastfmApiBackend{})
expectInterface[models.LovesExport](t, &lastfm.LastfmApiBackend{})
expectInterface[models.LovesImport](t, &lastfm.LastfmApiBackend{})
expectInterface[models.ListensExport](t, &listenbrainz.ListenBrainzApiBackend{}) expectInterface[models.ListensExport](t, &listenbrainz.ListenBrainzApiBackend{})
// expectInterface[models.ListensImport](t, &listenbrainz.ListenBrainzApiBackend{}) expectInterface[models.ListensImport](t, &listenbrainz.ListenBrainzApiBackend{})
expectInterface[models.LovesExport](t, &listenbrainz.ListenBrainzApiBackend{}) expectInterface[models.LovesExport](t, &listenbrainz.ListenBrainzApiBackend{})
expectInterface[models.LovesImport](t, &listenbrainz.ListenBrainzApiBackend{}) expectInterface[models.LovesImport](t, &listenbrainz.ListenBrainzApiBackend{})
expectInterface[models.ListensExport](t, &maloja.MalojaApiBackend{}) expectInterface[models.ListensExport](t, &maloja.MalojaApiBackend{})
expectInterface[models.ListensImport](t, &maloja.MalojaApiBackend{}) expectInterface[models.ListensImport](t, &maloja.MalojaApiBackend{})
expectInterface[models.ListensExport](t, &spotify.SpotifyApiBackend{})
expectInterface[models.LovesExport](t, &spotify.SpotifyApiBackend{})
// expectInterface[models.LovesImport](t, &spotify.SpotifyApiBackend{})
expectInterface[models.ListensExport](t, &scrobblerlog.ScrobblerLogBackend{}) expectInterface[models.ListensExport](t, &scrobblerlog.ScrobblerLogBackend{})
expectInterface[models.ListensImport](t, &scrobblerlog.ScrobblerLogBackend{}) expectInterface[models.ListensImport](t, &scrobblerlog.ScrobblerLogBackend{})

View file

@ -47,5 +47,10 @@ func (s lastfmStrategy) AuthCodeURL(verifier string, state string) auth.AuthUrl
func (s lastfmStrategy) ExchangeToken(code auth.CodeResponse, verifier string) (*oauth2.Token, error) { func (s lastfmStrategy) ExchangeToken(code auth.CodeResponse, verifier string) (*oauth2.Token, error) {
// The token is directly valid // The token is directly valid
return &oauth2.Token{AccessToken: code.Code}, nil err := s.client.LoginWithToken(code.Code)
if err != nil {
return nil, err
}
sk := s.client.GetSessionKey()
return &oauth2.Token{AccessToken: sk}, nil
} }

View file

@ -16,6 +16,7 @@ Scotty. If not, see <https://www.gnu.org/licenses/>.
package lastfm package lastfm
import ( import (
"fmt"
"net/url" "net/url"
"sort" "sort"
"strconv" "strconv"
@ -45,6 +46,9 @@ func (b *LastfmApiBackend) FromConfig(config *viper.Viper) models.Backend {
return b return b
} }
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{ return lastfmStrategy{
client: b.client, client: b.client,
@ -57,7 +61,8 @@ func (b *LastfmApiBackend) OAuth2Setup(token oauth2.TokenSource) error {
if err != nil { if err != nil {
return err return err
} }
return b.client.LoginWithToken(t.AccessToken) b.client.SetSession(t.AccessToken)
return nil
} }
func (b *LastfmApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress) { func (b *LastfmApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress) {
@ -130,3 +135,25 @@ out:
results <- models.LovesResult{Loves: loves, Total: totalCount} results <- models.LovesResult{Loves: loves, Total: totalCount}
progress <- p.Complete() progress <- p.Complete()
} }
func (b *LastfmApiBackend) ImportLoves(export models.LovesResult, importResult models.ImportResult, progress chan models.Progress) (models.ImportResult, error) {
for _, love := range export.Loves {
err := b.client.Track.Love(lastfm.P{
"track": love.TrackName,
"artist": love.ArtistName(),
})
if err == nil {
importResult.UpdateTimestamp(love.Created)
importResult.ImportCount += 1
} else {
msg := fmt.Sprintf("Failed import of \"%s\" by %s: %v",
love.TrackName, love.ArtistName(), err.Error())
importResult.ImportErrors = append(importResult.ImportErrors, msg)
}
progress <- models.Progress{}.FromImportResult(importResult)
}
return importResult, nil
}