mirror of
https://dev.narayana.im/narayana/telegabber.git
synced 2026-08-05 04:07:07 +00:00
Merge branch 'master' into adhoc
This commit is contained in:
commit
f4d5ebc3ad
4 changed files with 400 additions and 59 deletions
|
|
@ -22,6 +22,12 @@ type DelayedStatus struct {
|
|||
TimestampExpired int64
|
||||
}
|
||||
|
||||
// HashedAvatar stores a SHA-1 hash and a Telegram file ID
|
||||
type HashedAvatar struct {
|
||||
Hash string
|
||||
File int32
|
||||
}
|
||||
|
||||
// Client stores the metadata for lazily invoked TDlib instance
|
||||
type Client struct {
|
||||
client *client.Client
|
||||
|
|
@ -48,6 +54,12 @@ type Client struct {
|
|||
lastMsgIds map[int64]string
|
||||
msgHashSeed maphash.Seed
|
||||
|
||||
XmppClientFeatures map[string]*[]string
|
||||
XmppClientFeaturesLock sync.Mutex
|
||||
|
||||
AvatarHashes map[int64]*HashedAvatar
|
||||
AvatarHashesLock sync.Mutex
|
||||
|
||||
locks clientLocks
|
||||
SendMessageLock sync.Mutex
|
||||
}
|
||||
|
|
@ -109,20 +121,22 @@ func NewClient(conf config.TelegramConfig, jid string, component *xmpp.Component
|
|||
}
|
||||
|
||||
return &Client{
|
||||
parameters: ¶meters,
|
||||
xmpp: component,
|
||||
jid: jid,
|
||||
Session: session,
|
||||
resources: make(map[string]bool),
|
||||
content: &conf.Content,
|
||||
cache: cache.NewCache(),
|
||||
outbox: make(map[string]string),
|
||||
editOutbox: make(map[string]string),
|
||||
options: options,
|
||||
DelayedStatuses: make(map[int64]*DelayedStatus),
|
||||
lastMsgHashes: make(map[int64]uint64),
|
||||
lastMsgIds: make(map[int64]string),
|
||||
msgHashSeed: maphash.MakeSeed(),
|
||||
parameters: ¶meters,
|
||||
xmpp: component,
|
||||
jid: jid,
|
||||
Session: session,
|
||||
resources: make(map[string]bool),
|
||||
content: &conf.Content,
|
||||
cache: cache.NewCache(),
|
||||
outbox: make(map[string]string),
|
||||
editOutbox: make(map[string]string),
|
||||
options: options,
|
||||
DelayedStatuses: make(map[int64]*DelayedStatus),
|
||||
lastMsgHashes: make(map[int64]uint64),
|
||||
lastMsgIds: make(map[int64]string),
|
||||
msgHashSeed: maphash.MakeSeed(),
|
||||
XmppClientFeatures: make(map[string]*[]string),
|
||||
AvatarHashes: make(map[int64]*HashedAvatar),
|
||||
locks: clientLocks{
|
||||
chatMessageLocks: make(map[int64]*sync.Mutex),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package telegram
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
|
|
@ -45,6 +47,11 @@ type messageStub struct {
|
|||
Text string
|
||||
}
|
||||
|
||||
const (
|
||||
typeFileDataSha1 byte = iota
|
||||
typeFileDataBase64
|
||||
)
|
||||
|
||||
var errOffline = errors.New("TDlib instance is offline")
|
||||
var errOverLimit = errors.New("Over limit")
|
||||
|
||||
|
|
@ -258,6 +265,85 @@ func (c *Client) LastSeenStatus(timestamp int64) string {
|
|||
Format("Last seen at 15:04 02/01/2006")
|
||||
}
|
||||
|
||||
func (c *Client) getFileData(tgFile *client.File, typ byte) string {
|
||||
var priority int32
|
||||
if typ == typeFileDataSha1 {
|
||||
priority = 1
|
||||
} else if typ == typeFileDataBase64 {
|
||||
priority = 32
|
||||
}
|
||||
|
||||
file, path, err := c.ForceOpenFile(tgFile, priority)
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
|
||||
if typ == typeFileDataSha1 {
|
||||
hash := sha1.New()
|
||||
_, err = io.Copy(hash, file)
|
||||
if err == nil {
|
||||
return fmt.Sprintf("%x", hash.Sum(nil))
|
||||
} else {
|
||||
log.Errorf("Error calculating hash: %v", path)
|
||||
}
|
||||
} else if typ == typeFileDataBase64 {
|
||||
buf := new(bytes.Buffer)
|
||||
binval := base64.NewEncoder(base64.StdEncoding, buf)
|
||||
_, err = io.Copy(binval, file)
|
||||
binval.Close()
|
||||
if err == nil {
|
||||
return buf.String()
|
||||
} else {
|
||||
log.Errorf("Error calculating base64: %v", path)
|
||||
}
|
||||
}
|
||||
} else if path != "" {
|
||||
log.Errorf("Photo does not exist: %v", path)
|
||||
} else {
|
||||
log.Errorf("PHOTO: %#v", err.Error())
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// SetEmptyAvatarHash puts a dummy value into the cache to avoid attempting to fetch surely missing avatars
|
||||
func (c *Client) SetEmptyAvatarHash(chatId int64) {
|
||||
c.AvatarHashesLock.Lock()
|
||||
c.AvatarHashes[chatId] = &HashedAvatar{
|
||||
Hash: "",
|
||||
File: 0,
|
||||
}
|
||||
c.AvatarHashesLock.Unlock()
|
||||
}
|
||||
|
||||
// GetPhotoSha1AndSize obtains data for PEP
|
||||
func (c *Client) GetPhotoSha1AndSize(photo *client.File, chatId int64) (string, int64) {
|
||||
sha1 := c.GetPhotoSha1(photo, chatId)
|
||||
|
||||
size := photo.Size
|
||||
if size == 0 {
|
||||
size = photo.ExpectedSize
|
||||
}
|
||||
|
||||
return sha1, size
|
||||
}
|
||||
|
||||
// GetPhotoSha1 computes the photo hash
|
||||
func (c *Client) GetPhotoSha1(photo *client.File, chatId int64) string {
|
||||
sha1 := c.getFileData(photo, typeFileDataSha1)
|
||||
c.AvatarHashesLock.Lock()
|
||||
c.AvatarHashes[chatId] = &HashedAvatar{
|
||||
Hash: sha1,
|
||||
File: photo.Id,
|
||||
}
|
||||
c.AvatarHashesLock.Unlock()
|
||||
return sha1
|
||||
}
|
||||
|
||||
// GetPhotoBase64 reads file data as Base64
|
||||
func (c *Client) GetPhotoBase64(photo *client.File) string {
|
||||
return c.getFileData(photo, typeFileDataBase64)
|
||||
}
|
||||
|
||||
// ProcessStatusUpdate sets contact status
|
||||
func (c *Client) ProcessStatusUpdate(chatID int64, status string, show string, oldArgs ...args.V) error {
|
||||
if !c.Online() {
|
||||
|
|
@ -275,20 +361,7 @@ func (c *Client) ProcessStatusUpdate(chatID int64, status string, show string, o
|
|||
|
||||
var photo string
|
||||
if chat != nil && chat.Photo != nil {
|
||||
file, path, err := c.ForceOpenFile(chat.Photo.Small, 1)
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
|
||||
hash := sha1.New()
|
||||
_, err = io.Copy(hash, file)
|
||||
if err == nil {
|
||||
photo = fmt.Sprintf("%x", hash.Sum(nil))
|
||||
} else {
|
||||
log.Errorf("Error calculating hash: %v", path)
|
||||
}
|
||||
} else if path != "" {
|
||||
log.Errorf("Photo does not exist: %v", path)
|
||||
}
|
||||
photo = c.GetPhotoSha1(chat.Photo.Small, chatID)
|
||||
}
|
||||
|
||||
var presenceType string
|
||||
|
|
@ -1094,6 +1167,24 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) {
|
|||
c.cache.SetChat(chatId, chat)
|
||||
go c.ProcessStatusUpdate(chatId, "", "", gateway.SPImmed(true))
|
||||
text = "<Chat photo has changed>"
|
||||
|
||||
if chat.Photo == nil {
|
||||
c.SetEmptyAvatarHash(chatId)
|
||||
} else {
|
||||
sha1, size := c.GetPhotoSha1AndSize(chat.Photo.Small, chatId)
|
||||
|
||||
for resource := range c.resourcesRange() {
|
||||
features, ok := c.XmppClientFeatures[resource]
|
||||
if ok && features != nil {
|
||||
for _, feature := range *features {
|
||||
if feature == gateway.NodeAvatarMetadataNotify {
|
||||
go gateway.SendPubSubAvatarNotification(c.xmpp, c.jid+"/"+resource, chatId, sha1, size)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
text = c.messageToText(message, false)
|
||||
|
|
@ -1315,6 +1406,11 @@ func (c *Client) prepareOutgoingMessageContent(text string, file *client.InputFi
|
|||
return content
|
||||
}
|
||||
|
||||
// ChatsKeys proxies the following function from unexported cache
|
||||
func (c *Client) ChatsKeys() []int64 {
|
||||
return c.cache.ChatsKeys()
|
||||
}
|
||||
|
||||
// StatusesRange proxies the following function from unexported cache
|
||||
func (c *Client) StatusesRange() chan *cache.Status {
|
||||
return c.cache.StatusesRange()
|
||||
|
|
@ -1389,6 +1485,13 @@ func (c *Client) getLastMessages(id int64, query string, from int64, count int32
|
|||
})
|
||||
}
|
||||
|
||||
// GetFile retrieves a file object by id given by TDlib
|
||||
func (c *Client) GetFile(id int32) (*client.File, error) {
|
||||
return c.client.GetFile(&client.GetFileRequest{
|
||||
FileId: id,
|
||||
})
|
||||
}
|
||||
|
||||
// DownloadFile actually obtains a file by id given by TDlib
|
||||
func (c *Client) DownloadFile(id int32, priority int32, synchronous bool) (*client.File, error) {
|
||||
return c.client.DownloadFile(&client.DownloadFileRequest{
|
||||
|
|
|
|||
|
|
@ -37,6 +37,11 @@ type marker struct {
|
|||
}
|
||||
|
||||
const NSNick string = "http://jabber.org/protocol/nick"
|
||||
const NodeVCard4 string = "urn:xmpp:vcard4"
|
||||
const NodeAvatarMetadata string = "urn:xmpp:avatar:metadata"
|
||||
const NodeAvatarMetadataNotify string = NodeAvatarMetadata + "+notify"
|
||||
const NodeAvatarData string = "urn:xmpp:avatar:data"
|
||||
const NSCommand string = "http://jabber.org/protocol/commands"
|
||||
|
||||
// Queue stores presences to send later
|
||||
var Queue = make(map[string]*stanza.Presence)
|
||||
|
|
@ -435,3 +440,46 @@ func SplitJID(from string) (string, string, bool) {
|
|||
}
|
||||
return fromJid.Bare(), fromJid.Resource, true
|
||||
}
|
||||
|
||||
// SendPubSubAvatarNotification encourages clients to fetch an avatar
|
||||
func SendPubSubAvatarNotification(component *xmpp.Component, jid string, chatId int64, sha1 string, size int64) {
|
||||
info := stanza.Node{
|
||||
XMLName: xml.Name{Local: "info"},
|
||||
Attrs: []xml.Attr{
|
||||
xml.Attr{Name: xml.Name{Local: "bytes"}, Value: strconv.FormatInt(size, 10)},
|
||||
xml.Attr{Name: xml.Name{Local: "height"}, Value: "160"},
|
||||
xml.Attr{Name: xml.Name{Local: "id"}, Value: sha1},
|
||||
xml.Attr{Name: xml.Name{Local: "type"}, Value: "image/jpeg"},
|
||||
xml.Attr{Name: xml.Name{Local: "width"}, Value: "160"},
|
||||
},
|
||||
}
|
||||
log.WithFields(log.Fields{
|
||||
"chatId": chatId,
|
||||
}).Debugf("%#v", info)
|
||||
|
||||
event := &stanza.PubSubEvent{
|
||||
EventElement: &stanza.ItemsEvent{
|
||||
Node: NodeAvatarMetadata,
|
||||
Items: []stanza.ItemEvent{
|
||||
stanza.ItemEvent{
|
||||
Id: sha1,
|
||||
Any: &stanza.Node{
|
||||
XMLName: xml.Name{Local: "metadata", Space: NodeAvatarMetadata},
|
||||
Nodes: []stanza.Node{info},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
message := stanza.Message{
|
||||
Attrs: stanza.Attrs{
|
||||
From: strconv.FormatInt(chatId, 10) + "@" + Jid.Bare(),
|
||||
To: jid,
|
||||
Type: stanza.MessageTypeHeadline,
|
||||
},
|
||||
Extensions: []stanza.MsgExtension{event},
|
||||
}
|
||||
|
||||
_ = ResumableSend(component, message)
|
||||
}
|
||||
|
|
|
|||
238
xmpp/handlers.go
238
xmpp/handlers.go
|
|
@ -1,12 +1,9 @@
|
|||
package xmpp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -16,6 +13,7 @@ import (
|
|||
"dev.narayana.im/narayana/telegabber/xmpp/extensions"
|
||||
"dev.narayana.im/narayana/telegabber/xmpp/gateway"
|
||||
|
||||
"github.com/google/uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/soheilhy/args"
|
||||
"gosrc.io/xmpp"
|
||||
|
|
@ -26,8 +24,6 @@ const (
|
|||
TypeVCardTemp byte = iota
|
||||
TypeVCard4
|
||||
)
|
||||
const NodeVCard4 string = "urn:xmpp:vcard4"
|
||||
const NSCommand string = "http://jabber.org/protocol/commands"
|
||||
|
||||
func logPacketType(p stanza.Packet) {
|
||||
log.Warnf("Ignoring packet: %T\n", p)
|
||||
|
|
@ -42,18 +38,22 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) {
|
|||
}
|
||||
|
||||
log.Debugf("%#v", iq)
|
||||
if iq.Type == "get" {
|
||||
if iq.Type == stanza.IQTypeGet {
|
||||
_, ok := iq.Payload.(*extensions.IqVcardTemp)
|
||||
if ok {
|
||||
go handleGetVcardIq(s, iq, TypeVCardTemp)
|
||||
return
|
||||
}
|
||||
pubsub, ok := iq.Payload.(*stanza.PubSubGeneric)
|
||||
if ok {
|
||||
if pubsub.Items != nil && pubsub.Items.Node == NodeVCard4 {
|
||||
if ok && pubsub.Items != nil {
|
||||
if pubsub.Items.Node == gateway.NodeVCard4 {
|
||||
go handleGetVcardIq(s, iq, TypeVCard4)
|
||||
return
|
||||
}
|
||||
if pubsub.Items.Node == gateway.NodeAvatarData {
|
||||
go handleGetAvatarDataIq(s, iq, pubsub)
|
||||
return
|
||||
}
|
||||
}
|
||||
discoInfo, ok := iq.Payload.(*stanza.DiscoInfo)
|
||||
if ok {
|
||||
|
|
@ -70,7 +70,7 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) {
|
|||
go handleGetQueryRegister(s, iq)
|
||||
return
|
||||
}
|
||||
} else if iq.Type == "set" {
|
||||
} else if iq.Type == stanza.IQTypeSet {
|
||||
query, ok := iq.Payload.(*extensions.QueryRegister)
|
||||
if ok {
|
||||
go handleSetQueryRegister(s, iq, query)
|
||||
|
|
@ -81,6 +81,12 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) {
|
|||
go handleSetQueryCommand(s, iq, command)
|
||||
return
|
||||
}
|
||||
} else if iq.Type == stanza.IQTypeResult {
|
||||
discoInfo, ok := iq.Payload.(*stanza.DiscoInfo)
|
||||
if ok {
|
||||
go handleClientFeatures(s, iq, discoInfo)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -419,6 +425,7 @@ func handlePresence(s xmpp.Sender, p stanza.Presence) {
|
|||
newArgs...,
|
||||
)
|
||||
}
|
||||
probeClientFeatures(p.From, component)
|
||||
session.UpdateChatNicknames()
|
||||
}
|
||||
}()
|
||||
|
|
@ -475,6 +482,113 @@ func handleGetVcardIq(s xmpp.Sender, iq *stanza.IQ, typ byte) {
|
|||
_ = gateway.ResumableSend(component, &answer)
|
||||
}
|
||||
|
||||
func handleGetAvatarDataIq(s xmpp.Sender, iq *stanza.IQ, pubsub *stanza.PubSubGeneric) {
|
||||
fromJid, err := stanza.NewJid(iq.From)
|
||||
if err != nil {
|
||||
log.Errorf("Invalid from JID %v", iq.From)
|
||||
return
|
||||
}
|
||||
|
||||
chatId, ok := toToID(iq.To)
|
||||
if !ok {
|
||||
log.Errorf("Invalid chat id in To JID %v", iq.To)
|
||||
return
|
||||
}
|
||||
|
||||
session, ok := sessions[fromJid.Bare()]
|
||||
if !ok {
|
||||
log.Errorf("IQ from stranger %v", iq.From)
|
||||
return
|
||||
}
|
||||
|
||||
var id string
|
||||
if len(pubsub.Items.List) > 0 {
|
||||
id = pubsub.Items.List[0].Id
|
||||
}
|
||||
log.Infof("Avatar id %v for chat %v", id, iq.To);
|
||||
|
||||
pubsubAnswer := stanza.PubSubGeneric{
|
||||
Items: &stanza.Items{
|
||||
Node: gateway.NodeAvatarData,
|
||||
},
|
||||
}
|
||||
|
||||
answer := stanza.IQ{
|
||||
Attrs: stanza.Attrs{
|
||||
From: iq.To,
|
||||
To: iq.From,
|
||||
Id: iq.Id,
|
||||
Type: "result",
|
||||
},
|
||||
Payload: &pubsubAnswer,
|
||||
}
|
||||
|
||||
component, ok := s.(*xmpp.Component)
|
||||
if !ok {
|
||||
log.Error("Not a component")
|
||||
return
|
||||
}
|
||||
|
||||
defer gateway.ResumableSend(component, &answer)
|
||||
|
||||
hashedAvatar, ok := session.AvatarHashes[chatId]
|
||||
if !ok {
|
||||
log.Info("Could not find avatar in cache, fetching immediately")
|
||||
|
||||
chat, _, err := session.GetContactByID(chatId, nil)
|
||||
if err != nil || chat == nil || chat.Photo == nil {
|
||||
return
|
||||
}
|
||||
|
||||
file := chat.Photo.Small
|
||||
|
||||
sha1 := session.GetPhotoSha1(file, chatId)
|
||||
hashedAvatar = &telegram.HashedAvatar{
|
||||
Hash: sha1,
|
||||
File: file.Id,
|
||||
}
|
||||
|
||||
session.AvatarHashesLock.Lock()
|
||||
session.AvatarHashes[chatId] = hashedAvatar
|
||||
session.AvatarHashesLock.Unlock()
|
||||
}
|
||||
|
||||
if id != "" && hashedAvatar.Hash != id {
|
||||
log.Infof("Cache contains %v hash for chat %v, but %v was requested; aborting", hashedAvatar.Hash, iq.To, id)
|
||||
return
|
||||
}
|
||||
if hashedAvatar.File == 0 {
|
||||
log.Infof("Avatar for chat %v is explicitly missing", iq.To)
|
||||
return
|
||||
}
|
||||
|
||||
file, err := session.GetFile(hashedAvatar.File)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"chatId": chatId,
|
||||
}).Error(errors.Wrap(err, "Cannot get avatar file"))
|
||||
return
|
||||
}
|
||||
|
||||
dataString := session.GetPhotoBase64(file)
|
||||
if dataString == "" {
|
||||
log.Errorf("Error reading avatar file for chat %v", iq.To)
|
||||
return
|
||||
}
|
||||
|
||||
pubsubAnswer.Items.List = append(pubsubAnswer.Items.List, stanza.Item{
|
||||
Id: hashedAvatar.Hash,
|
||||
Any: &stanza.Node{
|
||||
XMLName: xml.Name{Local: "data", Space: gateway.NodeAvatarData},
|
||||
Content: dataString,
|
||||
},
|
||||
})
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"length": len(dataString),
|
||||
}).Debugf("%#v", answer)
|
||||
}
|
||||
|
||||
func getTelegramChatType(from string, to string) (telegram.ChatType, error) {
|
||||
toId, ok := toToID(to)
|
||||
if ok {
|
||||
|
|
@ -514,7 +628,7 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ, di *stanza.DiscoInfo) {
|
|||
disco.AddIdentity("Telegram Gateway", "gateway", "telegram")
|
||||
disco.AddFeatures("jabber:iq:register")
|
||||
}
|
||||
disco.AddFeatures(NSCommand)
|
||||
disco.AddFeatures(gateway.NSCommand)
|
||||
} else {
|
||||
chatType, chatTypeErr := getTelegramChatType(iq.From, iq.To)
|
||||
|
||||
|
|
@ -532,7 +646,7 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ, di *stanza.DiscoInfo) {
|
|||
}
|
||||
answer.Payload = di
|
||||
di.AddIdentity(telegram.CommandToHelpString(name, command), "automation", "command-node")
|
||||
di.AddFeatures(NSCommand, "jabber:x:data")
|
||||
di.AddFeatures(gateway.NSCommand, "jabber:x:data")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
@ -566,7 +680,7 @@ func handleGetDiscoItems(s xmpp.Sender, iq *stanza.IQ, di *stanza.DiscoItems) {
|
|||
log.Debugf("discoItems: %#v", di)
|
||||
|
||||
_, ok := toToID(iq.To)
|
||||
if di.Node == NSCommand {
|
||||
if di.Node == gateway.NSCommand {
|
||||
answer.Payload = di
|
||||
|
||||
chatType, chatTypeErr := getTelegramChatType(iq.From, iq.To)
|
||||
|
|
@ -947,6 +1061,85 @@ func iqAnswerSetError(answer *stanza.IQ, payload *extensions.QueryRegister, code
|
|||
}
|
||||
}
|
||||
|
||||
func probeClientFeatures(jid string, component *xmpp.Component) {
|
||||
id, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
log.Error("Could not generate ID for a client features probe")
|
||||
return
|
||||
}
|
||||
|
||||
probe := stanza.IQ{
|
||||
Attrs: stanza.Attrs{
|
||||
From: gateway.Jid.Bare(),
|
||||
To: jid,
|
||||
Id: id.String(),
|
||||
Type: stanza.IQTypeGet,
|
||||
},
|
||||
Payload: &stanza.DiscoInfo{},
|
||||
}
|
||||
log.Debugf("%#v", probe)
|
||||
|
||||
gateway.ResumableSend(component, &probe)
|
||||
}
|
||||
|
||||
func handleClientFeatures(s xmpp.Sender, iq *stanza.IQ, discoInfo *stanza.DiscoInfo) {
|
||||
fromJid, err := stanza.NewJid(iq.From)
|
||||
if err != nil {
|
||||
log.Error("Invalid from JID!")
|
||||
return
|
||||
}
|
||||
bareFrom := fromJid.Bare()
|
||||
|
||||
session, ok := sessions[bareFrom]
|
||||
if !ok {
|
||||
log.Errorf("Got client features for unknown JID %v", bareFrom)
|
||||
return
|
||||
}
|
||||
|
||||
var features []string
|
||||
var avatarNotify bool
|
||||
for _, feature := range discoInfo.Features {
|
||||
features = append(features, feature.Var)
|
||||
if feature.Var == gateway.NodeAvatarMetadataNotify {
|
||||
avatarNotify = true
|
||||
}
|
||||
}
|
||||
|
||||
session.XmppClientFeaturesLock.Lock()
|
||||
session.XmppClientFeatures[fromJid.Resource] = &features
|
||||
session.XmppClientFeaturesLock.Unlock()
|
||||
|
||||
log.Debugf("Features for %v: %#v", iq.From, features)
|
||||
|
||||
if avatarNotify {
|
||||
go sendPubSubAvatarNotifications(s, iq.From, session)
|
||||
}
|
||||
}
|
||||
|
||||
func sendPubSubAvatarNotifications(s xmpp.Sender, jid string, session *telegram.Client) {
|
||||
component, ok := s.(*xmpp.Component)
|
||||
if !ok {
|
||||
log.Error("Not a component")
|
||||
return
|
||||
}
|
||||
|
||||
for _, chatId := range session.ChatsKeys() {
|
||||
chat, _, err := session.GetContactByID(chatId, nil)
|
||||
if err != nil || chat == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if chat.Photo == nil {
|
||||
session.SetEmptyAvatarHash(chatId)
|
||||
continue
|
||||
}
|
||||
|
||||
sha1, size := session.GetPhotoSha1AndSize(chat.Photo.Small, chat.Id)
|
||||
|
||||
gateway.SendPubSubAvatarNotification(component, jid, chat.Id, sha1, size)
|
||||
}
|
||||
}
|
||||
|
||||
func toToID(to string) (int64, bool) {
|
||||
toParts := strings.Split(to, "@")
|
||||
if len(toParts) < 2 {
|
||||
|
|
@ -965,24 +1158,7 @@ func toToID(to string) (int64, bool) {
|
|||
func makeVCardPayload(typ byte, id string, info telegram.VCardInfo, session *telegram.Client) stanza.IQPayload {
|
||||
var base64Photo string
|
||||
if info.Photo != nil {
|
||||
file, path, err := session.ForceOpenFile(info.Photo, 32)
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
binval := base64.NewEncoder(base64.StdEncoding, buf)
|
||||
_, err = io.Copy(binval, file)
|
||||
binval.Close()
|
||||
if err == nil {
|
||||
base64Photo = buf.String()
|
||||
} else {
|
||||
log.Errorf("Error calculating base64: %v", path)
|
||||
}
|
||||
} else if path != "" {
|
||||
log.Errorf("Photo does not exist: %v", path)
|
||||
} else {
|
||||
log.Errorf("PHOTO: %#v", err.Error())
|
||||
}
|
||||
base64Photo = session.GetPhotoBase64(info.Photo)
|
||||
}
|
||||
|
||||
if typ == TypeVCardTemp {
|
||||
|
|
@ -1083,7 +1259,7 @@ func makeVCardPayload(typ byte, id string, info telegram.VCardInfo, session *tel
|
|||
|
||||
pubsub := &stanza.PubSubGeneric{
|
||||
Items: &stanza.Items{
|
||||
Node: NodeVCard4,
|
||||
Node: gateway.NodeVCard4,
|
||||
List: []stanza.Item{
|
||||
stanza.Item{
|
||||
Id: id,
|
||||
|
|
|
|||
Loading…
Reference in a new issue