Files
web_server/backend/services/spotify.go
Adam French 932e257152
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 4m19s
Add HTTPS support in dev mode and fix mobile layout issues
Generate self-signed certs for local HTTPS, add port 443 and full SSL
server block to dev nginx config, add Spotify redirect URI env var,
improve Spotify token error handling, and fix Chat/Steam mobile sizing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 11:18:32 +00:00

122 lines
2.9 KiB
Go

package services
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"
"github.com/zmb3/spotify/v2"
spotifyauth "github.com/zmb3/spotify/v2/auth"
"golang.org/x/oauth2"
)
type SpotifyConfig struct {
AuthState string
RedirectURL string
ClientID string
ClientSecret string
}
const SPOTIFY_TOKEN_JSON_PATH = "/backend/token/spotify_token.json"
func SaveSpotifyToken(path string, tok *oauth2.Token) error {
data := struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
TokenType string `json:"token_type"`
Expiry time.Time `json:"expiry"`
}{
AccessToken: tok.AccessToken,
RefreshToken: tok.RefreshToken,
TokenType: tok.TokenType,
Expiry: tok.Expiry,
}
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
return fmt.Errorf("creating token directory: %w", err)
}
jsonBytes, err := json.MarshalIndent(data, "", " ")
if err != nil {
return err
}
// 0600 ensures only your user can read/write the file
return os.WriteFile(path, jsonBytes, 0600)
}
func LoadSpotifyToken(path string) (*oauth2.Token, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var saved struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
TokenType string `json:"token_type"`
Expiry time.Time `json:"expiry"`
}
if err := json.Unmarshal(data, &saved); err != nil {
return nil, err
}
tok := &oauth2.Token{
AccessToken: saved.AccessToken,
RefreshToken: saved.RefreshToken,
TokenType: saved.TokenType,
Expiry: saved.Expiry,
}
return tok, nil
}
func InitSpotifyAuth(config *SpotifyConfig) (*spotifyauth.Authenticator, *spotify.Client) {
auth := spotifyauth.New(
spotifyauth.WithRedirectURL(config.RedirectURL),
spotifyauth.WithClientID(config.ClientID),
spotifyauth.WithClientSecret(config.ClientSecret),
spotifyauth.WithScopes(
spotifyauth.ScopeUserReadPlaybackState,
spotifyauth.ScopeUserReadCurrentlyPlaying,
spotifyauth.ScopeUserReadRecentlyPlayed,
),
)
// check if token exists locally
token, err := LoadSpotifyToken(SPOTIFY_TOKEN_JSON_PATH)
if err != nil || token == nil {
fmt.Println("No token saved. Authenticate Spotify with:")
fmt.Println(auth.AuthURL(config.AuthState))
return auth, nil
}
// refresh token and client
client, err := RefreshClient(auth, token)
if err != nil {
fmt.Println("Failed to refresh token. Authenticate Spotify with:")
fmt.Println(auth.AuthURL(config.AuthState))
return auth, nil
}
return auth, client
}
func RefreshClient(auth *spotifyauth.Authenticator, token *oauth2.Token) (*spotify.Client, error) {
ctx := context.Background()
token, err := auth.RefreshToken(ctx, token)
if err != nil {
return nil, err
}
SaveSpotifyToken(SPOTIFY_TOKEN_JSON_PATH, token)
client := spotify.New(auth.Client(ctx, token))
return client, nil
}