forked from jshiffer/go-xmpp
Implement XEP-0084 (User Avatar) (#120)
* Implement XEP-0084 (User Avatar) * Fix style with gofmt
This commit is contained in:
parent
899ef71e80
commit
37fa6ef92f
54
xmpp.go
54
xmpp.go
@ -642,7 +642,20 @@ func (c *Client) Recv() (stanza interface{}, err error) {
|
||||
case *clientMessage:
|
||||
if v.Event.XMLNS == XMPPNS_PUBSUB_EVENT {
|
||||
// Handle Pubsub notifications
|
||||
return pubsubClientToReturn(v.Event), nil
|
||||
switch v.Event.Items.Node {
|
||||
case XMPPNS_AVATAR_PEP_METADATA:
|
||||
return handleAvatarMetadata(v.Event.Items.Items[0].Body,
|
||||
v.From)
|
||||
// I am not sure whether this can even happen.
|
||||
// XEP-0084 only specifies a subscription to
|
||||
// the metadata node.
|
||||
/*case XMPPNS_AVATAR_PEP_DATA:
|
||||
return handleAvatarData(v.Event.Items.Items[0].Body,
|
||||
v.From,
|
||||
v.Event.Items.Items[0].ID)*/
|
||||
default:
|
||||
return pubsubClientToReturn(v.Event), nil
|
||||
}
|
||||
}
|
||||
|
||||
stamp, _ := time.Parse(
|
||||
@ -741,10 +754,41 @@ func (c *Client) Recv() (stanza interface{}, err error) {
|
||||
return PubsubItems{}, err
|
||||
}
|
||||
|
||||
return PubsubItems{
|
||||
p.Node,
|
||||
pubsubItemsToReturn(p.Items),
|
||||
}, nil
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}*/
|
||||
}
|
||||
case v.Query.XMLName.Local == "":
|
||||
return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type}, nil
|
||||
|
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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user