mirror of
https://github.com/navidrome/navidrome.git
synced 2025-04-04 21:17:37 +03:00
115 lines
2.6 KiB
Go
115 lines
2.6 KiB
Go
package spotify
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/deluan/navidrome/log"
|
|
)
|
|
|
|
const apiBaseUrl = "https://api.spotify.com/v1/"
|
|
|
|
var (
|
|
ErrNotFound = errors.New("spotify: not found")
|
|
)
|
|
|
|
type HttpClient interface {
|
|
Do(req *http.Request) (*http.Response, error)
|
|
}
|
|
|
|
func NewClient(id, secret string, hc HttpClient) *Client {
|
|
return &Client{id, secret, hc}
|
|
}
|
|
|
|
type Client struct {
|
|
id string
|
|
secret string
|
|
hc HttpClient
|
|
}
|
|
|
|
func (c *Client) SearchArtists(ctx context.Context, name string, limit int) ([]Artist, error) {
|
|
token, err := c.authorize(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
params := url.Values{}
|
|
params.Add("type", "artist")
|
|
params.Add("q", name)
|
|
params.Add("offset", "0")
|
|
params.Add("limit", strconv.Itoa(limit))
|
|
req, _ := http.NewRequest("GET", apiBaseUrl+"search", nil)
|
|
req.URL.RawQuery = params.Encode()
|
|
req.Header.Add("Authorization", "Bearer "+token)
|
|
|
|
var results SearchResults
|
|
err = c.makeRequest(req, &results)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(results.Artists.Items) == 0 {
|
|
return nil, ErrNotFound
|
|
}
|
|
log.Debug(ctx, "Found artist in Spotify", "artist", results.Artists.Items[0].Name)
|
|
return results.Artists.Items, err
|
|
}
|
|
|
|
func (c *Client) authorize(ctx context.Context) (string, error) {
|
|
payload := url.Values{}
|
|
payload.Add("grant_type", "client_credentials")
|
|
|
|
req, _ := http.NewRequest("POST", "https://accounts.spotify.com/api/token", strings.NewReader(payload.Encode()))
|
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
|
req.Header.Add("Content-Length", strconv.Itoa(len(payload.Encode())))
|
|
auth := c.id + ":" + c.secret
|
|
req.Header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth)))
|
|
|
|
response := map[string]interface{}{}
|
|
err := c.makeRequest(req, &response)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if v, ok := response["access_token"]; ok {
|
|
return v.(string), nil
|
|
}
|
|
log.Error(ctx, "Invalid spotify response", "resp", response)
|
|
return "", errors.New("invalid response")
|
|
}
|
|
|
|
func (c *Client) makeRequest(req *http.Request, response interface{}) error {
|
|
resp, err := c.hc.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
data, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if resp.StatusCode != 200 {
|
|
return c.parseError(data)
|
|
}
|
|
|
|
return json.Unmarshal(data, response)
|
|
}
|
|
|
|
func (c *Client) parseError(data []byte) error {
|
|
var e Error
|
|
err := json.Unmarshal(data, &e)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return fmt.Errorf("spotify error(%s): %s", e.Code, e.Message)
|
|
}
|