mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-19 17:16:16 +00:00
260 lines
7.5 KiB
Go
260 lines
7.5 KiB
Go
package proxy
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/gesellix/bose-soundtouch/pkg/service/constants"
|
|
)
|
|
|
|
// SCMUDCRequest represents the structure of SCMUDC telemetry requests
|
|
type SCMUDCRequest struct {
|
|
Envelope struct {
|
|
MonoTime int64 `json:"monoTime"`
|
|
PayloadProtocolVersion string `json:"payloadProtocolVersion"`
|
|
PayloadType string `json:"payloadType"`
|
|
ProtocolVersion string `json:"protocolVersion"`
|
|
Time string `json:"time"`
|
|
UniqueID string `json:"uniqueId"`
|
|
} `json:"envelope"`
|
|
Payload struct {
|
|
DeviceInfo struct {
|
|
BoseID string `json:"boseID"`
|
|
DeviceID string `json:"deviceID"`
|
|
DeviceType string `json:"deviceType"`
|
|
SerialNumber string `json:"serialNumber"`
|
|
SoftwareVersion string `json:"softwareVersion"`
|
|
SystemSerialNumber string `json:"systemSerialNumber"`
|
|
} `json:"deviceInfo"`
|
|
Events []SCMUDCEvent `json:"events"`
|
|
} `json:"payload"`
|
|
}
|
|
|
|
// SCMUDCEvent represents individual events within SCMUDC requests
|
|
type SCMUDCEvent struct {
|
|
Type string `json:"type"`
|
|
Data struct {
|
|
ButtonID string `json:"buttonId,omitempty"`
|
|
Origin string `json:"origin"`
|
|
ContentItem string `json:"contentItem,omitempty"`
|
|
Preset string `json:"preset,omitempty"`
|
|
} `json:"data"`
|
|
}
|
|
|
|
// EnrichedSCMUDCEvent contains human-readable analysis of SCMUDC events
|
|
type EnrichedSCMUDCEvent struct {
|
|
Origin string `json:"origin"`
|
|
Action string `json:"action"`
|
|
Command string `json:"command"`
|
|
Summary string `json:"summary"`
|
|
DecodedData *DecodedContent `json:"decoded_data,omitempty"`
|
|
}
|
|
|
|
// DecodedContent represents decoded ContentItem XML data
|
|
type DecodedContent struct {
|
|
ContentType string `json:"content_type"`
|
|
ItemName string `json:"item_name"`
|
|
SourceAccount string `json:"source_account,omitempty"`
|
|
Location string `json:"location,omitempty"`
|
|
ArtworkURL string `json:"artwork_url,omitempty"`
|
|
IsPresetable bool `json:"is_presetable,omitempty"`
|
|
XMLContent string `json:"xml_content,omitempty"`
|
|
}
|
|
|
|
// ContentItem represents the XML structure within Base64-encoded content
|
|
type ContentItem struct {
|
|
XMLName xml.Name `xml:"ContentItem"`
|
|
Source string `xml:"source,attr"`
|
|
Type string `xml:"type,attr"`
|
|
Location string `xml:"location,attr"`
|
|
SourceAccount string `xml:"sourceAccount,attr"`
|
|
IsPresetable string `xml:"isPresetable,attr"`
|
|
ItemName string `xml:"itemName"`
|
|
ContainerArt string `xml:"containerArt"`
|
|
}
|
|
|
|
// enrichSCMUDCRequest analyzes and enriches SCMUDC request data
|
|
func enrichSCMUDCRequest(body []byte) *EnrichedSCMUDCEvent {
|
|
var scmudcReq SCMUDCRequest
|
|
if err := json.Unmarshal(body, &scmudcReq); err != nil {
|
|
return nil
|
|
}
|
|
|
|
// Process the first event (most requests contain single events)
|
|
if len(scmudcReq.Payload.Events) == 0 {
|
|
return nil
|
|
}
|
|
|
|
event := scmudcReq.Payload.Events[0]
|
|
enriched := &EnrichedSCMUDCEvent{
|
|
Origin: event.Data.Origin,
|
|
Action: event.Type,
|
|
}
|
|
|
|
switch event.Data.Origin {
|
|
case "gabbo":
|
|
enriched.Command = event.Data.ButtonID
|
|
enriched.Summary = fmt.Sprintf("App: %s", formatButton(event.Data.ButtonID))
|
|
|
|
case "console":
|
|
enriched.Command = event.Data.ButtonID
|
|
enriched.Summary = fmt.Sprintf("Device: %s", formatButton(event.Data.ButtonID))
|
|
|
|
case "device":
|
|
if event.Data.ContentItem != "" {
|
|
if decoded := decodeContentItem(event.Data.ContentItem); decoded != nil {
|
|
enriched.Command = decoded.ItemName
|
|
enriched.Summary = fmt.Sprintf("Device: %s", summarizeContent(decoded))
|
|
enriched.DecodedData = decoded
|
|
} else {
|
|
enriched.Command = "Content Item"
|
|
enriched.Summary = "Device: Content Action"
|
|
}
|
|
} else {
|
|
enriched.Command = "System Action"
|
|
enriched.Summary = "Device: Internal Action"
|
|
}
|
|
}
|
|
|
|
return enriched
|
|
}
|
|
|
|
// decodeContentItem decodes Base64-encoded XML content
|
|
func decodeContentItem(base64Content string) *DecodedContent {
|
|
data, err := base64.StdEncoding.DecodeString(base64Content)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
var contentItem ContentItem
|
|
if err := xml.Unmarshal(data, &contentItem); err != nil {
|
|
return nil
|
|
}
|
|
|
|
decoded := &DecodedContent{
|
|
ContentType: contentItem.Source,
|
|
ItemName: contentItem.ItemName,
|
|
SourceAccount: contentItem.SourceAccount,
|
|
Location: contentItem.Location,
|
|
ArtworkURL: contentItem.ContainerArt,
|
|
XMLContent: string(data),
|
|
}
|
|
|
|
if contentItem.IsPresetable == "true" {
|
|
decoded.IsPresetable = true
|
|
}
|
|
|
|
return decoded
|
|
}
|
|
|
|
// formatButton converts button IDs to human-readable names
|
|
func formatButton(buttonID string) string {
|
|
switch buttonID {
|
|
case "POWER":
|
|
return "Power Button"
|
|
case "PLAY":
|
|
return "Play Button"
|
|
case "PAUSE":
|
|
return "Pause Button"
|
|
case "STOP":
|
|
return "Stop Button"
|
|
case "NEXT_TRACK":
|
|
return "Next Track"
|
|
case "PREV_TRACK":
|
|
return "Previous Track"
|
|
case "PRESET_1", "PRESET_2", "PRESET_3", "PRESET_4", "PRESET_5", "PRESET_6":
|
|
return fmt.Sprintf("Preset %s", strings.TrimPrefix(buttonID, "PRESET_"))
|
|
default:
|
|
return buttonID
|
|
}
|
|
}
|
|
|
|
// summarizeContent creates a brief summary of content items
|
|
func summarizeContent(decoded *DecodedContent) string {
|
|
switch decoded.ContentType {
|
|
case constants.ProviderSpotify:
|
|
return fmt.Sprintf("Spotify: %s", decoded.ItemName)
|
|
case constants.ProviderPandora:
|
|
return fmt.Sprintf("Pandora: %s", decoded.ItemName)
|
|
case constants.ProviderInternetRadio:
|
|
return fmt.Sprintf("Radio: %s", decoded.ItemName)
|
|
case constants.ProviderStoredMusic:
|
|
return fmt.Sprintf("Library: %s", decoded.ItemName)
|
|
default:
|
|
if decoded.ItemName != "" {
|
|
return fmt.Sprintf("%s: %s", decoded.ContentType, decoded.ItemName)
|
|
}
|
|
|
|
return fmt.Sprintf("%s Content", decoded.ContentType)
|
|
}
|
|
}
|
|
|
|
// getOriginDescription returns human-readable origin descriptions
|
|
func getOriginDescription(origin string) string {
|
|
switch origin {
|
|
case "gabbo":
|
|
return "SoundTouch App"
|
|
case "console":
|
|
return "Device Hardware"
|
|
case "device":
|
|
return "Internal System"
|
|
default:
|
|
return origin
|
|
}
|
|
}
|
|
|
|
// generateSCMUDCComments creates enriched comments for .http files
|
|
func generateSCMUDCComments(enriched *EnrichedSCMUDCEvent) []string {
|
|
if enriched == nil {
|
|
return nil
|
|
}
|
|
|
|
comments := []string{
|
|
fmt.Sprintf("// Origin: %s (%s)", getOriginDescription(enriched.Origin), enriched.Origin),
|
|
fmt.Sprintf("// Action: %s", enriched.Action),
|
|
fmt.Sprintf("// Command: %s", enriched.Command),
|
|
fmt.Sprintf("// Summary: %s", enriched.Summary),
|
|
}
|
|
|
|
if enriched.DecodedData != nil {
|
|
comments = append(comments,
|
|
"//",
|
|
"// Decoded Content:",
|
|
fmt.Sprintf("// - Source: %s", enriched.DecodedData.ContentType),
|
|
fmt.Sprintf("// - Item: %s", enriched.DecodedData.ItemName),
|
|
)
|
|
|
|
if enriched.DecodedData.SourceAccount != "" {
|
|
comments = append(comments, fmt.Sprintf("// - Account: %s", enriched.DecodedData.SourceAccount))
|
|
}
|
|
|
|
if enriched.DecodedData.ArtworkURL != "" {
|
|
comments = append(comments, fmt.Sprintf("// - Artwork: %s", enriched.DecodedData.ArtworkURL))
|
|
}
|
|
|
|
if enriched.DecodedData.IsPresetable {
|
|
comments = append(comments, "// - Presetable: Yes")
|
|
}
|
|
|
|
if enriched.DecodedData.XMLContent != "" {
|
|
comments = append(comments,
|
|
"//",
|
|
"// Full XML Content:",
|
|
)
|
|
|
|
// Add XML content as comments, line by line
|
|
lines := strings.Split(enriched.DecodedData.XMLContent, "\n")
|
|
for _, line := range lines {
|
|
if strings.TrimSpace(line) != "" {
|
|
comments = append(comments, fmt.Sprintf("// %s", strings.TrimSpace(line)))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return comments
|
|
}
|