Documentation
¶
Overview ¶
Package api provides the Spotify HTTP client, OAuth authentication flow, and token management. It never imports ui/ — data flows via messages and store.
Package api provides the Spotify HTTP client and all typed API response models. This file implements the device listing and playback transfer API calls.
Package api provides the Spotify HTTP client and gateway infrastructure.
token bucket rate limiter for the API gateway — 10 tokens/second, burst 10; background requests drain the bucket; interactive requests bypass it.
in-flight request deduplication — same (Method, Path) key → one HTTP call; all concurrent waiters receive a copy of the response body.
Package api provides the Spotify HTTP client, OAuth authentication flow, and all typed API response models. It never imports ui/ — data flows via messages and store.
NOTE: Core domain types (Track, PlaybackState, Device, etc.) now live in internal/domain/types.go. They are re-exported here as type aliases so that existing code referencing api.Track, api.PlaybackState, etc. continues to work without changes. New code should import internal/domain directly.
Index ¶
- Constants
- Variables
- func BuildAuthURL(clientID, redirectURI, challenge, scopes string) string
- func BuildTokenEndpoint(baseURL string) string
- func ComputeCodeChallenge(verifier string) string
- func GenerateCodeVerifier() (string, error)
- func Refresh(ctx context.Context, httpClient *http.Client, tokenBaseURL string, ...) error
- func StartCallbackServer(port int) (*callbackServer, <-chan CallbackResult, error)
- func WithPriority(ctx context.Context, p Priority) context.Context
- type Album
- type Artist
- type BaseClient
- type CallbackResult
- type Device
- type DevicesAPI
- type DevicesClient
- type ForbiddenError
- type FullAlbum
- type FullArtist
- type Gateway
- func (g *Gateway) CheckAndEmitBackoffExpiry()
- func (g *Gateway) CheckAndEmitRefill()
- func (g *Gateway) Do(ctx context.Context, priority Priority, key RequestKey, ...) (*http.Response, error)
- func (g *Gateway) IsThrottled() bool
- func (g *Gateway) RetryAfterSecs() int
- func (g *Gateway) SetRecorder(r domain.GatewayEventRecorder)
- type LibraryAPI
- type LibraryClient
- func (l *LibraryClient) AlbumTracks(ctx context.Context, albumID string, limit, offset int) ([]Track, bool, error)
- func (l *LibraryClient) GetPlaylist(ctx context.Context, playlistID string) ([]Track, int, bool, error)
- func (l *LibraryClient) LikeTrack(ctx context.Context, trackID string) error
- func (l *LibraryClient) LikedTracks(ctx context.Context, limit, offset int) ([]SavedTrack, error)
- func (l *LibraryClient) PlaylistTracks(ctx context.Context, playlistID string, limit, offset int) ([]Track, int, bool, error)
- func (l *LibraryClient) Playlists(ctx context.Context, limit, offset int) ([]SimplePlaylist, error)
- func (l *LibraryClient) RecentlyPlayed(ctx context.Context, limit int) ([]PlayHistory, error)
- func (l *LibraryClient) SavedAlbums(ctx context.Context, limit, offset int) ([]SavedAlbum, error)
- func (l *LibraryClient) SetHTTPClient(c *http.Client)
- func (l *LibraryClient) UnlikeTrack(ctx context.Context, trackID string) error
- type PlayHistory
- type PlayOptions
- type PlaybackState
- type Player
- func (p *Player) AddToQueue(ctx context.Context, trackURI string) error
- func (p *Player) Next(ctx context.Context) error
- func (p *Player) Pause(ctx context.Context) error
- func (p *Player) Play(ctx context.Context, opts PlayOptions) error
- func (p *Player) PlaybackState(ctx context.Context) (*PlaybackState, error)
- func (p *Player) Previous(ctx context.Context) error
- func (p *Player) Queue(ctx context.Context) (*QueueResponse, error)
- func (p *Player) Seek(ctx context.Context, positionMs int) error
- func (p *Player) SetHTTPClient(c *http.Client)
- func (p *Player) SetRepeat(ctx context.Context, mode string) error
- func (p *Player) SetShuffle(ctx context.Context, state bool) error
- func (p *Player) SetVolume(ctx context.Context, volume int) error
- type PlayerAPI
- type PlaylistsAPI
- type PlaylistsClient
- func (p *PlaylistsClient) AddTracksToPlaylist(ctx context.Context, playlistID string, uris []string) error
- func (p *PlaylistsClient) CreatePlaylist(ctx context.Context, name, description string, public bool) (*SimplePlaylist, error)
- func (p *PlaylistsClient) RemoveTracksFromPlaylist(ctx context.Context, playlistID string, uris []string) error
- func (p *PlaylistsClient) ReorderPlaylistTracks(ctx context.Context, id string, rangeStart, insertBefore, rangeLength int) error
- func (p *PlaylistsClient) SetHTTPClient(c *http.Client)
- func (p *PlaylistsClient) UpdatePlaylist(ctx context.Context, id, name, description string) error
- type Priority
- type QueueResponse
- type RateLimitError
- type RequestKey
- type SavedAlbum
- type SavedTrack
- type SearchAPI
- type SearchAlbum
- type SearchAlbumsResult
- type SearchArtist
- type SearchArtistsResult
- type SearchClient
- type SearchPlaylist
- type SearchPlaylistsResult
- type SearchResult
- type SearchTracksResult
- type SimplePlaylist
- type SimplePlaylistOwner
- type StaticTokenProvider
- type TokenPair
- type TokenProvider
- type Track
- type UnauthorizedError
- type UserAPI
- type UserClient
- func (u *UserClient) Profile(ctx context.Context) (UserProfile, error)
- func (u *UserClient) RecentlyPlayed(ctx context.Context, limit int) ([]PlayHistory, error)
- func (u *UserClient) SetHTTPClient(c *http.Client)
- func (u *UserClient) TopArtists(ctx context.Context, timeRange string, limit int) ([]FullArtist, error)
- func (u *UserClient) TopTracks(ctx context.Context, timeRange string, limit int) ([]Track, error)
- type UserProfile
Constants ¶
const SpotifyScopes = "user-read-playback-state user-modify-playback-state " +
"user-read-currently-playing playlist-read-private playlist-read-collaborative " +
"playlist-modify-public playlist-modify-private user-library-read " +
"user-library-modify user-read-private user-read-email " +
"user-top-read user-follow-read user-read-recently-played"
SpotifyScopes defines the OAuth scopes requested at initial authorization. All scopes are requested at once so users are not prompted again later.
Variables ¶
var ErrInvalidGrant = errors.New("invalid grant: refresh token rejected, re-authentication required")
ErrInvalidGrant is returned by Refresh when Spotify rejects the refresh token (HTTP 400), indicating the user must re-authenticate.
var OpenBrowser = openBrowserPlatform
OpenBrowser opens the given URL in the default browser. It is a variable so tests can replace it with a no-op.
Functions ¶
func BuildAuthURL ¶
BuildAuthURL constructs the Spotify authorization URL with all required PKCE and OAuth parameters.
func BuildTokenEndpoint ¶
BuildTokenEndpoint constructs the token endpoint URL. If baseURL is empty, it returns the production Spotify token URL. Otherwise it appends /api/token to the base URL (for test mocks). Exported for testing.
func ComputeCodeChallenge ¶
ComputeCodeChallenge computes the PKCE code challenge for a given verifier. It returns the base64url-encoded SHA256 hash of the verifier, without padding.
func GenerateCodeVerifier ¶
GenerateCodeVerifier generates a cryptographically random PKCE code verifier. It produces 96 random bytes, base64url-encodes them to exactly 128 characters. 96 bytes × (4/3) = 128 base64 chars exactly. The output contains only [A-Za-z0-9_-].
func Refresh ¶
func Refresh( ctx context.Context, httpClient *http.Client, tokenBaseURL string, refreshToken, clientID string, store keychain.TokenStore, ) error
Refresh exchanges a refresh token for a new access token via the Spotify token endpoint. On success, the new access token is stored in the keychain. On HTTP 400 (invalid grant), it returns ErrInvalidGrant so the caller can trigger re-auth. The tokenBaseURL parameter allows overriding for tests (use "" for production).
func StartCallbackServer ¶
func StartCallbackServer(port int) (*callbackServer, <-chan CallbackResult, error)
StartCallbackServer starts a local HTTP server on the given port to receive the OAuth callback from Spotify. Pass port=0 to let the OS assign a random port (useful in tests). Returns the server, a result channel, and any error.
Types ¶
type BaseClient ¶
type BaseClient struct {
// contains filtered or unexported fields
}
BaseClient provides shared HTTP functionality for all API clients. All six Spotify API clients embed BaseClient to avoid duplicating newRequest/doJSON/doNoContent across each client file.
func NewBaseClient ¶
func NewBaseClient(baseURL, accessToken string) BaseClient
NewBaseClient creates a BaseClient with sensible defaults. The access token string is wrapped in a StaticTokenProvider so all existing client constructors remain unchanged. Pass "" for baseURL to use the production Spotify API.
func NewBaseClientWithProvider ¶
func NewBaseClientWithProvider(baseURL string, tp TokenProvider) BaseClient
NewBaseClientWithProvider creates a BaseClient that calls tp for every request to obtain a fresh access token. Use this when you need per-request token resolution (e.g. a RefreshableTokenProvider).
func (*BaseClient) SetGateway ¶
func (b *BaseClient) SetGateway(gw *Gateway)
SetGateway attaches a Gateway to the BaseClient. When set, all requests are routed through the gateway for rate limiting, concurrency capping, and dedup. The priority is read from the request context via PriorityFromContext. Safe to call concurrently with doJSON/doNoContent.
type CallbackResult ¶
CallbackResult holds the result of a local callback server waiting for the OAuth redirect from Spotify.
type DevicesAPI ¶
type DevicesAPI interface {
Devices(ctx context.Context) ([]Device, error)
TransferPlayback(ctx context.Context, deviceID string, play bool) error
}
DevicesAPI defines all Spotify Connect device operations. Concrete implementation: *DevicesClient.
type DevicesClient ¶
type DevicesClient struct {
BaseClient
}
DevicesClient handles Spotify Connect device listing and playback transfer. It embeds BaseClient for shared HTTP functionality. It never imports ui/ — data flows through messages and the central store.
func NewDevicesClient ¶
func NewDevicesClient(baseURL, token string) *DevicesClient
NewDevicesClient returns a DevicesClient configured with the given base URL and token. In production, baseURL is "https://api.spotify.com"; in tests it is the mock server URL.
func (*DevicesClient) Devices ¶
func (c *DevicesClient) Devices(ctx context.Context) ([]Device, error)
Devices fetches all available Spotify Connect devices for the current user. Returns an empty (non-nil) slice when Spotify reports no devices.
func (*DevicesClient) SetHTTPClient ¶
func (c *DevicesClient) SetHTTPClient(cl *http.Client)
SetHTTPClient overrides the default HTTP client used for API calls.
func (*DevicesClient) TransferPlayback ¶
TransferPlayback transfers Spotify playback to the device identified by deviceID. When play is true, playback starts immediately on the new device.
type ForbiddenError ¶
type ForbiddenError struct {
Message string
}
ForbiddenError is returned when the Spotify API responds with 403.
func (*ForbiddenError) Error ¶
func (e *ForbiddenError) Error() string
type FullArtist ¶
type FullArtist = domain.FullArtist
FullArtist re-exports domain.FullArtist for backward compatibility.
type Gateway ¶
type Gateway struct {
// contains filtered or unexported fields
}
Gateway is the central control point for all outbound Spotify API requests. It enforces:
- Token-bucket rate limiting (10 req/s burst of 10)
- Concurrency cap of 5 simultaneous in-flight requests
- In-flight request deduplication (same key → only one HTTP call)
- 429 backoff: both priorities are rejected immediately; Interactive requests are not queued so stale commands do not pile up (F27-S126)
func NewGateway ¶
func NewGateway() *Gateway
NewGateway creates a Gateway with default limits: 10 requests/second token bucket, burst of 10, max 5 concurrent in-flight.
func (*Gateway) CheckAndEmitBackoffExpiry ¶
func (g *Gateway) CheckAndEmitBackoffExpiry()
CheckAndEmitBackoffExpiry checks if the 429 backoff period has just expired and emits EventBackoffExpired on the active→cleared transition. Called by the app on viz.TickMsg (every 200ms).
func (*Gateway) CheckAndEmitRefill ¶
func (g *Gateway) CheckAndEmitRefill()
CheckAndEmitRefill checks if the token bucket level has changed since the last emission and emits EventTokenRefilled if so. Called by the app on viz.TickMsg (every 200ms). Does NOT mutate bucket.tokens — the lazy refill stays as-is for the hot path.
func (*Gateway) Do ¶
func (g *Gateway) Do(ctx context.Context, priority Priority, key RequestKey, fn func() (*http.Response, error)) (*http.Response, error)
Do executes fn as a controlled HTTP call through the gateway.
For Background requests:
- Go through the token bucket.
- If in 429 backoff, return a RateLimitError immediately.
- GET requests: check the in-flight map; if a matching Background GET is already running, join as a waiter and return the shared result.
For Interactive requests:
- Consume a token from the bucket (same as Background).
- If in 429 backoff, return a RateLimitError immediately — Interactive requests are never queued; they are rejected so the caller can surface a "rate limited" notification rather than pile up sleeping goroutines.
- Skip the in-flight map entirely — always fire a fresh HTTP call.
Both priorities:
- Acquire the concurrency semaphore.
- On 429 response, set backoffUntil and return RateLimitError.
When a GatewayEventRecorder is attached, Do() emits fine-grained lifecycle events at every decision point (entered, token consumed, semaphore acquired/released, request allowed/blocked/waited, dedup joined/resolved, HTTP completed, backoff started).
func (*Gateway) IsThrottled ¶
IsThrottled returns true when the gateway is in a 429 backoff period.
func (*Gateway) RetryAfterSecs ¶
RetryAfterSecs returns the Retry-After duration in seconds from the last 429.
func (*Gateway) SetRecorder ¶
func (g *Gateway) SetRecorder(r domain.GatewayEventRecorder)
SetRecorder sets the gateway event recorder. Pass nil to disable recording. Thread-safe.
type LibraryAPI ¶
type LibraryAPI interface {
Playlists(ctx context.Context, limit, offset int) ([]SimplePlaylist, error)
// GetPlaylist fetches a playlist's metadata and first page of items via GET /playlists/{id}.
// Use this for the initial drill-down (offset=0); use PlaylistTracks for subsequent pages.
GetPlaylist(ctx context.Context, playlistID string) ([]Track, int, bool, error)
// PlaylistTracks fetches a page of playlist items via GET /playlists/{id}/items. Returns tracks,
// total count, and whether a next page exists. Use for pagination (offset > 0).
PlaylistTracks(ctx context.Context, playlistID string, limit, offset int) ([]Track, int, bool, error)
// AlbumTracks fetches a page of tracks for the given album.
// Returns the tracks slice, a hasNext bool (true if more pages exist), and any error.
AlbumTracks(ctx context.Context, albumID string, limit, offset int) ([]Track, bool, error)
SavedAlbums(ctx context.Context, limit, offset int) ([]SavedAlbum, error)
LikedTracks(ctx context.Context, limit, offset int) ([]SavedTrack, error)
RecentlyPlayed(ctx context.Context, limit int) ([]PlayHistory, error)
LikeTrack(ctx context.Context, trackID string) error
UnlikeTrack(ctx context.Context, trackID string) error
}
LibraryAPI defines all Spotify library read and like/unlike operations. Concrete implementation: *LibraryClient.
type LibraryClient ¶
type LibraryClient struct {
BaseClient
}
LibraryClient provides all Spotify library API calls: playlists, saved albums, liked tracks, recently played, and track like/unlike. It embeds BaseClient for shared HTTP functionality.
func NewLibraryClient ¶
func NewLibraryClient(baseURL, accessToken string) *LibraryClient
NewLibraryClient constructs a LibraryClient using the given base URL and access token. Pass "" for baseURL to use the production Spotify API.
func (*LibraryClient) AlbumTracks ¶
func (l *LibraryClient) AlbumTracks(ctx context.Context, albumID string, limit, offset int) ([]Track, bool, error)
AlbumTracks fetches a page of tracks for the given album ID via GET /v1/albums/{id}/tracks. Returns the tracks, a hasNext bool (true when the API's "next" field is non-empty, indicating more pages), and any error. The caller controls pagination via limit and offset.
NOTE: Album tracks are SimplifiedTrackObject — no "album" field in the response. The Album field on each returned domain.Track is intentionally empty; the caller already knows the album from context.
func (*LibraryClient) GetPlaylist ¶
func (l *LibraryClient) GetPlaylist(ctx context.Context, playlistID string) ([]Track, int, bool, error)
GetPlaylist fetches a playlist's first page of items via GET /playlists/{id}. NOTE: Spotify only embeds the items container in this response for playlists owned by the authenticated user. For non-owned (followed) playlists the response contains only metadata and no items. Use PlaylistTracks (GET /playlists/{id}/items) when ownership is unknown or the playlist is not owned by the user. Local files, null track objects, and podcast episodes are skipped. Returns tracks, total track count, whether a next page exists, and any error.
func (*LibraryClient) LikeTrack ¶
func (l *LibraryClient) LikeTrack(ctx context.Context, trackID string) error
LikeTrack adds the given track to the user's liked songs via PUT /me/tracks. Errors are wrapped with context.
func (*LibraryClient) LikedTracks ¶
func (l *LibraryClient) LikedTracks(ctx context.Context, limit, offset int) ([]SavedTrack, error)
LikedTracks fetches the user's liked tracks via GET /me/tracks. Returns a slice of SavedTrack. Errors are wrapped with context.
func (*LibraryClient) PlaylistTracks ¶
func (l *LibraryClient) PlaylistTracks(ctx context.Context, playlistID string, limit, offset int) ([]Track, int, bool, error)
PlaylistTracks fetches one page of playlist items via GET /playlists/{id}/items. Use this for pagination (offset > 0) after the initial GetPlaylist call. Local files (is_local=true), unavailable tracks (null track object), and podcast episodes (type != "track") are skipped. Errors are wrapped with context.
func (*LibraryClient) Playlists ¶
func (l *LibraryClient) Playlists(ctx context.Context, limit, offset int) ([]SimplePlaylist, error)
Playlists fetches the user's saved playlists via GET /me/playlists. Returns a slice of SimplePlaylist. Errors are wrapped with context.
func (*LibraryClient) RecentlyPlayed ¶
func (l *LibraryClient) RecentlyPlayed(ctx context.Context, limit int) ([]PlayHistory, error)
RecentlyPlayed fetches recently played tracks via GET /me/player/recently-played. Returns a slice of PlayHistory items. Errors are wrapped with context.
func (*LibraryClient) SavedAlbums ¶
func (l *LibraryClient) SavedAlbums(ctx context.Context, limit, offset int) ([]SavedAlbum, error)
SavedAlbums fetches the user's saved albums via GET /me/albums. Returns a slice of SavedAlbum. Errors are wrapped with context.
func (*LibraryClient) SetHTTPClient ¶
func (l *LibraryClient) SetHTTPClient(c *http.Client)
SetHTTPClient overrides the default HTTP client used for API calls.
func (*LibraryClient) UnlikeTrack ¶
func (l *LibraryClient) UnlikeTrack(ctx context.Context, trackID string) error
UnlikeTrack removes the given track from the user's liked songs via DELETE /me/tracks. Errors are wrapped with context.
type PlayHistory ¶
type PlayHistory = domain.PlayHistory
PlayHistory re-exports domain.PlayHistory for backward compatibility.
type PlayOptions ¶
type PlayOptions = domain.PlayOptions
PlayOptions re-exports domain.PlayOptions for backward compatibility.
type PlaybackState ¶
type PlaybackState = domain.PlaybackState
PlaybackState re-exports domain.PlaybackState for backward compatibility.
type Player ¶
type Player struct {
BaseClient
}
Player provides all Spotify playback control API calls. It embeds BaseClient for shared HTTP functionality.
func NewPlayer ¶
NewPlayer constructs a Player using the given base URL and access token. Pass "" for baseURL to use the production Spotify API.
func (*Player) AddToQueue ¶
AddToQueue adds a track to the user's playback queue via POST /me/player/queue?uri=<uri>. trackURI must be a Spotify track URI (e.g. "spotify:track:...").
func (*Player) Play ¶
func (p *Player) Play(ctx context.Context, opts PlayOptions) error
Play starts or resumes playback using the given PlayOptions. Sends PUT /me/player/play.
func (*Player) PlaybackState ¶
func (p *Player) PlaybackState(ctx context.Context) (*PlaybackState, error)
PlaybackState fetches the current playback state from GET /me/player. Returns nil, nil when Spotify returns 204 (nothing playing). Returns an error on 429 or other non-2xx status codes. Routes through the gateway when one is attached for rate limiting and dedup.
func (*Player) Queue ¶
func (p *Player) Queue(ctx context.Context) (*QueueResponse, error)
Queue fetches the current play queue from GET /me/player/queue. Returns the currently playing track and the list of upcoming tracks. Returns nil, nil when Spotify returns 204 (nothing playing). Routes through the gateway when one is attached for rate limiting and dedup.
func (*Player) SetHTTPClient ¶
SetHTTPClient overrides the default HTTP client used for API calls.
func (*Player) SetRepeat ¶
SetRepeat sets the repeat mode via PUT /me/player/repeat?state=<mode>. mode must be one of "off", "context", or "track".
func (*Player) SetShuffle ¶
SetShuffle enables or disables shuffle via PUT /me/player/shuffle?state=<bool>.
type PlayerAPI ¶
type PlayerAPI interface {
PlaybackState(ctx context.Context) (*PlaybackState, error)
Play(ctx context.Context, opts PlayOptions) error
Pause(ctx context.Context) error
Next(ctx context.Context) error
Previous(ctx context.Context) error
Seek(ctx context.Context, positionMs int) error
SetVolume(ctx context.Context, volume int) error
SetShuffle(ctx context.Context, state bool) error
SetRepeat(ctx context.Context, mode string) error
AddToQueue(ctx context.Context, trackURI string) error
Queue(ctx context.Context) (*QueueResponse, error)
}
PlayerAPI defines all Spotify playback control operations. Concrete implementation: *Player.
type PlaylistsAPI ¶
type PlaylistsAPI interface {
CreatePlaylist(ctx context.Context, name, description string, public bool) (*SimplePlaylist, error)
UpdatePlaylist(ctx context.Context, id, name, description string) error
AddTracksToPlaylist(ctx context.Context, playlistID string, uris []string) error
RemoveTracksFromPlaylist(ctx context.Context, playlistID string, uris []string) error
ReorderPlaylistTracks(ctx context.Context, id string, rangeStart, insertBefore, rangeLength int) error
}
PlaylistsAPI defines all Spotify playlist mutation operations. Concrete implementation: *PlaylistsClient.
type PlaylistsClient ¶
type PlaylistsClient struct {
BaseClient
}
PlaylistsClient provides Spotify playlist mutation API calls: create, rename, add/remove tracks, and reorder tracks. It embeds BaseClient for shared HTTP functionality.
func NewPlaylistsClient ¶
func NewPlaylistsClient(baseURL, accessToken string) *PlaylistsClient
NewPlaylistsClient constructs a PlaylistsClient using the given base URL and access token. Pass "" for baseURL to use the production Spotify API.
func (*PlaylistsClient) AddTracksToPlaylist ¶
func (p *PlaylistsClient) AddTracksToPlaylist(ctx context.Context, playlistID string, uris []string) error
AddTracksToPlaylist adds one or more tracks to a playlist via POST /playlists/{id}/items. uris should be Spotify track URIs (e.g. "spotify:track:..."). Errors are wrapped with context.
func (*PlaylistsClient) CreatePlaylist ¶
func (p *PlaylistsClient) CreatePlaylist(ctx context.Context, name, description string, public bool) (*SimplePlaylist, error)
CreatePlaylist creates a new playlist for the current user via POST /me/playlists. Returns the created SimplePlaylist on success. Errors are wrapped with context.
func (*PlaylistsClient) RemoveTracksFromPlaylist ¶
func (p *PlaylistsClient) RemoveTracksFromPlaylist(ctx context.Context, playlistID string, uris []string) error
RemoveTracksFromPlaylist removes one or more tracks from a playlist via DELETE /playlists/{id}/items. uris should be Spotify track URIs. Errors are wrapped with context.
func (*PlaylistsClient) ReorderPlaylistTracks ¶
func (p *PlaylistsClient) ReorderPlaylistTracks(ctx context.Context, id string, rangeStart, insertBefore, rangeLength int) error
ReorderPlaylistTracks moves a range of tracks in a playlist via PUT /playlists/{id}/tracks with range_start, insert_before, and range_length. Errors are wrapped with context.
func (*PlaylistsClient) SetHTTPClient ¶
func (p *PlaylistsClient) SetHTTPClient(c *http.Client)
SetHTTPClient overrides the default HTTP client used for API calls.
func (*PlaylistsClient) UpdatePlaylist ¶
func (p *PlaylistsClient) UpdatePlaylist(ctx context.Context, id, name, description string) error
UpdatePlaylist renames a playlist and updates its description via PUT /playlists/{id}. Errors are wrapped with context.
type Priority ¶
type Priority int
Priority classifies a request so the gateway can apply different policies. Interactive requests come from user key presses and should feel instant. Background requests come from polling loops and can be throttled or dropped.
func PriorityFromContext ¶
PriorityFromContext extracts the Priority from ctx, defaulting to Background if none is set.
type QueueResponse ¶
type QueueResponse = domain.QueueResponse
QueueResponse re-exports domain.QueueResponse for backward compatibility.
type RateLimitError ¶
type RateLimitError struct {
RetryAfter int // seconds to wait before retrying
}
RateLimitError is returned when the Spotify API responds with 429.
func (*RateLimitError) Error ¶
func (e *RateLimitError) Error() string
type RequestKey ¶
RequestKey uniquely identifies a request for deduplication purposes. Two requests with the same Method, Path, and Priority are considered identical. Interactive requests skip the inflight map entirely, so only {GET, path, Background} entries ever exist in practice.
type SavedAlbum ¶
type SavedAlbum = domain.SavedAlbum
SavedAlbum re-exports domain.SavedAlbum for backward compatibility.
type SavedTrack ¶
type SavedTrack = domain.SavedTrack
SavedTrack re-exports domain.SavedTrack for backward compatibility.
type SearchAPI ¶
type SearchAPI interface {
Search(ctx context.Context, query string, types []string, limit, offset int) (*SearchResult, error)
}
SearchAPI defines the Spotify search operation. Concrete implementation: *SearchClient.
type SearchAlbum ¶
type SearchAlbum = domain.SearchAlbum
SearchAlbum re-exports domain.SearchAlbum for backward compatibility.
type SearchAlbumsResult ¶
type SearchAlbumsResult = domain.SearchAlbumsResult
SearchAlbumsResult re-exports domain.SearchAlbumsResult for backward compatibility.
type SearchArtist ¶
type SearchArtist = domain.SearchArtist
SearchArtist re-exports domain.SearchArtist for backward compatibility.
type SearchArtistsResult ¶
type SearchArtistsResult = domain.SearchArtistsResult
SearchArtistsResult re-exports domain.SearchArtistsResult for backward compatibility.
type SearchClient ¶
type SearchClient struct {
BaseClient
}
SearchClient provides the Spotify search API call. It embeds BaseClient for shared HTTP functionality.
func NewSearchClient ¶
func NewSearchClient(baseURL, accessToken string) *SearchClient
NewSearchClient constructs a SearchClient using the given base URL and access token. Pass "" for baseURL to use the production Spotify API.
func (*SearchClient) Search ¶
func (s *SearchClient) Search(ctx context.Context, query string, types []string, limit, offset int) (*SearchResult, error)
Search calls GET /v1/search with the given query, types, per-type limit, and page offset. Always includes market=from_token per Spotify API recommendations. types should contain one or more of: "track", "artist", "album", "playlist". offset shifts the result window for pagination (0-based); pass 0 for the first page. Returns a fully populated SearchResult on success.
func (*SearchClient) SetHTTPClient ¶
func (s *SearchClient) SetHTTPClient(c *http.Client)
SetHTTPClient overrides the default HTTP client used for API calls.
type SearchPlaylist ¶
type SearchPlaylist = domain.SearchPlaylist
SearchPlaylist re-exports domain.SearchPlaylist for backward compatibility.
type SearchPlaylistsResult ¶
type SearchPlaylistsResult = domain.SearchPlaylistsResult
SearchPlaylistsResult re-exports domain.SearchPlaylistsResult for backward compatibility.
type SearchResult ¶
type SearchResult = domain.SearchResult
SearchResult re-exports domain.SearchResult for backward compatibility. state/ should import domain.SearchResult directly rather than api.SearchResult.
type SearchTracksResult ¶
type SearchTracksResult = domain.SearchTracksResult
SearchTracksResult re-exports domain.SearchTracksResult for backward compatibility.
type SimplePlaylist ¶
type SimplePlaylist = domain.SimplePlaylist
SimplePlaylist re-exports domain.SimplePlaylist for backward compatibility.
type SimplePlaylistOwner ¶
type SimplePlaylistOwner = domain.SimplePlaylistOwner
SimplePlaylistOwner re-exports domain.SimplePlaylistOwner for backward compatibility.
type StaticTokenProvider ¶
type StaticTokenProvider struct {
// Token is the fixed access token returned on every call.
Token string
}
StaticTokenProvider returns a fixed token. Used in tests and initial construction via NewBaseClient, which wraps the caller-supplied string automatically.
func (*StaticTokenProvider) AccessToken ¶
func (s *StaticTokenProvider) AccessToken(_ context.Context) (string, error)
AccessToken returns the fixed token without any I/O. It never returns an error.
type TokenPair ¶
TokenPair holds an access token and optional refresh token returned from token exchange or refresh operations.
func ExchangeCode ¶
func ExchangeCode( ctx context.Context, httpClient *http.Client, tokenBaseURL string, code, verifier, redirectURI, clientID string, store keychain.TokenStore, ) (TokenPair, error)
ExchangeCode exchanges an authorization code for access and refresh tokens. It POSTs to the Spotify token endpoint and stores the resulting tokens in the store. The tokenBaseURL parameter allows overriding for tests (use "" for production).
type TokenProvider ¶
type TokenProvider interface {
// AccessToken returns a valid access token for use in an Authorization header.
// Implementations may perform I/O (e.g. token refresh) and may return an error.
AccessToken(ctx context.Context) (string, error)
}
TokenProvider resolves an access token for each API request. This allows future implementations (e.g. RefreshableTokenProvider) to silently refresh the token when it expires, without restarting the app.
type UnauthorizedError ¶
type UnauthorizedError struct{}
UnauthorizedError is returned when the Spotify API responds with 401.
func (*UnauthorizedError) Error ¶
func (e *UnauthorizedError) Error() string
type UserAPI ¶
type UserAPI interface {
// Profile fetches the authenticated user's Spotify profile (GET /v1/me).
// Used to determine playlist ownership.
Profile(ctx context.Context) (UserProfile, error)
TopTracks(ctx context.Context, timeRange string, limit int) ([]Track, error)
TopArtists(ctx context.Context, timeRange string, limit int) ([]FullArtist, error)
RecentlyPlayed(ctx context.Context, limit int) ([]PlayHistory, error)
}
UserAPI defines operations on the authenticated Spotify user: identity (Profile) and listening statistics (TopTracks, TopArtists, RecentlyPlayed). Concrete implementation: *UserClient.
type UserClient ¶
type UserClient struct {
BaseClient
}
UserClient provides Spotify API calls for user-specific data: top tracks, top artists, and recently played history. It embeds BaseClient for shared HTTP functionality.
func NewUserClient ¶
func NewUserClient(baseURL, accessToken string) *UserClient
NewUserClient constructs a UserClient using the given base URL and access token. Pass "" for baseURL to use the production Spotify API.
func (*UserClient) Profile ¶
func (u *UserClient) Profile(ctx context.Context) (UserProfile, error)
Profile fetches the authenticated user's Spotify profile via GET /v1/me. Returns a UserProfile with the user's ID and display name. Errors are wrapped with context.
func (*UserClient) RecentlyPlayed ¶
func (u *UserClient) RecentlyPlayed(ctx context.Context, limit int) ([]PlayHistory, error)
RecentlyPlayed fetches the user's recently played tracks via GET /me/player/recently-played. Returns a slice of PlayHistory items. Errors are wrapped with context.
func (*UserClient) SetHTTPClient ¶
func (u *UserClient) SetHTTPClient(c *http.Client)
SetHTTPClient overrides the default HTTP client used for API calls.
func (*UserClient) TopArtists ¶
func (u *UserClient) TopArtists(ctx context.Context, timeRange string, limit int) ([]FullArtist, error)
TopArtists fetches the user's top artists via GET /me/top/artists. timeRange must be "short_term", "medium_term", or "long_term". Returns a slice of FullArtist. Errors are wrapped with context.
type UserProfile ¶
type UserProfile = domain.UserProfile
UserProfile re-exports domain.UserProfile so api/ callers can reference the type without importing domain/ directly.