mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-09 12:16:15 +00:00
129 lines
3.3 KiB
Go
129 lines
3.3 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/urfave/cli/v2"
|
|
)
|
|
|
|
// cloudCommand assembles the `soundtouch-cli cloud …` command group.
|
|
// All subcommands talk to the AfterTouch service (not the speaker directly)
|
|
// and require --service-url.
|
|
func cloudCommand() *cli.Command {
|
|
return &cli.Command{
|
|
Name: "cloud",
|
|
Usage: "Manage AfterTouch service data (sources, accounts, devices)",
|
|
Subcommands: []*cli.Command{
|
|
cloudSourceCmd(),
|
|
},
|
|
}
|
|
}
|
|
|
|
func cloudSourceCmd() *cli.Command {
|
|
return &cli.Command{
|
|
Name: "source",
|
|
Usage: "Manage sources stored in AfterTouch",
|
|
Subcommands: []*cli.Command{
|
|
cloudSourceRemoveCmd(),
|
|
},
|
|
}
|
|
}
|
|
|
|
func cloudSourceRemoveCmd() *cli.Command {
|
|
return &cli.Command{
|
|
Name: "remove",
|
|
Usage: "Remove a source from AfterTouch's datastore for a specific device",
|
|
Flags: append(CloudCommonFlags,
|
|
&cli.StringFlag{
|
|
Name: "account",
|
|
Aliases: []string{"a"},
|
|
Usage: "Account ID",
|
|
Required: true,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "device",
|
|
Aliases: []string{"d"},
|
|
Usage: "Device ID",
|
|
Required: true,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "id",
|
|
Usage: "Source ID to remove (e.g. 10002)",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "type",
|
|
Aliases: []string{"t"},
|
|
Usage: "Source type to remove (e.g. INTERNET_RADIO). Resolved to a canonical ID; fails if multiple sources share the type.",
|
|
},
|
|
),
|
|
Action: cloudSourceRemove,
|
|
}
|
|
}
|
|
|
|
// canonicalSourceID maps well-known SourceKeyType values to their canonical IDs.
|
|
// Used to resolve --type to an ID without requiring a round-trip GET.
|
|
// TODO We need to ensure that ids here are consistent with the ones used in the AfterTouch service.
|
|
var canonicalSourceID = map[string]string{
|
|
"AUX": "10001",
|
|
"INTERNET_RADIO": "10002",
|
|
"LOCAL_INTERNET_RADIO": "10003",
|
|
"TUNEIN": "10004",
|
|
"RADIO_BROWSER": "10005",
|
|
}
|
|
|
|
func cloudSourceRemove(c *cli.Context) error {
|
|
serviceURL := strings.TrimRight(c.String("service-url"), "/")
|
|
account := c.String("account")
|
|
device := c.String("device")
|
|
sourceID := c.String("id")
|
|
sourceType := strings.ToUpper(c.String("type"))
|
|
|
|
if sourceID == "" && sourceType == "" {
|
|
return fmt.Errorf("one of --id or --type is required")
|
|
}
|
|
|
|
if sourceID != "" && sourceType != "" {
|
|
return fmt.Errorf("only one of --id or --type may be given")
|
|
}
|
|
|
|
if sourceType != "" {
|
|
id, ok := canonicalSourceID[sourceType]
|
|
if !ok {
|
|
return fmt.Errorf("unknown source type %q; use --id for non-canonical sources", sourceType)
|
|
}
|
|
|
|
sourceID = id
|
|
}
|
|
|
|
url := fmt.Sprintf("%s/setup/sources/%s/%s/%s", serviceURL, account, device, sourceID)
|
|
|
|
req, err := http.NewRequest(http.MethodDelete, url, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("build request: %w", err)
|
|
}
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("request failed: %w", err)
|
|
}
|
|
|
|
defer func() { _ = resp.Body.Close() }()
|
|
|
|
if resp.StatusCode == http.StatusNoContent {
|
|
PrintSuccess(fmt.Sprintf("Removed source %s from device %s (account %s)", sourceID, device, account))
|
|
|
|
if sourceType != "" {
|
|
fmt.Printf(" Type: %s\n", sourceType)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
body, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<10))
|
|
|
|
return fmt.Errorf("service returned %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
|
}
|