aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBohdan Horbeshko <bodqhrohro@gmail.com>2023-05-17 01:17:44 +0300
committerBohdan Horbeshko <bodqhrohro@gmail.com>2023-05-17 01:17:44 +0300
commit9c28af848d3adc9e998b6456b3ebc71172ca68fd (patch)
treed06e84018b0dbce92815aaf67c1c12536228a282
parent75f0532193440294f4bbf7cd687174b5e9a16249 (diff)
Calls [WIP]
-rw-r--r--telegram/cache/cache.go19
-rw-r--r--telegram/utils.go34
-rw-r--r--xmpp/gateway/gateway.go116
-rw-r--r--xmpp/gateway/gateway_test.go5
-rw-r--r--xmpp/handlers.go13
5 files changed, 184 insertions, 3 deletions
diff --git a/telegram/cache/cache.go b/telegram/cache/cache.go
index 3d9608d..c4a59bf 100644
--- a/telegram/cache/cache.go
+++ b/telegram/cache/cache.go
@@ -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
+}
diff --git a/telegram/utils.go b/telegram/utils.go
index 9a247c4..7fa13d3 100644
--- a/telegram/utils.go
+++ b/telegram/utils.go
@@ -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
+}
diff --git a/xmpp/gateway/gateway.go b/xmpp/gateway/gateway.go
index 534ee7e..9cd2102 100644
--- a/xmpp/gateway/gateway.go
+++ b/xmpp/gateway/gateway.go
@@ -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,88 @@ 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(">")
+ }
+
+ for disco
+ s.WriteString(
+}
diff --git a/xmpp/gateway/gateway_test.go b/xmpp/gateway/gateway_test.go
index 6191844..b75db4d 100644
--- a/xmpp/gateway/gateway_test.go
+++ b/xmpp/gateway/gateway_test.go
@@ -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>")
+}
diff --git a/xmpp/handlers.go b/xmpp/handlers.go
index db2b6ea..4e1aea4 100644
--- a/xmpp/handlers.go
+++ b/xmpp/handlers.go
@@ -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)