mirror of
https://github.com/skidoodle/spotify-ws
synced 2025-10-09 05:22:43 +02:00
refactor
This commit is contained in:
74
internal/spotify/client.go
Normal file
74
internal/spotify/client.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package spotify
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
const (
|
||||
tokenURL = "https://accounts.spotify.com/api/token"
|
||||
currentlyPlayingURL = "https://api.spotify.com/v1/me/player/currently-playing"
|
||||
)
|
||||
|
||||
// Client is a thread-safe client for interacting with the Spotify API.
|
||||
type Client struct {
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// NewClient creates a new Spotify API client using the refresh token flow.
|
||||
// The returned client is safe for concurrent use.
|
||||
func NewClient(ctx context.Context, clientID, clientSecret, refreshToken string) *Client {
|
||||
conf := &oauth2.Config{
|
||||
ClientID: clientID,
|
||||
ClientSecret: clientSecret,
|
||||
Endpoint: oauth2.Endpoint{
|
||||
TokenURL: tokenURL,
|
||||
},
|
||||
}
|
||||
|
||||
token := &oauth2.Token{
|
||||
RefreshToken: refreshToken,
|
||||
}
|
||||
|
||||
// The TokenSource is concurrency-safe and handles token refreshes automatically.
|
||||
tokenSource := conf.TokenSource(ctx, token)
|
||||
|
||||
return &Client{
|
||||
httpClient: oauth2.NewClient(ctx, tokenSource),
|
||||
}
|
||||
}
|
||||
|
||||
// CurrentlyPlaying fetches the user's currently playing track from the Spotify API.
|
||||
func (c *Client) CurrentlyPlaying(ctx context.Context) (*CurrentlyPlaying, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, currentlyPlayingURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
slog.Warn("failed to close spotify api response body", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// When nothing is playing, Spotify returns 204 No Content.
|
||||
// We normalize this to a consistent struct response for the caller.
|
||||
if resp.StatusCode == http.StatusNoContent {
|
||||
return &CurrentlyPlaying{IsPlaying: false, Item: nil}, nil
|
||||
}
|
||||
|
||||
var currentlyPlaying CurrentlyPlaying
|
||||
if err := json.NewDecoder(resp.Body).Decode(¤tlyPlaying); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ¤tlyPlaying, nil
|
||||
}
|
||||
25
internal/spotify/model.go
Normal file
25
internal/spotify/model.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package spotify
|
||||
|
||||
// TrackItem represents the track object from the Spotify API.
|
||||
type TrackItem struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
DurationMs int `json:"duration_ms"`
|
||||
Artists []struct {
|
||||
Name string `json:"name"`
|
||||
} `json:"artists"`
|
||||
Album struct {
|
||||
Images []struct {
|
||||
URL string `json:"url"`
|
||||
} `json:"images"`
|
||||
} `json:"album"`
|
||||
}
|
||||
|
||||
// CurrentlyPlaying represents the currently playing object from the Spotify API.
|
||||
// The Item field is a pointer to handle cases where nothing is playing (item is null).
|
||||
type CurrentlyPlaying struct {
|
||||
IsPlaying bool `json:"is_playing"`
|
||||
ProgressMs int `json:"progress_ms"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Item *TrackItem `json:"item"`
|
||||
}
|
||||
Reference in New Issue
Block a user