package main import ( "encoding/json" "errors" "fmt" "io" "net/http" "os" "os/exec" "time" ) 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) } type DevicesResponse struct { Devices []struct { ID string `json:"id"` IsActive bool `json:"is_active"` IsPrivateSession bool `json:"is_private_session"` IsRestricted bool `json:"is_restricted"` Name string `json:"name"` SupportsVolume bool `json:"supports_volume"` Type string `json:"type"` VolumePercent int `json:"volume_percent"` } `json:"devices"` } var API_URL string var API_KEY string var DEVICE_NAME string var MAX_FAILED = 5 var CHECK_INTERVAL = 5 * time.Second func main() { API_URL = os.Getenv("API_URL") API_KEY = os.Getenv("API_KEY") DEVICE_NAME = os.Getenv("DEVICE_NAME") if API_URL == "" { fmt.Println("API_URL not set") os.Exit(1) } if API_KEY == "" { fmt.Println("API_KEY not set") os.Exit(1) } if DEVICE_NAME == "" { fmt.Println("DEVICE_NAME not set") fmt.Println("Using default: Spotifyd") DEVICE_NAME = "Spotifyd" } client := &http.Client{ Transport: &headerTransport{ baseTransport: http.DefaultTransport, headers: map[string]string{ "X-API-KEY": API_KEY, }, }, } runLoop(client) } func runLoop(client *http.Client) { proc := runSpotifyd() go func() { defer func() { fmt.Println("Killing spotifyd") proc.Process.Kill() }() failedCheck := 0 for { time.Sleep(CHECK_INTERVAL) devices, err := getDevices(client) if err != nil { fmt.Println("Error getting devices: " + err.Error()) continue } if containsDevice(devices, DEVICE_NAME) { failedCheck = 0 continue } failedCheck++ fmt.Printf("Can't find device %s. Failed check %d of %d \n", DEVICE_NAME, failedCheck, MAX_FAILED) if failedCheck >= MAX_FAILED { return // kill handled by defer } } }() proc.Run() } func containsDevice(response *DevicesResponse, deviceName string) bool { for _, device := range response.Devices { if device.Name == DEVICE_NAME { return true } } return false } func getDevices(client *http.Client) (*DevicesResponse, error) { // https://developer.spotify.com/documentation/web-api/reference/get-a-users-available-devices resp, err := client.Get(API_URL + "/me/player/devices") if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { return nil, errors.New("Not OK. Got " + resp.Status + " instead") } data, err := io.ReadAll(resp.Body) if err != nil { return nil, err } var devicesResponse DevicesResponse err = json.Unmarshal(data, &devicesResponse) if err != nil { return nil, err } return &devicesResponse, nil } func runSpotifyd() *exec.Cmd { proc := exec.Command("/app/start.sh") proc.Stdout = os.Stdout proc.Stderr = os.Stderr return proc }