forked from tulir/whatsmeow
/
node.go
140 lines (126 loc) · 3.72 KB
/
node.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// Copyright (c) 2021 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Package binary implements encoding and decoding documents in WhatsApp's binary XML format.
package binary
import (
"fmt"
"github.com/goccy/go-json"
"github.com/go-whatsapp/whatsmeow/types"
)
// Attrs is a type alias for the attributes of an XML element (Node).
type Attrs = map[string]any
// Node represents an XML element.
type Node struct {
Tag string // The tag of the element.
Attrs Attrs // The attributes of the element.
Content interface{} // The content inside the element. Can be nil, a list of Nodes, or a byte array.
}
type marshalableNode struct {
Tag string
Attrs Attrs
Content json.RawMessage
}
func (n *Node) UnmarshalJSON(data []byte) error {
var mn marshalableNode
err := json.Unmarshal(data, &mn)
if err != nil {
return err
}
for key, val := range mn.Attrs {
switch typedVal := val.(type) {
case string:
parsed, err := types.ParseJID(typedVal)
if err == nil && parsed.Server == types.DefaultUserServer || parsed.Server == types.NewsletterServer || parsed.Server == types.GroupServer || parsed.Server == types.BroadcastServer {
mn.Attrs[key] = parsed
}
case float64:
mn.Attrs[key] = int64(typedVal)
}
}
n.Tag = mn.Tag
n.Attrs = mn.Attrs
if len(mn.Content) > 0 {
if mn.Content[0] == '[' {
var nodes []Node
err = json.Unmarshal(mn.Content, &nodes)
if err != nil {
return err
}
n.Content = nodes
} else if mn.Content[0] == '"' {
var binaryContent []byte
err = json.Unmarshal(mn.Content, &binaryContent)
if err != nil {
return err
}
n.Content = binaryContent
} else {
return fmt.Errorf("node content must be an array of nodes or a base64 string")
}
}
return nil
}
// GetChildren returns the Content of the node as a list of nodes. If the content is not a list of nodes, this returns nil.
func (n *Node) GetChildren() []Node {
if n.Content == nil {
return nil
}
children, ok := n.Content.([]Node)
if !ok {
return nil
}
return children
}
// GetChildrenByTag returns the same list as GetChildren, but filters it by tag first.
func (n *Node) GetChildrenByTag(tag string) (children []Node) {
for _, node := range n.GetChildren() {
if node.Tag == tag {
children = append(children, node)
}
}
return
}
// GetOptionalChildByTag finds the first child with the given tag and returns it.
// Each provided tag will recurse in, so this is useful for getting a specific nested element.
func (n *Node) GetOptionalChildByTag(tags ...string) (val Node, ok bool) {
val = *n
Outer:
for _, tag := range tags {
for _, child := range val.GetChildren() {
if child.Tag == tag {
val = child
continue Outer
}
}
// If no matching children are found, return false
return
}
// All iterations of loop found a matching child, return it
ok = true
return
}
// GetChildByTag does the same thing as GetOptionalChildByTag, but returns the Node directly without the ok boolean.
func (n *Node) GetChildByTag(tags ...string) Node {
node, _ := n.GetOptionalChildByTag(tags...)
return node
}
// Marshal encodes an XML element (Node) into WhatsApp's binary XML representation.
func Marshal(n Node) ([]byte, error) {
w := newEncoder()
w.writeNode(n)
return w.getData(), nil
}
// Unmarshal decodes WhatsApp's binary XML representation into a Node.
func Unmarshal(data []byte) (*Node, error) {
r := newDecoder(data)
n, err := r.readNode()
if err != nil {
return nil, err
} else if r.index != len(r.data) {
return n, fmt.Errorf("%d leftover bytes after decoding", len(r.data)-r.index)
}
return n, nil
}