Compare commits
2 commits
Author | SHA1 | Date | |
---|---|---|---|
Bohdan Horbeshko | 94b51a70df | ||
Bohdan Horbeshko | 9c28af848d |
19
telegram/cache/cache.go
vendored
19
telegram/cache/cache.go
vendored
|
@ -19,9 +19,11 @@ type Cache struct {
|
||||||
chats map[int64]*client.Chat
|
chats map[int64]*client.Chat
|
||||||
users map[int64]*client.User
|
users map[int64]*client.User
|
||||||
statuses map[int64]*Status
|
statuses map[int64]*Status
|
||||||
|
capsVers map[int64]string
|
||||||
chatsLock sync.Mutex
|
chatsLock sync.Mutex
|
||||||
usersLock sync.Mutex
|
usersLock sync.Mutex
|
||||||
statusesLock sync.Mutex
|
statusesLock sync.Mutex
|
||||||
|
capsVersLock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCache initializes a cache
|
// NewCache initializes a cache
|
||||||
|
@ -106,6 +108,15 @@ func (cache *Cache) GetStatus(id int64) (*Status, bool) {
|
||||||
return status, ok
|
return status, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCapsVer retrieves capabilities verification string by id if it's present in the cache
|
||||||
|
func (cache *Cache) GetCapsVer(id int64) (string, bool) {
|
||||||
|
cache.capsVersLock.Lock()
|
||||||
|
defer cache.capsVersLock.Unlock()
|
||||||
|
|
||||||
|
ver, ok := cache.capsVers[id]
|
||||||
|
return ver, ok
|
||||||
|
}
|
||||||
|
|
||||||
// SetChat stores a chat in the cache
|
// SetChat stores a chat in the cache
|
||||||
func (cache *Cache) SetChat(id int64, chat *client.Chat) {
|
func (cache *Cache) SetChat(id int64, chat *client.Chat) {
|
||||||
cache.chatsLock.Lock()
|
cache.chatsLock.Lock()
|
||||||
|
@ -133,3 +144,11 @@ func (cache *Cache) SetStatus(id int64, show string, status string) {
|
||||||
Description: status,
|
Description: status,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetCapsVer stores a capabilities verification string in the cache
|
||||||
|
func (cache *Cache) SetCapsVer(id int64, ver string) {
|
||||||
|
cache.capsVersLock.Lock()
|
||||||
|
defer cache.capsVersLock.Unlock()
|
||||||
|
|
||||||
|
cache.capsVers[id] = ver
|
||||||
|
}
|
||||||
|
|
|
@ -253,6 +253,20 @@ func (c *Client) ProcessStatusUpdate(chatID int64, status string, show string, o
|
||||||
newArgs = append(newArgs, gateway.SPType(presenceType))
|
newArgs = append(newArgs, gateway.SPType(presenceType))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ver, ok := c.cache.GetCapsVer(chatID)
|
||||||
|
if !ok {
|
||||||
|
if c.isCallable(chat, user) {
|
||||||
|
ver, err = gateway.GetCapsVer([]gateway.CapsType{gateway.CapsAudio}})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("<caps ver error: %s>", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.cache.SetCapsVer(ver)
|
||||||
|
}
|
||||||
|
if ver != "" {
|
||||||
|
newArgs = append(newArgs, gateway.SPCaps(ver))
|
||||||
|
}
|
||||||
|
|
||||||
return gateway.SendPresence(
|
return gateway.SendPresence(
|
||||||
c.xmpp,
|
c.xmpp,
|
||||||
c.jid,
|
c.jid,
|
||||||
|
@ -1248,3 +1262,23 @@ func (c *Client) prepareDiskSpace(size uint64) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) isCallable(chat *client.Chat, user, *client.User) bool {
|
||||||
|
if chat == nil || user == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
chatType := chat.Type.ChatTypeType()
|
||||||
|
if chatType == client.TypeChatTypePrivate {
|
||||||
|
privateType, _ := chat.Type.(*client.ChatTypePrivate)
|
||||||
|
fullInfo, err := c.client.GetUserFullInfo(&client.GetUserFullInfoRequest{
|
||||||
|
UserId: privateType.UserId,
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
return fullInfo.CanBeCalled && (user.Username != "" || user.PhoneNumber != "")
|
||||||
|
} else {
|
||||||
|
log.Warnf("Coudln't retrieve private chat info: %v", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
package gateway
|
package gateway
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
@ -37,6 +42,19 @@ var DirtySessions = false
|
||||||
// MessageOutgoingPermission allows to fake outgoing messages by foreign JIDs
|
// MessageOutgoingPermission allows to fake outgoing messages by foreign JIDs
|
||||||
var MessageOutgoingPermission = false
|
var MessageOutgoingPermission = false
|
||||||
|
|
||||||
|
// CapsType is a capability category
|
||||||
|
type CapsType int
|
||||||
|
const (
|
||||||
|
CapsAudio CapsType = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
// ContactType is a disco JID category
|
||||||
|
type ContactType int
|
||||||
|
const (
|
||||||
|
ContactTransport CapsType = iota
|
||||||
|
ContactPM
|
||||||
|
)
|
||||||
|
|
||||||
// SendMessage creates and sends a message stanza
|
// SendMessage creates and sends a message stanza
|
||||||
func SendMessage(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, isOutgoing bool) {
|
func SendMessage(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, isOutgoing bool) {
|
||||||
sendMessageWrapper(to, from, body, id, component, reply, "", isOutgoing)
|
sendMessageWrapper(to, from, body, id, component, reply, "", isOutgoing)
|
||||||
|
@ -225,6 +243,9 @@ var SPResource = args.NewString()
|
||||||
// SPImmed skips queueing
|
// SPImmed skips queueing
|
||||||
var SPImmed = args.NewBool(args.Default(true))
|
var SPImmed = args.NewBool(args.Default(true))
|
||||||
|
|
||||||
|
// SPCaps is a XEP-0115 verification string
|
||||||
|
var SPCaps = args.NewString()
|
||||||
|
|
||||||
func newPresence(bareJid string, to string, args ...args.V) stanza.Presence {
|
func newPresence(bareJid string, to string, args ...args.V) stanza.Presence {
|
||||||
var presenceFrom string
|
var presenceFrom string
|
||||||
if SPFrom.IsSet(args) {
|
if SPFrom.IsSet(args) {
|
||||||
|
@ -280,6 +301,16 @@ func newPresence(bareJid string, to string, args ...args.V) stanza.Presence {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if SPCaps.IsSet(args) {
|
||||||
|
ver := SPCaps.Get(args)
|
||||||
|
if ver != "" {
|
||||||
|
presence.Extensions = append(presence.Extensions, extensions.CapsExtension{
|
||||||
|
Hash: "sha-1",
|
||||||
|
Node: "https://dev.narayana.im/narayana/telegabber/",
|
||||||
|
Ver: ver,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return presence
|
return presence
|
||||||
}
|
}
|
||||||
|
@ -356,3 +387,104 @@ func SplitJID(from string) (string, string, bool) {
|
||||||
}
|
}
|
||||||
return fromJid.Bare(), fromJid.Resource, true
|
return fromJid.Bare(), fromJid.Resource, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getDiscoFeatures(caps []CapsType) []string {
|
||||||
|
features := []string{
|
||||||
|
"http://jabber.org/protocol/caps",
|
||||||
|
"http://jabber.org/protocol/disco#info",
|
||||||
|
}
|
||||||
|
for typ := range features {
|
||||||
|
switch typ {
|
||||||
|
case CapsAudio:
|
||||||
|
features = append(
|
||||||
|
features,
|
||||||
|
"urn:xmpp:jingle-message:0",
|
||||||
|
"urn:xmpp:jingle:1",
|
||||||
|
"urn:xmpp:jingle:apps:dtls:0",
|
||||||
|
"urn:xmpp:jingle:apps:rtp:1",
|
||||||
|
"urn:xmpp:jingle:apps:rtp:audio",
|
||||||
|
"urn:xmpp:jingle:transports:ice-udp:1",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return features
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDiscoInfo generates a disco info IQ query response
|
||||||
|
func GetDiscoInfo(typ ContactType, features []string) *stanza.DiscoInfo {
|
||||||
|
disco := stanza.DiscoInfo{}
|
||||||
|
if typ == ContactPM {
|
||||||
|
disco.AddIdentity("", "account", "registered")
|
||||||
|
} else {
|
||||||
|
disco.AddIdentity("Telegram Gateway", "gateway", "telegram")
|
||||||
|
}
|
||||||
|
disco.AddFeatures(features...)
|
||||||
|
return &disco
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// GetCapsVer hashes a capabilities set into a verification string
|
||||||
|
func GetCapsVer(caps []CapsType) (string, error) {
|
||||||
|
features := getDiscoFeatures(caps)
|
||||||
|
disco := GetDiscoInfo(features)
|
||||||
|
discoToCapsHash(disco)
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
binval := base64.NewEncoder(base64.StdEncoding, buf)
|
||||||
|
_, err = io.Copy(binval, file)
|
||||||
|
binval.Close()
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(err, "Error calculating caps base64")
|
||||||
|
}
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func iOctetComparator(a, b string) bool {
|
||||||
|
return a < b
|
||||||
|
}
|
||||||
|
|
||||||
|
func discoToCaps(disco *stanza.DiscoInfo) string {
|
||||||
|
var s strings.Builder
|
||||||
|
var identities, vars, capsForms []string
|
||||||
|
|
||||||
|
for _, identity := range disco.Identity {
|
||||||
|
identities = append(identities, fmt.Sprintf(
|
||||||
|
"%s/%s//%s",
|
||||||
|
identity.Category,
|
||||||
|
identity.Type,
|
||||||
|
identity.Name,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
sort.Slice(identities, iOctetComparator)
|
||||||
|
for _, identity := range identities {
|
||||||
|
s.WriteString(identity)
|
||||||
|
s.WriteString(">")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, feature := range disco.Features {
|
||||||
|
vars = append(vars, feature.Var)
|
||||||
|
}
|
||||||
|
sort.Slice(vars, iOctetComparator)
|
||||||
|
for _, var := range vars {
|
||||||
|
s.WriteString(var)
|
||||||
|
s.WriteString(">")
|
||||||
|
}
|
||||||
|
|
||||||
|
if disco.Form != nil {
|
||||||
|
fields := make([]*stanza.Field, len(disco.Form.Fields))
|
||||||
|
copy(fields, disco.Form.Fields)
|
||||||
|
sort.Slice(fields, func(a, b *stanza.Field) bool {
|
||||||
|
if a.Var == "FORM_TYPE" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if b.Var == "FORM_TYPE" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return a.Var < b.Var
|
||||||
|
})
|
||||||
|
for _, field := range fields {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.String()
|
||||||
|
}
|
||||||
|
|
|
@ -52,3 +52,8 @@ func TestPresencePhoto(t *testing.T) {
|
||||||
presence := newPresence("from@test", "to@test", SPPhoto("01b87fcd030b72895ff8e88db57ec525450f000d"))
|
presence := newPresence("from@test", "to@test", SPPhoto("01b87fcd030b72895ff8e88db57ec525450f000d"))
|
||||||
testPresence(t, presence, "<presence from=\"from@test\" to=\"to@test\"><x xmlns=\"vcard-temp:x:update\"><photo>01b87fcd030b72895ff8e88db57ec525450f000d</photo></x></presence>")
|
testPresence(t, presence, "<presence from=\"from@test\" to=\"to@test\"><x xmlns=\"vcard-temp:x:update\"><photo>01b87fcd030b72895ff8e88db57ec525450f000d</photo></x></presence>")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPresenceCaps(t *testing.T) {
|
||||||
|
caps := newPresence("from@test", "to@test", SPCaps("QgayPKawpkPSDYmwT/WM94uAlu0="))
|
||||||
|
testPresence(t, presence, "<presence from=\"from@test\" to=\"to@test\"><c xmlns=\"http://jabber.org/protocol/caps\" hash=\"sha-1\" node=\"https://dev.narayana.im/narayana/telegabber\" ver=\"QgayPKawpkPSDYmwT/WM94uAlu0=\"/></presence>")
|
||||||
|
}
|
||||||
|
|
|
@ -379,6 +379,11 @@ func handleGetVcardIq(s xmpp.Sender, iq *stanza.IQ, typ byte) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
|
func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
|
||||||
|
iqDisco, ok := iq.Payload.(*stanza.DiscoInfo)
|
||||||
|
if !ok {
|
||||||
|
log.Error("Not a disco info request")
|
||||||
|
return
|
||||||
|
}
|
||||||
answer, err := stanza.NewIQ(stanza.Attrs{
|
answer, err := stanza.NewIQ(stanza.Attrs{
|
||||||
Type: stanza.IQTypeResult,
|
Type: stanza.IQTypeResult,
|
||||||
From: iq.To,
|
From: iq.To,
|
||||||
|
@ -391,13 +396,15 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
disco := answer.DiscoInfo()
|
|
||||||
_, ok := toToID(iq.To)
|
_, ok := toToID(iq.To)
|
||||||
|
typ gateway.ContactType
|
||||||
if ok {
|
if ok {
|
||||||
disco.AddIdentity("", "account", "registered")
|
typ = gateway.ContactPM
|
||||||
} else {
|
} else {
|
||||||
disco.AddIdentity("Telegram Gateway", "gateway", "telegram")
|
typ = gateway.ContactTransport
|
||||||
}
|
}
|
||||||
|
disco := gateway.GetDiscoInfo(typ, []string{})
|
||||||
|
disco.Node = iqDisco.Node
|
||||||
answer.Payload = disco
|
answer.Payload = disco
|
||||||
|
|
||||||
log.Debugf("%#v", answer)
|
log.Debugf("%#v", answer)
|
||||||
|
|
Loading…
Reference in a new issue