package morningalarm import ( "context" "encoding/json" "fmt" "math/rand" "net/http" "os" "github.com/gin-gonic/gin" "github.com/zmb3/spotify/v2" spotifyauth "github.com/zmb3/spotify/v2/auth" "golang.org/x/oauth2" ) const redirectURI = "/api/spotifycb" type headerTransport struct { baseTransport http.RoundTripper headers map[string]string } func (t *headerTransport) RoundTrip(req *http.Request) (*http.Response, error) { for key, value := range t.headers { req.Header.Set(key, value) } return t.baseTransport.RoundTrip(req) } func (ma *MorningAlarm) setupSpotifyWithProxy() { client := &http.Client{ Transport: &headerTransport{ baseTransport: http.DefaultTransport, headers: map[string]string{ "X-API-KEY": ma.config.OAuthProxyAPIKey, }, }, } ma.sp = spotify.New(client, spotify.WithBaseURL(ma.config.OAuthProxyURL)) } func (ma *MorningAlarm) setupSpotify() error { auth := spotifyauth.New( spotifyauth.WithRedirectURL(ma.config.RedirectURL+redirectURI), spotifyauth.WithClientID(ma.config.SpotifyClientID), spotifyauth.WithClientSecret(ma.config.SpotifyClientSecret), spotifyauth.WithScopes( spotifyauth.ScopeUserReadPrivate, spotifyauth.ScopeUserModifyPlaybackState, spotifyauth.ScopeStreaming, spotifyauth.ScopeUserReadPlaybackState, ), ) loadedTokenJSON, err := os.ReadFile("spotifyToken.json") if err == nil { fmt.Println("Found access token in file") var token oauth2.Token err = json.Unmarshal(loadedTokenJSON, &token) if err != nil { return err } ma.sp = spotify.New(auth.Client(context.Background(), &token), spotify.WithRetry(true)) newToken, err := ma.sp.Token() if err != nil { return err } tokenJSON, err := json.Marshal(newToken) if err != nil { return err } os.WriteFile("spotifyToken.json", tokenJSON, os.ModePerm) return nil } state := "abc123" ma.ro.GET(redirectURI, func(c *gin.Context) { token, err := auth.Token(c, state, c.Request) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } tokenJSON, err := json.Marshal(token) os.WriteFile("spotifyToken.json", tokenJSON, os.ModePerm) ma.sp = spotify.New(auth.Client(c, token)) }) url := auth.AuthURL(state) fmt.Println("Please log in to Spotify by visiting the following page in your browser:", url) return nil } // playWakeUpMusic plays the playlist specified in the config at the specified device. // Returns true if the playback was started successfully. // Returns error if any error occurred. // Can still return an error even if the playback was started successfully. func (ma *MorningAlarm) playWakeUpMusic() (bool, error) { playContextURI := spotify.URI("spotify:playlist:" + ma.config.PlaylistID) devID, err := ma.getDeviceIDFromName(ma.config.DeviceName) if err != nil { return false, err } deviceID := spotify.ID(devID) playlist, err := ma.sp.GetPlaylistItems(context.Background(), spotify.ID(ma.config.PlaylistID)) if err != nil { return true, err } randomTrackIndex := rand.Intn(playlist.Total) err = ma.sp.PlayOpt(context.Background(), &spotify.PlayOptions{ PlaybackContext: &playContextURI, DeviceID: &deviceID, PlaybackOffset: &spotify.PlaybackOffset{Position: randomTrackIndex}, }) if err != nil { return true, err } err = ma.sp.ShuffleOpt(context.Background(), true, &spotify.PlayOptions{ DeviceID: &deviceID, }) return true, err } func (ma *MorningAlarm) getAvailableDevices() ([]Device, error) { devices, err := ma.sp.PlayerDevices(context.Background()) if err != nil { return nil, err } var availableDevices []Device for _, device := range devices { if !device.Restricted { availableDevices = append(availableDevices, Device{ ID: string(device.ID), Name: device.Name, }) } } return availableDevices, nil } func (ma *MorningAlarm) getDeviceIDFromName(name string) (string, error) { dev, err := ma.getAvailableDevices() if err != nil { return "", err } for _, device := range dev { if device.Name == name { return device.ID, nil } } return "", fmt.Errorf("device with name %s not found", name) }