mirror of
https://git.sr.ht/~phw/scotty
synced 2025-04-30 05:37:05 +02:00
Code style: All uppercase acronyms URL, ISRC, ID, HTTP
This commit is contained in:
parent
39b31fc664
commit
d51c97c648
26 changed files with 137 additions and 137 deletions
|
@ -27,7 +27,7 @@ type OAuth2Authenticator interface {
|
||||||
models.Backend
|
models.Backend
|
||||||
|
|
||||||
// Returns OAuth2 config suitable for this backend
|
// Returns OAuth2 config suitable for this backend
|
||||||
OAuth2Strategy(redirectUrl *url.URL) OAuth2Strategy
|
OAuth2Strategy(redirectURL *url.URL) OAuth2Strategy
|
||||||
|
|
||||||
// Setup the OAuth2 client
|
// Setup the OAuth2 client
|
||||||
OAuth2Setup(token oauth2.TokenSource) error
|
OAuth2Setup(token oauth2.TokenSource) error
|
||||||
|
|
|
@ -24,14 +24,14 @@ import (
|
||||||
type OAuth2Strategy interface {
|
type OAuth2Strategy interface {
|
||||||
Config() oauth2.Config
|
Config() oauth2.Config
|
||||||
|
|
||||||
AuthCodeURL(verifier string, state string) AuthUrl
|
AuthCodeURL(verifier string, state string) AuthURL
|
||||||
|
|
||||||
ExchangeToken(code CodeResponse, verifier string) (*oauth2.Token, error)
|
ExchangeToken(code CodeResponse, verifier string) (*oauth2.Token, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthUrl struct {
|
type AuthURL struct {
|
||||||
// The URL the user must visit to approve access
|
// The URL the user must visit to approve access
|
||||||
Url string
|
URL string
|
||||||
// Random state string passed on to the callback.
|
// Random state string passed on to the callback.
|
||||||
// Leave empty if the service does not support state.
|
// Leave empty if the service does not support state.
|
||||||
State string
|
State string
|
||||||
|
@ -56,10 +56,10 @@ func (s StandardStrategy) Config() oauth2.Config {
|
||||||
return s.conf
|
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))
|
url := s.conf.AuthCodeURL(state, oauth2.AccessTypeOffline, oauth2.S256ChallengeOption(verifier))
|
||||||
return AuthUrl{
|
return AuthURL{
|
||||||
Url: url,
|
URL: url,
|
||||||
State: state,
|
State: state,
|
||||||
Param: "code",
|
Param: "code",
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,10 +33,10 @@ func (s deezerStrategy) Config() oauth2.Config {
|
||||||
return s.conf
|
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))
|
url := s.conf.AuthCodeURL(state, oauth2.AccessTypeOffline, oauth2.S256ChallengeOption(verifier))
|
||||||
return auth.AuthUrl{
|
return auth.AuthURL{
|
||||||
Url: url,
|
URL: url,
|
||||||
State: state,
|
State: state,
|
||||||
Param: "code",
|
Param: "code",
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ const MaxItemsPerGet = 1000
|
||||||
const DefaultRateLimitWaitSeconds = 5
|
const DefaultRateLimitWaitSeconds = 5
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
HttpClient *resty.Client
|
HTTPClient *resty.Client
|
||||||
token oauth2.TokenSource
|
token oauth2.TokenSource
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ func NewClient(token oauth2.TokenSource) Client {
|
||||||
client.SetHeader("User-Agent", version.UserAgent())
|
client.SetHeader("User-Agent", version.UserAgent())
|
||||||
client.SetRetryCount(5)
|
client.SetRetryCount(5)
|
||||||
return Client{
|
return Client{
|
||||||
HttpClient: client,
|
HTTPClient: client,
|
||||||
token: token,
|
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) {
|
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{
|
SetQueryParams(map[string]string{
|
||||||
"index": strconv.Itoa(offset),
|
"index": strconv.Itoa(offset),
|
||||||
"limit": strconv.Itoa(limit),
|
"limit": strconv.Itoa(limit),
|
||||||
|
|
|
@ -44,7 +44,7 @@ func TestGetUserHistory(t *testing.T) {
|
||||||
|
|
||||||
token := oauth2.StaticTokenSource(&oauth2.Token{})
|
token := oauth2.StaticTokenSource(&oauth2.Token{})
|
||||||
client := deezer.NewClient(token)
|
client := deezer.NewClient(token)
|
||||||
setupHttpMock(t, client.HttpClient.GetClient(),
|
setupHTTPMock(t, client.HTTPClient.GetClient(),
|
||||||
"https://api.deezer.com/user/me/history",
|
"https://api.deezer.com/user/me/history",
|
||||||
"testdata/user-history.json")
|
"testdata/user-history.json")
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ func TestGetUserTracks(t *testing.T) {
|
||||||
|
|
||||||
token := oauth2.StaticTokenSource(&oauth2.Token{})
|
token := oauth2.StaticTokenSource(&oauth2.Token{})
|
||||||
client := deezer.NewClient(token)
|
client := deezer.NewClient(token)
|
||||||
setupHttpMock(t, client.HttpClient.GetClient(),
|
setupHTTPMock(t, client.HTTPClient.GetClient(),
|
||||||
"https://api.deezer.com/user/me/tracks",
|
"https://api.deezer.com/user/me/tracks",
|
||||||
"testdata/user-tracks.json")
|
"testdata/user-tracks.json")
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ func TestGetUserTracks(t *testing.T) {
|
||||||
assert.Equal("Outland", track1.Track.Album.Title)
|
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)
|
httpmock.ActivateNonDefault(client)
|
||||||
|
|
||||||
responder, err := httpmock.NewJsonResponder(200, httpmock.File(testDataPath))
|
responder, err := httpmock.NewJsonResponder(200, httpmock.File(testDataPath))
|
||||||
|
|
|
@ -31,7 +31,7 @@ import (
|
||||||
|
|
||||||
type DeezerApiBackend struct {
|
type DeezerApiBackend struct {
|
||||||
client Client
|
client Client
|
||||||
clientId string
|
clientID string
|
||||||
clientSecret string
|
clientSecret string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,19 +50,19 @@ func (b *DeezerApiBackend) Options() []models.BackendOption {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *DeezerApiBackend) InitConfig(config *config.ServiceConfig) error {
|
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")
|
b.clientSecret = config.GetString("client-secret")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *DeezerApiBackend) OAuth2Strategy(redirectUrl *url.URL) auth.OAuth2Strategy {
|
func (b *DeezerApiBackend) OAuth2Strategy(redirectURL *url.URL) auth.OAuth2Strategy {
|
||||||
conf := oauth2.Config{
|
conf := oauth2.Config{
|
||||||
ClientID: b.clientId,
|
ClientID: b.clientID,
|
||||||
ClientSecret: b.clientSecret,
|
ClientSecret: b.clientSecret,
|
||||||
Scopes: []string{
|
Scopes: []string{
|
||||||
"offline_access,basic_access,listening_history",
|
"offline_access,basic_access,listening_history",
|
||||||
},
|
},
|
||||||
RedirectURL: redirectUrl.String(),
|
RedirectURL: redirectURL.String(),
|
||||||
Endpoint: oauth2.Endpoint{
|
Endpoint: oauth2.Endpoint{
|
||||||
AuthURL: "https://connect.deezer.com/oauth/auth.php",
|
AuthURL: "https://connect.deezer.com/oauth/auth.php",
|
||||||
TokenURL: "https://connect.deezer.com/oauth/access_token.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["music_service"] = "deezer.com"
|
||||||
info["origin_url"] = t.Link
|
info["origin_url"] = t.Link
|
||||||
info["deezer_id"] = t.Link
|
info["deezer_id"] = t.Link
|
||||||
info["deezer_album_id"] = fmt.Sprintf("https://www.deezer.com/album/%v", t.Album.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)
|
info["deezer_artist_id"] = fmt.Sprintf("https://www.deezer.com/artist/%v", t.Artist.ID)
|
||||||
|
|
||||||
return track
|
return track
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ type HistoryResult struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Track struct {
|
type Track struct {
|
||||||
Id int `json:"id"`
|
ID int `json:"id"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Link string `json:"link"`
|
Link string `json:"link"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
|
@ -75,7 +75,7 @@ type LovedTrack struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Album struct {
|
type Album struct {
|
||||||
Id int `json:"id"`
|
ID int `json:"id"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Link string `json:"link"`
|
Link string `json:"link"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
|
@ -83,7 +83,7 @@ type Album struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Artist struct {
|
type Artist struct {
|
||||||
Id int `json:"id"`
|
ID int `json:"id"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Link string `json:"link"`
|
Link string `json:"link"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
|
@ -33,13 +33,13 @@ import (
|
||||||
const MaxItemsPerGet = 50
|
const MaxItemsPerGet = 50
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
HttpClient *resty.Client
|
HTTPClient *resty.Client
|
||||||
token string
|
token string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(serverUrl string, token string) Client {
|
func NewClient(serverURL string, token string) Client {
|
||||||
client := resty.New()
|
client := resty.New()
|
||||||
client.SetBaseURL(serverUrl)
|
client.SetBaseURL(serverURL)
|
||||||
client.SetAuthScheme("Bearer")
|
client.SetAuthScheme("Bearer")
|
||||||
client.SetAuthToken(token)
|
client.SetAuthToken(token)
|
||||||
client.SetHeader("Accept", "application/json")
|
client.SetHeader("Accept", "application/json")
|
||||||
|
@ -49,14 +49,14 @@ func NewClient(serverUrl string, token string) Client {
|
||||||
ratelimit.EnableHTTPHeaderRateLimit(client, "Retry-After")
|
ratelimit.EnableHTTPHeaderRateLimit(client, "Retry-After")
|
||||||
|
|
||||||
return Client{
|
return Client{
|
||||||
HttpClient: client,
|
HTTPClient: client,
|
||||||
token: token,
|
token: token,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Client) GetHistoryListenings(user string, page int, perPage int) (result ListeningsResult, err error) {
|
func (c Client) GetHistoryListenings(user string, page int, perPage int) (result ListeningsResult, err error) {
|
||||||
const path = "/api/v1/history/listenings"
|
const path = "/api/v1/history/listenings"
|
||||||
response, err := c.HttpClient.R().
|
response, err := c.HTTPClient.R().
|
||||||
SetQueryParams(map[string]string{
|
SetQueryParams(map[string]string{
|
||||||
"username": user,
|
"username": user,
|
||||||
"page": strconv.Itoa(page),
|
"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) {
|
func (c Client) GetFavoriteTracks(page int, perPage int) (result FavoriteTracksResult, err error) {
|
||||||
const path = "/api/v1/favorites/tracks"
|
const path = "/api/v1/favorites/tracks"
|
||||||
response, err := c.HttpClient.R().
|
response, err := c.HTTPClient.R().
|
||||||
SetQueryParams(map[string]string{
|
SetQueryParams(map[string]string{
|
||||||
"page": strconv.Itoa(page),
|
"page": strconv.Itoa(page),
|
||||||
"page_size": strconv.Itoa(perPage),
|
"page_size": strconv.Itoa(perPage),
|
||||||
|
|
|
@ -32,20 +32,20 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewClient(t *testing.T) {
|
func TestNewClient(t *testing.T) {
|
||||||
serverUrl := "https://funkwhale.example.com"
|
serverURL := "https://funkwhale.example.com"
|
||||||
token := "foobar123"
|
token := "foobar123"
|
||||||
client := funkwhale.NewClient(serverUrl, token)
|
client := funkwhale.NewClient(serverURL, token)
|
||||||
assert.Equal(t, serverUrl, client.HttpClient.BaseURL)
|
assert.Equal(t, serverURL, client.HTTPClient.BaseURL)
|
||||||
assert.Equal(t, token, client.HttpClient.Token)
|
assert.Equal(t, token, client.HTTPClient.Token)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetHistoryListenings(t *testing.T) {
|
func TestGetHistoryListenings(t *testing.T) {
|
||||||
defer httpmock.DeactivateAndReset()
|
defer httpmock.DeactivateAndReset()
|
||||||
|
|
||||||
serverUrl := "https://funkwhale.example.com"
|
serverURL := "https://funkwhale.example.com"
|
||||||
token := "thetoken"
|
token := "thetoken"
|
||||||
client := funkwhale.NewClient(serverUrl, token)
|
client := funkwhale.NewClient(serverURL, token)
|
||||||
setupHttpMock(t, client.HttpClient.GetClient(),
|
setupHTTPMock(t, client.HTTPClient.GetClient(),
|
||||||
"https://funkwhale.example.com/api/v1/history/listenings",
|
"https://funkwhale.example.com/api/v1/history/listenings",
|
||||||
"testdata/listenings.json")
|
"testdata/listenings.json")
|
||||||
|
|
||||||
|
@ -67,9 +67,9 @@ func TestGetFavoriteTracks(t *testing.T) {
|
||||||
defer httpmock.DeactivateAndReset()
|
defer httpmock.DeactivateAndReset()
|
||||||
|
|
||||||
token := "thetoken"
|
token := "thetoken"
|
||||||
serverUrl := "https://funkwhale.example.com"
|
serverURL := "https://funkwhale.example.com"
|
||||||
client := funkwhale.NewClient(serverUrl, token)
|
client := funkwhale.NewClient(serverURL, token)
|
||||||
setupHttpMock(t, client.HttpClient.GetClient(),
|
setupHTTPMock(t, client.HTTPClient.GetClient(),
|
||||||
"https://funkwhale.example.com/api/v1/favorites/tracks",
|
"https://funkwhale.example.com/api/v1/favorites/tracks",
|
||||||
"testdata/favorite-tracks.json")
|
"testdata/favorite-tracks.json")
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ func TestGetFavoriteTracks(t *testing.T) {
|
||||||
assert.Equal("phw", fav1.User.UserName)
|
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)
|
httpmock.ActivateNonDefault(client)
|
||||||
|
|
||||||
responder, err := httpmock.NewJsonResponder(200, httpmock.File(testDataPath))
|
responder, err := httpmock.NewJsonResponder(200, httpmock.File(testDataPath))
|
||||||
|
|
|
@ -31,7 +31,7 @@ type ListeningsResult struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Listening struct {
|
type Listening struct {
|
||||||
Id int `json:"int"`
|
ID int `json:"int"`
|
||||||
User User `json:"user"`
|
User User `json:"user"`
|
||||||
Track Track `json:"track"`
|
Track Track `json:"track"`
|
||||||
CreationDate string `json:"creation_date"`
|
CreationDate string `json:"creation_date"`
|
||||||
|
@ -45,14 +45,14 @@ type FavoriteTracksResult struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type FavoriteTrack struct {
|
type FavoriteTrack struct {
|
||||||
Id int `json:"int"`
|
ID int `json:"int"`
|
||||||
User User `json:"user"`
|
User User `json:"user"`
|
||||||
Track Track `json:"track"`
|
Track Track `json:"track"`
|
||||||
CreationDate string `json:"creation_date"`
|
CreationDate string `json:"creation_date"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Track struct {
|
type Track struct {
|
||||||
Id int `json:"int"`
|
ID int `json:"int"`
|
||||||
Artist Artist `json:"artist"`
|
Artist Artist `json:"artist"`
|
||||||
Album Album `json:"album"`
|
Album Album `json:"album"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
|
@ -64,13 +64,13 @@ type Track struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Artist struct {
|
type Artist struct {
|
||||||
Id int `json:"int"`
|
ID int `json:"int"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
ArtistMBID mbtypes.MBID `json:"mbid"`
|
ArtistMBID mbtypes.MBID `json:"mbid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Album struct {
|
type Album struct {
|
||||||
Id int `json:"int"`
|
ID int `json:"int"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
AlbumArtist Artist `json:"artist"`
|
AlbumArtist Artist `json:"artist"`
|
||||||
ReleaseDate string `json:"release_date"`
|
ReleaseDate string `json:"release_date"`
|
||||||
|
@ -79,7 +79,7 @@ type Album struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
Id int `json:"int"`
|
ID int `json:"int"`
|
||||||
UserName string `json:"username"`
|
UserName string `json:"username"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,7 @@ func (b *JSPFBackend) InitConfig(config *config.ServiceConfig) error {
|
||||||
Identifier: config.GetString("identifier"),
|
Identifier: config.GetString("identifier"),
|
||||||
Tracks: make([]jspf.Track, 0),
|
Tracks: make([]jspf.Track, 0),
|
||||||
Extension: map[string]any{
|
Extension: map[string]any{
|
||||||
jspf.MusicBrainzPlaylistExtensionId: jspf.MusicBrainzPlaylistExtension{
|
jspf.MusicBrainzPlaylistExtensionID: jspf.MusicBrainzPlaylistExtension{
|
||||||
LastModifiedAt: time.Now(),
|
LastModifiedAt: time.Now(),
|
||||||
Public: true,
|
Public: true,
|
||||||
},
|
},
|
||||||
|
@ -116,7 +116,7 @@ func listenAsTrack(l models.Listen) jspf.Track {
|
||||||
extension := makeMusicBrainzExtension(l.Track)
|
extension := makeMusicBrainzExtension(l.Track)
|
||||||
extension.AddedAt = l.ListenedAt
|
extension.AddedAt = l.ListenedAt
|
||||||
extension.AddedBy = l.UserName
|
extension.AddedBy = l.UserName
|
||||||
track.Extension[jspf.MusicBrainzTrackExtensionId] = extension
|
track.Extension[jspf.MusicBrainzTrackExtensionID] = extension
|
||||||
|
|
||||||
if l.RecordingMBID != "" {
|
if l.RecordingMBID != "" {
|
||||||
track.Identifier = append(track.Identifier, "https://musicbrainz.org/recording/"+string(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 := makeMusicBrainzExtension(l.Track)
|
||||||
extension.AddedAt = l.Created
|
extension.AddedAt = l.Created
|
||||||
extension.AddedBy = l.UserName
|
extension.AddedBy = l.UserName
|
||||||
track.Extension[jspf.MusicBrainzTrackExtensionId] = extension
|
track.Extension[jspf.MusicBrainzTrackExtensionID] = extension
|
||||||
|
|
||||||
recordingMBID := l.Track.RecordingMBID
|
recordingMBID := l.Track.RecordingMBID
|
||||||
if l.RecordingMBID != "" {
|
if l.RecordingMBID != "" {
|
||||||
|
|
|
@ -25,21 +25,21 @@ import (
|
||||||
|
|
||||||
type lastfmStrategy struct {
|
type lastfmStrategy struct {
|
||||||
client *lastfm.Api
|
client *lastfm.Api
|
||||||
redirectUrl *url.URL
|
redirectURL *url.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s lastfmStrategy) Config() oauth2.Config {
|
func (s lastfmStrategy) Config() oauth2.Config {
|
||||||
return 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
|
// Last.fm does not use OAuth2, but the provided authorization flow with
|
||||||
// callback URL is close enough we can shoehorn it into the existing
|
// callback URL is close enough we can shoehorn it into the existing
|
||||||
// authentication strategy.
|
// authentication strategy.
|
||||||
// TODO: Investigate and use callback-less flow with api.GetAuthTokenUrl(token)
|
// TODO: Investigate and use callback-less flow with api.GetAuthTokenUrl(token)
|
||||||
url := s.client.GetAuthRequestUrl(s.redirectUrl.String())
|
url := s.client.GetAuthRequestUrl(s.redirectURL.String())
|
||||||
return auth.AuthUrl{
|
return auth.AuthURL{
|
||||||
Url: url,
|
URL: url,
|
||||||
State: "", // last.fm does not use state
|
State: "", // last.fm does not use state
|
||||||
Param: "token",
|
Param: "token",
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,9 +62,9 @@ func (b *LastfmApiBackend) Options() []models.BackendOption {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *LastfmApiBackend) InitConfig(config *config.ServiceConfig) error {
|
func (b *LastfmApiBackend) InitConfig(config *config.ServiceConfig) error {
|
||||||
clientId := config.GetString("client-id")
|
clientID := config.GetString("client-id")
|
||||||
clientSecret := config.GetString("client-secret")
|
clientSecret := config.GetString("client-secret")
|
||||||
b.client = lastfm.New(clientId, clientSecret)
|
b.client = lastfm.New(clientID, clientSecret)
|
||||||
b.username = config.GetString("username")
|
b.username = config.GetString("username")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -72,10 +72,10 @@ func (b *LastfmApiBackend) InitConfig(config *config.ServiceConfig) error {
|
||||||
func (b *LastfmApiBackend) StartImport() error { return nil }
|
func (b *LastfmApiBackend) StartImport() error { return nil }
|
||||||
func (b *LastfmApiBackend) FinishImport() 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,
|
||||||
redirectUrl: redirectUrl,
|
redirectURL: redirectURL,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
HttpClient *resty.Client
|
HTTPClient *resty.Client
|
||||||
MaxResults int
|
MaxResults int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ func NewClient(token string) Client {
|
||||||
ratelimit.EnableHTTPHeaderRateLimit(client, "X-RateLimit-Reset-In")
|
ratelimit.EnableHTTPHeaderRateLimit(client, "X-RateLimit-Reset-In")
|
||||||
|
|
||||||
return Client{
|
return Client{
|
||||||
HttpClient: client,
|
HTTPClient: client,
|
||||||
MaxResults: DefaultItemsPerGet,
|
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) {
|
func (c Client) GetListens(user string, maxTime time.Time, minTime time.Time) (result GetListensResult, err error) {
|
||||||
const path = "/user/{username}/listens"
|
const path = "/user/{username}/listens"
|
||||||
errorResult := ErrorResult{}
|
errorResult := ErrorResult{}
|
||||||
response, err := c.HttpClient.R().
|
response, err := c.HTTPClient.R().
|
||||||
SetPathParam("username", user).
|
SetPathParam("username", user).
|
||||||
SetQueryParams(map[string]string{
|
SetQueryParams(map[string]string{
|
||||||
"max_ts": strconv.FormatInt(maxTime.Unix(), 10),
|
"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) {
|
func (c Client) SubmitListens(listens ListenSubmission) (result StatusResult, err error) {
|
||||||
const path = "/submit-listens"
|
const path = "/submit-listens"
|
||||||
errorResult := ErrorResult{}
|
errorResult := ErrorResult{}
|
||||||
response, err := c.HttpClient.R().
|
response, err := c.HTTPClient.R().
|
||||||
SetBody(listens).
|
SetBody(listens).
|
||||||
SetResult(&result).
|
SetResult(&result).
|
||||||
SetError(&errorResult).
|
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) {
|
func (c Client) GetFeedback(user string, status int, offset int) (result GetFeedbackResult, err error) {
|
||||||
const path = "/feedback/user/{username}/get-feedback"
|
const path = "/feedback/user/{username}/get-feedback"
|
||||||
errorResult := ErrorResult{}
|
errorResult := ErrorResult{}
|
||||||
response, err := c.HttpClient.R().
|
response, err := c.HTTPClient.R().
|
||||||
SetPathParam("username", user).
|
SetPathParam("username", user).
|
||||||
SetQueryParams(map[string]string{
|
SetQueryParams(map[string]string{
|
||||||
"status": strconv.Itoa(status),
|
"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) {
|
func (c Client) SendFeedback(feedback Feedback) (result StatusResult, err error) {
|
||||||
const path = "/feedback/recording-feedback"
|
const path = "/feedback/recording-feedback"
|
||||||
errorResult := ErrorResult{}
|
errorResult := ErrorResult{}
|
||||||
response, err := c.HttpClient.R().
|
response, err := c.HTTPClient.R().
|
||||||
SetBody(feedback).
|
SetBody(feedback).
|
||||||
SetResult(&result).
|
SetResult(&result).
|
||||||
SetError(&errorResult).
|
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) {
|
func (c Client) Lookup(recordingName string, artistName string) (result LookupResult, err error) {
|
||||||
const path = "/metadata/lookup"
|
const path = "/metadata/lookup"
|
||||||
errorResult := ErrorResult{}
|
errorResult := ErrorResult{}
|
||||||
response, err := c.HttpClient.R().
|
response, err := c.HTTPClient.R().
|
||||||
SetQueryParams(map[string]string{
|
SetQueryParams(map[string]string{
|
||||||
"recording_name": recordingName,
|
"recording_name": recordingName,
|
||||||
"artist_name": artistName,
|
"artist_name": artistName,
|
||||||
|
|
|
@ -36,7 +36,7 @@ import (
|
||||||
func TestNewClient(t *testing.T) {
|
func TestNewClient(t *testing.T) {
|
||||||
token := "foobar123"
|
token := "foobar123"
|
||||||
client := listenbrainz.NewClient(token)
|
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)
|
assert.Equal(t, listenbrainz.DefaultItemsPerGet, client.MaxResults)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ func TestGetListens(t *testing.T) {
|
||||||
|
|
||||||
client := listenbrainz.NewClient("thetoken")
|
client := listenbrainz.NewClient("thetoken")
|
||||||
client.MaxResults = 2
|
client.MaxResults = 2
|
||||||
setupHttpMock(t, client.HttpClient.GetClient(),
|
setupHTTPMock(t, client.HTTPClient.GetClient(),
|
||||||
"https://api.listenbrainz.org/1/user/outsidecontext/listens",
|
"https://api.listenbrainz.org/1/user/outsidecontext/listens",
|
||||||
"testdata/listens.json")
|
"testdata/listens.json")
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ func TestGetListens(t *testing.T) {
|
||||||
|
|
||||||
func TestSubmitListens(t *testing.T) {
|
func TestSubmitListens(t *testing.T) {
|
||||||
client := listenbrainz.NewClient("thetoken")
|
client := listenbrainz.NewClient("thetoken")
|
||||||
httpmock.ActivateNonDefault(client.HttpClient.GetClient())
|
httpmock.ActivateNonDefault(client.HTTPClient.GetClient())
|
||||||
|
|
||||||
responder, err := httpmock.NewJsonResponder(200, listenbrainz.StatusResult{
|
responder, err := httpmock.NewJsonResponder(200, listenbrainz.StatusResult{
|
||||||
Status: "ok",
|
Status: "ok",
|
||||||
|
@ -103,7 +103,7 @@ func TestGetFeedback(t *testing.T) {
|
||||||
|
|
||||||
client := listenbrainz.NewClient("thetoken")
|
client := listenbrainz.NewClient("thetoken")
|
||||||
client.MaxResults = 2
|
client.MaxResults = 2
|
||||||
setupHttpMock(t, client.HttpClient.GetClient(),
|
setupHTTPMock(t, client.HTTPClient.GetClient(),
|
||||||
"https://api.listenbrainz.org/1/feedback/user/outsidecontext/get-feedback",
|
"https://api.listenbrainz.org/1/feedback/user/outsidecontext/get-feedback",
|
||||||
"testdata/feedback.json")
|
"testdata/feedback.json")
|
||||||
|
|
||||||
|
@ -120,7 +120,7 @@ func TestGetFeedback(t *testing.T) {
|
||||||
|
|
||||||
func TestSendFeedback(t *testing.T) {
|
func TestSendFeedback(t *testing.T) {
|
||||||
client := listenbrainz.NewClient("thetoken")
|
client := listenbrainz.NewClient("thetoken")
|
||||||
httpmock.ActivateNonDefault(client.HttpClient.GetClient())
|
httpmock.ActivateNonDefault(client.HTTPClient.GetClient())
|
||||||
|
|
||||||
responder, err := httpmock.NewJsonResponder(200, listenbrainz.StatusResult{
|
responder, err := httpmock.NewJsonResponder(200, listenbrainz.StatusResult{
|
||||||
Status: "ok",
|
Status: "ok",
|
||||||
|
@ -145,7 +145,7 @@ func TestLookup(t *testing.T) {
|
||||||
defer httpmock.DeactivateAndReset()
|
defer httpmock.DeactivateAndReset()
|
||||||
|
|
||||||
client := listenbrainz.NewClient("thetoken")
|
client := listenbrainz.NewClient("thetoken")
|
||||||
setupHttpMock(t, client.HttpClient.GetClient(),
|
setupHTTPMock(t, client.HTTPClient.GetClient(),
|
||||||
"https://api.listenbrainz.org/1/metadata/lookup",
|
"https://api.listenbrainz.org/1/metadata/lookup",
|
||||||
"testdata/lookup.json")
|
"testdata/lookup.json")
|
||||||
|
|
||||||
|
@ -158,7 +158,7 @@ func TestLookup(t *testing.T) {
|
||||||
assert.Equal(mbtypes.MBID("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) {
|
func setupHTTPMock(t *testing.T, client *http.Client, url string, testDataPath string) {
|
||||||
httpmock.ActivateNonDefault(client)
|
httpmock.ActivateNonDefault(client)
|
||||||
|
|
||||||
responder, err := httpmock.NewJsonResponder(200, httpmock.File(testDataPath))
|
responder, err := httpmock.NewJsonResponder(200, httpmock.File(testDataPath))
|
||||||
|
|
|
@ -131,7 +131,7 @@ func TestTrackTrackNumberString(t *testing.T) {
|
||||||
assert.Equal(t, 12, track.TrackNumber())
|
assert.Equal(t, 12, track.TrackNumber())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTrackIsrc(t *testing.T) {
|
func TestTrackISRC(t *testing.T) {
|
||||||
expected := mbtypes.ISRC("TCAEJ1934417")
|
expected := mbtypes.ISRC("TCAEJ1934417")
|
||||||
track := listenbrainz.Track{
|
track := listenbrainz.Track{
|
||||||
AdditionalInfo: map[string]any{
|
AdditionalInfo: map[string]any{
|
||||||
|
|
|
@ -32,25 +32,25 @@ import (
|
||||||
const MaxItemsPerGet = 1000
|
const MaxItemsPerGet = 1000
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
HttpClient *resty.Client
|
HTTPClient *resty.Client
|
||||||
token string
|
token string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(serverUrl string, token string) Client {
|
func NewClient(serverURL string, token string) Client {
|
||||||
client := resty.New()
|
client := resty.New()
|
||||||
client.SetBaseURL(serverUrl)
|
client.SetBaseURL(serverURL)
|
||||||
client.SetHeader("Accept", "application/json")
|
client.SetHeader("Accept", "application/json")
|
||||||
client.SetHeader("User-Agent", version.UserAgent())
|
client.SetHeader("User-Agent", version.UserAgent())
|
||||||
client.SetRetryCount(5)
|
client.SetRetryCount(5)
|
||||||
return Client{
|
return Client{
|
||||||
HttpClient: client,
|
HTTPClient: client,
|
||||||
token: token,
|
token: token,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Client) GetScrobbles(page int, perPage int) (result GetScrobblesResult, err error) {
|
func (c Client) GetScrobbles(page int, perPage int) (result GetScrobblesResult, err error) {
|
||||||
const path = "/apis/mlj_1/scrobbles"
|
const path = "/apis/mlj_1/scrobbles"
|
||||||
response, err := c.HttpClient.R().
|
response, err := c.HTTPClient.R().
|
||||||
SetQueryParams(map[string]string{
|
SetQueryParams(map[string]string{
|
||||||
"page": strconv.Itoa(page),
|
"page": strconv.Itoa(page),
|
||||||
"perpage": strconv.Itoa(perPage),
|
"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) {
|
func (c Client) NewScrobble(scrobble NewScrobble) (result NewScrobbleResult, err error) {
|
||||||
const path = "/apis/mlj_1/newscrobble"
|
const path = "/apis/mlj_1/newscrobble"
|
||||||
scrobble.Key = c.token
|
scrobble.Key = c.token
|
||||||
response, err := c.HttpClient.R().
|
response, err := c.HTTPClient.R().
|
||||||
SetBody(scrobble).
|
SetBody(scrobble).
|
||||||
SetResult(&result).
|
SetResult(&result).
|
||||||
Post(path)
|
Post(path)
|
||||||
|
|
|
@ -32,19 +32,19 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewClient(t *testing.T) {
|
func TestNewClient(t *testing.T) {
|
||||||
serverUrl := "https://maloja.example.com"
|
serverURL := "https://maloja.example.com"
|
||||||
token := "foobar123"
|
token := "foobar123"
|
||||||
client := maloja.NewClient(serverUrl, token)
|
client := maloja.NewClient(serverURL, token)
|
||||||
assert.Equal(t, serverUrl, client.HttpClient.BaseURL)
|
assert.Equal(t, serverURL, client.HTTPClient.BaseURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetScrobbles(t *testing.T) {
|
func TestGetScrobbles(t *testing.T) {
|
||||||
defer httpmock.DeactivateAndReset()
|
defer httpmock.DeactivateAndReset()
|
||||||
|
|
||||||
serverUrl := "https://maloja.example.com"
|
serverURL := "https://maloja.example.com"
|
||||||
token := "thetoken"
|
token := "thetoken"
|
||||||
client := maloja.NewClient(serverUrl, token)
|
client := maloja.NewClient(serverURL, token)
|
||||||
setupHttpMock(t, client.HttpClient.GetClient(),
|
setupHTTPMock(t, client.HTTPClient.GetClient(),
|
||||||
"https://maloja.example.com/apis/mlj_1/scrobbles",
|
"https://maloja.example.com/apis/mlj_1/scrobbles",
|
||||||
"testdata/scrobbles.json")
|
"testdata/scrobbles.json")
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ func TestGetScrobbles(t *testing.T) {
|
||||||
func TestNewScrobble(t *testing.T) {
|
func TestNewScrobble(t *testing.T) {
|
||||||
server := "https://maloja.example.com"
|
server := "https://maloja.example.com"
|
||||||
client := maloja.NewClient(server, "thetoken")
|
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"))
|
responder, err := httpmock.NewJsonResponder(200, httpmock.File("testdata/newscrobble-result.json"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -80,7 +80,7 @@ func TestNewScrobble(t *testing.T) {
|
||||||
assert.Equal(t, "success", result.Status)
|
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)
|
httpmock.ActivateNonDefault(client)
|
||||||
|
|
||||||
responder, err := httpmock.NewJsonResponder(200, httpmock.File(testDataPath))
|
responder, err := httpmock.NewJsonResponder(200, httpmock.File(testDataPath))
|
||||||
|
|
|
@ -40,7 +40,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
HttpClient *resty.Client
|
HTTPClient *resty.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(token oauth2.TokenSource) Client {
|
func NewClient(token oauth2.TokenSource) Client {
|
||||||
|
@ -55,7 +55,7 @@ func NewClient(token oauth2.TokenSource) Client {
|
||||||
ratelimit.EnableHTTPHeaderRateLimit(client, "Retry-After")
|
ratelimit.EnableHTTPHeaderRateLimit(client, "Retry-After")
|
||||||
|
|
||||||
return Client{
|
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) {
|
func (c Client) recentlyPlayed(after *time.Time, before *time.Time, limit int) (result RecentlyPlayedResult, err error) {
|
||||||
const path = "/me/player/recently-played"
|
const path = "/me/player/recently-played"
|
||||||
request := c.HttpClient.R().
|
request := c.HTTPClient.R().
|
||||||
SetQueryParam("limit", strconv.Itoa(limit)).
|
SetQueryParam("limit", strconv.Itoa(limit)).
|
||||||
SetResult(&result)
|
SetResult(&result)
|
||||||
if after != nil {
|
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) {
|
func (c Client) UserTracks(offset int, limit int) (result TracksResult, err error) {
|
||||||
const path = "/me/tracks"
|
const path = "/me/tracks"
|
||||||
response, err := c.HttpClient.R().
|
response, err := c.HTTPClient.R().
|
||||||
SetQueryParams(map[string]string{
|
SetQueryParams(map[string]string{
|
||||||
"offset": strconv.Itoa(offset),
|
"offset": strconv.Itoa(offset),
|
||||||
"limit": strconv.Itoa(limit),
|
"limit": strconv.Itoa(limit),
|
||||||
|
|
|
@ -43,7 +43,7 @@ func TestRecentlyPlayedAfter(t *testing.T) {
|
||||||
defer httpmock.DeactivateAndReset()
|
defer httpmock.DeactivateAndReset()
|
||||||
|
|
||||||
client := spotify.NewClient(nil)
|
client := spotify.NewClient(nil)
|
||||||
setupHttpMock(t, client.HttpClient.GetClient(),
|
setupHTTPMock(t, client.HTTPClient.GetClient(),
|
||||||
"https://api.spotify.com/v1/me/player/recently-played",
|
"https://api.spotify.com/v1/me/player/recently-played",
|
||||||
"testdata/recently-played.json")
|
"testdata/recently-played.json")
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ func TestGetUserTracks(t *testing.T) {
|
||||||
defer httpmock.DeactivateAndReset()
|
defer httpmock.DeactivateAndReset()
|
||||||
|
|
||||||
client := spotify.NewClient(nil)
|
client := spotify.NewClient(nil)
|
||||||
setupHttpMock(t, client.HttpClient.GetClient(),
|
setupHTTPMock(t, client.HTTPClient.GetClient(),
|
||||||
"https://api.spotify.com/v1/me/tracks",
|
"https://api.spotify.com/v1/me/tracks",
|
||||||
"testdata/user-tracks.json")
|
"testdata/user-tracks.json")
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ func TestGetUserTracks(t *testing.T) {
|
||||||
assert.Equal("Zeal & Ardor", track1.Track.Album.Name)
|
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)
|
httpmock.ActivateNonDefault(client)
|
||||||
|
|
||||||
responder, err := httpmock.NewJsonResponder(200, httpmock.File(testDataPath))
|
responder, err := httpmock.NewJsonResponder(200, httpmock.File(testDataPath))
|
||||||
|
|
|
@ -58,7 +58,7 @@ type Listen struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Track struct {
|
type Track struct {
|
||||||
Id string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Href string `json:"href"`
|
Href string `json:"href"`
|
||||||
Uri string `json:"uri"`
|
Uri string `json:"uri"`
|
||||||
|
@ -69,14 +69,14 @@ type Track struct {
|
||||||
Explicit bool `json:"explicit"`
|
Explicit bool `json:"explicit"`
|
||||||
IsLocal bool `json:"is_local"`
|
IsLocal bool `json:"is_local"`
|
||||||
Popularity int `json:"popularity"`
|
Popularity int `json:"popularity"`
|
||||||
ExternalIds ExternalIds `json:"external_ids"`
|
ExternalIDs ExternalIDs `json:"external_ids"`
|
||||||
ExternalUrls ExternalUrls `json:"external_urls"`
|
ExternalURLs ExternalURLs `json:"external_urls"`
|
||||||
Album Album `json:"album"`
|
Album Album `json:"album"`
|
||||||
Artists []Artist `json:"artists"`
|
Artists []Artist `json:"artists"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Album struct {
|
type Album struct {
|
||||||
Id string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Href string `json:"href"`
|
Href string `json:"href"`
|
||||||
Uri string `json:"uri"`
|
Uri string `json:"uri"`
|
||||||
|
@ -85,32 +85,32 @@ type Album struct {
|
||||||
ReleaseDate string `json:"release_date"`
|
ReleaseDate string `json:"release_date"`
|
||||||
ReleaseDatePrecision string `json:"release_date_precision"`
|
ReleaseDatePrecision string `json:"release_date_precision"`
|
||||||
AlbumType string `json:"album_type"`
|
AlbumType string `json:"album_type"`
|
||||||
ExternalUrls ExternalUrls `json:"external_urls"`
|
ExternalURLs ExternalURLs `json:"external_urls"`
|
||||||
Artists []Artist `json:"artists"`
|
Artists []Artist `json:"artists"`
|
||||||
Images []Image `json:"images"`
|
Images []Image `json:"images"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Artist struct {
|
type Artist struct {
|
||||||
Id string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Href string `json:"href"`
|
Href string `json:"href"`
|
||||||
Uri string `json:"uri"`
|
Uri string `json:"uri"`
|
||||||
Type string `json:"type"`
|
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"`
|
ISRC mbtypes.ISRC `json:"isrc"`
|
||||||
EAN string `json:"ean"`
|
EAN string `json:"ean"`
|
||||||
UPC string `json:"upc"`
|
UPC string `json:"upc"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExternalUrls struct {
|
type ExternalURLs struct {
|
||||||
Spotify string `json:"spotify"`
|
Spotify string `json:"spotify"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Image struct {
|
type Image struct {
|
||||||
Url string `json:"url"`
|
URL string `json:"url"`
|
||||||
Height int `json:"height"`
|
Height int `json:"height"`
|
||||||
Width int `json:"width"`
|
Width int `json:"width"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ import (
|
||||||
|
|
||||||
type SpotifyApiBackend struct {
|
type SpotifyApiBackend struct {
|
||||||
client Client
|
client Client
|
||||||
clientId string
|
clientID string
|
||||||
clientSecret string
|
clientSecret string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,14 +53,14 @@ func (b *SpotifyApiBackend) Options() []models.BackendOption {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *SpotifyApiBackend) InitConfig(config *config.ServiceConfig) error {
|
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")
|
b.clientSecret = config.GetString("client-secret")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *SpotifyApiBackend) OAuth2Strategy(redirectUrl *url.URL) auth.OAuth2Strategy {
|
func (b *SpotifyApiBackend) OAuth2Strategy(redirectURL *url.URL) auth.OAuth2Strategy {
|
||||||
conf := oauth2.Config{
|
conf := oauth2.Config{
|
||||||
ClientID: b.clientId,
|
ClientID: b.clientID,
|
||||||
ClientSecret: b.clientSecret,
|
ClientSecret: b.clientSecret,
|
||||||
Scopes: []string{
|
Scopes: []string{
|
||||||
"user-read-currently-playing",
|
"user-read-currently-playing",
|
||||||
|
@ -68,16 +68,16 @@ func (b *SpotifyApiBackend) OAuth2Strategy(redirectUrl *url.URL) auth.OAuth2Stra
|
||||||
"user-library-read",
|
"user-library-read",
|
||||||
"user-library-modify",
|
"user-library-modify",
|
||||||
},
|
},
|
||||||
RedirectURL: redirectUrl.String(),
|
RedirectURL: redirectURL.String(),
|
||||||
Endpoint: spotify.Endpoint,
|
Endpoint: spotify.Endpoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
return auth.NewStandardStrategy(conf)
|
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{
|
return oauth2.Config{
|
||||||
ClientID: b.clientId,
|
ClientID: b.clientID,
|
||||||
ClientSecret: b.clientSecret,
|
ClientSecret: b.clientSecret,
|
||||||
Scopes: []string{
|
Scopes: []string{
|
||||||
"user-read-currently-playing",
|
"user-read-currently-playing",
|
||||||
|
@ -85,7 +85,7 @@ func (b *SpotifyApiBackend) OAuth2Config(redirectUrl *url.URL) oauth2.Config {
|
||||||
"user-library-read",
|
"user-library-read",
|
||||||
"user-library-modify",
|
"user-library-modify",
|
||||||
},
|
},
|
||||||
RedirectURL: redirectUrl.String(),
|
RedirectURL: redirectURL.String(),
|
||||||
Endpoint: spotify.Endpoint,
|
Endpoint: spotify.Endpoint,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -251,7 +251,7 @@ func (t Track) AsTrack() models.Track {
|
||||||
Duration: time.Duration(t.DurationMs * int(time.Millisecond)),
|
Duration: time.Duration(t.DurationMs * int(time.Millisecond)),
|
||||||
TrackNumber: t.TrackNumber,
|
TrackNumber: t.TrackNumber,
|
||||||
DiscNumber: t.DiscNumber,
|
DiscNumber: t.DiscNumber,
|
||||||
ISRC: t.ExternalIds.ISRC,
|
ISRC: t.ExternalIDs.ISRC,
|
||||||
AdditionalInfo: map[string]any{},
|
AdditionalInfo: map[string]any{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,30 +264,30 @@ func (t Track) AsTrack() models.Track {
|
||||||
info["music_service"] = "spotify.com"
|
info["music_service"] = "spotify.com"
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.ExternalUrls.Spotify != "" {
|
if t.ExternalURLs.Spotify != "" {
|
||||||
info["origin_url"] = t.ExternalUrls.Spotify
|
info["origin_url"] = t.ExternalURLs.Spotify
|
||||||
info["spotify_id"] = t.ExternalUrls.Spotify
|
info["spotify_id"] = t.ExternalURLs.Spotify
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.Album.ExternalUrls.Spotify != "" {
|
if t.Album.ExternalURLs.Spotify != "" {
|
||||||
info["spotify_album_id"] = t.Album.ExternalUrls.Spotify
|
info["spotify_album_id"] = t.Album.ExternalURLs.Spotify
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(t.Artists) > 0 {
|
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 {
|
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
|
return track
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractArtistIds(artists []Artist) []string {
|
func extractArtistIDs(artists []Artist) []string {
|
||||||
artistIds := make([]string, len(artists))
|
artistIDs := make([]string, len(artists))
|
||||||
for i, artist := range artists {
|
for i, artist := range artists {
|
||||||
artistIds[i] = artist.ExternalUrls.Spotify
|
artistIDs[i] = artist.ExternalURLs.Spotify
|
||||||
}
|
}
|
||||||
return artistIds
|
return artistIDs
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,8 +92,8 @@ func (i HistoryItem) AsListen() models.Listen {
|
||||||
PlaybackDuration: time.Duration(i.MillisecondsPlayed * int(time.Millisecond)),
|
PlaybackDuration: time.Duration(i.MillisecondsPlayed * int(time.Millisecond)),
|
||||||
UserName: i.UserName,
|
UserName: i.UserName,
|
||||||
}
|
}
|
||||||
if trackUrl, err := formatSpotifyUri(i.SpotifyTrackUri); err != nil {
|
if trackURL, err := formatSpotifyUri(i.SpotifyTrackUri); err != nil {
|
||||||
listen.AdditionalInfo["spotify_id"] = trackUrl
|
listen.AdditionalInfo["spotify_id"] = trackURL
|
||||||
}
|
}
|
||||||
return listen
|
return listen
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,20 +43,20 @@ func AuthenticationFlow(service config.ServiceConfig, backend auth.OAuth2Authent
|
||||||
|
|
||||||
state := auth.RandomState()
|
state := auth.RandomState()
|
||||||
// Redirect user to consent page to ask for permission specified scopes.
|
// 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
|
// Start an HTTP server to listen for the response
|
||||||
responseChan := make(chan auth.CodeResponse)
|
responseChan := make(chan auth.CodeResponse)
|
||||||
auth.RunOauth2CallbackServer(*redirectURL, authUrl.Param, responseChan)
|
auth.RunOauth2CallbackServer(*redirectURL, authURL.Param, responseChan)
|
||||||
|
|
||||||
// Open the URL
|
// Open the URL
|
||||||
fmt.Println(i18n.Tr("Visit the URL for authorization: %v", authUrl.Url))
|
fmt.Println(i18n.Tr("Visit the URL for authorization: %v", authURL.URL))
|
||||||
err = browser.OpenURL(authUrl.Url)
|
err = browser.OpenURL(authURL.URL)
|
||||||
cobra.CheckErr(err)
|
cobra.CheckErr(err)
|
||||||
|
|
||||||
// Retrieve the code from the authentication callback
|
// Retrieve the code from the authentication callback
|
||||||
code := <-responseChan
|
code := <-responseChan
|
||||||
if code.State != authUrl.State {
|
if code.State != authURL.State {
|
||||||
cobra.CompErrorln(i18n.Tr("Error: OAuth state mismatch"))
|
cobra.CompErrorln(i18n.Tr("Error: OAuth state mismatch"))
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,9 +26,9 @@ import "time"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// The identifier for the MusicBrainz / ListenBrainz JSPF playlist extension
|
// 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
|
// 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
|
// MusicBrainz / ListenBrainz JSPF track extension
|
||||||
|
|
|
@ -39,7 +39,7 @@ func ExampleMusicBrainzTrackExtension() {
|
||||||
{
|
{
|
||||||
Title: "Oweynagat",
|
Title: "Oweynagat",
|
||||||
Extension: map[string]any{
|
Extension: map[string]any{
|
||||||
jspf.MusicBrainzTrackExtensionId: jspf.MusicBrainzTrackExtension{
|
jspf.MusicBrainzTrackExtensionID: jspf.MusicBrainzTrackExtension{
|
||||||
AddedAt: time.Date(2023, 11, 24, 07, 47, 50, 0, time.UTC),
|
AddedAt: time.Date(2023, 11, 24, 07, 47, 50, 0, time.UTC),
|
||||||
AddedBy: "scotty",
|
AddedBy: "scotty",
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue