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)
|
COMMIT := $(shell git rev-parse --short HEAD)
|
||||||
TD_COMMIT := "5bbfc1cf5dab94f82e02f3430ded7241d4653551"
|
TD_COMMIT := "5bbfc1cf5dab94f82e02f3430ded7241d4653551"
|
||||||
VERSION := "v1.9.5"
|
VERSION := "v1.10.0-dev"
|
||||||
MAKEOPTS := "-j4"
|
MAKEOPTS := "-j4"
|
||||||
|
|
||||||
all:
|
all:
|
||||||
|
|
5
go.mod
5
go.mod
|
@ -4,6 +4,7 @@ go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/dgraph-io/badger/v4 v4.1.0
|
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/pkg/errors v0.9.1
|
||||||
github.com/santhosh-tekuri/jsonschema v1.2.4
|
github.com/santhosh-tekuri/jsonschema v1.2.4
|
||||||
github.com/sirupsen/logrus v1.4.2
|
github.com/sirupsen/logrus v1.4.2
|
||||||
|
@ -23,7 +24,6 @@ require (
|
||||||
github.com/golang/protobuf v1.3.2 // indirect
|
github.com/golang/protobuf v1.3.2 // indirect
|
||||||
github.com/golang/snappy v0.0.3 // indirect
|
github.com/golang/snappy v0.0.3 // indirect
|
||||||
github.com/google/flatbuffers v1.12.1 // 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/klauspost/compress v1.12.3 // indirect
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||||
go.opencensus.io v0.22.5 // indirect
|
go.opencensus.io v0.22.5 // indirect
|
||||||
|
@ -33,5 +33,6 @@ require (
|
||||||
nhooyr.io/websocket v1.6.5 // indirect
|
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
|
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-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 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-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/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/agnivade/wasmbrowsertest v0.3.1/go.mod h1:zQt6ZTdl338xxRaMW395qccVE2eQm0SjC/SDz0mPWQI=
|
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=
|
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 GOCACHE=/go-cache
|
||||||
RUN go env -w GOMODCACHE=/gomod-cache
|
RUN go env -w GOMODCACHE=/gomod-cache
|
||||||
RUN --mount=type=cache,target=/gomod-cache \
|
RUN --mount=type=cache,target=/gomod-cache \
|
||||||
--mount=type=bind,source=./,target=/src \
|
--mount=type=bind,source=./,target=/src,rw \
|
||||||
go mod download
|
/bin/bash -c 'go mod tidy; go get -t'
|
||||||
|
|
||||||
FROM cache AS build
|
FROM cache AS build
|
||||||
ARG MAKEOPTS
|
ARG MAKEOPTS
|
||||||
|
|
|
@ -16,7 +16,7 @@ import (
|
||||||
goxmpp "gosrc.io/xmpp"
|
goxmpp "gosrc.io/xmpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "1.9.5"
|
var version string = "1.10.0-dev"
|
||||||
var commit string
|
var commit string
|
||||||
|
|
||||||
var sm *goxmpp.StreamManager
|
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 errOffline = errors.New("TDlib instance is offline")
|
||||||
|
var errOverLimit = errors.New("Over limit")
|
||||||
|
|
||||||
var spaceRegex = regexp.MustCompile(`\s+`)
|
var spaceRegex = regexp.MustCompile(`\s+`)
|
||||||
var replyRegex = regexp.MustCompile("\\A>>? ?([0-9]+)\\n")
|
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 newlineChar string = "\n"
|
||||||
const messageHeaderSeparator string = " | " // no hrunicode allowed here yet
|
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
|
// GetContactByUsername resolves username to user id retrieves user and chat information
|
||||||
func (c *Client) GetContactByUsername(username string) (*client.Chat, *client.User, error) {
|
func (c *Client) GetContactByUsername(username string) (*client.Chat, *client.User, error) {
|
||||||
if !c.Online() {
|
if !c.Online() {
|
||||||
|
@ -130,10 +153,10 @@ func (c *Client) GetContactByID(id int64, chat *client.Chat) (*client.Chat, *cli
|
||||||
return chat, user, nil
|
return chat, user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsPM checks if a chat is PM
|
// GetChatType obtains chat type from its information
|
||||||
func (c *Client) IsPM(id int64) (bool, error) {
|
func (c *Client) GetChatType(id int64) (ChatType, error) {
|
||||||
if !c.Online() || id == 0 {
|
if !c.Online() || id == 0 {
|
||||||
return false, errOffline
|
return ChatTypeUnknown, errOffline
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
@ -144,14 +167,38 @@ func (c *Client) IsPM(id int64) (bool, error) {
|
||||||
ChatId: id,
|
ChatId: id,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return ChatTypeUnknown, err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.cache.SetChat(id, chat)
|
c.cache.SetChat(id, chat)
|
||||||
}
|
}
|
||||||
|
|
||||||
chatType := chat.Type.ChatTypeType()
|
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 true, nil
|
||||||
}
|
}
|
||||||
return false, nil
|
return false, nil
|
||||||
|
@ -294,7 +341,8 @@ func (c *Client) ProcessStatusUpdate(chatID int64, status string, show string, o
|
||||||
return c.sendPresence(newArgs...)
|
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 {
|
if chatID == 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -326,23 +374,27 @@ func (c *Client) formatContact(chatID int64) string {
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) getSenderId(message *client.Message) (senderId int64) {
|
func (c *Client) GetSenderId(sender client.MessageSender) (senderId int64) {
|
||||||
if message.SenderId != nil {
|
switch sender.MessageSenderType() {
|
||||||
switch message.SenderId.MessageSenderType() {
|
|
||||||
case client.TypeMessageSenderUser:
|
case client.TypeMessageSenderUser:
|
||||||
senderUser, _ := message.SenderId.(*client.MessageSenderUser)
|
senderUser, _ := sender.(*client.MessageSenderUser)
|
||||||
senderId = senderUser.UserId
|
senderId = senderUser.UserId
|
||||||
case client.TypeMessageSenderChat:
|
case client.TypeMessageSenderChat:
|
||||||
senderChat, _ := message.SenderId.(*client.MessageSenderChat)
|
senderChat, _ := sender.(*client.MessageSenderChat)
|
||||||
senderId = senderChat.ChatId
|
senderId = senderChat.ChatId
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) getMessageSenderId(message *client.Message) (senderId int64) {
|
||||||
|
if message.SenderId != nil {
|
||||||
|
senderId = c.GetSenderId(message.SenderId)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) formatSender(message *client.Message) string {
|
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 {
|
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{
|
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,
|
Id: replyId,
|
||||||
}
|
}
|
||||||
} else if !noContent {
|
} else if !noContent {
|
||||||
|
@ -409,7 +461,7 @@ func (c *Client) getMessageReply(message *client.Message, preview bool, noConten
|
||||||
}
|
}
|
||||||
|
|
||||||
tgReply = &messageStub{
|
tgReply = &messageStub{
|
||||||
Sender: c.formatOrigin(replyTo.Origin) + " @ " + c.formatContact(replyTo.ChatId),
|
Sender: c.formatOrigin(replyTo.Origin) + " @ " + c.FormatContact(replyTo.ChatId),
|
||||||
Date: replyTo.OriginSendDate,
|
Date: replyTo.OriginSendDate,
|
||||||
Text: text,
|
Text: text,
|
||||||
}
|
}
|
||||||
|
@ -479,14 +531,14 @@ func (c *Client) formatOrigin(origin client.MessageOrigin) string {
|
||||||
switch origin.MessageOriginType() {
|
switch origin.MessageOriginType() {
|
||||||
case client.TypeMessageOriginUser:
|
case client.TypeMessageOriginUser:
|
||||||
originUser := origin.(*client.MessageOriginUser)
|
originUser := origin.(*client.MessageOriginUser)
|
||||||
return c.formatContact(originUser.SenderUserId)
|
return c.FormatContact(originUser.SenderUserId)
|
||||||
case client.TypeMessageOriginChat:
|
case client.TypeMessageOriginChat:
|
||||||
originChat := origin.(*client.MessageOriginChat)
|
originChat := origin.(*client.MessageOriginChat)
|
||||||
var signature string
|
var signature string
|
||||||
if originChat.AuthorSignature != "" {
|
if originChat.AuthorSignature != "" {
|
||||||
signature = fmt.Sprintf(" (%s)", originChat.AuthorSignature)
|
signature = fmt.Sprintf(" (%s)", originChat.AuthorSignature)
|
||||||
}
|
}
|
||||||
return c.formatContact(originChat.SenderChatId) + signature
|
return c.FormatContact(originChat.SenderChatId) + signature
|
||||||
case client.TypeMessageOriginHiddenUser:
|
case client.TypeMessageOriginHiddenUser:
|
||||||
originUser := origin.(*client.MessageOriginHiddenUser)
|
originUser := origin.(*client.MessageOriginHiddenUser)
|
||||||
return originUser.SenderName
|
return originUser.SenderName
|
||||||
|
@ -496,7 +548,7 @@ func (c *Client) formatOrigin(origin client.MessageOrigin) string {
|
||||||
if channel.AuthorSignature != "" {
|
if channel.AuthorSignature != "" {
|
||||||
signature = fmt.Sprintf(" (%s)", channel.AuthorSignature)
|
signature = fmt.Sprintf(" (%s)", channel.AuthorSignature)
|
||||||
}
|
}
|
||||||
return c.formatContact(channel.ChatId) + signature
|
return c.FormatContact(channel.ChatId) + signature
|
||||||
}
|
}
|
||||||
return "Unknown origin type"
|
return "Unknown origin type"
|
||||||
}
|
}
|
||||||
|
@ -665,13 +717,13 @@ func (c *Client) messageContentToText(content client.MessageContent, chatId int6
|
||||||
|
|
||||||
text := "invited "
|
text := "invited "
|
||||||
if len(addMembers.MemberUserIds) > 0 {
|
if len(addMembers.MemberUserIds) > 0 {
|
||||||
text += c.formatContact(addMembers.MemberUserIds[0])
|
text += c.FormatContact(addMembers.MemberUserIds[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
return text
|
return text
|
||||||
case client.TypeMessageChatDeleteMember:
|
case client.TypeMessageChatDeleteMember:
|
||||||
deleteMember, _ := content.(*client.MessageChatDeleteMember)
|
deleteMember, _ := content.(*client.MessageChatDeleteMember)
|
||||||
return "kicked " + c.formatContact(deleteMember.UserId)
|
return "kicked " + c.FormatContact(deleteMember.UserId)
|
||||||
case client.TypeMessagePinMessage:
|
case client.TypeMessagePinMessage:
|
||||||
pinMessage, _ := content.(*client.MessagePinMessage)
|
pinMessage, _ := content.(*client.MessagePinMessage)
|
||||||
return "pinned message: " + c.formatMessage(chatId, pinMessage.MessageId, preview, nil)
|
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:
|
case client.TypeMessageChatSetMessageAutoDeleteTime:
|
||||||
ttl, _ := content.(*client.MessageChatSetMessageAutoDeleteTime)
|
ttl, _ := content.(*client.MessageChatSetMessageAutoDeleteTime)
|
||||||
name := c.formatContact(ttl.FromUserId)
|
name := c.FormatContact(ttl.FromUserId)
|
||||||
if name == "" {
|
if name == "" {
|
||||||
if ttl.MessageAutoDeleteTime == 0 {
|
if ttl.MessageAutoDeleteTime == 0 {
|
||||||
return "The self-destruct timer was disabled"
|
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, "!")) {
|
if replaceId == 0 && (strings.HasPrefix(text, "/") || strings.HasPrefix(text, "!")) {
|
||||||
// try to execute commands
|
// try to execute commands
|
||||||
response, isCommand := c.ProcessChatCommand(chatID, text)
|
response, isCommand, _ := c.ProcessChatCommand(chatID, text)
|
||||||
if response != "" {
|
if response != "" {
|
||||||
c.returnMessage(returnJid, chatID, response)
|
c.returnMessage(returnJid, chatID, response)
|
||||||
}
|
}
|
||||||
|
@ -1628,3 +1680,76 @@ func (c *Client) usernamesToString(usernames []string) string {
|
||||||
}
|
}
|
||||||
return strings.Join(atUsernames, ", ")
|
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) {
|
func GetSenderIdEmpty(t *testing.T) {
|
||||||
message := client.Message{}
|
message := client.Message{}
|
||||||
senderId := (&Client{}).getSenderId(&message)
|
senderId := (&Client{}).getMessageSenderId(&message)
|
||||||
if senderId != 0 {
|
if senderId != 0 {
|
||||||
t.Errorf("Wrong sender id: %v", senderId)
|
t.Errorf("Wrong sender id: %v", senderId)
|
||||||
}
|
}
|
||||||
|
@ -605,7 +605,7 @@ func GetSenderIdUser(t *testing.T) {
|
||||||
UserId: 42,
|
UserId: 42,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
senderId := (&Client{}).getSenderId(&message)
|
senderId := (&Client{}).getMessageSenderId(&message)
|
||||||
if senderId != 42 {
|
if senderId != 42 {
|
||||||
t.Errorf("Wrong sender id: %v", senderId)
|
t.Errorf("Wrong sender id: %v", senderId)
|
||||||
}
|
}
|
||||||
|
@ -617,7 +617,7 @@ func GetSenderIdChat(t *testing.T) {
|
||||||
ChatId: -42,
|
ChatId: -42,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
senderId := (&Client{}).getSenderId(&message)
|
senderId := (&Client{}).getMessageSenderId(&message)
|
||||||
if senderId != -42 {
|
if senderId != -42 {
|
||||||
t.Errorf("Wrong sender id: %v", senderId)
|
t.Errorf("Wrong sender id: %v", senderId)
|
||||||
}
|
}
|
||||||
|
|
274
xmpp/handlers.go
274
xmpp/handlers.go
|
@ -7,6 +7,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"io"
|
"io"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -26,6 +27,7 @@ const (
|
||||||
TypeVCard4
|
TypeVCard4
|
||||||
)
|
)
|
||||||
const NodeVCard4 string = "urn:xmpp:vcard4"
|
const NodeVCard4 string = "urn:xmpp:vcard4"
|
||||||
|
const NSCommand string = "http://jabber.org/protocol/commands"
|
||||||
|
|
||||||
func logPacketType(p stanza.Packet) {
|
func logPacketType(p stanza.Packet) {
|
||||||
log.Warnf("Ignoring packet: %T\n", p)
|
log.Warnf("Ignoring packet: %T\n", p)
|
||||||
|
@ -53,14 +55,14 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, ok = iq.Payload.(*stanza.DiscoInfo)
|
discoInfo, ok := iq.Payload.(*stanza.DiscoInfo)
|
||||||
if ok {
|
if ok {
|
||||||
go handleGetDiscoInfo(s, iq)
|
go handleGetDiscoInfo(s, iq, discoInfo)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, ok = iq.Payload.(*stanza.DiscoItems)
|
discoItems, ok := iq.Payload.(*stanza.DiscoItems)
|
||||||
if ok {
|
if ok {
|
||||||
go handleGetDiscoItems(s, iq)
|
go handleGetDiscoItems(s, iq, discoItems)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, ok = iq.Payload.(*extensions.QueryRegister)
|
_, ok = iq.Payload.(*extensions.QueryRegister)
|
||||||
|
@ -74,6 +76,11 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) {
|
||||||
go handleSetQueryRegister(s, iq, query)
|
go handleSetQueryRegister(s, iq, query)
|
||||||
return
|
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 {
|
} else {
|
||||||
toJid, err := stanza.NewJid(msg.To)
|
toJid, err := stanza.NewJid(msg.To)
|
||||||
if err == nil && toJid.Bare() == gatewayJid && (strings.HasPrefix(msg.Body, "/") || strings.HasPrefix(msg.Body, "!")) {
|
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 != "" {
|
if response != "" {
|
||||||
gateway.SendServiceMessage(msg.From, response, component)
|
gateway.SendServiceMessage(msg.From, response, component)
|
||||||
}
|
}
|
||||||
|
@ -468,7 +475,22 @@ func handleGetVcardIq(s xmpp.Sender, iq *stanza.IQ, typ byte) {
|
||||||
_ = gateway.ResumableSend(component, &answer)
|
_ = 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{
|
answer, err := stanza.NewIQ(stanza.Attrs{
|
||||||
Type: stanza.IQTypeResult,
|
Type: stanza.IQTypeResult,
|
||||||
From: iq.To,
|
From: iq.To,
|
||||||
|
@ -483,6 +505,7 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
|
||||||
|
|
||||||
disco := answer.DiscoInfo()
|
disco := answer.DiscoInfo()
|
||||||
_, ok := toToID(iq.To)
|
_, ok := toToID(iq.To)
|
||||||
|
if di.Node == "" {
|
||||||
if ok {
|
if ok {
|
||||||
disco.AddIdentity("", "account", "registered")
|
disco.AddIdentity("", "account", "registered")
|
||||||
disco.AddFeatures(stanza.NSMsgChatMarkers)
|
disco.AddFeatures(stanza.NSMsgChatMarkers)
|
||||||
|
@ -491,6 +514,29 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
|
||||||
disco.AddIdentity("Telegram Gateway", "gateway", "telegram")
|
disco.AddIdentity("Telegram Gateway", "gateway", "telegram")
|
||||||
disco.AddFeatures("jabber:iq:register")
|
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
|
answer.Payload = disco
|
||||||
|
|
||||||
log.Debugf("%#v", answer)
|
log.Debugf("%#v", answer)
|
||||||
|
@ -504,7 +550,7 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
|
||||||
_ = gateway.ResumableSend(component, answer)
|
_ = 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{
|
answer, err := stanza.NewIQ(stanza.Attrs{
|
||||||
Type: stanza.IQTypeResult,
|
Type: stanza.IQTypeResult,
|
||||||
From: iq.To,
|
From: iq.To,
|
||||||
|
@ -517,7 +563,32 @@ func handleGetDiscoItems(s xmpp.Sender, iq *stanza.IQ) {
|
||||||
return
|
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()
|
answer.Payload = answer.DiscoItems()
|
||||||
|
}
|
||||||
|
|
||||||
component, ok := s.(*xmpp.Component)
|
component, ok := s.(*xmpp.Component)
|
||||||
if !ok {
|
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) {
|
func iqAnswerSetError(answer *stanza.IQ, payload *extensions.QueryRegister, code int) {
|
||||||
answer.Type = stanza.IQTypeError
|
answer.Type = stanza.IQTypeError
|
||||||
answer.Payload = *payload
|
answer.Payload = *payload
|
||||||
|
|
Loading…
Reference in a new issue