Calls [WIP]

This commit is contained in:
Bohdan Horbeshko 2023-05-16 18:17:44 -04:00
parent 75f0532193
commit 9c28af848d
5 changed files with 184 additions and 3 deletions

View file

@ -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
}

View file

@ -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
}

View file

@ -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,88 @@ 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(">")
}
for disco
s.WriteString(
}

View file

@ -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>")
}

View file

@ -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)