diff --git a/message.go b/message.go
index 2426f21..6b57f65 100644
--- a/message.go
+++ b/message.go
@@ -81,7 +81,7 @@ func (msg *Message) XMPPFormat() string {
return string(out)
}
-// UnmarshalXML implements custom parsing for IQs
+// UnmarshalXML implements custom parsing for messages
func (msg *Message) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
msg.XMLName = start.Name
diff --git a/pres_muc.go b/pres_muc.go
new file mode 100644
index 0000000..10d947e
--- /dev/null
+++ b/pres_muc.go
@@ -0,0 +1,29 @@
+package xmpp
+
+import (
+ "encoding/xml"
+ "time"
+)
+
+// ============================================================================
+// MUC Presence extension
+
+// MucPresence implements XEP-0045: Multi-User Chat - 19.1
+type MucPresence struct {
+ PresExtension
+ XMLName xml.Name `xml:"http://jabber.org/protocol/muc x"`
+ Password string `xml:"password,omitempty"`
+ History History `xml:"history,omitempty"`
+}
+
+// History implements XEP-0045: Multi-User Chat - 19.1
+type History struct {
+ MaxChars int `xml:"maxchars,attr,omitempty"`
+ MaxStanzas int `xml:"maxstanzas,attr,omitempty"`
+ Seconds int `xml:"seconds,attr,omitempty"`
+ Since time.Time `xml:"since,attr,omitempty"`
+}
+
+func init() {
+ TypeRegistry.MapExtension(PKTPresence, xml.Name{"http://jabber.org/protocol/muc", "x"}, MucPresence{})
+}
diff --git a/pres_muc_test.go b/pres_muc_test.go
new file mode 100644
index 0000000..b2120e3
--- /dev/null
+++ b/pres_muc_test.go
@@ -0,0 +1,60 @@
+package xmpp_test
+
+import (
+ "encoding/xml"
+ "testing"
+
+ "gosrc.io/xmpp"
+)
+
+// https://xmpp.org/extensions/xep-0045.html#example-27
+func TestMucPassword(t *testing.T) {
+ str := `
+
+ cauldronburn
+
+`
+
+ var parsedPresence xmpp.Presence
+ if err := xml.Unmarshal([]byte(str), &parsedPresence); err != nil {
+ t.Errorf("Unmarshal(%s) returned error", str)
+ }
+
+ var muc xmpp.MucPresence
+ if ok := parsedPresence.Get(&muc); !ok {
+ t.Error("muc presence extension was not found")
+ }
+
+ if muc.Password != "cauldronburn" {
+ t.Errorf("incorrect password: '%s'", muc.Password)
+ }
+}
+
+// https://xmpp.org/extensions/xep-0045.html#example-37
+func TestMucHistory(t *testing.T) {
+ str := `
+
+
+
+`
+
+ var parsedPresence xmpp.Presence
+ if err := xml.Unmarshal([]byte(str), &parsedPresence); err != nil {
+ t.Errorf("Unmarshal(%s) returned error", str)
+ }
+
+ var muc xmpp.MucPresence
+ if ok := parsedPresence.Get(&muc); !ok {
+ t.Error("muc presence extension was not found")
+ }
+
+ if muc.History.MaxStanzas != 20 {
+ t.Errorf("incorrect max stanza: '%d'", muc.History.MaxStanzas)
+ }
+}
diff --git a/presence.go b/presence.go
index be9c6f2..466b9e1 100644
--- a/presence.go
+++ b/presence.go
@@ -1,6 +1,9 @@
package xmpp
-import "encoding/xml"
+import (
+ "encoding/xml"
+ "reflect"
+)
// ============================================================================
// Presence Packet
@@ -9,10 +12,11 @@ import "encoding/xml"
type Presence struct {
XMLName xml.Name `xml:"presence"`
Attrs
- Show PresenceShow `xml:"show,omitempty"`
- Status string `xml:"status,omitempty"`
- Priority int8 `xml:"priority,omitempty"` // default: 0
- Error Err `xml:"error,omitempty"`
+ Show PresenceShow `xml:"show,omitempty"`
+ Status string `xml:"status,omitempty"`
+ Priority int8 `xml:"priority,omitempty"` // default: 0
+ Error Err `xml:"error,omitempty"`
+ Extensions []PresExtension `xml:",omitempty"`
}
func (Presence) Name() string {
@@ -26,6 +30,37 @@ func NewPresence(a Attrs) Presence {
}
}
+// Get search and extracts a specific extension on a presence stanza.
+// It receives a pointer to an PresExtension. It will panic if the caller
+// does not pass a pointer.
+// It will return true if the passed extension is found and set the pointer
+// to the extension passed as parameter to the found extension.
+// It will return false if the extension is not found on the presence.
+//
+// Example usage:
+// var muc xmpp.MucPresence
+// if ok := msg.Get(&muc); ok {
+// // muc presence extension has been found
+// }
+func (pres *Presence) Get(ext PresExtension) bool {
+ target := reflect.ValueOf(ext)
+ if target.Kind() != reflect.Ptr {
+ panic("you must pass a pointer to the message Get method")
+ }
+
+ for _, e := range pres.Extensions {
+ if reflect.TypeOf(e) == target.Type() {
+ source := reflect.ValueOf(e)
+ if source.Kind() != reflect.Ptr {
+ source = source.Elem()
+ }
+ target.Elem().Set(source.Elem())
+ return true
+ }
+ }
+ return false
+}
+
type presenceDecoder struct{}
var presence presenceDecoder
@@ -36,3 +71,69 @@ func (presenceDecoder) decode(p *xml.Decoder, se xml.StartElement) (Presence, er
// TODO Add default presence type (when omitted)
return packet, err
}
+
+// UnmarshalXML implements custom parsing for presence stanza
+func (pres *Presence) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+ pres.XMLName = start.Name
+
+ // Extract packet attributes
+ for _, attr := range start.Attr {
+ if attr.Name.Local == "id" {
+ pres.Id = attr.Value
+ }
+ if attr.Name.Local == "type" {
+ pres.Type = StanzaType(attr.Value)
+ }
+ if attr.Name.Local == "to" {
+ pres.To = attr.Value
+ }
+ if attr.Name.Local == "from" {
+ pres.From = attr.Value
+ }
+ if attr.Name.Local == "lang" {
+ pres.Lang = attr.Value
+ }
+ }
+
+ // decode inner elements
+ for {
+ t, err := d.Token()
+ if err != nil {
+ return err
+ }
+
+ switch tt := t.(type) {
+
+ case xml.StartElement:
+ if presExt := TypeRegistry.GetPresExtension(tt.Name); presExt != nil {
+ // Decode message extension
+ err = d.DecodeElement(presExt, &tt)
+ if err != nil {
+ return err
+ }
+ pres.Extensions = append(pres.Extensions, presExt)
+ } else {
+ // Decode standard message sub-elements
+ var err error
+ switch tt.Name.Local {
+ case "show":
+ err = d.DecodeElement(&pres.Show, &tt)
+ case "status":
+ err = d.DecodeElement(&pres.Status, &tt)
+ case "priority":
+ err = d.DecodeElement(&pres.Priority, &tt)
+ case "error":
+ err = d.DecodeElement(&pres.Error, &tt)
+ }
+ if err != nil {
+ return err
+ }
+ }
+
+ case xml.EndElement:
+ if tt == start.End() {
+ return nil
+ }
+ }
+ }
+}
diff --git a/registry.go b/registry.go
index e5afcf9..8edacb4 100644
--- a/registry.go
+++ b/registry.go
@@ -7,6 +7,7 @@ import (
)
type MsgExtension interface{}
+type PresExtension interface{}
// The Registry for msg and IQ types is a global variable.
// TODO: Move to the client init process to remove the dependency on a global variable.
@@ -78,6 +79,19 @@ func (r *registry) GetExtensionType(pktType PacketType, name xml.Name) reflect.T
return result
}
+// GetPresExtension returns an instance of PresExtension, by matching packet type and XML
+// tag name against the registry.
+func (r *registry) GetPresExtension(name xml.Name) PresExtension {
+ if extensionType := r.GetExtensionType(PKTPresence, name); extensionType != nil {
+ val := reflect.New(extensionType)
+ elt := val.Interface()
+ if presExt, ok := elt.(PresExtension); ok {
+ return presExt
+ }
+ }
+ return nil
+}
+
// GetMsgExtension returns an instance of MsgExtension, by matching packet type and XML
// tag name against the registry.
func (r *registry) GetMsgExtension(name xml.Name) MsgExtension {