forked from jshiffer/go-xmpp
Compare commits
128 Commits
7ec2b8b7de
...
370c500a5e
Author | SHA1 | Date | |
---|---|---|---|
370c500a5e | |||
|
7154bfeb76 | ||
|
243a438354 | ||
|
e9123cc4b3 | ||
|
4be597a84a | ||
|
464fbe04ef | ||
|
e223dcf94b | ||
|
321c2b14a5 | ||
|
9161feef4d | ||
|
fc3ed9a0b8 | ||
|
d9df620fa4 | ||
|
f067814851 | ||
|
961b7e435e | ||
|
12a04e0950 | ||
|
2f331ed19c | ||
|
b0f55a8f7f | ||
|
7486b7a363 | ||
|
da2377ecb0 | ||
|
44095406a2 | ||
|
d7aee6b636 | ||
|
0324b31f56 | ||
|
ca4e49201e | ||
|
6e5d6e449e | ||
|
0ae62a33a2 | ||
|
ce687243c1 | ||
|
78d07e9eee | ||
|
416bb6e7b7 | ||
|
aef1257ed1 | ||
|
da17a46e6f | ||
|
eedd7259cb | ||
|
07196efcf3 | ||
|
bbd90cc04b | ||
|
0c7ee22452 | ||
|
862c21f845 | ||
|
bc81053dbc | ||
|
94ab540b80 | ||
|
f6a9836fdf | ||
|
8ab32d885f | ||
|
73f06c9f3d | ||
|
9c5e758356 | ||
|
ea4874e8c9 | ||
|
dab6865bd2 | ||
|
c051d69509 | ||
|
aed021cf3e | ||
|
2c4708e724 | ||
|
746409f074 | ||
|
9684a8ff69 | ||
|
b7ea9f4be1 | ||
|
e2bc7bf6d7 | ||
|
0bcc057225 | ||
|
49054ca9e9 | ||
|
b369b7df10 | ||
|
cc481e54e7 | ||
|
88855eac82 | ||
|
0cc0a72c15 | ||
|
d6e9a15f29 | ||
|
6ffd595a06 | ||
|
62928b3483 | ||
|
d67787ca0f | ||
|
f8a24505f4 | ||
|
685570cbd8 | ||
|
7bfa331758 | ||
|
3f0cbac307 | ||
|
7ccad52e63 | ||
|
705f68d1a5 | ||
|
b49bdce100 | ||
|
f4c732fdc7 | ||
|
d3d16d5db9 | ||
|
34d683d25a | ||
|
dffa92c129 | ||
|
8531e2e36a | ||
|
e7d5b17113 | ||
|
c1b9689e75 | ||
|
424970d23c | ||
|
5fdcf18a81 | ||
|
794ed98f9f | ||
|
2f9bd427e8 | ||
|
70c2fe6900 | ||
|
39f5b80375 | ||
|
2449f4192b | ||
|
3462085098 | ||
|
6c9243326e | ||
|
31c7eb6919 | ||
|
9dcf67c0ad | ||
|
4c385a334c | ||
|
24e0f536cb | ||
|
a6b124c9b2 | ||
|
6138e9dbe5 | ||
|
98ff0d4df7 | ||
|
bef3e549f7 | ||
|
9129a110df | ||
|
d72a0f3154 | ||
|
9fc0b1236c | ||
|
05cd75074a | ||
|
369824c83a | ||
|
2eb234970c | ||
|
3b26f73300 | ||
|
1411b9cc8b | ||
|
99ddfc1aa4 | ||
|
e773596ea0 | ||
|
912ba61489 | ||
|
3871461df9 | ||
|
db1339b3a5 | ||
|
b40e129499 | ||
|
42ee290fc5 | ||
|
da2b7586cd | ||
|
37fa6ef92f | ||
|
899ef71e80 | ||
|
3e4868bd3e | ||
|
a86b6abcb3 | ||
|
ac4c216a42 | ||
|
6093f50721 | ||
|
1f614e5b8d | ||
|
ef6a1a617c | ||
|
65fd08aee2 | ||
|
a79a0e59ef | ||
|
5709ddefa8 | ||
|
51b558cd2c | ||
|
66c008d798 | ||
|
224305b3ef | ||
|
1e7b50b41c | ||
|
c18873b880 | ||
|
2c5079ea28 | ||
|
113d9c0420 | ||
|
e543ad3fcd | ||
|
4fdbee9ac5 | ||
|
8a5843171f | ||
|
04ea54f191 |
@ -3,4 +3,4 @@ go-xmpp
|
|||||||
|
|
||||||
go xmpp library (original was written by russ cox )
|
go xmpp library (original was written by russ cox )
|
||||||
|
|
||||||
[Documentation](https://godoc.org/github.com/mattn/go-xmpp)
|
[Documentation](https://godoc.org/github.com/xmppo/go-xmpp)
|
||||||
|
@ -2,11 +2,12 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"github.com/mattn/go-gtk/gtk"
|
|
||||||
"github.com/mattn/go-xmpp"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/matterbridge/go-xmpp"
|
||||||
|
"github.com/mattn/go-gtk/gtk"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -5,20 +5,23 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mattn/go-xmpp"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/matterbridge/go-xmpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var server = flag.String("server", "talk.google.com:443", "server")
|
var (
|
||||||
var username = flag.String("username", "", "username")
|
server = flag.String("server", "talk.google.com:443", "server")
|
||||||
var password = flag.String("password", "", "password")
|
username = flag.String("username", "", "username")
|
||||||
var status = flag.String("status", "xa", "status")
|
password = flag.String("password", "", "password")
|
||||||
var statusMessage = flag.String("status-msg", "I for one welcome our new codebot overlords.", "status message")
|
status = flag.String("status", "xa", "status")
|
||||||
var notls = flag.Bool("notls", false, "No TLS")
|
statusMessage = flag.String("status-msg", "I for one welcome our new codebot overlords.", "status message")
|
||||||
var debug = flag.Bool("debug", false, "debug output")
|
notls = flag.Bool("notls", false, "No TLS")
|
||||||
var session = flag.Bool("session", false, "use server session")
|
debug = flag.Bool("debug", false, "debug output")
|
||||||
|
session = flag.Bool("session", false, "use server session")
|
||||||
|
)
|
||||||
|
|
||||||
func serverName(host string) string {
|
func serverName(host string) string {
|
||||||
return strings.Split(host, ":")[0]
|
return strings.Split(host, ":")[0]
|
||||||
@ -48,7 +51,8 @@ func main() {
|
|||||||
|
|
||||||
var talk *xmpp.Client
|
var talk *xmpp.Client
|
||||||
var err error
|
var err error
|
||||||
options := xmpp.Options{Host: *server,
|
options := xmpp.Options{
|
||||||
|
Host: *server,
|
||||||
User: *username,
|
User: *username,
|
||||||
Password: *password,
|
Password: *password,
|
||||||
NoTLS: *notls,
|
NoTLS: *notls,
|
||||||
@ -59,7 +63,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
talk, err = options.NewClient()
|
talk, err = options.NewClient()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
8
go.mod
Normal file
8
go.mod
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
module github.com/matterbridge/go-xmpp
|
||||||
|
|
||||||
|
go 1.21.5
|
||||||
|
|
||||||
|
require (
|
||||||
|
golang.org/x/crypto v0.23.0
|
||||||
|
golang.org/x/net v0.25.0
|
||||||
|
)
|
4
go.sum
Normal file
4
go.sum
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||||
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
|
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||||
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
125
xmpp_avatar.go
Normal file
125
xmpp_avatar.go
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
package xmpp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
XMPPNS_AVATAR_PEP_DATA = "urn:xmpp:avatar:data"
|
||||||
|
XMPPNS_AVATAR_PEP_METADATA = "urn:xmpp:avatar:metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
type clientAvatarData struct {
|
||||||
|
XMLName xml.Name `xml:"data"`
|
||||||
|
Data []byte `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientAvatarInfo struct {
|
||||||
|
XMLName xml.Name `xml:"info"`
|
||||||
|
Bytes string `xml:"bytes,attr"`
|
||||||
|
Width string `xml:"width,attr"`
|
||||||
|
Height string `xml:"height,attr"`
|
||||||
|
ID string `xml:"id,attr"`
|
||||||
|
Type string `xml:"type,attr"`
|
||||||
|
URL string `xml:"url,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientAvatarMetadata struct {
|
||||||
|
XMLName xml.Name `xml:"metadata"`
|
||||||
|
XMLNS string `xml:"xmlns,attr"`
|
||||||
|
Info clientAvatarInfo `xml:"info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AvatarData struct {
|
||||||
|
Data []byte
|
||||||
|
From string
|
||||||
|
}
|
||||||
|
|
||||||
|
type AvatarMetadata struct {
|
||||||
|
From string
|
||||||
|
Bytes int
|
||||||
|
Width int
|
||||||
|
Height int
|
||||||
|
ID string
|
||||||
|
Type string
|
||||||
|
URL string
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAvatarData(itemsBody []byte, from, id string) (AvatarData, error) {
|
||||||
|
var data clientAvatarData
|
||||||
|
err := xml.Unmarshal(itemsBody, &data)
|
||||||
|
if err != nil {
|
||||||
|
return AvatarData{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base64-decode the avatar data to check its SHA1 hash
|
||||||
|
dataRaw, err := base64.StdEncoding.DecodeString(
|
||||||
|
string(data.Data))
|
||||||
|
if err != nil {
|
||||||
|
return AvatarData{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := sha1.Sum(dataRaw)
|
||||||
|
hashStr := hex.EncodeToString(hash[:])
|
||||||
|
if hashStr != id {
|
||||||
|
return AvatarData{}, errors.New("SHA1 hashes do not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
return AvatarData{
|
||||||
|
Data: dataRaw,
|
||||||
|
From: from,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAvatarMetadata(body []byte, from string) (AvatarMetadata, error) {
|
||||||
|
var meta clientAvatarMetadata
|
||||||
|
err := xml.Unmarshal(body, &meta)
|
||||||
|
if err != nil {
|
||||||
|
return AvatarMetadata{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return AvatarMetadata{
|
||||||
|
From: from,
|
||||||
|
Bytes: atoiw(meta.Info.Bytes),
|
||||||
|
Width: atoiw(meta.Info.Width),
|
||||||
|
Height: atoiw(meta.Info.Height),
|
||||||
|
ID: meta.Info.ID,
|
||||||
|
Type: meta.Info.Type,
|
||||||
|
URL: meta.Info.URL,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A wrapper for atoi which just returns -1 if an error occurs
|
||||||
|
func atoiw(str string) int {
|
||||||
|
i, err := strconv.Atoi(str)
|
||||||
|
if err != nil {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) AvatarSubscribeMetadata(jid string) {
|
||||||
|
c.PubsubSubscribeNode(XMPPNS_AVATAR_PEP_METADATA, jid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) AvatarUnsubscribeMetadata(jid string) {
|
||||||
|
c.PubsubUnsubscribeNode(XMPPNS_AVATAR_PEP_METADATA, jid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) AvatarRequestData(jid string) {
|
||||||
|
c.PubsubRequestLastItems(XMPPNS_AVATAR_PEP_DATA, jid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) AvatarRequestDataByID(jid, id string) {
|
||||||
|
c.PubsubRequestItem(XMPPNS_AVATAR_PEP_DATA, jid, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) AvatarRequestMetadata(jid string) {
|
||||||
|
c.PubsubRequestLastItems(XMPPNS_AVATAR_PEP_METADATA, jid)
|
||||||
|
}
|
99
xmpp_disco.go
Normal file
99
xmpp_disco.go
Normal file
@ -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
|
||||||
|
}
|
@ -5,27 +5,45 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
const IQTypeGet = "get"
|
const (
|
||||||
const IQTypeSet = "set"
|
IQTypeGet = "get"
|
||||||
const IQTypeResult = "result"
|
IQTypeSet = "set"
|
||||||
|
IQTypeResult = "result"
|
||||||
|
)
|
||||||
|
|
||||||
func (c *Client) Discovery() (string, error) {
|
func (c *Client) Discovery() (string, error) {
|
||||||
const namespace = "http://jabber.org/protocol/disco#items"
|
// use UUIDv4 for a pseudo random id.
|
||||||
// use getCookie for a pseudo random id.
|
|
||||||
reqID := strconv.FormatUint(uint64(getCookie()), 10)
|
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("<query xmlns='%s' node='%s'/>", 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("<query xmlns='%s'/>", XMPPNS_DISCO_ITEMS)
|
||||||
|
return c.RawInformation(c.jid, jid, "info1", IQTypeGet, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RawInformationQuery sends an information query request to the server.
|
// RawInformationQuery sends an information query request to the server.
|
||||||
func (c *Client) RawInformationQuery(from, to, id, iqType, requestNamespace, body string) (string, error) {
|
func (c *Client) RawInformationQuery(from, to, id, iqType, requestNamespace, body string) (string, error) {
|
||||||
const xmlIQ = "<iq from='%s' to='%s' id='%s' type='%s'><query xmlns='%s'>%s</query></iq>"
|
const xmlIQ = "<iq from='%s' to='%s' id='%s' type='%s'><query xmlns='%s'>%s</query></iq>\n"
|
||||||
_, err := fmt.Fprintf(c.conn, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, requestNamespace, body)
|
_, err := fmt.Fprintf(c.stanzaWriter, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, requestNamespace, body)
|
||||||
return id, err
|
return id, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// rawInformation send a IQ request with the the payload body to the server
|
// rawInformation send a IQ request with the payload body to the server
|
||||||
func (c *Client) RawInformation(from, to, id, iqType, body string) (string, error) {
|
func (c *Client) RawInformation(from, to, id, iqType, body string) (string, error) {
|
||||||
const xmlIQ = "<iq from='%s' to='%s' id='%s' type='%s'>%s</iq>"
|
const xmlIQ = "<iq from='%s' to='%s' id='%s' type='%s'>%s</iq>\n"
|
||||||
_, err := fmt.Fprintf(c.conn, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, body)
|
_, err := fmt.Fprintf(c.stanzaWriter, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, body)
|
||||||
return id, err
|
return id, err
|
||||||
}
|
}
|
||||||
|
132
xmpp_muc.go
132
xmpp_muc.go
@ -8,24 +8,24 @@
|
|||||||
package xmpp
|
package xmpp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
"errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
nsMUC = "http://jabber.org/protocol/muc"
|
nsMUC = "http://jabber.org/protocol/muc"
|
||||||
nsMUCUser = "http://jabber.org/protocol/muc#user"
|
nsMUCUser = "http://jabber.org/protocol/muc#user"
|
||||||
NoHistory = 0
|
NoHistory = 0
|
||||||
CharHistory = 1
|
CharHistory = 1
|
||||||
StanzaHistory = 2
|
StanzaHistory = 2
|
||||||
SecondsHistory = 3
|
SecondsHistory = 3
|
||||||
SinceHistory = 4
|
SinceHistory = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
// Send sends room topic wrapped inside an XMPP message stanza body.
|
// Send sends room topic wrapped inside an XMPP message stanza body.
|
||||||
func (c *Client) SendTopic(chat Chat) (n int, err error) {
|
func (c *Client) SendTopic(chat Chat) (n int, err error) {
|
||||||
return fmt.Fprintf(c.conn, "<message to='%s' type='%s' xml:lang='en'>"+"<subject>%s</subject></message>",
|
return fmt.Fprintf(c.stanzaWriter, "<message to='%s' type='%s' xml:lang='en'>"+"<subject>%s</subject></message>\n",
|
||||||
xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text))
|
xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,10 +33,10 @@ func (c *Client) JoinMUCNoHistory(jid, nick string) (n int, err error) {
|
|||||||
if nick == "" {
|
if nick == "" {
|
||||||
nick = c.jid
|
nick = c.jid
|
||||||
}
|
}
|
||||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+
|
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
|
||||||
"<x xmlns='%s'>"+
|
"<x xmlns='%s'>"+
|
||||||
"<history maxchars='0'/></x>\n"+
|
"<history maxchars='0'/></x>"+
|
||||||
"</presence>",
|
"</presence>\n",
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC)
|
xmlEscape(jid), xmlEscape(nick), nsMUC)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,35 +47,35 @@ func (c *Client) JoinMUC(jid, nick string, history_type, history int, history_da
|
|||||||
}
|
}
|
||||||
switch history_type {
|
switch history_type {
|
||||||
case NoHistory:
|
case NoHistory:
|
||||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
|
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
|
||||||
"<x xmlns='%s' />\n" +
|
"<x xmlns='%s' />"+
|
||||||
"</presence>",
|
"</presence>\n",
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC)
|
xmlEscape(jid), xmlEscape(nick), nsMUC)
|
||||||
case CharHistory:
|
case CharHistory:
|
||||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
|
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
|
||||||
"<x xmlns='%s'>\n" +
|
"<x xmlns='%s'>"+
|
||||||
"<history maxchars='%d'/></x>\n"+
|
"<history maxchars='%d'/></x>"+
|
||||||
"</presence>",
|
"</presence>\n",
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC, history)
|
xmlEscape(jid), xmlEscape(nick), nsMUC, history)
|
||||||
case StanzaHistory:
|
case StanzaHistory:
|
||||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
|
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
|
||||||
"<x xmlns='%s'>\n" +
|
"<x xmlns='%s'>"+
|
||||||
"<history maxstanzas='%d'/></x>\n"+
|
"<history maxstanzas='%d'/></x>"+
|
||||||
"</presence>",
|
"</presence>\n",
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC, history)
|
xmlEscape(jid), xmlEscape(nick), nsMUC, history)
|
||||||
case SecondsHistory:
|
case SecondsHistory:
|
||||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
|
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
|
||||||
"<x xmlns='%s'>\n" +
|
"<x xmlns='%s'>"+
|
||||||
"<history seconds='%d'/></x>\n"+
|
"<history seconds='%d'/></x>"+
|
||||||
"</presence>",
|
"</presence>\n",
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC, history)
|
xmlEscape(jid), xmlEscape(nick), nsMUC, history)
|
||||||
case SinceHistory:
|
case SinceHistory:
|
||||||
if history_date != nil {
|
if history_date != nil {
|
||||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
|
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
|
||||||
"<x xmlns='%s'>\n" +
|
"<x xmlns='%s'>"+
|
||||||
"<history since='%s'/></x>\n" +
|
"<history since='%s'/></x>"+
|
||||||
"</presence>",
|
"</presence>\n",
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC, history_date.Format(time.RFC3339))
|
xmlEscape(jid), xmlEscape(nick), nsMUC, history_date.Format(time.RFC3339))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0, errors.New("Unknown history option")
|
return 0, errors.New("Unknown history option")
|
||||||
@ -88,41 +88,41 @@ func (c *Client) JoinProtectedMUC(jid, nick string, password string, history_typ
|
|||||||
}
|
}
|
||||||
switch history_type {
|
switch history_type {
|
||||||
case NoHistory:
|
case NoHistory:
|
||||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
|
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
|
||||||
"<x xmlns='%s'>\n" +
|
"<x xmlns='%s'>"+
|
||||||
"<password>%s</password>" +
|
"<password>%s</password>"+
|
||||||
"</x>\n" +
|
"</x>"+
|
||||||
"</presence>",
|
"</presence>\n",
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password))
|
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password))
|
||||||
case CharHistory:
|
case CharHistory:
|
||||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
|
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
|
||||||
"<x xmlns='%s'>\n" +
|
"<x xmlns='%s'>"+
|
||||||
"<password>%s</password>\n"+
|
"<password>%s</password>"+
|
||||||
"<history maxchars='%d'/></x>\n"+
|
"<history maxchars='%d'/></x>"+
|
||||||
"</presence>",
|
"</presence>\n",
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
|
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
|
||||||
case StanzaHistory:
|
case StanzaHistory:
|
||||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
|
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
|
||||||
"<x xmlns='%s'>\n" +
|
"<x xmlns='%s'>"+
|
||||||
"<password>%s</password>\n"+
|
"<password>%s</password>"+
|
||||||
"<history maxstanzas='%d'/></x>\n"+
|
"<history maxstanzas='%d'/></x>"+
|
||||||
"</presence>",
|
"</presence>\n",
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
|
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
|
||||||
case SecondsHistory:
|
case SecondsHistory:
|
||||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
|
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
|
||||||
"<x xmlns='%s'>\n" +
|
"<x xmlns='%s'>"+
|
||||||
"<password>%s</password>\n"+
|
"<password>%s</password>"+
|
||||||
"<history seconds='%d'/></x>\n"+
|
"<history seconds='%d'/></x>"+
|
||||||
"</presence>",
|
"</presence>\n",
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
|
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
|
||||||
case SinceHistory:
|
case SinceHistory:
|
||||||
if history_date != nil {
|
if history_date != nil {
|
||||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
|
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>"+
|
||||||
"<x xmlns='%s'>\n" +
|
"<x xmlns='%s'>"+
|
||||||
"<password>%s</password>\n"+
|
"<password>%s</password>"+
|
||||||
"<history since='%s'/></x>\n" +
|
"<history since='%s'/></x>"+
|
||||||
"</presence>",
|
"</presence>\n",
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history_date.Format(time.RFC3339))
|
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history_date.Format(time.RFC3339))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0, errors.New("Unknown history option")
|
return 0, errors.New("Unknown history option")
|
||||||
@ -130,6 +130,6 @@ func (c *Client) JoinProtectedMUC(jid, nick string, password string, history_typ
|
|||||||
|
|
||||||
// xep-0045 7.14
|
// xep-0045 7.14
|
||||||
func (c *Client) LeaveMUC(jid string) (n int, err error) {
|
func (c *Client) LeaveMUC(jid string) (n int, err error) {
|
||||||
return fmt.Fprintf(c.conn, "<presence from='%s' to='%s' type='unavailable' />",
|
return fmt.Fprintf(c.stanzaWriter, "<presence from='%s' to='%s' type='unavailable' />\n",
|
||||||
c.jid, xmlEscape(jid))
|
c.jid, xmlEscape(jid))
|
||||||
}
|
}
|
||||||
|
14
xmpp_ping.go
14
xmpp_ping.go
@ -11,23 +11,23 @@ func (c *Client) PingC2S(jid, server string) error {
|
|||||||
if server == "" {
|
if server == "" {
|
||||||
server = c.domain
|
server = c.domain
|
||||||
}
|
}
|
||||||
_, err := fmt.Fprintf(c.conn, "<iq from='%s' to='%s' id='c2s1' type='get'>\n"+
|
_, err := fmt.Fprintf(c.stanzaWriter, "<iq from='%s' to='%s' id='c2s1' type='get'>"+
|
||||||
"<ping xmlns='urn:xmpp:ping'/>\n"+
|
"<ping xmlns='urn:xmpp:ping'/>"+
|
||||||
"</iq>",
|
"</iq>\n",
|
||||||
xmlEscape(jid), xmlEscape(server))
|
xmlEscape(jid), xmlEscape(server))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) PingS2S(fromServer, toServer string) error {
|
func (c *Client) PingS2S(fromServer, toServer string) error {
|
||||||
_, err := fmt.Fprintf(c.conn, "<iq from='%s' to='%s' id='s2s1' type='get'>\n"+
|
_, err := fmt.Fprintf(c.stanzaWriter, "<iq from='%s' to='%s' id='s2s1' type='get'>"+
|
||||||
"<ping xmlns='urn:xmpp:ping'/>\n"+
|
"<ping xmlns='urn:xmpp:ping'/>"+
|
||||||
"</iq>",
|
"</iq>\n",
|
||||||
xmlEscape(fromServer), xmlEscape(toServer))
|
xmlEscape(fromServer), xmlEscape(toServer))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) SendResultPing(id, toServer string) error {
|
func (c *Client) SendResultPing(id, toServer string) error {
|
||||||
_, err := fmt.Fprintf(c.conn, "<iq type='result' to='%s' id='%s'/>",
|
_, err := fmt.Fprintf(c.stanzaWriter, "<iq type='result' to='%s' id='%s'/>\n",
|
||||||
xmlEscape(toServer), xmlEscape(id))
|
xmlEscape(toServer), xmlEscape(id))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
128
xmpp_pubsub.go
Normal file
128
xmpp_pubsub.go
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
package xmpp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
XMPPNS_PUBSUB = "http://jabber.org/protocol/pubsub"
|
||||||
|
XMPPNS_PUBSUB_EVENT = "http://jabber.org/protocol/pubsub#event"
|
||||||
|
)
|
||||||
|
|
||||||
|
type clientPubsubItem struct {
|
||||||
|
XMLName xml.Name `xml:"item"`
|
||||||
|
ID string `xml:"id,attr"`
|
||||||
|
Body []byte `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientPubsubItems struct {
|
||||||
|
XMLName xml.Name `xml:"items"`
|
||||||
|
Node string `xml:"node,attr"`
|
||||||
|
Items []clientPubsubItem `xml:"item"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientPubsubEvent struct {
|
||||||
|
XMLName xml.Name `xml:"event"`
|
||||||
|
XMLNS string `xml:"xmlns,attr"`
|
||||||
|
Items clientPubsubItems `xml:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientPubsubError struct {
|
||||||
|
XMLName xml.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientPubsubSubscription struct {
|
||||||
|
XMLName xml.Name `xml:"subscription"`
|
||||||
|
Node string `xml:"node,attr"`
|
||||||
|
JID string `xml:"jid,attr"`
|
||||||
|
SubID string `xml:"subid,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PubsubEvent struct {
|
||||||
|
Node string
|
||||||
|
Items []PubsubItem
|
||||||
|
}
|
||||||
|
|
||||||
|
type PubsubSubscription struct {
|
||||||
|
SubID string
|
||||||
|
JID string
|
||||||
|
Node string
|
||||||
|
Errors []string
|
||||||
|
}
|
||||||
|
type PubsubUnsubscription PubsubSubscription
|
||||||
|
|
||||||
|
type PubsubItem struct {
|
||||||
|
ID string
|
||||||
|
InnerXML []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type PubsubItems struct {
|
||||||
|
Node string
|
||||||
|
Items []PubsubItem
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts []clientPubsubItem to []PubsubItem
|
||||||
|
func pubsubItemsToReturn(items []clientPubsubItem) []PubsubItem {
|
||||||
|
var tmp []PubsubItem
|
||||||
|
for _, i := range items {
|
||||||
|
tmp = append(tmp, PubsubItem{
|
||||||
|
ID: i.ID,
|
||||||
|
InnerXML: i.Body,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
func pubsubClientToReturn(event clientPubsubEvent) PubsubEvent {
|
||||||
|
return PubsubEvent{
|
||||||
|
Node: event.Items.Node,
|
||||||
|
Items: pubsubItemsToReturn(event.Items.Items),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pubsubStanza(body string) string {
|
||||||
|
return fmt.Sprintf("<pubsub xmlns='%s'>%s</pubsub>",
|
||||||
|
XMPPNS_PUBSUB, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func pubsubSubscriptionStanza(node, jid string) string {
|
||||||
|
body := fmt.Sprintf("<subscribe node='%s' jid='%s'/>",
|
||||||
|
xmlEscape(node),
|
||||||
|
xmlEscape(jid))
|
||||||
|
return pubsubStanza(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func pubsubUnsubscriptionStanza(node, jid string) string {
|
||||||
|
body := fmt.Sprintf("<unsubscribe node='%s' jid='%s'/>",
|
||||||
|
xmlEscape(node),
|
||||||
|
xmlEscape(jid))
|
||||||
|
return pubsubStanza(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) PubsubSubscribeNode(node, jid string) {
|
||||||
|
c.RawInformation(c.jid,
|
||||||
|
jid,
|
||||||
|
"sub1",
|
||||||
|
"set",
|
||||||
|
pubsubSubscriptionStanza(node, c.jid))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) PubsubUnsubscribeNode(node, jid string) {
|
||||||
|
c.RawInformation(c.jid,
|
||||||
|
jid,
|
||||||
|
"unsub1",
|
||||||
|
"set",
|
||||||
|
pubsubUnsubscriptionStanza(node, c.jid))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) PubsubRequestLastItems(node, jid string) {
|
||||||
|
body := fmt.Sprintf("<items node='%s'/>", node)
|
||||||
|
c.RawInformation(c.jid, jid, "items1", "get", pubsubStanza(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) PubsubRequestItem(node, jid, id string) {
|
||||||
|
body := fmt.Sprintf("<items node='%s'><item id='%s'/></items>", node, id)
|
||||||
|
c.RawInformation(c.jid, jid, "items3", "get", pubsubStanza(body))
|
||||||
|
}
|
@ -5,16 +5,21 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (c *Client) ApproveSubscription(jid string) {
|
func (c *Client) ApproveSubscription(jid string) {
|
||||||
fmt.Fprintf(c.conn, "<presence to='%s' type='subscribed'/>",
|
fmt.Fprintf(c.stanzaWriter, "<presence to='%s' type='subscribed'/>\n",
|
||||||
xmlEscape(jid))
|
xmlEscape(jid))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) RevokeSubscription(jid string) {
|
func (c *Client) RevokeSubscription(jid string) {
|
||||||
fmt.Fprintf(c.conn, "<presence to='%s' type='unsubscribed'/>",
|
fmt.Fprintf(c.stanzaWriter, "<presence to='%s' type='unsubscribed'/>\n",
|
||||||
|
xmlEscape(jid))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) RetrieveSubscription(jid string) {
|
||||||
|
fmt.Fprintf(c.conn, "<presence to='%s' type='unsubscribe'/>\n",
|
||||||
xmlEscape(jid))
|
xmlEscape(jid))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) RequestSubscription(jid string) {
|
func (c *Client) RequestSubscription(jid string) {
|
||||||
fmt.Fprintf(c.conn, "<presence to='%s' type='subscribe'/>",
|
fmt.Fprintf(c.stanzaWriter, "<presence to='%s' type='subscribe'/>\n",
|
||||||
xmlEscape(jid))
|
xmlEscape(jid))
|
||||||
}
|
}
|
||||||
|
31
xmpp_test.go
31
xmpp_test.go
@ -85,12 +85,14 @@ func TestStanzaError(t *testing.T) {
|
|||||||
"\n\t\t\n\t\t\n\t",
|
"\n\t\t\n\t\t\n\t",
|
||||||
},
|
},
|
||||||
OtherElem: []XMLElement{
|
OtherElem: []XMLElement{
|
||||||
XMLElement{
|
{
|
||||||
XMLName: xml.Name{Space: "google:mobile:data", Local: "gcm"},
|
XMLName: xml.Name{Space: "google:mobile:data", Local: "gcm"},
|
||||||
|
Attr: []xml.Attr{{Name: xml.Name{Space: "", Local: "xmlns"}, Value: "google:mobile:data"}},
|
||||||
InnerXML: "\n\t\t{\"random\": \"<text>\"}\n\t",
|
InnerXML: "\n\t\t{\"random\": \"<text>\"}\n\t",
|
||||||
},
|
},
|
||||||
XMLElement{
|
{
|
||||||
XMLName: xml.Name{Space: "jabber:client", Local: "error"},
|
XMLName: xml.Name{Space: "jabber:client", Local: "error"},
|
||||||
|
Attr: []xml.Attr{{Name: xml.Name{Space: "", Local: "code"}, Value: "400"}, {Name: xml.Name{Space: "", Local: "type"}, Value: "modify"}},
|
||||||
InnerXML: `
|
InnerXML: `
|
||||||
<bad-request xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
|
<bad-request xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
|
||||||
<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">
|
<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">
|
||||||
@ -114,3 +116,28 @@ func TestEOFError(t *testing.T) {
|
|||||||
t.Errorf("Recv() did not return io.EOF on end of input stream")
|
t.Errorf("Recv() did not return io.EOF on end of input stream")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var emptyPubSub = strings.TrimSpace(`
|
||||||
|
<iq xmlns="jabber:client" type='result' from='juliet@capulet.lit' id='items3'>
|
||||||
|
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
|
||||||
|
<items node='urn:xmpp:avatar:data'></items>
|
||||||
|
</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 {
|
||||||
|
t.Errorf("Expected an error to be returned")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Errorf("Recv() = %v", m)
|
||||||
|
t.Errorf("Expected a return value of AvatarData")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user