~samwhited/xmpp

xmpp/stanza/message.go -rw-r--r-- 4.9 KiB
70cfc50dSam Whited internal/pubsub: fix publish payload 2 hours ago
                                                                                
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
141
142
143
144
145
146
147
// Copyright 2016 The Mellium Contributors.
// Use of this source code is governed by the BSD 2-clause
// license that can be found in the LICENSE file.

package stanza

import (
	"encoding/xml"

	"mellium.im/xmlstream"
	"mellium.im/xmpp/internal/ns"
	"mellium.im/xmpp/jid"
)

// Message is an XMPP stanza that contains a payload for direct one-to-one
// communication with another network entity. It is often used for sending chat
// messages to an individual or group chat server, or for notifications and
// alerts that don't require a response.
type Message struct {
	XMLName xml.Name    `xml:"message"`
	ID      string      `xml:"id,attr,omitempty"`
	To      jid.JID     `xml:"to,attr,omitempty"`
	From    jid.JID     `xml:"from,attr,omitempty"`
	Lang    string      `xml:"http://www.w3.org/XML/1998/namespace lang,attr,omitempty"`
	Type    MessageType `xml:"type,attr,omitempty"`
}

// NewMessage unmarshals an XML token into a Message.
func NewMessage(start xml.StartElement) (Message, error) {
	v := Message{
		XMLName: start.Name,
	}
	for _, attr := range start.Attr {
		if attr.Name.Local == "lang" && attr.Name.Space == ns.XML {
			v.Lang = attr.Value
			continue
		}
		if attr.Name.Space != "" && attr.Name.Space != start.Name.Space {
			continue
		}

		var err error
		switch attr.Name.Local {
		case "id":
			v.ID = attr.Value
		case "to":
			if attr.Value != "" {
				v.To, err = jid.Parse(attr.Value)
				if err != nil {
					return v, err
				}
			}
		case "from":
			if attr.Value != "" {
				v.From, err = jid.Parse(attr.Value)
				if err != nil {
					return v, err
				}
			}
		case "type":
			v.Type = MessageType(attr.Value)
		}
	}
	return v, nil
}

// StartElement converts the Message into an XML token.
func (msg Message) StartElement() xml.StartElement {
	// Keep whatever namespace we're already using but make sure the localname is
	// "message".
	name := msg.XMLName
	name.Local = "message"

	attr := make([]xml.Attr, 0, 5)
	attr = append(attr, xml.Attr{Name: xml.Name{Local: "type"}, Value: string(msg.Type)})
	if !msg.To.Equal(jid.JID{}) {
		attr = append(attr, xml.Attr{Name: xml.Name{Local: "to"}, Value: msg.To.String()})
	}
	if !msg.From.Equal(jid.JID{}) {
		attr = append(attr, xml.Attr{Name: xml.Name{Local: "from"}, Value: msg.From.String()})
	}
	if msg.ID != "" {
		attr = append(attr, xml.Attr{Name: xml.Name{Local: "id"}, Value: msg.ID})
	}
	if msg.Lang != "" {
		attr = append(attr, xml.Attr{Name: xml.Name{Space: ns.XML, Local: "lang"}, Value: msg.Lang})
	}

	return xml.StartElement{
		Name: name,
		Attr: attr,
	}
}

// Wrap wraps the payload in a stanza.
func (msg Message) Wrap(payload xml.TokenReader) xml.TokenReader {
	return xmlstream.Wrap(payload, msg.StartElement())
}

// Error returns a token reader that wraps the provided Error in a message
// stanza with the to and from attributes switched and the type set to
// ErrorMessage.
func (msg Message) Error(err Error) xml.TokenReader {
	msg.Type = ErrorMessage
	msg.From, msg.To = msg.To, msg.From
	return msg.Wrap(err.TokenReader())
}

// MessageType is the type of a message stanza.
// It should normally be one of the constants defined in this package.
type MessageType string

const (
	// NormalMessage is a standalone message that is sent outside the context of a
	// one-to-one conversation or groupchat, and to which it is expected that the
	// recipient will reply. Typically a receiving client will present a message
	// of type "normal" in an interface that enables the recipient to reply, but
	// without a conversation history.
	NormalMessage MessageType = "normal"

	// ChatMessage represents a message sent in the context of a one-to-one chat
	// session.  Typically an interactive client will present a message of type
	// "chat" in an interface that enables one-to-one chat between the two
	// parties, including an appropriate conversation history.
	ChatMessage MessageType = "chat"

	// ErrorMessage is generated by an entity that experiences an error when
	// processing a message received from another entity.
	ErrorMessage MessageType = "error"

	// GroupChatMessage is sent in the context of a multi-user chat environment.
	// Typically a receiving client will present a message of type "groupchat" in
	// an interface that enables many-to-many chat between the parties, including
	// a roster of parties in the chatroom and an appropriate conversation
	// history.
	GroupChatMessage MessageType = "groupchat"

	// HeadlineMessage provides an alert, a notification, or other transient
	// information to which no reply is expected (e.g., news headlines, sports
	// updates, near-real-time market data, or syndicated content). Because no
	// reply to the message is expected, typically a receiving client will present
	// a message of type "headline" in an interface that appropriately
	// differentiates the message from standalone messages, chat messages, and
	// groupchat messages (e.g., by not providing the recipient with the ability
	// to reply).
	HeadlineMessage MessageType = "headline"
)