diff --git a/xmpp.go b/xmpp.go
index 2b180e2..30a7ff7 100644
--- a/xmpp.go
+++ b/xmpp.go
@@ -335,7 +335,6 @@ func cnonce() string {
}
func (c *Client) init(o *Options) error {
-
var domain string
var user string
a := strings.SplitN(o.User, "@", 2)
@@ -501,7 +500,7 @@ func (c *Client) init(o *Options) error {
c.domain = domain
if o.Session {
- //if server support session, open it
+ // if server support session, open it
fmt.Fprintf(c.conn, "", xmlEscape(domain), cookie, nsSession)
}
@@ -537,7 +536,7 @@ func (c *Client) startTLSIfRequired(f *streamFeatures, o *Options, domain string
tc := o.TLSConfig
if tc == nil {
tc = DefaultConfig.Clone()
- //TODO(scott): we should consider using the server's address or reverse lookup
+ // TODO(scott): we should consider using the server's address or reverse lookup
tc.ServerName = domain
}
t := tls.Client(c.conn, tc)
@@ -608,6 +607,8 @@ type Chat struct {
Thread string
Ooburl string
Oobdesc string
+ ID string
+ ReplaceID string
Roster Roster
Other []string
OtherElem []XMLElement
@@ -681,6 +682,8 @@ func (c *Client) Recv() (stanza interface{}, err error) {
Text: v.Body,
Subject: v.Subject,
Thread: v.Thread,
+ ID: v.ID,
+ ReplaceID: v.ReplaceID.ID,
Other: v.OtherStrings(),
OtherElem: v.Other,
Stamp: stamp,
@@ -722,93 +725,127 @@ func (c *Client) Recv() (stanza interface{}, err error) {
Errors: errsStr,
}, nil
}
- case v.Type == "result" && v.ID == "unsub1":
- // Unsubscribing MAY contain a pubsub element. But it does
- // not have to
- return PubsubUnsubscription{
- SubID: "",
- JID: v.From,
- Node: "",
- Errors: nil,
- }, nil
- case v.Query.XMLName.Local == "pubsub":
+ case v.Type == "result":
switch v.ID {
case "sub1":
- // Subscription or unsubscription was successful
- var sub clientPubsubSubscription
- err := xml.Unmarshal([]byte(v.Query.InnerXML), &sub)
- if err != nil {
- return PubsubSubscription{}, err
- }
+ if v.Query.XMLName.Local == "pubsub" {
+ // Subscription or unsubscription was successful
+ var sub clientPubsubSubscription
+ err := xml.Unmarshal([]byte(v.Query.InnerXML), &sub)
+ if err != nil {
+ return PubsubSubscription{}, err
+ }
- return PubsubSubscription{
- SubID: sub.SubID,
- JID: sub.JID,
- Node: sub.Node,
- Errors: nil,
- }, nil
- case "unsub1":
- var sub clientPubsubSubscription
- err := xml.Unmarshal([]byte(v.Query.InnerXML), &sub)
- if err != nil {
- return PubsubUnsubscription{}, err
+ return PubsubSubscription{
+ SubID: sub.SubID,
+ JID: sub.JID,
+ Node: sub.Node,
+ Errors: nil,
+ }, nil
}
+ case "unsub1":
+ if v.Query.XMLName.Local == "pubsub" {
+ var sub clientPubsubSubscription
+ err := xml.Unmarshal([]byte(v.Query.InnerXML), &sub)
+ if err != nil {
+ return PubsubUnsubscription{}, err
+ }
- return PubsubUnsubscription{
- SubID: sub.SubID,
- JID: v.From,
- Node: sub.Node,
- Errors: nil,
- }, nil
- case "items1", "items3":
- var p clientPubsubItems
- err := xml.Unmarshal([]byte(v.Query.InnerXML), &p)
- if err != nil {
- return PubsubItems{}, err
+ return PubsubUnsubscription{
+ SubID: sub.SubID,
+ JID: v.From,
+ Node: sub.Node,
+ Errors: nil,
+ }, nil
+ } else {
+ // Unsubscribing MAY contain a pubsub element. But it does
+ // not have to
+ return PubsubUnsubscription{
+ SubID: "",
+ JID: v.From,
+ Node: "",
+ Errors: nil,
+ }, nil
}
-
- switch p.Node {
- case XMPPNS_AVATAR_PEP_DATA:
- if len(p.Items) == 0 {
- return AvatarData{}, errors.New("No avatar data items available")
+ case "info1":
+ if v.Query.XMLName.Space == XMPPNS_DISCO_ITEMS {
+ var itemsQuery clientDiscoItemsQuery
+ err := xml.Unmarshal(v.InnerXML, &itemsQuery)
+ if err != nil {
+ return []DiscoItem{}, err
}
- return handleAvatarData(p.Items[0].Body,
- v.From,
- p.Items[0].ID)
- case XMPPNS_AVATAR_PEP_METADATA:
- if len(p.Items) == 0 {
- return AvatarMetadata{}, errors.New("No avatar metadata items available")
+ return DiscoItems{
+ Jid: v.From,
+ Items: clientDiscoItemsToReturn(itemsQuery.Items),
+ }, nil
+ }
+ case "info3":
+ if v.Query.XMLName.Space == XMPPNS_DISCO_INFO {
+ var disco clientDiscoQuery
+ err := xml.Unmarshal(v.InnerXML, &disco)
+ if err != nil {
+ return DiscoResult{}, err
}
- return handleAvatarMetadata(p.Items[0].Body,
- v.From)
- default:
- return PubsubItems{
- p.Node,
- pubsubItemsToReturn(p.Items),
+ return DiscoResult{
+ Features: clientFeaturesToReturn(disco.Features),
+ Identities: clientIdentitiesToReturn(disco.Identities),
}, nil
}
+ case "items1", "items3":
+ if v.Query.XMLName.Local == "pubsub" {
+ var p clientPubsubItems
+ err := xml.Unmarshal([]byte(v.Query.InnerXML), &p)
+ if err != nil {
+ return PubsubItems{}, err
+ }
+
+ switch p.Node {
+ case XMPPNS_AVATAR_PEP_DATA:
+ if len(p.Items) == 0 {
+ return AvatarData{}, errors.New("No avatar data items available")
+ }
+
+ return handleAvatarData(p.Items[0].Body,
+ v.From,
+ p.Items[0].ID)
+ case XMPPNS_AVATAR_PEP_METADATA:
+ if len(p.Items) == 0 {
+ return AvatarMetadata{}, errors.New("No avatar metadata items available")
+ }
+
+ return handleAvatarMetadata(p.Items[0].Body,
+ v.From)
+ default:
+ return PubsubItems{
+ p.Node,
+ pubsubItemsToReturn(p.Items),
+ }, nil
+ }
+ }
// Note: XEP-0084 states that metadata and data
// should be fetched with an id of retrieve1.
// Since we already have PubSub implemented, we
// can just use items1 and items3 to do the same
// as an Avatar node is just a PEP (PubSub) node.
/*case "retrieve1":
- var p clientPubsubItems
- err := xml.Unmarshal([]byte(v.Query.InnerXML), &p)
- if err != nil {
- return PubsubItems{}, err
- }
+ if v.Query.XMLName.Local == "pubsub" {
+ var p clientPubsubItems
+ err := xml.Unmarshal([]byte(v.Query.InnerXML), &p)
+ if err != nil {
+ return PubsubItems{}, err
+ }
- switch p.Node {
- case XMPPNS_AVATAR_PEP_DATA:
- return handleAvatarData(p.Items[0].Body,
- v.From,
- p.Items[0].ID)
- case XMPPNS_AVATAR_PEP_METADATA:
- return handleAvatarMetadata(p.Items[0].Body,
- v
+ switch p.Node {
+ case XMPPNS_AVATAR_PEP_DATA:
+ return handleAvatarData(p.Items[0].Body,
+ v.From,
+ p.Items[0].ID)
+ case XMPPNS_AVATAR_PEP_METADATA:
+ return handleAvatarMetadata(p.Items[0].Body,
+ v.From)
+ }
}*/
}
case v.Query.XMLName.Local == "":
@@ -819,8 +856,10 @@ func (c *Client) Recv() (stanza interface{}, err error) {
return Chat{}, err
}
- return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type,
- Query: res}, nil
+ return IQ{
+ ID: v.ID, From: v.From, To: v.To, Type: v.Type,
+ Query: res,
+ }, nil
}
}
}
@@ -828,7 +867,7 @@ func (c *Client) Recv() (stanza interface{}, err error) {
// Send sends the message wrapped inside an XMPP message stanza body.
func (c *Client) Send(chat Chat) (n int, err error) {
- var subtext, thdtext, oobtext string
+ var subtext, thdtext, oobtext, msgidtext, msgcorrecttext string
if chat.Subject != `` {
subtext = `` + xmlEscape(chat.Subject) + ``
}
@@ -843,10 +882,19 @@ func (c *Client) Send(chat Chat) (n int, err error) {
oobtext += ``
}
- stanza := "" + subtext + "%s" + oobtext + thdtext + ""
+ if chat.ID != `` {
+ msgidtext = `id='` + xmlEscape(chat.ID) + `'`
+ } else {
+ msgidtext = `id='` + cnonce() + `'`
+ }
+
+ if chat.ReplaceID != `` {
+ msgcorrecttext = ``
+ }
+
+ stanza := "" + subtext + "%s" + msgcorrecttext + oobtext + thdtext + ""
- return fmt.Fprintf(c.conn, stanza,
- xmlEscape(chat.Remote), xmlEscape(chat.Type), cnonce(), xmlEscape(chat.Text))
+ return fmt.Fprintf(c.conn, stanza, xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text))
}
// SendOOB sends OOB data wrapped inside an XMPP message stanza, without actual body.
@@ -961,6 +1009,11 @@ type bindBind struct {
Jid string `xml:"jid"`
}
+type clientMessageCorrect struct {
+ XMLName xml.Name `xml:"urn:xmpp:message-correct:0 replace"`
+ ID string `xml:"id,attr"`
+}
+
// RFC 3921 B.1 jabber:client
type clientMessage struct {
XMLName xml.Name `xml:"jabber:client message"`
@@ -970,9 +1023,10 @@ type clientMessage struct {
Type string `xml:"type,attr"` // chat, error, groupchat, headline, or normal
// These should technically be []clientText, but string is much more convenient.
- Subject string `xml:"subject"`
- Body string `xml:"body"`
- Thread string `xml:"thread"`
+ Subject string `xml:"subject"`
+ Body string `xml:"body"`
+ Thread string `xml:"thread"`
+ ReplaceID clientMessageCorrect
// Pubsub
Event clientPubsubEvent `xml:"event"`
@@ -1051,6 +1105,8 @@ type clientIQ struct {
Query XMLElement `xml:",any"`
Error clientError
Bind bindBind
+
+ InnerXML []byte `xml:",innerxml"`
}
type clientError struct {
diff --git a/xmpp_disco.go b/xmpp_disco.go
new file mode 100644
index 0000000..0bca664
--- /dev/null
+++ b/xmpp_disco.go
@@ -0,0 +1,99 @@
+package xmpp
+
+import (
+ "encoding/xml"
+)
+
+const (
+ XMPPNS_DISCO_ITEMS = "http://jabber.org/protocol/disco#items"
+ XMPPNS_DISCO_INFO = "http://jabber.org/protocol/disco#info"
+)
+
+type clientDiscoFeature struct {
+ XMLName xml.Name `xml:"feature"`
+ Var string `xml:"var,attr"`
+}
+
+type clientDiscoIdentity struct {
+ XMLName xml.Name `xml:"identity"`
+ Category string `xml:"category,attr"`
+ Type string `xml:"type,attr"`
+ Name string `xml:"name,attr"`
+}
+
+type clientDiscoQuery struct {
+ XMLName xml.Name `xml:"query"`
+ Features []clientDiscoFeature `xml:"feature"`
+ Identities []clientDiscoIdentity `xml:"identity"`
+}
+
+type clientDiscoItem struct {
+ XMLName xml.Name `xml:"item"`
+ Jid string `xml:"jid,attr"`
+ Node string `xml:"node,attr"`
+ Name string `xml:"name,attr"`
+}
+
+type clientDiscoItemsQuery struct {
+ XMLName xml.Name `xml:"query"`
+ Items []clientDiscoItem `xml:"item"`
+}
+
+type DiscoIdentity struct {
+ Category string
+ Type string
+ Name string
+}
+
+type DiscoItem struct {
+ Jid string
+ Name string
+ Node string
+}
+
+type DiscoResult struct {
+ Features []string
+ Identities []DiscoIdentity
+}
+
+type DiscoItems struct {
+ Jid string
+ Items []DiscoItem
+}
+
+func clientFeaturesToReturn(features []clientDiscoFeature) []string {
+ var ret []string
+
+ for _, feature := range features {
+ ret = append(ret, feature.Var)
+ }
+
+ return ret
+}
+
+func clientIdentitiesToReturn(identities []clientDiscoIdentity) []DiscoIdentity {
+ var ret []DiscoIdentity
+
+ for _, id := range identities {
+ ret = append(ret, DiscoIdentity{
+ Category: id.Category,
+ Type: id.Type,
+ Name: id.Name,
+ })
+ }
+
+ return ret
+}
+
+func clientDiscoItemsToReturn(items []clientDiscoItem) []DiscoItem {
+ var ret []DiscoItem
+ for _, item := range items {
+ ret = append(ret, DiscoItem{
+ Jid: item.Jid,
+ Name: item.Name,
+ Node: item.Node,
+ })
+ }
+
+ return ret
+}
diff --git a/xmpp_information_query.go b/xmpp_information_query.go
index 7f6d9c1..90dee95 100644
--- a/xmpp_information_query.go
+++ b/xmpp_information_query.go
@@ -10,10 +10,26 @@ const IQTypeSet = "set"
const IQTypeResult = "result"
func (c *Client) Discovery() (string, error) {
- const namespace = "http://jabber.org/protocol/disco#items"
// use getCookie for a pseudo random id.
reqID := strconv.FormatUint(uint64(getCookie()), 10)
- return c.RawInformationQuery(c.jid, c.domain, reqID, IQTypeGet, namespace, "")
+ return c.RawInformationQuery(c.jid, c.domain, reqID, IQTypeGet, XMPPNS_DISCO_ITEMS, "")
+}
+
+// Discover information about a node
+func (c *Client) DiscoverNodeInfo(node string) (string, error) {
+ query := fmt.Sprintf("", XMPPNS_DISCO_INFO, node)
+ return c.RawInformation(c.jid, c.domain, "info3", IQTypeGet, query)
+}
+
+// Discover items that the server exposes
+func (c *Client) DiscoverServerItems() (string, error) {
+ return c.DiscoverEntityItems(c.domain)
+}
+
+// Discover items that an entity exposes
+func (c *Client) DiscoverEntityItems(jid string) (string, error) {
+ query := fmt.Sprintf("", XMPPNS_DISCO_ITEMS)
+ return c.RawInformation(c.jid, jid, "info1", IQTypeGet, query)
}
// RawInformationQuery sends an information query request to the server.