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
|
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
|
// Client stores the metadata for lazily invoked TDlib instance
|
||||||
type Client struct {
|
type Client struct {
|
||||||
client *client.Client
|
client *client.Client
|
||||||
|
|
@ -48,6 +54,12 @@ type Client struct {
|
||||||
lastMsgIds map[int64]string
|
lastMsgIds map[int64]string
|
||||||
msgHashSeed maphash.Seed
|
msgHashSeed maphash.Seed
|
||||||
|
|
||||||
|
XmppClientFeatures map[string]*[]string
|
||||||
|
XmppClientFeaturesLock sync.Mutex
|
||||||
|
|
||||||
|
AvatarHashes map[int64]*HashedAvatar
|
||||||
|
AvatarHashesLock sync.Mutex
|
||||||
|
|
||||||
locks clientLocks
|
locks clientLocks
|
||||||
SendMessageLock sync.Mutex
|
SendMessageLock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
@ -123,6 +135,8 @@ func NewClient(conf config.TelegramConfig, jid string, component *xmpp.Component
|
||||||
lastMsgHashes: make(map[int64]uint64),
|
lastMsgHashes: make(map[int64]uint64),
|
||||||
lastMsgIds: make(map[int64]string),
|
lastMsgIds: make(map[int64]string),
|
||||||
msgHashSeed: maphash.MakeSeed(),
|
msgHashSeed: maphash.MakeSeed(),
|
||||||
|
XmppClientFeatures: make(map[string]*[]string),
|
||||||
|
AvatarHashes: make(map[int64]*HashedAvatar),
|
||||||
locks: clientLocks{
|
locks: clientLocks{
|
||||||
chatMessageLocks: make(map[int64]*sync.Mutex),
|
chatMessageLocks: make(map[int64]*sync.Mutex),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package telegram
|
package telegram
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
@ -45,6 +47,11 @@ type messageStub struct {
|
||||||
Text string
|
Text string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
typeFileDataSha1 byte = iota
|
||||||
|
typeFileDataBase64
|
||||||
|
)
|
||||||
|
|
||||||
var errOffline = errors.New("TDlib instance is offline")
|
var errOffline = errors.New("TDlib instance is offline")
|
||||||
var errOverLimit = errors.New("Over limit")
|
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")
|
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
|
// ProcessStatusUpdate sets contact status
|
||||||
func (c *Client) ProcessStatusUpdate(chatID int64, status string, show string, oldArgs ...args.V) error {
|
func (c *Client) ProcessStatusUpdate(chatID int64, status string, show string, oldArgs ...args.V) error {
|
||||||
if !c.Online() {
|
if !c.Online() {
|
||||||
|
|
@ -275,20 +361,7 @@ func (c *Client) ProcessStatusUpdate(chatID int64, status string, show string, o
|
||||||
|
|
||||||
var photo string
|
var photo string
|
||||||
if chat != nil && chat.Photo != nil {
|
if chat != nil && chat.Photo != nil {
|
||||||
file, path, err := c.ForceOpenFile(chat.Photo.Small, 1)
|
photo = c.GetPhotoSha1(chat.Photo.Small, chatID)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var presenceType string
|
var presenceType string
|
||||||
|
|
@ -1094,6 +1167,24 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) {
|
||||||
c.cache.SetChat(chatId, chat)
|
c.cache.SetChat(chatId, chat)
|
||||||
go c.ProcessStatusUpdate(chatId, "", "", gateway.SPImmed(true))
|
go c.ProcessStatusUpdate(chatId, "", "", gateway.SPImmed(true))
|
||||||
text = "<Chat photo has changed>"
|
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 {
|
} else {
|
||||||
text = c.messageToText(message, false)
|
text = c.messageToText(message, false)
|
||||||
|
|
@ -1315,6 +1406,11 @@ func (c *Client) prepareOutgoingMessageContent(text string, file *client.InputFi
|
||||||
return content
|
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
|
// StatusesRange proxies the following function from unexported cache
|
||||||
func (c *Client) StatusesRange() chan *cache.Status {
|
func (c *Client) StatusesRange() chan *cache.Status {
|
||||||
return c.cache.StatusesRange()
|
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
|
// DownloadFile actually obtains a file by id given by TDlib
|
||||||
func (c *Client) DownloadFile(id int32, priority int32, synchronous bool) (*client.File, error) {
|
func (c *Client) DownloadFile(id int32, priority int32, synchronous bool) (*client.File, error) {
|
||||||
return c.client.DownloadFile(&client.DownloadFileRequest{
|
return c.client.DownloadFile(&client.DownloadFileRequest{
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,11 @@ type marker struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
const NSNick string = "http://jabber.org/protocol/nick"
|
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
|
// Queue stores presences to send later
|
||||||
var Queue = make(map[string]*stanza.Presence)
|
var Queue = make(map[string]*stanza.Presence)
|
||||||
|
|
@ -435,3 +440,46 @@ func SplitJID(from string) (string, string, bool) {
|
||||||
}
|
}
|
||||||
return fromJid.Bare(), fromJid.Resource, true
|
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
|
package xmpp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"io"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -16,6 +13,7 @@ import (
|
||||||
"dev.narayana.im/narayana/telegabber/xmpp/extensions"
|
"dev.narayana.im/narayana/telegabber/xmpp/extensions"
|
||||||
"dev.narayana.im/narayana/telegabber/xmpp/gateway"
|
"dev.narayana.im/narayana/telegabber/xmpp/gateway"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/soheilhy/args"
|
"github.com/soheilhy/args"
|
||||||
"gosrc.io/xmpp"
|
"gosrc.io/xmpp"
|
||||||
|
|
@ -26,8 +24,6 @@ const (
|
||||||
TypeVCardTemp byte = iota
|
TypeVCardTemp byte = iota
|
||||||
TypeVCard4
|
TypeVCard4
|
||||||
)
|
)
|
||||||
const NodeVCard4 string = "urn:xmpp:vcard4"
|
|
||||||
const NSCommand string = "http://jabber.org/protocol/commands"
|
|
||||||
|
|
||||||
func logPacketType(p stanza.Packet) {
|
func logPacketType(p stanza.Packet) {
|
||||||
log.Warnf("Ignoring packet: %T\n", p)
|
log.Warnf("Ignoring packet: %T\n", p)
|
||||||
|
|
@ -42,18 +38,22 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) {
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("%#v", iq)
|
log.Debugf("%#v", iq)
|
||||||
if iq.Type == "get" {
|
if iq.Type == stanza.IQTypeGet {
|
||||||
_, ok := iq.Payload.(*extensions.IqVcardTemp)
|
_, ok := iq.Payload.(*extensions.IqVcardTemp)
|
||||||
if ok {
|
if ok {
|
||||||
go handleGetVcardIq(s, iq, TypeVCardTemp)
|
go handleGetVcardIq(s, iq, TypeVCardTemp)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pubsub, ok := iq.Payload.(*stanza.PubSubGeneric)
|
pubsub, ok := iq.Payload.(*stanza.PubSubGeneric)
|
||||||
if ok {
|
if ok && pubsub.Items != nil {
|
||||||
if pubsub.Items != nil && pubsub.Items.Node == NodeVCard4 {
|
if pubsub.Items.Node == gateway.NodeVCard4 {
|
||||||
go handleGetVcardIq(s, iq, TypeVCard4)
|
go handleGetVcardIq(s, iq, TypeVCard4)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if pubsub.Items.Node == gateway.NodeAvatarData {
|
||||||
|
go handleGetAvatarDataIq(s, iq, pubsub)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
discoInfo, ok := iq.Payload.(*stanza.DiscoInfo)
|
discoInfo, ok := iq.Payload.(*stanza.DiscoInfo)
|
||||||
if ok {
|
if ok {
|
||||||
|
|
@ -70,7 +70,7 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) {
|
||||||
go handleGetQueryRegister(s, iq)
|
go handleGetQueryRegister(s, iq)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if iq.Type == "set" {
|
} else if iq.Type == stanza.IQTypeSet {
|
||||||
query, ok := iq.Payload.(*extensions.QueryRegister)
|
query, ok := iq.Payload.(*extensions.QueryRegister)
|
||||||
if ok {
|
if ok {
|
||||||
go handleSetQueryRegister(s, iq, query)
|
go handleSetQueryRegister(s, iq, query)
|
||||||
|
|
@ -81,6 +81,12 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) {
|
||||||
go handleSetQueryCommand(s, iq, command)
|
go handleSetQueryCommand(s, iq, command)
|
||||||
return
|
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...,
|
newArgs...,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
probeClientFeatures(p.From, component)
|
||||||
session.UpdateChatNicknames()
|
session.UpdateChatNicknames()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
@ -475,6 +482,113 @@ func handleGetVcardIq(s xmpp.Sender, iq *stanza.IQ, typ byte) {
|
||||||
_ = gateway.ResumableSend(component, &answer)
|
_ = 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) {
|
func getTelegramChatType(from string, to string) (telegram.ChatType, error) {
|
||||||
toId, ok := toToID(to)
|
toId, ok := toToID(to)
|
||||||
if ok {
|
if ok {
|
||||||
|
|
@ -514,7 +628,7 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ, di *stanza.DiscoInfo) {
|
||||||
disco.AddIdentity("Telegram Gateway", "gateway", "telegram")
|
disco.AddIdentity("Telegram Gateway", "gateway", "telegram")
|
||||||
disco.AddFeatures("jabber:iq:register")
|
disco.AddFeatures("jabber:iq:register")
|
||||||
}
|
}
|
||||||
disco.AddFeatures(NSCommand)
|
disco.AddFeatures(gateway.NSCommand)
|
||||||
} else {
|
} else {
|
||||||
chatType, chatTypeErr := getTelegramChatType(iq.From, iq.To)
|
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
|
answer.Payload = di
|
||||||
di.AddIdentity(telegram.CommandToHelpString(name, command), "automation", "command-node")
|
di.AddIdentity(telegram.CommandToHelpString(name, command), "automation", "command-node")
|
||||||
di.AddFeatures(NSCommand, "jabber:x:data")
|
di.AddFeatures(gateway.NSCommand, "jabber:x:data")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -566,7 +680,7 @@ func handleGetDiscoItems(s xmpp.Sender, iq *stanza.IQ, di *stanza.DiscoItems) {
|
||||||
log.Debugf("discoItems: %#v", di)
|
log.Debugf("discoItems: %#v", di)
|
||||||
|
|
||||||
_, ok := toToID(iq.To)
|
_, ok := toToID(iq.To)
|
||||||
if di.Node == NSCommand {
|
if di.Node == gateway.NSCommand {
|
||||||
answer.Payload = di
|
answer.Payload = di
|
||||||
|
|
||||||
chatType, chatTypeErr := getTelegramChatType(iq.From, iq.To)
|
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) {
|
func toToID(to string) (int64, bool) {
|
||||||
toParts := strings.Split(to, "@")
|
toParts := strings.Split(to, "@")
|
||||||
if len(toParts) < 2 {
|
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 {
|
func makeVCardPayload(typ byte, id string, info telegram.VCardInfo, session *telegram.Client) stanza.IQPayload {
|
||||||
var base64Photo string
|
var base64Photo string
|
||||||
if info.Photo != nil {
|
if info.Photo != nil {
|
||||||
file, path, err := session.ForceOpenFile(info.Photo, 32)
|
base64Photo = session.GetPhotoBase64(info.Photo)
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if typ == TypeVCardTemp {
|
if typ == TypeVCardTemp {
|
||||||
|
|
@ -1083,7 +1259,7 @@ func makeVCardPayload(typ byte, id string, info telegram.VCardInfo, session *tel
|
||||||
|
|
||||||
pubsub := &stanza.PubSubGeneric{
|
pubsub := &stanza.PubSubGeneric{
|
||||||
Items: &stanza.Items{
|
Items: &stanza.Items{
|
||||||
Node: NodeVCard4,
|
Node: gateway.NodeVCard4,
|
||||||
List: []stanza.Item{
|
List: []stanza.Item{
|
||||||
stanza.Item{
|
stanza.Item{
|
||||||
Id: id,
|
Id: id,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue