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
|
||||
users map[int64]*client.User
|
||||
statuses map[int64]*Status
|
||||
capsVers map[int64]string
|
||||
chatsLock sync.Mutex
|
||||
usersLock sync.Mutex
|
||||
statusesLock sync.Mutex
|
||||
capsVersLock sync.Mutex
|
||||
}
|
||||
|
||||
// NewCache initializes a cache
|
||||
|
@ -106,6 +108,15 @@ func (cache *Cache) GetStatus(id int64) (*Status, bool) {
|
|||
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
|
||||
func (cache *Cache) SetChat(id int64, chat *client.Chat) {
|
||||
cache.chatsLock.Lock()
|
||||
|
@ -133,3 +144,11 @@ func (cache *Cache) SetStatus(id int64, show string, status string) {
|
|||
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))
|
||||
}
|
||||
|
||||
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(
|
||||
c.xmpp,
|
||||
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
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"github.com/pkg/errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
|
@ -37,6 +42,19 @@ var DirtySessions = false
|
|||
// MessageOutgoingPermission allows to fake outgoing messages by foreign JIDs
|
||||
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
|
||||
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)
|
||||
|
@ -225,6 +243,9 @@ var SPResource = args.NewString()
|
|||
// SPImmed skips queueing
|
||||
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 {
|
||||
var presenceFrom string
|
||||
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
|
||||
}
|
||||
|
@ -356,3 +387,104 @@ func SplitJID(from string) (string, string, bool) {
|
|||
}
|
||||
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"))
|
||||
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) {
|
||||
iqDisco, ok := iq.Payload.(*stanza.DiscoInfo)
|
||||
if !ok {
|
||||
log.Error("Not a disco info request")
|
||||
return
|
||||
}
|
||||
answer, err := stanza.NewIQ(stanza.Attrs{
|
||||
Type: stanza.IQTypeResult,
|
||||
From: iq.To,
|
||||
|
@ -391,13 +396,15 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
|
|||
return
|
||||
}
|
||||
|
||||
disco := answer.DiscoInfo()
|
||||
_, ok := toToID(iq.To)
|
||||
typ gateway.ContactType
|
||||
if ok {
|
||||
disco.AddIdentity("", "account", "registered")
|
||||
typ = gateway.ContactPM
|
||||
} else {
|
||||
disco.AddIdentity("Telegram Gateway", "gateway", "telegram")
|
||||
typ = gateway.ContactTransport
|
||||
}
|
||||
disco := gateway.GetDiscoInfo(typ, []string{})
|
||||
disco.Node = iqDisco.Node
|
||||
answer.Payload = disco
|
||||
|
||||
log.Debugf("%#v", answer)
|
||||
|
|
Loading…
Reference in a new issue