Compare commits
19 commits
Author | SHA1 | Date | |
---|---|---|---|
Bohdan Horbeshko | bd5f41a76b | ||
Bohdan Horbeshko | e94a646e19 | ||
Bohdan Horbeshko | 249c942fc2 | ||
Bohdan Horbeshko | 9aec929e71 | ||
Bohdan Horbeshko | 4eae44b9a2 | ||
Bohdan Horbeshko | 43f9603b88 | ||
Bohdan Horbeshko | 154b59de44 | ||
Bohdan Horbeshko | 5dd60450c2 | ||
Bohdan Horbeshko | 0b1cbda1cc | ||
Bohdan Horbeshko | 9b5fee8826 | ||
Bohdan Horbeshko | dc6f99dc3c | ||
Bohdan Horbeshko | 772246ee4b | ||
Bohdan Horbeshko | b0c5302c82 | ||
Bohdan Horbeshko | a0180eff75 | ||
Bohdan Horbeshko | e7d5a2a266 | ||
Bohdan Horbeshko | 21dc5fa6c6 | ||
Bohdan Horbeshko | e3a5191905 | ||
Bohdan Horbeshko | eace19eef7 | ||
Bohdan Horbeshko | fd0d7411c2 |
2
Makefile
2
Makefile
|
@ -2,7 +2,7 @@
|
|||
|
||||
COMMIT := $(shell git rev-parse --short HEAD)
|
||||
TD_COMMIT := "5bbfc1cf5dab94f82e02f3430ded7241d4653551"
|
||||
VERSION := "v1.9.5"
|
||||
VERSION := "v1.10.0-dev"
|
||||
MAKEOPTS := "-j4"
|
||||
|
||||
all:
|
||||
|
|
5
go.mod
5
go.mod
|
@ -4,6 +4,7 @@ go 1.19
|
|||
|
||||
require (
|
||||
github.com/dgraph-io/badger/v4 v4.1.0
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/santhosh-tekuri/jsonschema v1.2.4
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
|
@ -23,7 +24,6 @@ require (
|
|||
github.com/golang/protobuf v1.3.2 // indirect
|
||||
github.com/golang/snappy v0.0.3 // indirect
|
||||
github.com/google/flatbuffers v1.12.1 // indirect
|
||||
github.com/google/uuid v1.1.1 // indirect
|
||||
github.com/klauspost/compress v1.12.3 // indirect
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||
go.opencensus.io v0.22.5 // indirect
|
||||
|
@ -33,5 +33,6 @@ require (
|
|||
nhooyr.io/websocket v1.6.5 // indirect
|
||||
)
|
||||
|
||||
replace gosrc.io/xmpp => dev.narayana.im/narayana/go-xmpp v0.0.0-20220524203317-306b4ff58e8f
|
||||
replace gosrc.io/xmpp => dev.narayana.im/narayana/go-xmpp v0.0.0-20240512132113-6725c3862314
|
||||
|
||||
replace github.com/zelenin/go-tdlib => dev.narayana.im/narayana/go-tdlib v0.0.0-20240124222245-b4c12addb061
|
||||
|
|
4
go.sum
4
go.sum
|
@ -7,6 +7,10 @@ dev.narayana.im/narayana/go-tdlib v0.0.0-20240124222245-b4c12addb061 h1:CWAQT74L
|
|||
dev.narayana.im/narayana/go-tdlib v0.0.0-20240124222245-b4c12addb061/go.mod h1:Xs8fXbk5n7VaPyrSs9DP7QYoBScWYsjX+lUcWmx1DIU=
|
||||
dev.narayana.im/narayana/go-xmpp v0.0.0-20220524203317-306b4ff58e8f h1:6249ajbMjgYz53Oq0IjTvjHXbxTfu29Mj1J/6swRHs4=
|
||||
dev.narayana.im/narayana/go-xmpp v0.0.0-20220524203317-306b4ff58e8f/go.mod h1:L3NFMqYOxyLz3JGmgFyWf7r9htE91zVGiK40oW4RwdY=
|
||||
dev.narayana.im/narayana/go-xmpp v0.0.0-20240131013505-18c46e6c59fd h1:+UW+E7JjI88aH4beDn1cw6D8rs1I061hN91HU4Y4pT8=
|
||||
dev.narayana.im/narayana/go-xmpp v0.0.0-20240131013505-18c46e6c59fd/go.mod h1:L3NFMqYOxyLz3JGmgFyWf7r9htE91zVGiK40oW4RwdY=
|
||||
dev.narayana.im/narayana/go-xmpp v0.0.0-20240512132113-6725c3862314 h1:29/NjOGOUDceO73Hk4Nj4uVa1je8MULJlsDSvKxSN/k=
|
||||
dev.narayana.im/narayana/go-xmpp v0.0.0-20240512132113-6725c3862314/go.mod h1:L3NFMqYOxyLz3JGmgFyWf7r9htE91zVGiK40oW4RwdY=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/agnivade/wasmbrowsertest v0.3.1/go.mod h1:zQt6ZTdl338xxRaMW395qccVE2eQm0SjC/SDz0mPWQI=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
|
|
|
@ -26,8 +26,8 @@ WORKDIR /src
|
|||
RUN go env -w GOCACHE=/go-cache
|
||||
RUN go env -w GOMODCACHE=/gomod-cache
|
||||
RUN --mount=type=cache,target=/gomod-cache \
|
||||
--mount=type=bind,source=./,target=/src \
|
||||
go mod download
|
||||
--mount=type=bind,source=./,target=/src,rw \
|
||||
/bin/bash -c 'go mod tidy; go get -t'
|
||||
|
||||
FROM cache AS build
|
||||
ARG MAKEOPTS
|
||||
|
|
|
@ -16,7 +16,7 @@ import (
|
|||
goxmpp "gosrc.io/xmpp"
|
||||
)
|
||||
|
||||
var version string = "1.9.5"
|
||||
var version string = "1.10.0-dev"
|
||||
var commit string
|
||||
|
||||
var sm *goxmpp.StreamManager
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -46,6 +46,7 @@ type messageStub struct {
|
|||
}
|
||||
|
||||
var errOffline = errors.New("TDlib instance is offline")
|
||||
var errOverLimit = errors.New("Over limit")
|
||||
|
||||
var spaceRegex = regexp.MustCompile(`\s+`)
|
||||
var replyRegex = regexp.MustCompile("\\A>>? ?([0-9]+)\\n")
|
||||
|
@ -53,6 +54,28 @@ var replyRegex = regexp.MustCompile("\\A>>? ?([0-9]+)\\n")
|
|||
const newlineChar string = "\n"
|
||||
const messageHeaderSeparator string = " | " // no hrunicode allowed here yet
|
||||
|
||||
// ChatType is an enum of chat types, roughly corresponding to TDLib's one but better
|
||||
type ChatType int
|
||||
|
||||
const (
|
||||
ChatTypeUnknown ChatType = iota
|
||||
ChatTypePrivate
|
||||
ChatTypeBasicGroup
|
||||
ChatTypeSupergroup
|
||||
ChatTypeSecret
|
||||
ChatTypeChannel
|
||||
)
|
||||
|
||||
// MembersList is an enum of member list filters
|
||||
type MembersList int
|
||||
|
||||
const (
|
||||
MembersListMembers MembersList = iota
|
||||
MembersListRestricted
|
||||
MembersListBanned
|
||||
MembersListBannedAndAdministrators
|
||||
)
|
||||
|
||||
// GetContactByUsername resolves username to user id retrieves user and chat information
|
||||
func (c *Client) GetContactByUsername(username string) (*client.Chat, *client.User, error) {
|
||||
if !c.Online() {
|
||||
|
@ -130,10 +153,10 @@ func (c *Client) GetContactByID(id int64, chat *client.Chat) (*client.Chat, *cli
|
|||
return chat, user, nil
|
||||
}
|
||||
|
||||
// IsPM checks if a chat is PM
|
||||
func (c *Client) IsPM(id int64) (bool, error) {
|
||||
// GetChatType obtains chat type from its information
|
||||
func (c *Client) GetChatType(id int64) (ChatType, error) {
|
||||
if !c.Online() || id == 0 {
|
||||
return false, errOffline
|
||||
return ChatTypeUnknown, errOffline
|
||||
}
|
||||
|
||||
var err error
|
||||
|
@ -144,14 +167,38 @@ func (c *Client) IsPM(id int64) (bool, error) {
|
|||
ChatId: id,
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
return ChatTypeUnknown, err
|
||||
}
|
||||
|
||||
c.cache.SetChat(id, chat)
|
||||
}
|
||||
|
||||
chatType := chat.Type.ChatTypeType()
|
||||
if chatType == client.TypeChatTypePrivate || chatType == client.TypeChatTypeSecret {
|
||||
if chatType == client.TypeChatTypePrivate {
|
||||
return ChatTypePrivate, nil
|
||||
} else if chatType == client.TypeChatTypeBasicGroup {
|
||||
return ChatTypeBasicGroup, nil
|
||||
} else if chatType == client.TypeChatTypeSupergroup {
|
||||
supergroup, _ := chat.Type.(*client.ChatTypeSupergroup)
|
||||
if supergroup.IsChannel {
|
||||
return ChatTypeChannel, nil
|
||||
}
|
||||
return ChatTypeSupergroup, nil
|
||||
} else if chatType == client.TypeChatTypeSecret {
|
||||
return ChatTypeSecret, nil
|
||||
}
|
||||
|
||||
return ChatTypeUnknown, errors.New("Unknown chat type")
|
||||
}
|
||||
|
||||
// IsPM checks if a chat is PM
|
||||
func (c *Client) IsPM(id int64) (bool, error) {
|
||||
typ, err := c.GetChatType(id)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if typ == ChatTypePrivate || typ == ChatTypeSecret {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
|
@ -294,7 +341,8 @@ func (c *Client) ProcessStatusUpdate(chatID int64, status string, show string, o
|
|||
return c.sendPresence(newArgs...)
|
||||
}
|
||||
|
||||
func (c *Client) formatContact(chatID int64) string {
|
||||
// FormatContact retrieves a complete "full name (@usernames)" string for display
|
||||
func (c *Client) FormatContact(chatID int64) string {
|
||||
if chatID == 0 {
|
||||
return ""
|
||||
}
|
||||
|
@ -326,23 +374,27 @@ func (c *Client) formatContact(chatID int64) string {
|
|||
return str
|
||||
}
|
||||
|
||||
func (c *Client) getSenderId(message *client.Message) (senderId int64) {
|
||||
if message.SenderId != nil {
|
||||
switch message.SenderId.MessageSenderType() {
|
||||
func (c *Client) GetSenderId(sender client.MessageSender) (senderId int64) {
|
||||
switch sender.MessageSenderType() {
|
||||
case client.TypeMessageSenderUser:
|
||||
senderUser, _ := message.SenderId.(*client.MessageSenderUser)
|
||||
senderUser, _ := sender.(*client.MessageSenderUser)
|
||||
senderId = senderUser.UserId
|
||||
case client.TypeMessageSenderChat:
|
||||
senderChat, _ := message.SenderId.(*client.MessageSenderChat)
|
||||
senderChat, _ := sender.(*client.MessageSenderChat)
|
||||
senderId = senderChat.ChatId
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) getMessageSenderId(message *client.Message) (senderId int64) {
|
||||
if message.SenderId != nil {
|
||||
senderId = c.GetSenderId(message.SenderId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) formatSender(message *client.Message) string {
|
||||
return c.formatContact(c.getSenderId(message))
|
||||
return c.FormatContact(c.getMessageSenderId(message))
|
||||
}
|
||||
|
||||
func (c *Client) messageToStub(message *client.Message, preview bool, text string) *messageStub {
|
||||
|
@ -392,7 +444,7 @@ func (c *Client) getMessageReply(message *client.Message, preview bool, noConten
|
|||
}
|
||||
|
||||
gatewayReply = &gateway.Reply{
|
||||
Author: fmt.Sprintf("%v@%s", c.getSenderId(replyMsg), gateway.Jid.Full()),
|
||||
Author: fmt.Sprintf("%v@%s", c.getMessageSenderId(replyMsg), gateway.Jid.Full()),
|
||||
Id: replyId,
|
||||
}
|
||||
} else if !noContent {
|
||||
|
@ -409,7 +461,7 @@ func (c *Client) getMessageReply(message *client.Message, preview bool, noConten
|
|||
}
|
||||
|
||||
tgReply = &messageStub{
|
||||
Sender: c.formatOrigin(replyTo.Origin) + " @ " + c.formatContact(replyTo.ChatId),
|
||||
Sender: c.formatOrigin(replyTo.Origin) + " @ " + c.FormatContact(replyTo.ChatId),
|
||||
Date: replyTo.OriginSendDate,
|
||||
Text: text,
|
||||
}
|
||||
|
@ -479,14 +531,14 @@ func (c *Client) formatOrigin(origin client.MessageOrigin) string {
|
|||
switch origin.MessageOriginType() {
|
||||
case client.TypeMessageOriginUser:
|
||||
originUser := origin.(*client.MessageOriginUser)
|
||||
return c.formatContact(originUser.SenderUserId)
|
||||
return c.FormatContact(originUser.SenderUserId)
|
||||
case client.TypeMessageOriginChat:
|
||||
originChat := origin.(*client.MessageOriginChat)
|
||||
var signature string
|
||||
if originChat.AuthorSignature != "" {
|
||||
signature = fmt.Sprintf(" (%s)", originChat.AuthorSignature)
|
||||
}
|
||||
return c.formatContact(originChat.SenderChatId) + signature
|
||||
return c.FormatContact(originChat.SenderChatId) + signature
|
||||
case client.TypeMessageOriginHiddenUser:
|
||||
originUser := origin.(*client.MessageOriginHiddenUser)
|
||||
return originUser.SenderName
|
||||
|
@ -496,7 +548,7 @@ func (c *Client) formatOrigin(origin client.MessageOrigin) string {
|
|||
if channel.AuthorSignature != "" {
|
||||
signature = fmt.Sprintf(" (%s)", channel.AuthorSignature)
|
||||
}
|
||||
return c.formatContact(channel.ChatId) + signature
|
||||
return c.FormatContact(channel.ChatId) + signature
|
||||
}
|
||||
return "Unknown origin type"
|
||||
}
|
||||
|
@ -665,13 +717,13 @@ func (c *Client) messageContentToText(content client.MessageContent, chatId int6
|
|||
|
||||
text := "invited "
|
||||
if len(addMembers.MemberUserIds) > 0 {
|
||||
text += c.formatContact(addMembers.MemberUserIds[0])
|
||||
text += c.FormatContact(addMembers.MemberUserIds[0])
|
||||
}
|
||||
|
||||
return text
|
||||
case client.TypeMessageChatDeleteMember:
|
||||
deleteMember, _ := content.(*client.MessageChatDeleteMember)
|
||||
return "kicked " + c.formatContact(deleteMember.UserId)
|
||||
return "kicked " + c.FormatContact(deleteMember.UserId)
|
||||
case client.TypeMessagePinMessage:
|
||||
pinMessage, _ := content.(*client.MessagePinMessage)
|
||||
return "pinned message: " + c.formatMessage(chatId, pinMessage.MessageId, preview, nil)
|
||||
|
@ -821,7 +873,7 @@ func (c *Client) messageContentToText(content client.MessageContent, chatId int6
|
|||
}
|
||||
case client.TypeMessageChatSetMessageAutoDeleteTime:
|
||||
ttl, _ := content.(*client.MessageChatSetMessageAutoDeleteTime)
|
||||
name := c.formatContact(ttl.FromUserId)
|
||||
name := c.FormatContact(ttl.FromUserId)
|
||||
if name == "" {
|
||||
if ttl.MessageAutoDeleteTime == 0 {
|
||||
return "The self-destruct timer was disabled"
|
||||
|
@ -1127,7 +1179,7 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str
|
|||
|
||||
if replaceId == 0 && (strings.HasPrefix(text, "/") || strings.HasPrefix(text, "!")) {
|
||||
// try to execute commands
|
||||
response, isCommand := c.ProcessChatCommand(chatID, text)
|
||||
response, isCommand, _ := c.ProcessChatCommand(chatID, text)
|
||||
if response != "" {
|
||||
c.returnMessage(returnJid, chatID, response)
|
||||
}
|
||||
|
@ -1628,3 +1680,76 @@ func (c *Client) usernamesToString(usernames []string) string {
|
|||
}
|
||||
return strings.Join(atUsernames, ", ")
|
||||
}
|
||||
|
||||
// GetChatMembers retrieves a list of chat members. "Limited" mode works only if there are no more than 20 members at all
|
||||
func (c *Client) GetChatMembers(chatID int64, limited bool, query string, membersList MembersList) ([]*client.ChatMember, error) {
|
||||
var filters []client.ChatMembersFilter
|
||||
switch membersList {
|
||||
case MembersListMembers:
|
||||
filters = []client.ChatMembersFilter{&client.ChatMembersFilterMembers{}}
|
||||
case MembersListRestricted:
|
||||
filters = []client.ChatMembersFilter{&client.ChatMembersFilterRestricted{}}
|
||||
case MembersListBanned:
|
||||
filters = []client.ChatMembersFilter{&client.ChatMembersFilterBanned{}}
|
||||
case MembersListBannedAndAdministrators:
|
||||
filters = []client.ChatMembersFilter{&client.ChatMembersFilterBanned{}, &client.ChatMembersFilterAdministrators{}}
|
||||
}
|
||||
|
||||
limit := int32(9999)
|
||||
if limited {
|
||||
limit = 20
|
||||
|
||||
chat, _, err := c.GetContactByID(chatID, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if chat == nil {
|
||||
return nil, errors.New("Chat not found")
|
||||
}
|
||||
|
||||
chatType := chat.Type.ChatTypeType()
|
||||
if chatType == client.TypeChatTypeBasicGroup {
|
||||
basicGroupType, _ := chat.Type.(*client.ChatTypeBasicGroup)
|
||||
fullInfo, err := c.client.GetBasicGroupFullInfo(&client.GetBasicGroupFullInfoRequest{
|
||||
BasicGroupId: basicGroupType.BasicGroupId,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(fullInfo.Members) > int(limit) {
|
||||
return nil, errOverLimit
|
||||
}
|
||||
|
||||
return fullInfo.Members, nil
|
||||
} else if chatType == client.TypeChatTypeSupergroup {
|
||||
supergroupType, _ := chat.Type.(*client.ChatTypeSupergroup)
|
||||
fullInfo, err := c.client.GetSupergroupFullInfo(&client.GetSupergroupFullInfoRequest{
|
||||
SupergroupId: supergroupType.SupergroupId,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if fullInfo.MemberCount > limit {
|
||||
return nil, errOverLimit
|
||||
}
|
||||
} else {
|
||||
return nil, errors.New("Inapplicable chat type")
|
||||
}
|
||||
}
|
||||
|
||||
var members []*client.ChatMember
|
||||
for _, filter := range filters {
|
||||
chatMembers, err := c.client.SearchChatMembers(&client.SearchChatMembersRequest{
|
||||
ChatId: chatID,
|
||||
Limit: limit,
|
||||
Query: query,
|
||||
Filter: filter,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
members = append(members, chatMembers.Members...)
|
||||
}
|
||||
return members, nil
|
||||
}
|
||||
|
|
|
@ -593,7 +593,7 @@ func TestMessageToPrefix8(t *testing.T) {
|
|||
|
||||
func GetSenderIdEmpty(t *testing.T) {
|
||||
message := client.Message{}
|
||||
senderId := (&Client{}).getSenderId(&message)
|
||||
senderId := (&Client{}).getMessageSenderId(&message)
|
||||
if senderId != 0 {
|
||||
t.Errorf("Wrong sender id: %v", senderId)
|
||||
}
|
||||
|
@ -605,7 +605,7 @@ func GetSenderIdUser(t *testing.T) {
|
|||
UserId: 42,
|
||||
},
|
||||
}
|
||||
senderId := (&Client{}).getSenderId(&message)
|
||||
senderId := (&Client{}).getMessageSenderId(&message)
|
||||
if senderId != 42 {
|
||||
t.Errorf("Wrong sender id: %v", senderId)
|
||||
}
|
||||
|
@ -617,7 +617,7 @@ func GetSenderIdChat(t *testing.T) {
|
|||
ChatId: -42,
|
||||
},
|
||||
}
|
||||
senderId := (&Client{}).getSenderId(&message)
|
||||
senderId := (&Client{}).getMessageSenderId(&message)
|
||||
if senderId != -42 {
|
||||
t.Errorf("Wrong sender id: %v", senderId)
|
||||
}
|
||||
|
|
274
xmpp/handlers.go
274
xmpp/handlers.go
|
@ -7,6 +7,7 @@ import (
|
|||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
@ -26,6 +27,7 @@ const (
|
|||
TypeVCard4
|
||||
)
|
||||
const NodeVCard4 string = "urn:xmpp:vcard4"
|
||||
const NSCommand string = "http://jabber.org/protocol/commands"
|
||||
|
||||
func logPacketType(p stanza.Packet) {
|
||||
log.Warnf("Ignoring packet: %T\n", p)
|
||||
|
@ -53,14 +55,14 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) {
|
|||
return
|
||||
}
|
||||
}
|
||||
_, ok = iq.Payload.(*stanza.DiscoInfo)
|
||||
discoInfo, ok := iq.Payload.(*stanza.DiscoInfo)
|
||||
if ok {
|
||||
go handleGetDiscoInfo(s, iq)
|
||||
go handleGetDiscoInfo(s, iq, discoInfo)
|
||||
return
|
||||
}
|
||||
_, ok = iq.Payload.(*stanza.DiscoItems)
|
||||
discoItems, ok := iq.Payload.(*stanza.DiscoItems)
|
||||
if ok {
|
||||
go handleGetDiscoItems(s, iq)
|
||||
go handleGetDiscoItems(s, iq, discoItems)
|
||||
return
|
||||
}
|
||||
_, ok = iq.Payload.(*extensions.QueryRegister)
|
||||
|
@ -74,6 +76,11 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) {
|
|||
go handleSetQueryRegister(s, iq, query)
|
||||
return
|
||||
}
|
||||
command, ok := iq.Payload.(*stanza.Command)
|
||||
if ok {
|
||||
go handleSetQueryCommand(s, iq, command)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -223,7 +230,7 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) {
|
|||
} else {
|
||||
toJid, err := stanza.NewJid(msg.To)
|
||||
if err == nil && toJid.Bare() == gatewayJid && (strings.HasPrefix(msg.Body, "/") || strings.HasPrefix(msg.Body, "!")) {
|
||||
response := session.ProcessTransportCommand(msg.Body, resource)
|
||||
response, _ := session.ProcessTransportCommand(msg.Body, resource)
|
||||
if response != "" {
|
||||
gateway.SendServiceMessage(msg.From, response, component)
|
||||
}
|
||||
|
@ -468,7 +475,22 @@ func handleGetVcardIq(s xmpp.Sender, iq *stanza.IQ, typ byte) {
|
|||
_ = gateway.ResumableSend(component, &answer)
|
||||
}
|
||||
|
||||
func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
|
||||
func getTelegramChatType(from string, to string) (telegram.ChatType, error) {
|
||||
toId, ok := toToID(to)
|
||||
if ok {
|
||||
bare, _, ok := gateway.SplitJID(from)
|
||||
if ok {
|
||||
session, ok := sessions[bare]
|
||||
if ok {
|
||||
return session.GetChatType(toId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return telegram.ChatTypeUnknown, errors.New("Unknown chat type")
|
||||
}
|
||||
|
||||
func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ, di *stanza.DiscoInfo) {
|
||||
answer, err := stanza.NewIQ(stanza.Attrs{
|
||||
Type: stanza.IQTypeResult,
|
||||
From: iq.To,
|
||||
|
@ -483,6 +505,7 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
|
|||
|
||||
disco := answer.DiscoInfo()
|
||||
_, ok := toToID(iq.To)
|
||||
if di.Node == "" {
|
||||
if ok {
|
||||
disco.AddIdentity("", "account", "registered")
|
||||
disco.AddFeatures(stanza.NSMsgChatMarkers)
|
||||
|
@ -491,6 +514,29 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
|
|||
disco.AddIdentity("Telegram Gateway", "gateway", "telegram")
|
||||
disco.AddFeatures("jabber:iq:register")
|
||||
}
|
||||
disco.AddFeatures(NSCommand)
|
||||
} else {
|
||||
chatType, chatTypeErr := getTelegramChatType(iq.From, iq.To)
|
||||
|
||||
var cmdType telegram.CommandType
|
||||
if ok {
|
||||
cmdType = telegram.CommandTypeChat
|
||||
} else {
|
||||
cmdType = telegram.CommandTypeTransport
|
||||
}
|
||||
|
||||
for name, command := range telegram.GetCommands(cmdType) {
|
||||
if di.Node == name {
|
||||
if chatTypeErr == nil && !telegram.IsCommandForChatType(command, chatType) {
|
||||
break
|
||||
}
|
||||
answer.Payload = di
|
||||
di.AddIdentity(telegram.CommandToHelpString(name, command), "automation", "command-node")
|
||||
di.AddFeatures(NSCommand, "jabber:x:data")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
answer.Payload = disco
|
||||
|
||||
log.Debugf("%#v", answer)
|
||||
|
@ -504,7 +550,7 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
|
|||
_ = gateway.ResumableSend(component, answer)
|
||||
}
|
||||
|
||||
func handleGetDiscoItems(s xmpp.Sender, iq *stanza.IQ) {
|
||||
func handleGetDiscoItems(s xmpp.Sender, iq *stanza.IQ, di *stanza.DiscoItems) {
|
||||
answer, err := stanza.NewIQ(stanza.Attrs{
|
||||
Type: stanza.IQTypeResult,
|
||||
From: iq.To,
|
||||
|
@ -517,7 +563,32 @@ func handleGetDiscoItems(s xmpp.Sender, iq *stanza.IQ) {
|
|||
return
|
||||
}
|
||||
|
||||
log.Debugf("discoItems: %#v", di)
|
||||
|
||||
_, ok := toToID(iq.To)
|
||||
if di.Node == NSCommand {
|
||||
answer.Payload = di
|
||||
|
||||
chatType, chatTypeErr := getTelegramChatType(iq.From, iq.To)
|
||||
|
||||
var cmdType telegram.CommandType
|
||||
if ok {
|
||||
cmdType = telegram.CommandTypeChat
|
||||
} else {
|
||||
cmdType = telegram.CommandTypeTransport
|
||||
}
|
||||
|
||||
commands := telegram.GetCommands(cmdType)
|
||||
for _, name := range telegram.SortedCommandKeys(commands) {
|
||||
command := commands[name]
|
||||
if chatTypeErr == nil && !telegram.IsCommandForChatType(command, chatType) {
|
||||
continue
|
||||
}
|
||||
di.AddItem(iq.To, name, telegram.CommandToHelpString(name, command))
|
||||
}
|
||||
} else {
|
||||
answer.Payload = answer.DiscoItems()
|
||||
}
|
||||
|
||||
component, ok := s.(*xmpp.Component)
|
||||
if !ok {
|
||||
|
@ -647,6 +718,195 @@ func handleSetQueryRegister(s xmpp.Sender, iq *stanza.IQ, query *extensions.Quer
|
|||
}
|
||||
}
|
||||
|
||||
func handleSetQueryCommand(s xmpp.Sender, iq *stanza.IQ, command *stanza.Command) {
|
||||
component, ok := s.(*xmpp.Component)
|
||||
if !ok {
|
||||
log.Error("Not a component")
|
||||
return
|
||||
}
|
||||
|
||||
answer, err := stanza.NewIQ(stanza.Attrs{
|
||||
Type: stanza.IQTypeResult,
|
||||
From: iq.To,
|
||||
To: iq.From,
|
||||
Id: iq.Id,
|
||||
Lang: "en",
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create answer IQ: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
defer gateway.ResumableSend(component, answer)
|
||||
|
||||
log.Debugf("command: %#v", command)
|
||||
|
||||
bare, resource, ok := gateway.SplitJID(iq.From)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
toId, toOk := toToID(iq.To)
|
||||
|
||||
var cmdString string
|
||||
var cmdType telegram.CommandType
|
||||
var form *stanza.Form
|
||||
for _, ce := range command.CommandElements {
|
||||
fo, formOk := ce.(*stanza.Form)
|
||||
if formOk {
|
||||
form = fo
|
||||
break
|
||||
}
|
||||
}
|
||||
if toOk {
|
||||
cmdType = telegram.CommandTypeChat
|
||||
} else {
|
||||
cmdType = telegram.CommandTypeTransport
|
||||
}
|
||||
if form != nil {
|
||||
// just for the case the client messed the order somehow
|
||||
sort.Slice(form.Fields, func(i int, j int) bool {
|
||||
iField := form.Fields[i]
|
||||
jField := form.Fields[j]
|
||||
if iField != nil && jField != nil {
|
||||
ii, iErr := strconv.ParseInt(iField.Var, 10, 64)
|
||||
ji, jErr := strconv.ParseInt(jField.Var, 10, 64)
|
||||
return iErr == nil && jErr == nil && ii < ji
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
var cmd strings.Builder
|
||||
cmd.WriteString("/")
|
||||
cmd.WriteString(command.Node)
|
||||
for _, field := range form.Fields {
|
||||
cmd.WriteString(" ")
|
||||
if len(field.ValuesList) > 0 {
|
||||
cmd.WriteString(field.ValuesList[0])
|
||||
}
|
||||
}
|
||||
|
||||
cmdString = cmd.String()
|
||||
} else {
|
||||
if command.Action == "" || command.Action == stanza.CommandActionExecute {
|
||||
cmd, ok := telegram.GetCommand(cmdType, command.Node)
|
||||
if ok && len(cmd.Arguments) > 0 {
|
||||
var fields []*stanza.Field
|
||||
for i, arg := range cmd.Arguments {
|
||||
var required *string
|
||||
if i < cmd.RequiredArgs {
|
||||
dummyString := ""
|
||||
required = &dummyString
|
||||
}
|
||||
|
||||
var fieldType string
|
||||
var options []stanza.Option
|
||||
if toOk && i == 0 {
|
||||
switch command.Node {
|
||||
case "mute", "kick", "ban", "promote", "unmute", "unban":
|
||||
session, ok := sessions[bare]
|
||||
if ok {
|
||||
var membersList telegram.MembersList
|
||||
switch command.Node {
|
||||
case "unmute":
|
||||
membersList = telegram.MembersListRestricted
|
||||
case "unban":
|
||||
membersList = telegram.MembersListBannedAndAdministrators
|
||||
}
|
||||
members, err := session.GetChatMembers(toId, true, "", membersList)
|
||||
if err == nil {
|
||||
fieldType = stanza.FieldTypeListSingle
|
||||
switch command.Node {
|
||||
// allow empty form
|
||||
case "mute", "unmute":
|
||||
options = append(options, stanza.Option{
|
||||
ValuesList: []string{""},
|
||||
})
|
||||
}
|
||||
for _, member := range members {
|
||||
senderId := session.GetSenderId(member.MemberId)
|
||||
options = append(options, stanza.Option{
|
||||
Label: session.FormatContact(senderId),
|
||||
ValuesList: []string{strconv.FormatInt(senderId, 10)},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
field := stanza.Field{
|
||||
Var: strconv.FormatInt(int64(i), 10),
|
||||
Label: arg,
|
||||
Required: required,
|
||||
Type: fieldType,
|
||||
Options: options,
|
||||
}
|
||||
fields = append(fields, &field)
|
||||
log.Debugf("field: %#v", field)
|
||||
}
|
||||
form := stanza.Form{
|
||||
Type: stanza.FormTypeForm,
|
||||
Title: command.Node,
|
||||
Instructions: []string{cmd.Description},
|
||||
Fields: fields,
|
||||
}
|
||||
answer.Payload = &stanza.Command{
|
||||
SessionId: command.Node,
|
||||
Node: command.Node,
|
||||
Status: stanza.CommandStatusExecuting,
|
||||
CommandElements: []stanza.CommandElement{&form},
|
||||
}
|
||||
log.Debugf("form: %#v", form)
|
||||
} else {
|
||||
cmdString = "/" + command.Node
|
||||
}
|
||||
} else if command.Action == stanza.CommandActionCancel {
|
||||
answer.Payload = &stanza.Command{
|
||||
SessionId: command.Node,
|
||||
Node: command.Node,
|
||||
Status: stanza.CommandStatusCancelled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cmdString != "" {
|
||||
session, ok := sessions[bare]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var response string
|
||||
var success bool
|
||||
if toOk {
|
||||
response, _, success = session.ProcessChatCommand(toId, cmdString)
|
||||
} else {
|
||||
response, success = session.ProcessTransportCommand(cmdString, resource)
|
||||
}
|
||||
|
||||
var noteType string
|
||||
if success {
|
||||
noteType = stanza.CommandNoteTypeInfo
|
||||
} else {
|
||||
noteType = stanza.CommandNoteTypeErr
|
||||
}
|
||||
|
||||
answer.Payload = &stanza.Command{
|
||||
SessionId: command.Node,
|
||||
Node: command.Node,
|
||||
Status: stanza.CommandStatusCompleted,
|
||||
CommandElements: []stanza.CommandElement{
|
||||
&stanza.Note{
|
||||
Text: response,
|
||||
Type: noteType,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
log.Debugf("command response: %#v", answer.Payload)
|
||||
}
|
||||
|
||||
func iqAnswerSetError(answer *stanza.IQ, payload *extensions.QueryRegister, code int) {
|
||||
answer.Type = stanza.IQTypeError
|
||||
answer.Payload = *payload
|
||||
|
|
Loading…
Reference in a new issue