api

package
v0.1.0-rc3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 8, 2026 License: MIT Imports: 21 Imported by: 0

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

View Source
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

View Source
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.

View Source
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

func BuildAuthURL(clientID, redirectURI, challenge, scopes string) string

BuildAuthURL constructs the Spotify authorization URL with all required PKCE and OAuth parameters.

func BuildTokenEndpoint

func BuildTokenEndpoint(baseURL string) string

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

func ComputeCodeChallenge(verifier string) string

ComputeCodeChallenge computes the PKCE code challenge for a given verifier. It returns the base64url-encoded SHA256 hash of the verifier, without padding.

func GenerateCodeVerifier

func GenerateCodeVerifier() (string, error)

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.

func WithPriority

func WithPriority(ctx context.Context, p Priority) context.Context

WithPriority returns a new context that carries the given Priority. BaseClient reads this via PriorityFromContext when routing through the Gateway.

Types

type Album

type Album = domain.Album

Album re-exports domain.Album for backward compatibility.

type Artist

type Artist = domain.Artist

Artist re-exports domain.Artist for backward compatibility.

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

type CallbackResult struct {
	Code string
	Err  error
}

CallbackResult holds the result of a local callback server waiting for the OAuth redirect from Spotify.

type Device

type Device = domain.Device

Device re-exports domain.Device for backward compatibility.

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

func (c *DevicesClient) TransferPlayback(ctx context.Context, deviceID string, play bool) error

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 FullAlbum

type FullAlbum = domain.FullAlbum

FullAlbum re-exports domain.FullAlbum for backward compatibility.

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

func (g *Gateway) IsThrottled() bool

IsThrottled returns true when the gateway is in a 429 backoff period.

func (*Gateway) RetryAfterSecs

func (g *Gateway) RetryAfterSecs() int

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

func NewPlayer(baseURL, accessToken string) *Player

NewPlayer constructs a Player using the given base URL and access token. Pass "" for baseURL to use the production Spotify API.

func (*Player) AddToQueue

func (p *Player) AddToQueue(ctx context.Context, trackURI string) error

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) Next

func (p *Player) Next(ctx context.Context) error

Next skips to the next track via POST /me/player/next.

func (*Player) Pause

func (p *Player) Pause(ctx context.Context) error

Pause pauses playback via PUT /me/player/pause.

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) Previous

func (p *Player) Previous(ctx context.Context) error

Previous skips to the previous track via POST /me/player/previous.

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) Seek

func (p *Player) Seek(ctx context.Context, positionMs int) error

Seek moves playback to positionMs via PUT /me/player/seek?position_ms=<ms>.

func (*Player) SetHTTPClient

func (p *Player) SetHTTPClient(c *http.Client)

SetHTTPClient overrides the default HTTP client used for API calls.

func (*Player) SetRepeat

func (p *Player) SetRepeat(ctx context.Context, mode string) error

SetRepeat sets the repeat mode via PUT /me/player/repeat?state=<mode>. mode must be one of "off", "context", or "track".

func (*Player) SetShuffle

func (p *Player) SetShuffle(ctx context.Context, state bool) error

SetShuffle enables or disables shuffle via PUT /me/player/shuffle?state=<bool>.

func (*Player) SetVolume

func (p *Player) SetVolume(ctx context.Context, volume int) error

SetVolume sets the volume via PUT /me/player/volume?volume_percent=<vol>. Volume is clamped to [0, 100] before sending.

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.

const (
	// Background is for polling, pre-fetch, and any non-user-initiated request.
	Background Priority = iota
	// Interactive is for requests triggered by user key presses.
	Interactive
)

func PriorityFromContext

func PriorityFromContext(ctx context.Context) Priority

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

type RequestKey struct {
	Method   string
	Path     string
	Priority Priority
}

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

type TokenPair struct {
	AccessToken  string
	RefreshToken string
	ExpiresIn    int
}

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 Track

type Track = domain.Track

Track re-exports domain.Track for backward compatibility.

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.

func (*UserClient) TopTracks

func (u *UserClient) TopTracks(ctx context.Context, timeRange string, limit int) ([]Track, error)

TopTracks fetches the user's top tracks via GET /me/top/tracks. timeRange must be "short_term", "medium_term", or "long_term". Returns a slice of Track. 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.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL