Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion xmpp.go
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,10 @@ type Chat struct {
Oobdesc string
ID string
ReplaceID string
ReplyID string // XEP-0461: id of the message being replied to (use stanza-id for groupchat)
ReplyTo string // XEP-0461: JID of the author of the message being replied to
StanzaID string // XEP-0359: refers to stanza-id but named Stanza for brevity
StanzaBy string // XEP-0359: refers to stanza-id but named Stanza for brevity
Roster Roster
Other []string
OtherElem []XMLElement
Expand Down Expand Up @@ -684,6 +688,10 @@ func (c *Client) Recv() (stanza interface{}, err error) {
Thread: v.Thread,
ID: v.ID,
ReplaceID: v.ReplaceID.ID,
ReplyID: v.Reply.ID,
ReplyTo: v.Reply.To,
StanzaID: v.StanzaID.ID,
StanzaBy: v.StanzaID.By,
Other: v.OtherStrings(),
OtherElem: v.Other,
Stamp: stamp,
Expand Down Expand Up @@ -892,7 +900,16 @@ func (c *Client) Send(chat Chat) (n int, err error) {
msgcorrecttext = `<replace id='` + xmlEscape(chat.ReplaceID) + `' xmlns='urn:xmpp:message-correct:0'/>`
}

stanza := "<message to='%s' type='%s' " + msgidtext + " xml:lang='en'>" + subtext + "<body>%s</body>" + msgcorrecttext + oobtext + thdtext + "</message>"
var replytext string
if chat.ReplyID != `` {
replytext = `<reply id='` + xmlEscape(chat.ReplyID) + `'`
if chat.ReplyTo != `` {
replytext += ` to='` + xmlEscape(chat.ReplyTo) + `'`
}
replytext += ` xmlns='urn:xmpp:reply:0'/>`
}

stanza := "<message to='%s' type='%s' " + msgidtext + " xml:lang='en'>" + subtext + "<body>%s</body>" + replytext + msgcorrecttext + oobtext + thdtext + "</message>"

return fmt.Fprintf(c.conn, stanza, xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text))
}
Expand Down Expand Up @@ -1014,6 +1031,18 @@ type clientMessageCorrect struct {
ID string `xml:"id,attr"`
}

type clientReply struct {
XMLName xml.Name `xml:"urn:xmpp:reply:0 reply"`
ID string `xml:"id,attr"`
To string `xml:"to,attr"`
}

type clientStanzaID struct {
XMLName xml.Name `xml:"urn:xmpp:sid:0 stanza-id"`
ID string `xml:"id,attr"`
By string `xml:"by,attr"`
}

// RFC 3921 B.1 jabber:client
type clientMessage struct {
XMLName xml.Name `xml:"jabber:client message"`
Expand All @@ -1027,6 +1056,8 @@ type clientMessage struct {
Body string `xml:"body"`
Thread string `xml:"thread"`
ReplaceID clientMessageCorrect
Reply clientReply
StanzaID clientStanzaID

// Pubsub
Event clientPubsubEvent `xml:"event"`
Expand Down
76 changes: 74 additions & 2 deletions xmpp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (*testConn) SetWriteDeadline(time.Time) error {
}

var text = strings.TrimSpace(`
<message xmlns="jabber:client" id="3" type="error" to="123456789@gcm.googleapis.com/ABC">
<message xmlns="jabber:client" id="" type="error" to="123456789@gcm.googleapis.com/ABC">
<gcm xmlns="google:mobile:data">
{"random": "&lt;text&gt;"}
</gcm>
Expand Down Expand Up @@ -122,12 +122,13 @@ var emptyPubSub = strings.TrimSpace(`
</pubsub>
</iq>
`)

func TestEmptyPubsub(t *testing.T) {
var c Client
c.conn = tConnect(emptyPubSub)
c.p = xml.NewDecoder(c.conn)
m, err := c.Recv()

switch m.(type) {
case AvatarData:
if err == nil {
Expand All @@ -138,3 +139,74 @@ func TestEmptyPubsub(t *testing.T) {
t.Errorf("Expected a return value of AvatarData")
}
}

func TestSendReply(t *testing.T) {
var c Client
buf := &bytes.Buffer{}
c.conn = &testConn{buf}

chat := Chat{
Remote: "room@conference.example.com",
Type: "groupchat",
Text: "This is a reply",
ID: "msg-123",
ReplyTo: "original-msg-456",
ReplyID: "reply-id-789",
}

_, err := c.Send(chat)
if err != nil {
t.Fatalf("Send() returned error: %v", err)
}

output := buf.String()

// Check that the reply element is present with correct namespace
expectedReply := `<reply id='reply-id-789' to='original-msg-456' xmlns='urn:xmpp:reply:0'/>`
if !strings.Contains(output, expectedReply) {
t.Errorf("Send() output missing XEP-0461 reply element.\nGot: %s\nExpected to contain: %s", output, expectedReply)
}

// Check that the body is present
if !strings.Contains(output, "<body>This is a reply</body>") {
t.Errorf("Send() output missing body element.\nGot: %s", output)
}

// Check message attributes
if !strings.Contains(output, "to='room@conference.example.com'") {
t.Errorf("Send() output missing 'to' attribute.\nGot: %s", output)
}
if !strings.Contains(output, "type='groupchat'") {
t.Errorf("Send() output missing 'type' attribute.\nGot: %s", output)
}
}

func TestSendWithoutReply(t *testing.T) {
var c Client
buf := &bytes.Buffer{}
c.conn = &testConn{buf}

chat := Chat{
Remote: "room@conference.example.com",
Type: "groupchat",
Text: "Regular message",
ID: "msg-789",
}

_, err := c.Send(chat)
if err != nil {
t.Fatalf("Send() returned error: %v", err)
}

output := buf.String()

// Check that no reply element is present when ReplyTo is empty
if strings.Contains(output, "<reply") {
t.Errorf("Send() output should not contain reply element when ReplyTo is empty.\nGot: %s", output)
}

// Check that the body is still present
if !strings.Contains(output, "<body>Regular message</body>") {
t.Errorf("Send() output missing body element.\nGot: %s", output)
}
}