Files
go2tv/soapcalls/xmlparsers.go
2024-05-10 01:26:13 +03:00

154 lines
4.3 KiB
Go

package soapcalls
import (
"context"
"encoding/xml"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"github.com/pkg/errors"
)
var (
ErrWrongDMR = errors.New("something broke somewhere - wrong DMR URL?")
)
type rootNode struct {
XMLName xml.Name `xml:"root"`
Device struct {
XMLName xml.Name `xml:"device"`
ServiceList struct {
XMLName xml.Name `xml:"serviceList"`
Services []struct {
XMLName xml.Name `xml:"service"`
Type string `xml:"serviceType"`
ID string `xml:"serviceId"`
ControlURL string `xml:"controlURL"`
EventSubURL string `xml:"eventSubURL"`
} `xml:"service"`
} `xml:"serviceList"`
} `xml:"device"`
}
type eventPropertySet struct {
XMLName xml.Name `xml:"propertyset"`
EventInstance struct {
XMLName xml.Name `xml:"InstanceID"`
Value string `xml:"val,attr"`
EventCurrentTransportActions struct {
Value string `xml:"val,attr"`
} `xml:"CurrentTransportActions"`
EventTransportState struct {
Value string `xml:"val,attr"`
} `xml:"TransportState"`
} `xml:"property>LastChange>Event>InstanceID"`
}
// DMRextracted stored the services urls
type DMRextracted struct {
AvtransportControlURL string
AvtransportEventSubURL string
RenderingControlURL string
ConnectionManagerURL string
}
// DMRextractor extracts the services URLs from the main DMR xml.
func DMRextractor(ctx context.Context, dmrurl string) (*DMRextracted, error) {
var root rootNode
ex := &DMRextracted{}
parsedURL, err := url.Parse(dmrurl)
if err != nil || parsedURL.Scheme == "" || parsedURL.Host == "" {
return nil, fmt.Errorf("DMRextractor parse error: %w", err)
}
client := &http.Client{}
req, err := http.NewRequestWithContext(ctx, "GET", dmrurl, nil)
if err != nil {
return nil, fmt.Errorf("DMRextractor GET error: %w", err)
}
req.Header.Set("Connection", "close")
xmlresp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("DMRextractor Do GET error: %w", err)
}
defer xmlresp.Body.Close()
xmlbody, err := io.ReadAll(xmlresp.Body)
if err != nil {
return nil, fmt.Errorf("DMRextractor read error: %w", err)
}
err = xml.Unmarshal(xmlbody, &root)
if err != nil {
return nil, fmt.Errorf("DMRextractor unmarshal error: %w", err)
}
for i := 0; i < len(root.Device.ServiceList.Services); i++ {
service := root.Device.ServiceList.Services[i]
if !strings.HasPrefix(service.EventSubURL, "/") {
service.EventSubURL = "/" + service.EventSubURL
}
if !strings.HasPrefix(service.ControlURL, "/") {
service.ControlURL = "/" + service.ControlURL
}
if service.ID == "urn:upnp-org:serviceId:AVTransport" {
ex.AvtransportControlURL = parsedURL.Scheme + "://" + parsedURL.Host + service.ControlURL
ex.AvtransportEventSubURL = parsedURL.Scheme + "://" + parsedURL.Host + service.EventSubURL
_, err := url.ParseRequestURI(ex.AvtransportControlURL)
if err != nil {
return nil, fmt.Errorf("DMRextractor invalid AvtransportControlURL: %w", err)
}
_, err = url.ParseRequestURI(ex.AvtransportEventSubURL)
if err != nil {
return nil, fmt.Errorf("DMRextractor invalid AvtransportEventSubURL: %w", err)
}
}
if service.ID == "urn:upnp-org:serviceId:RenderingControl" {
ex.RenderingControlURL = parsedURL.Scheme + "://" + parsedURL.Host + service.ControlURL
_, err = url.ParseRequestURI(ex.RenderingControlURL)
if err != nil {
return nil, fmt.Errorf("DMRextractor invalid RenderingControlURL: %w", err)
}
}
if service.ID == "urn:upnp-org:serviceId:ConnectionManager" {
ex.ConnectionManagerURL = parsedURL.Scheme + "://" + parsedURL.Host + service.ControlURL
_, err = url.ParseRequestURI(ex.ConnectionManagerURL)
if err != nil {
return nil, fmt.Errorf("DMRextractor invalid ConnectionManagerURL: %w", err)
}
}
}
if ex.AvtransportControlURL != "" {
return ex, nil
}
return nil, ErrWrongDMR
}
// EventNotifyParser parses the Notify messages from the DMR device.
func EventNotifyParser(xmlbody string) (string, string, error) {
var root eventPropertySet
err := xml.Unmarshal([]byte(xmlbody), &root)
if err != nil {
return "", "", fmt.Errorf("EventNotifyParser unmarshal error: %w", err)
}
previousstate := root.EventInstance.EventCurrentTransportActions.Value
newstate := root.EventInstance.EventTransportState.Value
return previousstate, newstate, nil
}