156 lines
3.0 KiB
Go
156 lines
3.0 KiB
Go
|
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
|
||
|
}
|