Compare commits
25 commits
Author | SHA1 | Date | |
---|---|---|---|
Bohdan Horbeshko | 4972cb6d5e | ||
Bohdan Horbeshko | 1e7e761c6c | ||
Bohdan Horbeshko | b8a57c06b6 | ||
Bohdan Horbeshko | 02578440cd | ||
Bohdan Horbeshko | 47fa7bca49 | ||
Bohdan Horbeshko | a0803123b2 | ||
Bohdan Horbeshko | b70bb53c6d | ||
Bohdan Horbeshko | 41503c7fd4 | ||
Bohdan Horbeshko | cdaaa75c96 | ||
Bohdan Horbeshko | b68c07025d | ||
Bohdan Horbeshko | e8bde73164 | ||
Bohdan Horbeshko | e77caf2c42 | ||
Bohdan Horbeshko | c1887e5a1e | ||
Bohdan Horbeshko | 93abbe834e | ||
Bohdan Horbeshko | 6c65ef9988 | ||
Bohdan Horbeshko | 4249a8bf41 | ||
Bohdan Horbeshko | f99f4f6acc | ||
Bohdan Horbeshko | 776993894a | ||
Bohdan Horbeshko | 9dbd487dae | ||
Bohdan Horbeshko | 7eaf28ad7c | ||
Bohdan Horbeshko | 63521b8f90 | ||
Bohdan Horbeshko | 7ef32096af | ||
Bohdan Horbeshko | 63f12202d0 | ||
Bohdan Horbeshko | 6abb7ff9c2 | ||
Bohdan Horbeshko | afa21e10be |
2
Makefile
2
Makefile
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
COMMIT := $(shell git rev-parse --short HEAD)
|
COMMIT := $(shell git rev-parse --short HEAD)
|
||||||
TD_COMMIT := "8517026415e75a8eec567774072cbbbbb52376c1"
|
TD_COMMIT := "8517026415e75a8eec567774072cbbbbb52376c1"
|
||||||
VERSION := "v1.8.2"
|
VERSION := "v2.0.0-dev"
|
||||||
MAKEOPTS := "-j4"
|
MAKEOPTS := "-j4"
|
||||||
|
|
||||||
all:
|
all:
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -33,5 +33,5 @@ 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-20220708184440-35d9cd68e55f
|
||||||
replace github.com/zelenin/go-tdlib => dev.narayana.im/narayana/go-tdlib v0.0.0-20230730021136-47da33180615
|
replace github.com/zelenin/go-tdlib => dev.narayana.im/narayana/go-tdlib v0.0.0-20230730021136-47da33180615
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -3,6 +3,10 @@ dev.narayana.im/narayana/go-tdlib v0.0.0-20230730021136-47da33180615 h1:RRUZJSro
|
||||||
dev.narayana.im/narayana/go-tdlib v0.0.0-20230730021136-47da33180615/go.mod h1:Xs8fXbk5n7VaPyrSs9DP7QYoBScWYsjX+lUcWmx1DIU=
|
dev.narayana.im/narayana/go-tdlib v0.0.0-20230730021136-47da33180615/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-20220708184440-35d9cd68e55f h1:aT50UsPH1dLje9CCAquRRhr7I9ZvL3kQU6WIWTe8PZ0=
|
||||||
|
dev.narayana.im/narayana/go-xmpp v0.0.0-20220708184440-35d9cd68e55f/go.mod h1:L3NFMqYOxyLz3JGmgFyWf7r9htE91zVGiK40oW4RwdY=
|
||||||
|
github.com/Arman92/go-tdlib v0.0.0-20191002071913-526f4e1d15f7 h1:GbV1Lv3lVHsSeKAqPTBem72OCsGjXntW4jfJdXciE+w=
|
||||||
|
github.com/Arman92/go-tdlib v0.0.0-20191002071913-526f4e1d15f7/go.mod h1:ZzkRfuaFj8etIYMj/ECtXtgfz72RE6U+dos27b3XIwk=
|
||||||
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=
|
||||||
|
|
|
@ -39,6 +39,7 @@ type Session struct {
|
||||||
KeepOnline bool `yaml:":keeponline"`
|
KeepOnline bool `yaml:":keeponline"`
|
||||||
RawMessages bool `yaml:":rawmessages"`
|
RawMessages bool `yaml:":rawmessages"`
|
||||||
AsciiArrows bool `yaml:":asciiarrows"`
|
AsciiArrows bool `yaml:":asciiarrows"`
|
||||||
|
MUC bool `yaml:":muc"`
|
||||||
OOBMode bool `yaml:":oobmode"`
|
OOBMode bool `yaml:":oobmode"`
|
||||||
Carbons bool `yaml:":carbons"`
|
Carbons bool `yaml:":carbons"`
|
||||||
HideIds bool `yaml:":hideids"`
|
HideIds bool `yaml:":hideids"`
|
||||||
|
@ -49,6 +50,7 @@ var configKeys = []string{
|
||||||
"keeponline",
|
"keeponline",
|
||||||
"rawmessages",
|
"rawmessages",
|
||||||
"asciiarrows",
|
"asciiarrows",
|
||||||
|
"muc",
|
||||||
"oobmode",
|
"oobmode",
|
||||||
"carbons",
|
"carbons",
|
||||||
"hideids",
|
"hideids",
|
||||||
|
@ -124,6 +126,8 @@ func (s *Session) Get(key string) (string, error) {
|
||||||
return fromBool(s.RawMessages), nil
|
return fromBool(s.RawMessages), nil
|
||||||
case "asciiarrows":
|
case "asciiarrows":
|
||||||
return fromBool(s.AsciiArrows), nil
|
return fromBool(s.AsciiArrows), nil
|
||||||
|
case "muc":
|
||||||
|
return fromBool(s.MUC), nil
|
||||||
case "oobmode":
|
case "oobmode":
|
||||||
return fromBool(s.OOBMode), nil
|
return fromBool(s.OOBMode), nil
|
||||||
case "carbons":
|
case "carbons":
|
||||||
|
@ -173,6 +177,13 @@ func (s *Session) Set(key string, value string) (string, error) {
|
||||||
}
|
}
|
||||||
s.AsciiArrows = b
|
s.AsciiArrows = b
|
||||||
return value, nil
|
return value, nil
|
||||||
|
case "muc":
|
||||||
|
b, err := toBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
s.MUC = b
|
||||||
|
return value, nil
|
||||||
case "oobmode":
|
case "oobmode":
|
||||||
b, err := toBool(value)
|
b, err := toBool(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -47,12 +47,14 @@ func TestSessionToMap(t *testing.T) {
|
||||||
session := Session{
|
session := Session{
|
||||||
Timezone: "klsf",
|
Timezone: "klsf",
|
||||||
RawMessages: true,
|
RawMessages: true,
|
||||||
|
MUC: true,
|
||||||
OOBMode: true,
|
OOBMode: true,
|
||||||
}
|
}
|
||||||
m := session.ToMap()
|
m := session.ToMap()
|
||||||
sample := map[string]string{
|
sample := map[string]string{
|
||||||
"timezone": "klsf",
|
"timezone": "klsf",
|
||||||
"keeponline": "false",
|
"keeponline": "false",
|
||||||
|
"muc": "true",
|
||||||
"rawmessages": "true",
|
"rawmessages": "true",
|
||||||
"asciiarrows": "false",
|
"asciiarrows": "false",
|
||||||
"oobmode": "true",
|
"oobmode": "true",
|
||||||
|
|
|
@ -15,7 +15,7 @@ import (
|
||||||
goxmpp "gosrc.io/xmpp"
|
goxmpp "gosrc.io/xmpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "1.8.2"
|
var version string = "2.0.0-dev"
|
||||||
var commit string
|
var commit string
|
||||||
|
|
||||||
var sm *goxmpp.StreamManager
|
var sm *goxmpp.StreamManager
|
||||||
|
|
|
@ -41,6 +41,25 @@ type DelayedStatus struct {
|
||||||
TimestampExpired int64
|
TimestampExpired int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MUCState holds MUC metadata
|
||||||
|
type MUCState struct {
|
||||||
|
Resources map[string]bool
|
||||||
|
Members map[int64]*MUCMember
|
||||||
|
}
|
||||||
|
|
||||||
|
// MUCMember represents a MUC member
|
||||||
|
type MUCMember struct {
|
||||||
|
Nickname string
|
||||||
|
Affiliation string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMUCState() *MUCState {
|
||||||
|
return &MUCState{
|
||||||
|
Resources: make(map[string]bool),
|
||||||
|
Members: make(map[int64]*MUCMember),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Client stores the metadata for lazily invoked TDlib instance
|
// Client stores the metadata for lazily invoked TDlib instance
|
||||||
type Client struct {
|
type Client struct {
|
||||||
client *client.Client
|
client *client.Client
|
||||||
|
@ -64,6 +83,8 @@ type Client struct {
|
||||||
lastMsgHashes map[int64]uint64
|
lastMsgHashes map[int64]uint64
|
||||||
msgHashSeed maphash.Seed
|
msgHashSeed maphash.Seed
|
||||||
|
|
||||||
|
mucCache map[int64]*MUCState
|
||||||
|
|
||||||
locks clientLocks
|
locks clientLocks
|
||||||
SendMessageLock sync.Mutex
|
SendMessageLock sync.Mutex
|
||||||
}
|
}
|
||||||
|
@ -73,6 +94,7 @@ type clientLocks struct {
|
||||||
chatMessageLocks map[int64]*sync.Mutex
|
chatMessageLocks map[int64]*sync.Mutex
|
||||||
resourcesLock sync.Mutex
|
resourcesLock sync.Mutex
|
||||||
outboxLock sync.Mutex
|
outboxLock sync.Mutex
|
||||||
|
mucCacheLock sync.Mutex
|
||||||
lastMsgHashesLock sync.Mutex
|
lastMsgHashesLock sync.Mutex
|
||||||
|
|
||||||
authorizerReadLock sync.Mutex
|
authorizerReadLock sync.Mutex
|
||||||
|
@ -133,6 +155,7 @@ func NewClient(conf config.TelegramConfig, jid string, component *xmpp.Component
|
||||||
Session: session,
|
Session: session,
|
||||||
resources: make(map[string]bool),
|
resources: make(map[string]bool),
|
||||||
outbox: make(map[string]string),
|
outbox: make(map[string]string),
|
||||||
|
mucCache: make(map[int64]*MUCState),
|
||||||
content: &conf.Content,
|
content: &conf.Content,
|
||||||
cache: cache.NewCache(),
|
cache: cache.NewCache(),
|
||||||
options: options,
|
options: options,
|
||||||
|
|
|
@ -193,23 +193,6 @@ func (c *Client) unsubscribe(chatID int64) error {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) sendMessagesReverse(chatID int64, messages []*client.Message) {
|
|
||||||
for i := len(messages) - 1; i >= 0; i-- {
|
|
||||||
message := messages[i]
|
|
||||||
reply, _ := c.getMessageReply(message)
|
|
||||||
|
|
||||||
gateway.SendMessage(
|
|
||||||
c.jid,
|
|
||||||
strconv.FormatInt(chatID, 10),
|
|
||||||
c.formatMessage(0, 0, false, message),
|
|
||||||
strconv.FormatInt(message.Id, 10),
|
|
||||||
c.xmpp,
|
|
||||||
reply,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) usernameOrIDToID(username string) (int64, error) {
|
func (c *Client) usernameOrIDToID(username string) (int64, error) {
|
||||||
userID, err := strconv.ParseInt(username, 10, 64)
|
userID, err := strconv.ParseInt(username, 10, 64)
|
||||||
// couldn't parse the id, try to lookup as a username
|
// couldn't parse the id, try to lookup as a username
|
||||||
|
@ -1005,7 +988,7 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool)
|
||||||
return err.Error(), true
|
return err.Error(), true
|
||||||
}
|
}
|
||||||
|
|
||||||
c.sendMessagesReverse(chatID, messages.Messages)
|
c.sendMessagesReverse(chatID, messages.Messages, true, "")
|
||||||
// get latest entries from history
|
// get latest entries from history
|
||||||
case "history":
|
case "history":
|
||||||
var limit int32 = 10
|
var limit int32 = 10
|
||||||
|
@ -1016,32 +999,11 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var newMessages *client.Messages
|
messages, err := c.getNLastMessages(chatID, limit)
|
||||||
var messages []*client.Message
|
if err != nil {
|
||||||
var err error
|
return err.Error(), true
|
||||||
var fromId int64
|
|
||||||
for _ = range make([]struct{}, limit) { // safety limit
|
|
||||||
if len(messages) > 0 {
|
|
||||||
fromId = messages[len(messages)-1].Id
|
|
||||||
}
|
|
||||||
|
|
||||||
newMessages, err = c.client.GetChatHistory(&client.GetChatHistoryRequest{
|
|
||||||
ChatId: chatID,
|
|
||||||
FromMessageId: fromId,
|
|
||||||
Limit: limit,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err.Error(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
messages = append(messages, newMessages.Messages...)
|
|
||||||
|
|
||||||
if len(newMessages.Messages) == 0 || len(messages) >= int(limit) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
c.sendMessagesReverse(chatID, messages, true, "")
|
||||||
c.sendMessagesReverse(chatID, messages)
|
|
||||||
// chat members
|
// chat members
|
||||||
case "members":
|
case "members":
|
||||||
var query string
|
var query string
|
||||||
|
|
|
@ -153,6 +153,13 @@ func (c *Client) updateHandler() {
|
||||||
|
|
||||||
// new user discovered
|
// new user discovered
|
||||||
func (c *Client) updateUser(update *client.UpdateUser) {
|
func (c *Client) updateUser(update *client.UpdateUser) {
|
||||||
|
// check if MUC nicknames should be updated
|
||||||
|
cacheUser, ok := c.cache.GetUser(update.User.Id)
|
||||||
|
if ok && (cacheUser.FirstName != update.User.FirstName || cacheUser.LastName != update.User.LastName) {
|
||||||
|
newNickname := c.GetMUCNickname(update.User.Id)
|
||||||
|
c.updateMUCsNickname(update.User.Id, newNickname)
|
||||||
|
}
|
||||||
|
|
||||||
c.cache.SetUser(update.User.Id, update.User)
|
c.cache.SetUser(update.User.Id, update.User)
|
||||||
show, status, presenceType := c.userStatusToText(update.User.Status, update.User.Id)
|
show, status, presenceType := c.userStatusToText(update.User.Status, update.User.Id)
|
||||||
go c.ProcessStatusUpdate(update.User.Id, status, show, gateway.SPType(presenceType))
|
go c.ProcessStatusUpdate(update.User.Id, status, show, gateway.SPType(presenceType))
|
||||||
|
@ -265,7 +272,7 @@ func (c *Client) updateMessageContent(update *client.UpdateMessageContent) {
|
||||||
markupFunction,
|
markupFunction,
|
||||||
))
|
))
|
||||||
for _, jid := range jids {
|
for _, jid := range jids {
|
||||||
gateway.SendMessage(jid, strconv.FormatInt(update.ChatId, 10), text, "e"+strconv.FormatInt(update.MessageId, 10), c.xmpp, nil, false)
|
gateway.SendMessage(jid, strconv.FormatInt(update.ChatId, 10), text, "e"+strconv.FormatInt(update.MessageId, 10), c.xmpp, nil, 0, false, false, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -315,10 +322,14 @@ func (c *Client) updateMessageSendFailed(update *client.UpdateMessageSendFailed)
|
||||||
|
|
||||||
// chat title changed
|
// chat title changed
|
||||||
func (c *Client) updateChatTitle(update *client.UpdateChatTitle) {
|
func (c *Client) updateChatTitle(update *client.UpdateChatTitle) {
|
||||||
|
chat, user, _ := c.GetContactByID(update.ChatId, nil)
|
||||||
|
if c.Session.MUC && c.IsGroup(chat) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
gateway.SetNickname(c.jid, strconv.FormatInt(update.ChatId, 10), update.Title, c.xmpp)
|
gateway.SetNickname(c.jid, strconv.FormatInt(update.ChatId, 10), update.Title, c.xmpp)
|
||||||
|
|
||||||
// set also the status (for group chats only)
|
// set also the status (for group chats only)
|
||||||
chat, user, _ := c.GetContactByID(update.ChatId, nil)
|
|
||||||
if user == nil {
|
if user == nil {
|
||||||
c.ProcessStatusUpdate(update.ChatId, update.Title, "chat", gateway.SPImmed(true))
|
c.ProcessStatusUpdate(update.ChatId, update.Title, "chat", gateway.SPImmed(true))
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,12 @@ var replyRegex = regexp.MustCompile("\\A>>? ?([0-9]+)\\n")
|
||||||
const newlineChar string = "\n"
|
const newlineChar string = "\n"
|
||||||
const messageHeaderSeparator string = " | "
|
const messageHeaderSeparator string = " | "
|
||||||
|
|
||||||
|
const (
|
||||||
|
ChatTypeOther byte = iota
|
||||||
|
ChatTypePM
|
||||||
|
ChatTypeGroup
|
||||||
|
)
|
||||||
|
|
||||||
// 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() {
|
||||||
|
@ -121,10 +127,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 checks if a chat is PM or group
|
||||||
func (c *Client) IsPM(id int64) (bool, error) {
|
func (c *Client) GetChatType(id int64) (byte, error) {
|
||||||
if !c.Online() || id == 0 {
|
if !c.Online() || id == 0 {
|
||||||
return false, errOffline
|
return ChatTypeOther, errOffline
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
@ -135,7 +141,7 @@ func (c *Client) IsPM(id int64) (bool, error) {
|
||||||
ChatId: id,
|
ChatId: id,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return ChatTypeOther, err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.cache.SetChat(id, chat)
|
c.cache.SetChat(id, chat)
|
||||||
|
@ -143,9 +149,12 @@ func (c *Client) IsPM(id int64) (bool, error) {
|
||||||
|
|
||||||
chatType := chat.Type.ChatTypeType()
|
chatType := chat.Type.ChatTypeType()
|
||||||
if chatType == client.TypeChatTypePrivate || chatType == client.TypeChatTypeSecret {
|
if chatType == client.TypeChatTypePrivate || chatType == client.TypeChatTypeSecret {
|
||||||
return true, nil
|
return ChatTypePM, nil
|
||||||
}
|
}
|
||||||
return false, nil
|
if c.IsGroup(chat) {
|
||||||
|
return ChatTypeGroup, nil
|
||||||
|
}
|
||||||
|
return ChatTypeOther, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) userStatusToText(status client.UserStatus, chatID int64) (string, string, string) {
|
func (c *Client) userStatusToText(status client.UserStatus, chatID int64) (string, string, string) {
|
||||||
|
@ -217,6 +226,10 @@ func (c *Client) ProcessStatusUpdate(chatID int64, status string, show string, o
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if chat != nil && c.Session.MUC && c.IsGroup(chat) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var photo string
|
var photo string
|
||||||
if chat != nil && chat.Photo != nil {
|
if chat != nil && chat.Photo != nil {
|
||||||
file, path, err := c.ForceOpenFile(chat.Photo.Small, 1)
|
file, path, err := c.ForceOpenFile(chat.Photo.Small, 1)
|
||||||
|
@ -290,6 +303,214 @@ func (c *Client) ProcessStatusUpdate(chatID int64, status string, show string, o
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JoinMUC saves MUC join fact and sends initialization data
|
||||||
|
func (c *Client) JoinMUC(chatId int64, resource string, limit int32) {
|
||||||
|
// save the nickname in this MUC, also as a marker of join
|
||||||
|
c.locks.mucCacheLock.Lock()
|
||||||
|
mucState, ok := c.mucCache[chatId]
|
||||||
|
if !ok || mucState == nil {
|
||||||
|
mucState = NewMUCState()
|
||||||
|
c.mucCache[chatId] = mucState
|
||||||
|
}
|
||||||
|
_, ok = mucState.Resources[resource]
|
||||||
|
if ok {
|
||||||
|
// already joined, initializing anyway
|
||||||
|
} else {
|
||||||
|
mucState.Resources[resource] = true
|
||||||
|
}
|
||||||
|
c.locks.mucCacheLock.Unlock()
|
||||||
|
|
||||||
|
c.sendMUCStatuses(chatId)
|
||||||
|
|
||||||
|
messages, err := c.getNLastMessages(chatId, limit)
|
||||||
|
if err == nil {
|
||||||
|
c.sendMessagesReverse(chatId, messages, false, c.jid+"/"+resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.sendMUCSubject(chatId, resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) getFullName(user *client.User) string {
|
||||||
|
fullName := user.FirstName
|
||||||
|
if user.LastName != "" {
|
||||||
|
fullName = fullName + " " + user.LastName
|
||||||
|
}
|
||||||
|
return fullName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) sendMUCStatuses(chatID int64) {
|
||||||
|
c.locks.mucCacheLock.Lock()
|
||||||
|
defer c.locks.mucCacheLock.Unlock()
|
||||||
|
mucState, ok := c.mucCache[chatID]
|
||||||
|
if !ok || mucState == nil {
|
||||||
|
mucState = NewMUCState()
|
||||||
|
c.mucCache[chatID] = mucState
|
||||||
|
}
|
||||||
|
|
||||||
|
sChatId := strconv.FormatInt(chatID, 10)
|
||||||
|
myNickname := "me"
|
||||||
|
if c.me != nil {
|
||||||
|
myNickname = c.getFullName(c.me)
|
||||||
|
}
|
||||||
|
myAffiliation := "member"
|
||||||
|
|
||||||
|
members, err := c.client.SearchChatMembers(&client.SearchChatMembersRequest{
|
||||||
|
ChatId: chatID,
|
||||||
|
Limit: 200,
|
||||||
|
Filter: &client.ChatMembersFilterMembers{},
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
gatewayJidSuffix := "@" + gateway.Jid.Full()
|
||||||
|
|
||||||
|
for _, member := range members.Members {
|
||||||
|
var senderId int64
|
||||||
|
switch member.MemberId.MessageSenderType() {
|
||||||
|
case client.TypeMessageSenderUser:
|
||||||
|
memberUser, _ := member.MemberId.(*client.MessageSenderUser)
|
||||||
|
senderId = memberUser.UserId
|
||||||
|
case client.TypeMessageSenderChat:
|
||||||
|
memberChat, _ := member.MemberId.(*client.MessageSenderChat)
|
||||||
|
senderId = memberChat.ChatId
|
||||||
|
}
|
||||||
|
|
||||||
|
nickname := c.GetMUCNickname(senderId)
|
||||||
|
affiliation := c.memberStatusToAffiliation(member.Status)
|
||||||
|
mucState.Members[senderId] = &MUCMember{
|
||||||
|
Nickname: nickname,
|
||||||
|
Affiliation: affiliation,
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.me != nil && senderId == c.me.Id {
|
||||||
|
myNickname = nickname
|
||||||
|
myAffiliation = affiliation
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
gateway.SendPresence(
|
||||||
|
c.xmpp,
|
||||||
|
c.jid,
|
||||||
|
gateway.SPFrom(sChatId),
|
||||||
|
gateway.SPResource(nickname),
|
||||||
|
gateway.SPImmed(true),
|
||||||
|
gateway.SPMUCAffiliation(affiliation),
|
||||||
|
gateway.SPMUCJid(strconv.FormatInt(senderId, 10) + gatewayJidSuffix),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// according to the spec, own member entry should be sent the last
|
||||||
|
gateway.SendPresence(
|
||||||
|
c.xmpp,
|
||||||
|
c.jid,
|
||||||
|
gateway.SPFrom(sChatId),
|
||||||
|
gateway.SPResource(myNickname),
|
||||||
|
gateway.SPImmed(true),
|
||||||
|
gateway.SPMUCAffiliation(myAffiliation),
|
||||||
|
gateway.SPMUCStatusCodes([]uint16{100, 110, 210}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) sendMUCSubject(chatID int64, resource string) {
|
||||||
|
pin, err := c.client.GetChatPinnedMessage(&client.GetChatPinnedMessageRequest{
|
||||||
|
ChatId: chatID,
|
||||||
|
})
|
||||||
|
mucJid := strconv.FormatInt(chatID, 10) + "@" + gateway.Jid.Bare()
|
||||||
|
toJid := c.jid + "/" + resource
|
||||||
|
if err == nil {
|
||||||
|
gateway.SendSubjectMessage(
|
||||||
|
toJid,
|
||||||
|
mucJid + "/" + c.GetMUCNickname(c.GetSenderId(pin)),
|
||||||
|
c.messageToText(pin, false),
|
||||||
|
strconv.FormatInt(pin.Id, 10),
|
||||||
|
c.xmpp,
|
||||||
|
int64(pin.Date),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
gateway.SendSubjectMessage(toJid, mucJid, "", "", c.xmpp, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMUCNickname generates a unique nickname for a MUC member
|
||||||
|
func (c *Client) GetMUCNickname(chatID int64) string {
|
||||||
|
return c.formatContact(chatID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) updateMUCsNickname(memberID int64, newNickname string) {
|
||||||
|
c.locks.mucCacheLock.Lock()
|
||||||
|
defer c.locks.mucCacheLock.Unlock()
|
||||||
|
|
||||||
|
for mucId, state := range c.mucCache {
|
||||||
|
oldMember, ok := state.Members[memberID]
|
||||||
|
if ok {
|
||||||
|
state.Members[memberID] = &MUCMember{
|
||||||
|
Nickname: newNickname,
|
||||||
|
Affiliation: oldMember.Affiliation,
|
||||||
|
}
|
||||||
|
|
||||||
|
sMucId := strconv.FormatInt(mucId, 10)
|
||||||
|
unavailableStatusCodes := []uint16{303, 210}
|
||||||
|
availableStatusCodes := []uint16{100, 210}
|
||||||
|
if c.me != nil && memberID == c.me.Id {
|
||||||
|
unavailableStatusCodes = append(unavailableStatusCodes, 110)
|
||||||
|
availableStatusCodes = append(availableStatusCodes, 110)
|
||||||
|
}
|
||||||
|
gateway.SendPresence(
|
||||||
|
c.xmpp,
|
||||||
|
c.jid,
|
||||||
|
gateway.SPType("unavailable"),
|
||||||
|
gateway.SPFrom(sMucId),
|
||||||
|
gateway.SPResource(oldMember.Nickname),
|
||||||
|
gateway.SPImmed(true),
|
||||||
|
gateway.SPMUCAffiliation(oldMember.Affiliation),
|
||||||
|
gateway.SPMUCNick(newNickname),
|
||||||
|
gateway.SPMUCStatusCodes(unavailableStatusCodes),
|
||||||
|
)
|
||||||
|
gateway.SendPresence(
|
||||||
|
c.xmpp,
|
||||||
|
c.jid,
|
||||||
|
gateway.SPFrom(sMucId),
|
||||||
|
gateway.SPResource(newNickname),
|
||||||
|
gateway.SPImmed(true),
|
||||||
|
gateway.SPMUCAffiliation(oldMember.Affiliation),
|
||||||
|
gateway.SPMUCStatusCodes(availableStatusCodes),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MUCHasResource checks if a MUC was joined from a given resource
|
||||||
|
func (c *Client) MUCHasResource(chatID int64, resource string) bool {
|
||||||
|
c.locks.mucCacheLock.Lock()
|
||||||
|
defer c.locks.mucCacheLock.Unlock()
|
||||||
|
|
||||||
|
mucState, ok := c.mucCache[chatID]
|
||||||
|
if !ok || mucState == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, ok = mucState.Resources[resource]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMyMUCNickname obtains this account's nickname in a given MUC
|
||||||
|
func (c *Client) GetMyMUCNickname(chatID int64) (string, bool) {
|
||||||
|
if c.me == nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
c.locks.mucCacheLock.Lock()
|
||||||
|
defer c.locks.mucCacheLock.Unlock()
|
||||||
|
|
||||||
|
mucState, ok := c.mucCache[chatID]
|
||||||
|
if !ok || mucState == nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
member, ok := mucState.Members[c.me.Id]
|
||||||
|
if !ok {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return member.Nickname, true
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) formatContact(chatID int64) string {
|
func (c *Client) formatContact(chatID int64) string {
|
||||||
if chatID == 0 {
|
if chatID == 0 {
|
||||||
return ""
|
return ""
|
||||||
|
@ -322,7 +543,8 @@ func (c *Client) formatContact(chatID int64) string {
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) getSenderId(message *client.Message) (senderId int64) {
|
// GetSenderId extracts a sender id from a message
|
||||||
|
func (c *Client) GetSenderId(message *client.Message) (senderId int64) {
|
||||||
if message.SenderId != nil {
|
if message.SenderId != nil {
|
||||||
switch message.SenderId.MessageSenderType() {
|
switch message.SenderId.MessageSenderType() {
|
||||||
case client.TypeMessageSenderUser:
|
case client.TypeMessageSenderUser:
|
||||||
|
@ -338,7 +560,7 @@ func (c *Client) getSenderId(message *client.Message) (senderId int64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
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.GetSenderId(message))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) getMessageReply(message *client.Message) (reply *gateway.Reply, replyMsg *client.Message) {
|
func (c *Client) getMessageReply(message *client.Message) (reply *gateway.Reply, replyMsg *client.Message) {
|
||||||
|
@ -358,7 +580,7 @@ func (c *Client) getMessageReply(message *client.Message) (reply *gateway.Reply,
|
||||||
replyId = strconv.FormatInt(message.ReplyToMessageId, 10)
|
replyId = strconv.FormatInt(message.ReplyToMessageId, 10)
|
||||||
}
|
}
|
||||||
reply = &gateway.Reply{
|
reply = &gateway.Reply{
|
||||||
Author: fmt.Sprintf("%v@%s", c.getSenderId(replyMsg), gateway.Jid.Full()),
|
Author: fmt.Sprintf("%v@%s", c.GetSenderId(replyMsg), gateway.Jid.Full()),
|
||||||
Id: replyId,
|
Id: replyId,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -851,13 +1073,13 @@ func (c *Client) countCharsInLines(lines *[]string) (count int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) messageToPrefix(message *client.Message, previewString string, fileString string, replyMsg *client.Message) (string, int, int) {
|
func (c *Client) messageToPrefix(message *client.Message, previewString string, fileString string, replyMsg *client.Message) (string, int, int) {
|
||||||
isPM, err := c.IsPM(message.ChatId)
|
chatType, err := c.GetChatType(message.ChatId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Could not determine if chat is PM: %v", err)
|
log.Errorf("Could not determine chat type: %v", err)
|
||||||
}
|
}
|
||||||
isCarbonsEnabled := gateway.MessageOutgoingPermissionVersion > 0 && c.Session.Carbons
|
isCarbonsEnabled := gateway.MessageOutgoingPermissionVersion > 0 && c.Session.Carbons
|
||||||
// with carbons, hide for all messages in PM and only for outgoing in group chats
|
// with carbons, hide for all messages in PM and only for outgoing in group chats
|
||||||
hideSender := isCarbonsEnabled && (message.IsOutgoing || isPM)
|
hideSender := (isCarbonsEnabled && (message.IsOutgoing || chatType == ChatTypePM)) || (c.Session.MUC && chatType == ChatTypeGroup)
|
||||||
|
|
||||||
var replyStart, replyEnd int
|
var replyStart, replyEnd int
|
||||||
prefix := []string{}
|
prefix := []string{}
|
||||||
|
@ -878,7 +1100,7 @@ func (c *Client) messageToPrefix(message *client.Message, previewString string,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !isPM || !c.Session.HideIds {
|
if (chatType != ChatTypePM && !c.Session.MUC) || !c.Session.HideIds {
|
||||||
prefix = append(prefix, directionChar+strconv.FormatInt(message.Id, 10))
|
prefix = append(prefix, directionChar+strconv.FormatInt(message.Id, 10))
|
||||||
}
|
}
|
||||||
// show sender in group chats
|
// show sender in group chats
|
||||||
|
@ -931,10 +1153,29 @@ func (c *Client) ensureDownloadFile(file *client.File) *client.File {
|
||||||
return file
|
return file
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProcessIncomingMessage transfers a message to XMPP side and marks it as read on Telegram side
|
// ProcessIncomingMessage is a legacy wrapper for SendMessageToGateway aiming only PM messages
|
||||||
func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) {
|
func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) {
|
||||||
isCarbon := gateway.MessageOutgoingPermissionVersion > 0 && c.Session.Carbons && message.IsOutgoing
|
c.SendMessageToGateway(chatId, message, "", false, "", []string{})
|
||||||
jids := c.getCarbonFullJids(isCarbon, "")
|
}
|
||||||
|
|
||||||
|
// SendMessageToGateway transfers a message to XMPP side and marks it as read on Telegram side
|
||||||
|
func (c *Client) SendMessageToGateway(chatId int64, message *client.Message, id string, delay bool, groupChatFrom string, groupChatTos []string) {
|
||||||
|
var isCarbon bool
|
||||||
|
var jids []string
|
||||||
|
var isGroupchat bool
|
||||||
|
var originalFrom string
|
||||||
|
if len(groupChatTos) == 0 {
|
||||||
|
isCarbon = gateway.MessageOutgoingPermissionVersion > 0 && c.Session.Carbons && message.IsOutgoing
|
||||||
|
jids = c.getCarbonFullJids(isCarbon, "")
|
||||||
|
} else {
|
||||||
|
isGroupchat = true
|
||||||
|
jids = groupChatTos
|
||||||
|
|
||||||
|
senderId := c.GetSenderId(message)
|
||||||
|
if senderId != 0 {
|
||||||
|
originalFrom = strconv.FormatInt(senderId, 10) + "@" + gateway.Jid.Full()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var text, oob, auxText string
|
var text, oob, auxText string
|
||||||
|
|
||||||
|
@ -1006,13 +1247,29 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) {
|
||||||
})
|
})
|
||||||
|
|
||||||
// forward message to XMPP
|
// forward message to XMPP
|
||||||
sId := strconv.FormatInt(message.Id, 10)
|
var sId string
|
||||||
sChatId := strconv.FormatInt(chatId, 10)
|
if id == "" {
|
||||||
|
sId = strconv.FormatInt(message.Id, 10)
|
||||||
|
} else {
|
||||||
|
sId = id
|
||||||
|
}
|
||||||
|
|
||||||
|
var from string
|
||||||
|
if groupChatFrom == "" {
|
||||||
|
from = strconv.FormatInt(chatId, 10)
|
||||||
|
} else {
|
||||||
|
from = groupChatFrom
|
||||||
|
}
|
||||||
|
|
||||||
|
var timestamp int64
|
||||||
|
if delay {
|
||||||
|
timestamp = int64(message.Date)
|
||||||
|
}
|
||||||
|
|
||||||
for _, jid := range jids {
|
for _, jid := range jids {
|
||||||
gateway.SendMessageWithOOB(jid, sChatId, text, sId, c.xmpp, reply, oob, isCarbon)
|
gateway.SendMessageWithOOB(jid, from, text, sId, c.xmpp, reply, timestamp, oob, isCarbon, isGroupchat, originalFrom)
|
||||||
if auxText != "" {
|
if auxText != "" {
|
||||||
gateway.SendMessage(jid, sChatId, auxText, sId, c.xmpp, reply, isCarbon)
|
gateway.SendMessage(jid, from, auxText, sId, c.xmpp, reply, timestamp, isCarbon, isGroupchat, originalFrom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1023,21 +1280,21 @@ func (c *Client) PrepareOutgoingMessageContent(text string) client.InputMessageC
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProcessOutgoingMessage executes commands or sends messages to mapped chats, returns message id
|
// ProcessOutgoingMessage executes commands or sends messages to mapped chats, returns message id
|
||||||
func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid string, replyId int64, replaceId int64) int64 {
|
func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid string, replyId int64, replaceId int64, isGroupchat bool) *client.Message {
|
||||||
if !c.Online() {
|
if !c.Online() {
|
||||||
// we're offline
|
// we're offline
|
||||||
return 0
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
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, 0, isGroupchat)
|
||||||
}
|
}
|
||||||
// do not send on success
|
// do not send on success
|
||||||
if isCommand {
|
if isCommand {
|
||||||
return 0
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1059,27 +1316,31 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str
|
||||||
if c.content.Upload != "" && strings.HasPrefix(text, c.content.Upload) {
|
if c.content.Upload != "" && strings.HasPrefix(text, c.content.Upload) {
|
||||||
response, err := http.Get(text)
|
response, err := http.Get(text)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.returnError(returnJid, chatID, "Failed to fetch the uploaded file", err)
|
c.returnError(returnJid, chatID, "Failed to fetch the uploaded file", err, 500, isGroupchat)
|
||||||
}
|
}
|
||||||
if response != nil && response.Body != nil {
|
if response != nil && response.Body != nil {
|
||||||
defer response.Body.Close()
|
defer response.Body.Close()
|
||||||
|
|
||||||
if response.StatusCode != 200 {
|
if response.StatusCode != 200 {
|
||||||
c.returnMessage(returnJid, chatID, fmt.Sprintf("Received status code %v", response.StatusCode))
|
c.returnMessage(returnJid, chatID, fmt.Sprintf("Received status code %v", response.StatusCode), response.StatusCode, isGroupchat)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tempDir, err := ioutil.TempDir("", "telegabber-*")
|
tempDir, err := ioutil.TempDir("", "telegabber-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.returnError(returnJid, chatID, "Failed to create a temporary directory", err)
|
c.returnError(returnJid, chatID, "Failed to create a temporary directory", err, 500, isGroupchat)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
tempFile, err := os.Create(filepath.Join(tempDir, filepath.Base(text)))
|
tempFile, err := os.Create(filepath.Join(tempDir, filepath.Base(text)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.returnError(returnJid, chatID, "Failed to create a temporary file", err)
|
c.returnError(returnJid, chatID, "Failed to create a temporary file", err, 500, isGroupchat)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = io.Copy(tempFile, response.Body)
|
_, err = io.Copy(tempFile, response.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.returnError(returnJid, chatID, "Failed to write a temporary file", err)
|
c.returnError(returnJid, chatID, "Failed to write a temporary file", err, 500, isGroupchat)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
file = &client.InputFileLocal{
|
file = &client.InputFileLocal{
|
||||||
|
@ -1107,10 +1368,10 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str
|
||||||
InputMessageContent: content,
|
InputMessageContent: content,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.returnError(returnJid, chatID, "Not edited", err)
|
c.returnError(returnJid, chatID, "Not edited", err, 400, isGroupchat)
|
||||||
return 0
|
return nil
|
||||||
}
|
}
|
||||||
return tgMessage.Id
|
return tgMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
tgMessage, err := c.client.SendMessage(&client.SendMessageRequest{
|
tgMessage, err := c.client.SendMessage(&client.SendMessageRequest{
|
||||||
|
@ -1119,18 +1380,30 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str
|
||||||
InputMessageContent: content,
|
InputMessageContent: content,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.returnError(returnJid, chatID, "Not sent", err)
|
c.returnError(returnJid, chatID, "Not sent", err, 400, isGroupchat)
|
||||||
return 0
|
return nil
|
||||||
}
|
}
|
||||||
return tgMessage.Id
|
return tgMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) returnMessage(returnJid string, chatID int64, text string) {
|
func (c *Client) returnMessage(returnJid string, chatID int64, text string, code int, isGroupchat bool) {
|
||||||
gateway.SendTextMessage(returnJid, strconv.FormatInt(chatID, 10), text, c.xmpp)
|
sChatId := strconv.FormatInt(chatID, 10)
|
||||||
|
if isGroupchat {
|
||||||
|
gateway.SendErrorMessage(returnJid, sChatId + "@" + gateway.Jid.Bare(), text, code, isGroupchat, c.xmpp)
|
||||||
|
} else {
|
||||||
|
gateway.SendTextMessage(returnJid, sChatId, text, c.xmpp)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) returnError(returnJid string, chatID int64, msg string, err error) {
|
func (c *Client) returnError(returnJid string, chatID int64, msg string, err error, code int, isGroupchat bool) {
|
||||||
c.returnMessage(returnJid, chatID, fmt.Sprintf("%s: %s", msg, err.Error()))
|
responseError, ok := err.(client.ResponseError)
|
||||||
|
log.Debugf("responseError: %#v", responseError)
|
||||||
|
if ok && responseError.Err != nil {
|
||||||
|
if responseError.Err.Message == "Have no write access to the chat" {
|
||||||
|
code = 403
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.returnMessage(returnJid, chatID, fmt.Sprintf("%s: %s", msg, err.Error()), code, isGroupchat)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) prepareOutgoingMessageContent(text string, file *client.InputFileLocal) client.InputMessageContent {
|
func (c *Client) prepareOutgoingMessageContent(text string, file *client.InputFileLocal) client.InputMessageContent {
|
||||||
|
@ -1228,6 +1501,36 @@ func (c *Client) getLastMessages(id int64, query string, from int64, count int32
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) getNLastMessages(chatID int64, limit int32) ([]*client.Message, error) {
|
||||||
|
var newMessages *client.Messages
|
||||||
|
var messages []*client.Message
|
||||||
|
var err error
|
||||||
|
var fromId int64
|
||||||
|
|
||||||
|
for _ = range make([]struct{}, limit) { // safety limit
|
||||||
|
if len(messages) > 0 {
|
||||||
|
fromId = messages[len(messages)-1].Id
|
||||||
|
}
|
||||||
|
|
||||||
|
newMessages, err = c.client.GetChatHistory(&client.GetChatHistoryRequest{
|
||||||
|
ChatId: chatID,
|
||||||
|
FromMessageId: fromId,
|
||||||
|
Limit: limit,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
messages = append(messages, newMessages.Messages...)
|
||||||
|
|
||||||
|
if len(newMessages.Messages) == 0 || len(messages) >= int(limit) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages, nil
|
||||||
|
}
|
||||||
|
|
||||||
// DownloadFile actually obtains a file by id given by TDlib
|
// DownloadFile actually obtains a file by id given by TDlib
|
||||||
func (c *Client) DownloadFile(id int32, priority int32, synchronous bool) (*client.File, error) {
|
func (c *Client) DownloadFile(id int32, priority int32, synchronous bool) (*client.File, error) {
|
||||||
return c.client.DownloadFile(&client.DownloadFileRequest{
|
return c.client.DownloadFile(&client.DownloadFileRequest{
|
||||||
|
@ -1285,7 +1588,7 @@ func (c *Client) GetChatDescription(chat *client.Chat) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Warnf("Coudln't retrieve private chat info: %v", err.Error())
|
log.Warnf("Couldn't retrieve private chat info: %v", err.Error())
|
||||||
}
|
}
|
||||||
} else if chatType == client.TypeChatTypeBasicGroup {
|
} else if chatType == client.TypeChatTypeBasicGroup {
|
||||||
basicGroupType, _ := chat.Type.(*client.ChatTypeBasicGroup)
|
basicGroupType, _ := chat.Type.(*client.ChatTypeBasicGroup)
|
||||||
|
@ -1295,7 +1598,7 @@ func (c *Client) GetChatDescription(chat *client.Chat) string {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return fullInfo.Description
|
return fullInfo.Description
|
||||||
} else {
|
} else {
|
||||||
log.Warnf("Coudln't retrieve basic group info: %v", err.Error())
|
log.Warnf("Couldn't retrieve basic group info: %v", err.Error())
|
||||||
}
|
}
|
||||||
} else if chatType == client.TypeChatTypeSupergroup {
|
} else if chatType == client.TypeChatTypeSupergroup {
|
||||||
supergroupType, _ := chat.Type.(*client.ChatTypeSupergroup)
|
supergroupType, _ := chat.Type.(*client.ChatTypeSupergroup)
|
||||||
|
@ -1305,12 +1608,68 @@ func (c *Client) GetChatDescription(chat *client.Chat) string {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return fullInfo.Description
|
return fullInfo.Description
|
||||||
} else {
|
} else {
|
||||||
log.Warnf("Coudln't retrieve supergroup info: %v", err.Error())
|
log.Warnf("Couldn't retrieve supergroup info: %v", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetChatMemberCount obtains the member count depending on the chat type
|
||||||
|
func (c *Client) GetChatMemberCount(chat *client.Chat) int32 {
|
||||||
|
chatType := chat.Type.ChatTypeType()
|
||||||
|
if chatType == client.TypeChatTypePrivate {
|
||||||
|
return 2
|
||||||
|
} else if chatType == client.TypeChatTypeBasicGroup {
|
||||||
|
basicGroupType, _ := chat.Type.(*client.ChatTypeBasicGroup)
|
||||||
|
basicGroup, err := c.client.GetBasicGroup(&client.GetBasicGroupRequest{
|
||||||
|
BasicGroupId: basicGroupType.BasicGroupId,
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
return basicGroup.MemberCount
|
||||||
|
} else {
|
||||||
|
log.Warnf("Couldn't retrieve basic group: %v", err.Error())
|
||||||
|
}
|
||||||
|
} else if chatType == client.TypeChatTypeSupergroup {
|
||||||
|
supergroupType, _ := chat.Type.(*client.ChatTypeSupergroup)
|
||||||
|
supergroup, err := c.client.GetSupergroup(&client.GetSupergroupRequest{
|
||||||
|
SupergroupId: supergroupType.SupergroupId,
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
return supergroup.MemberCount
|
||||||
|
} else {
|
||||||
|
log.Warnf("Couldn't retrieve supergroup: %v", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGroupChats obtains all group chats
|
||||||
|
func (c *Client) GetGroupChats() []*client.Chat {
|
||||||
|
var groupChats []*client.Chat
|
||||||
|
|
||||||
|
chats, err := c.client.GetChats(&client.GetChatsRequest{
|
||||||
|
Limit: chatsLimit,
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
for _, id := range chats.ChatIds {
|
||||||
|
chat, _, _ := c.GetContactByID(id, nil)
|
||||||
|
if chat != nil && c.IsGroup(chat) {
|
||||||
|
groupChats = append(groupChats, chat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Errorf("Could not retrieve chats: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupChats
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsGroup determines if a chat is eligible to be represented as MUC
|
||||||
|
func (c *Client) IsGroup(chat *client.Chat) bool {
|
||||||
|
typ := chat.Type.ChatTypeType()
|
||||||
|
return typ == client.TypeChatTypeBasicGroup
|
||||||
|
}
|
||||||
|
|
||||||
// subscribe to a Telegram ID
|
// subscribe to a Telegram ID
|
||||||
func (c *Client) subscribeToID(id int64, chat *client.Chat) {
|
func (c *Client) subscribeToID(id int64, chat *client.Chat) {
|
||||||
var args []args.V
|
var args []args.V
|
||||||
|
@ -1321,6 +1680,10 @@ func (c *Client) subscribeToID(id int64, chat *client.Chat) {
|
||||||
chat, _, _ = c.GetContactByID(id, nil)
|
chat, _, _ = c.GetContactByID(id, nil)
|
||||||
}
|
}
|
||||||
if chat != nil {
|
if chat != nil {
|
||||||
|
if c.Session.MUC && c.IsGroup(chat) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
args = append(args, gateway.SPNickname(chat.Title))
|
args = append(args, gateway.SPNickname(chat.Title))
|
||||||
|
|
||||||
gateway.SetNickname(c.jid, strconv.FormatInt(id, 10), chat.Title, c.xmpp)
|
gateway.SetNickname(c.jid, strconv.FormatInt(id, 10), chat.Title, c.xmpp)
|
||||||
|
@ -1378,6 +1741,10 @@ func (c *Client) UpdateChatNicknames() {
|
||||||
for _, id := range c.cache.ChatsKeys() {
|
for _, id := range c.cache.ChatsKeys() {
|
||||||
chat, ok := c.cache.GetChat(id)
|
chat, ok := c.cache.GetChat(id)
|
||||||
if ok {
|
if ok {
|
||||||
|
if c.Session.MUC && c.IsGroup(chat) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
newArgs := []args.V{
|
newArgs := []args.V{
|
||||||
gateway.SPFrom(strconv.FormatInt(id, 10)),
|
gateway.SPFrom(strconv.FormatInt(id, 10)),
|
||||||
gateway.SPNickname(chat.Title),
|
gateway.SPNickname(chat.Title),
|
||||||
|
@ -1504,3 +1871,59 @@ func (c *Client) usernamesToString(usernames []string) string {
|
||||||
}
|
}
|
||||||
return strings.Join(atUsernames, ", ")
|
return strings.Join(atUsernames, ", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) memberStatusToAffiliation(memberStatus client.ChatMemberStatus) string {
|
||||||
|
switch memberStatus.ChatMemberStatusType() {
|
||||||
|
case client.TypeChatMemberStatusCreator:
|
||||||
|
return "owner"
|
||||||
|
case client.TypeChatMemberStatusAdministrator:
|
||||||
|
return "admin"
|
||||||
|
case client.TypeChatMemberStatusMember:
|
||||||
|
return "member"
|
||||||
|
case client.TypeChatMemberStatusRestricted:
|
||||||
|
return "outcast"
|
||||||
|
case client.TypeChatMemberStatusLeft:
|
||||||
|
return "none"
|
||||||
|
case client.TypeChatMemberStatusBanned:
|
||||||
|
return "outcast"
|
||||||
|
}
|
||||||
|
return "member"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) sendMessagesReverse(chatID int64, messages []*client.Message, plain bool, toJid string) {
|
||||||
|
sChatId := strconv.FormatInt(chatID, 10)
|
||||||
|
var mucJid string
|
||||||
|
if toJid != "" {
|
||||||
|
mucJid = sChatId + "@" + gateway.Jid.Bare()
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := len(messages) - 1; i >= 0; i-- {
|
||||||
|
message := messages[i]
|
||||||
|
|
||||||
|
if plain {
|
||||||
|
reply, _ := c.getMessageReply(message)
|
||||||
|
|
||||||
|
gateway.SendMessage(
|
||||||
|
c.jid,
|
||||||
|
sChatId,
|
||||||
|
c.formatMessage(0, 0, false, message),
|
||||||
|
strconv.FormatInt(message.Id, 10),
|
||||||
|
c.xmpp,
|
||||||
|
reply,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
c.SendMessageToGateway(
|
||||||
|
chatID,
|
||||||
|
message,
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
mucJid + "/" + c.GetMUCNickname(c.GetSenderId(message)),
|
||||||
|
[]string{toJid},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -213,6 +213,60 @@ type QueryRegisterRemove struct {
|
||||||
XMLName xml.Name `xml:"remove"`
|
XMLName xml.Name `xml:"remove"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PresenceXMucUserExtension is from XEP-0045
|
||||||
|
type PresenceXMucUserExtension struct {
|
||||||
|
XMLName xml.Name `xml:"http://jabber.org/protocol/muc#user x"`
|
||||||
|
Item PresenceXMucUserItem
|
||||||
|
Statuses []PresenceXMucUserStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
// PresenceXMucUserItem is from XEP-0045
|
||||||
|
type PresenceXMucUserItem struct {
|
||||||
|
XMLName xml.Name `xml:"item"`
|
||||||
|
Affiliation string `xml:"affiliation,attr"`
|
||||||
|
Jid string `xml:"jid,attr"`
|
||||||
|
Nick string `xml:"nick,attr,omitempty"`
|
||||||
|
Role string `xml:"role,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PresenceXMucUserStatus is from XEP-0045
|
||||||
|
type PresenceXMucUserStatus struct {
|
||||||
|
XMLName xml.Name `xml:"status"`
|
||||||
|
Code uint16 `xml:"code,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageDelay is from XEP-0203
|
||||||
|
type MessageDelay struct {
|
||||||
|
XMLName xml.Name `xml:"urn:xmpp:delay delay"`
|
||||||
|
From string `xml:"from,attr"`
|
||||||
|
Stamp string `xml:"stamp,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageDelayLegacy is from XEP-0203
|
||||||
|
type MessageDelayLegacy struct {
|
||||||
|
XMLName xml.Name `xml:"jabber:x:delay x"`
|
||||||
|
From string `xml:"from,attr"`
|
||||||
|
Stamp string `xml:"stamp,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageAddresses is from XEP-0033
|
||||||
|
type MessageAddresses struct {
|
||||||
|
XMLName xml.Name `xml:"http://jabber.org/protocol/address addresses"`
|
||||||
|
Addresses []MessageAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageAddress is from XEP-0033
|
||||||
|
type MessageAddress struct {
|
||||||
|
XMLName xml.Name `xml:"address"`
|
||||||
|
Type string `xml:"type,attr"`
|
||||||
|
Jid string `xml:"jid,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmptySubject is a dummy for MUCs to circumvent omitempty. Not registered as it would conflict with Subject field
|
||||||
|
type EmptySubject struct {
|
||||||
|
XMLName xml.Name `xml:"subject"`
|
||||||
|
}
|
||||||
|
|
||||||
// Namespace is a namespace!
|
// Namespace is a namespace!
|
||||||
func (c PresenceNickExtension) Namespace() string {
|
func (c PresenceNickExtension) Namespace() string {
|
||||||
return c.XMLName.Space
|
return c.XMLName.Space
|
||||||
|
@ -278,6 +332,21 @@ func (c QueryRegister) GetSet() *stanza.ResultSet {
|
||||||
return c.ResultSet
|
return c.ResultSet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Namespace is a namespace!
|
||||||
|
func (c PresenceXMucUserExtension) Namespace() string {
|
||||||
|
return c.XMLName.Space
|
||||||
|
}
|
||||||
|
|
||||||
|
// Namespace is a namespace!
|
||||||
|
func (c MessageDelay) Namespace() string {
|
||||||
|
return c.XMLName.Space
|
||||||
|
}
|
||||||
|
|
||||||
|
// Namespace is a namespace!
|
||||||
|
func (c MessageDelayLegacy) Namespace() string {
|
||||||
|
return c.XMLName.Space
|
||||||
|
}
|
||||||
|
|
||||||
// Name is a packet name
|
// Name is a packet name
|
||||||
func (ClientMessage) Name() string {
|
func (ClientMessage) Name() string {
|
||||||
return "message"
|
return "message"
|
||||||
|
@ -362,4 +431,28 @@ func init() {
|
||||||
"jabber:iq:register",
|
"jabber:iq:register",
|
||||||
"query",
|
"query",
|
||||||
}, QueryRegister{})
|
}, QueryRegister{})
|
||||||
|
|
||||||
|
// presence muc user
|
||||||
|
stanza.TypeRegistry.MapExtension(stanza.PKTPresence, xml.Name{
|
||||||
|
"http://jabber.org/protocol/muc#user",
|
||||||
|
"x",
|
||||||
|
}, PresenceXMucUserExtension{})
|
||||||
|
|
||||||
|
// message delay
|
||||||
|
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{
|
||||||
|
"urn:xmpp:delay",
|
||||||
|
"delay",
|
||||||
|
}, MessageDelay{})
|
||||||
|
|
||||||
|
// legacy message delay
|
||||||
|
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{
|
||||||
|
"jabber:x:delay",
|
||||||
|
"x",
|
||||||
|
}, MessageDelayLegacy{})
|
||||||
|
|
||||||
|
// message addresses
|
||||||
|
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{
|
||||||
|
"http://jabber.org/protocol/address",
|
||||||
|
"addresses",
|
||||||
|
}, MessageAddresses{})
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"dev.narayana.im/narayana/telegabber/badger"
|
"dev.narayana.im/narayana/telegabber/badger"
|
||||||
"dev.narayana.im/narayana/telegabber/xmpp/extensions"
|
"dev.narayana.im/narayana/telegabber/xmpp/extensions"
|
||||||
|
@ -42,26 +43,41 @@ var DirtySessions = false
|
||||||
var MessageOutgoingPermissionVersion = 0
|
var MessageOutgoingPermissionVersion = 0
|
||||||
|
|
||||||
// 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, isCarbon bool) {
|
func SendMessage(to, from, body, id string, component *xmpp.Component, reply *Reply, timestamp int64, isCarbon, isGroupchat bool, originalFrom string) {
|
||||||
sendMessageWrapper(to, from, body, id, component, reply, "", isCarbon)
|
sendMessageWrapper(to, from, body, "", "", id, component, reply, timestamp, "", isCarbon, isGroupchat, false, originalFrom, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendServiceMessage creates and sends a simple message stanza from transport
|
// SendServiceMessage creates and sends a simple message stanza from transport
|
||||||
func SendServiceMessage(to string, body string, component *xmpp.Component) {
|
func SendServiceMessage(to, body string, component *xmpp.Component) {
|
||||||
sendMessageWrapper(to, "", body, "", component, nil, "", false)
|
sendMessageWrapper(to, "", body, "", "", "", component, nil, 0, "", false, false, false, "", 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendTextMessage creates and sends a simple message stanza
|
// SendTextMessage creates and sends a simple message stanza
|
||||||
func SendTextMessage(to string, from string, body string, component *xmpp.Component) {
|
func SendTextMessage(to, from, body string, component *xmpp.Component) {
|
||||||
sendMessageWrapper(to, from, body, "", component, nil, "", false)
|
sendMessageWrapper(to, from, body, "", "", "", component, nil, 0, "", false, false, false, "", 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendErrorMessage creates and sends an error message stanza
|
||||||
|
func SendErrorMessage(to, from, text string, code int, isGroupchat bool, component *xmpp.Component) {
|
||||||
|
sendMessageWrapper(to, from, "", "", text, "", component, nil, 0, "", false, isGroupchat, false, "", code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendErrorMessageWithBody creates and sends an error message stanza with body payload
|
||||||
|
func SendErrorMessageWithBody(to, from, body, errorText, id string, code int, isGroupchat bool, component *xmpp.Component) {
|
||||||
|
sendMessageWrapper(to, from, body, "", errorText, id, component, nil, 0, "", false, isGroupchat, false, "", code)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendMessageWithOOB creates and sends a message stanza with OOB URL
|
// SendMessageWithOOB creates and sends a message stanza with OOB URL
|
||||||
func SendMessageWithOOB(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, oob string, isCarbon bool) {
|
func SendMessageWithOOB(to, from, body, id string, component *xmpp.Component, reply *Reply, timestamp int64, oob string, isCarbon, isGroupchat bool, originalFrom string) {
|
||||||
sendMessageWrapper(to, from, body, id, component, reply, oob, isCarbon)
|
sendMessageWrapper(to, from, body, "", "", id, component, reply, timestamp, oob, isCarbon, isGroupchat, false, originalFrom, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendMessageWrapper(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, oob string, isCarbon bool) {
|
// SendSubjectMessage creates and sends a MUC subject
|
||||||
|
func SendSubjectMessage(to, from, subject, id string, component *xmpp.Component, timestamp int64) {
|
||||||
|
sendMessageWrapper(to, from, "", subject, "", id, component, nil, timestamp, "", false, true, true, "", 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendMessageWrapper(to, from, body, subject, errorText, id string, component *xmpp.Component, reply *Reply, timestamp int64, oob string, isCarbon, isGroupchat, forceSubject bool, originalFrom string, errorCode int) {
|
||||||
toJid, err := stanza.NewJid(to)
|
toJid, err := stanza.NewJid(to)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
|
@ -76,12 +92,17 @@ func sendMessageWrapper(to string, from string, body string, id string, componen
|
||||||
var logFrom string
|
var logFrom string
|
||||||
var messageFrom string
|
var messageFrom string
|
||||||
var messageTo string
|
var messageTo string
|
||||||
if from == "" {
|
if isGroupchat {
|
||||||
logFrom = componentJid
|
|
||||||
messageFrom = componentJid
|
|
||||||
} else {
|
|
||||||
logFrom = from
|
logFrom = from
|
||||||
messageFrom = from + "@" + componentJid
|
messageFrom = from
|
||||||
|
} else {
|
||||||
|
if from == "" {
|
||||||
|
logFrom = componentJid
|
||||||
|
messageFrom = componentJid
|
||||||
|
} else {
|
||||||
|
logFrom = from
|
||||||
|
messageFrom = from + "@" + componentJid
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if isCarbon {
|
if isCarbon {
|
||||||
messageTo = messageFrom
|
messageTo = messageFrom
|
||||||
|
@ -95,14 +116,51 @@ func sendMessageWrapper(to string, from string, body string, id string, componen
|
||||||
"to": to,
|
"to": to,
|
||||||
}).Warn("Got message")
|
}).Warn("Got message")
|
||||||
|
|
||||||
|
var messageType stanza.StanzaType
|
||||||
|
if errorCode != 0 {
|
||||||
|
messageType = stanza.MessageTypeError
|
||||||
|
} else if isGroupchat {
|
||||||
|
messageType = stanza.MessageTypeGroupchat
|
||||||
|
} else {
|
||||||
|
messageType = stanza.MessageTypeChat
|
||||||
|
}
|
||||||
|
|
||||||
message := stanza.Message{
|
message := stanza.Message{
|
||||||
Attrs: stanza.Attrs{
|
Attrs: stanza.Attrs{
|
||||||
From: messageFrom,
|
From: messageFrom,
|
||||||
To: messageTo,
|
To: messageTo,
|
||||||
Type: "chat",
|
Type: messageType,
|
||||||
Id: id,
|
Id: id,
|
||||||
},
|
},
|
||||||
Body: body,
|
Subject: subject,
|
||||||
|
Body: body,
|
||||||
|
}
|
||||||
|
if errorCode != 0 {
|
||||||
|
message.Error = stanza.Err{
|
||||||
|
Code: errorCode,
|
||||||
|
Text: errorText,
|
||||||
|
}
|
||||||
|
switch errorCode {
|
||||||
|
case 400:
|
||||||
|
message.Error.Type = stanza.ErrorTypeModify
|
||||||
|
message.Error.Reason = "bad-request"
|
||||||
|
case 403:
|
||||||
|
message.Error.Type = stanza.ErrorTypeAuth
|
||||||
|
message.Error.Reason = "forbidden"
|
||||||
|
case 404:
|
||||||
|
message.Error.Type = stanza.ErrorTypeCancel
|
||||||
|
message.Error.Reason = "item-not-found"
|
||||||
|
case 406:
|
||||||
|
message.Error.Type = stanza.ErrorTypeModify
|
||||||
|
message.Error.Reason = "not-acceptable"
|
||||||
|
case 500:
|
||||||
|
message.Error.Type = stanza.ErrorTypeWait
|
||||||
|
message.Error.Reason = "internal-server-error"
|
||||||
|
default:
|
||||||
|
log.Error("Unknown error code, falling back with empty reason")
|
||||||
|
message.Error.Type = stanza.ErrorTypeCancel
|
||||||
|
message.Error.Reason = "undefined-condition"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oob != "" {
|
if oob != "" {
|
||||||
|
@ -119,16 +177,43 @@ func sendMessageWrapper(to string, from string, body string, id string, componen
|
||||||
message.Extensions = append(message.Extensions, extensions.NewReplyFallback(reply.Start, reply.End))
|
message.Extensions = append(message.Extensions, extensions.NewReplyFallback(reply.Start, reply.End))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !isCarbon && toJid.Resource != "" {
|
if !isGroupchat && !isCarbon && toJid.Resource != "" {
|
||||||
message.Extensions = append(message.Extensions, stanza.HintNoCopy{})
|
message.Extensions = append(message.Extensions, stanza.HintNoCopy{})
|
||||||
}
|
}
|
||||||
|
if timestamp != 0 {
|
||||||
|
var delayFrom string
|
||||||
|
if isGroupchat {
|
||||||
|
delayFrom, _, _ = SplitJID(from)
|
||||||
|
}
|
||||||
|
message.Extensions = append(message.Extensions, extensions.MessageDelay{
|
||||||
|
From: delayFrom,
|
||||||
|
Stamp: time.Unix(timestamp, 0).UTC().Format(time.RFC3339),
|
||||||
|
})
|
||||||
|
message.Extensions = append(message.Extensions, extensions.MessageDelayLegacy{
|
||||||
|
From: delayFrom,
|
||||||
|
Stamp: time.Unix(timestamp, 0).UTC().Format("20060102T15:04:05"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if originalFrom != "" {
|
||||||
|
message.Extensions = append(message.Extensions, extensions.MessageAddresses{
|
||||||
|
Addresses: []extensions.MessageAddress{
|
||||||
|
extensions.MessageAddress{
|
||||||
|
Type: "ofrom",
|
||||||
|
Jid: originalFrom,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if subject == "" && forceSubject {
|
||||||
|
message.Extensions = append(message.Extensions, extensions.EmptySubject{})
|
||||||
|
}
|
||||||
|
|
||||||
if isCarbon {
|
if isCarbon {
|
||||||
carbonMessage := extensions.ClientMessage{
|
carbonMessage := extensions.ClientMessage{
|
||||||
Attrs: stanza.Attrs{
|
Attrs: stanza.Attrs{
|
||||||
From: bareTo,
|
From: bareTo,
|
||||||
To: to,
|
To: to,
|
||||||
Type: "chat",
|
Type: messageType,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
carbonMessage.Extensions = append(carbonMessage.Extensions, extensions.CarbonSent{
|
carbonMessage.Extensions = append(carbonMessage.Extensions, extensions.CarbonSent{
|
||||||
|
@ -240,6 +325,18 @@ var SPResource = args.NewString()
|
||||||
// SPImmed skips queueing
|
// SPImmed skips queueing
|
||||||
var SPImmed = args.NewBool(args.Default(true))
|
var SPImmed = args.NewBool(args.Default(true))
|
||||||
|
|
||||||
|
// SPMUCAffiliation is a XEP-0045 MUC affiliation
|
||||||
|
var SPMUCAffiliation = args.NewString()
|
||||||
|
|
||||||
|
// SPMUCNick is a XEP-0045 MUC user nick
|
||||||
|
var SPMUCNick = args.NewString()
|
||||||
|
|
||||||
|
// SPMUCJid is a real jid of a MUC member
|
||||||
|
var SPMUCJid = args.NewString()
|
||||||
|
|
||||||
|
// SPMUCStatusCodes is a set of XEP-0045 MUC status codes
|
||||||
|
var SPMUCStatusCodes = args.New()
|
||||||
|
|
||||||
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) {
|
||||||
|
@ -295,6 +392,32 @@ func newPresence(bareJid string, to string, args ...args.V) stanza.Presence {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if SPMUCAffiliation.IsSet(args) {
|
||||||
|
affiliation := SPMUCAffiliation.Get(args)
|
||||||
|
if affiliation != "" {
|
||||||
|
userExt := extensions.PresenceXMucUserExtension{
|
||||||
|
Item: extensions.PresenceXMucUserItem{
|
||||||
|
Affiliation: affiliation,
|
||||||
|
Role: affilationToRole(affiliation),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if SPMUCNick.IsSet(args) {
|
||||||
|
userExt.Item.Nick = SPMUCNick.Get(args)
|
||||||
|
}
|
||||||
|
if SPMUCJid.IsSet(args) {
|
||||||
|
userExt.Item.Jid = SPMUCJid.Get(args)
|
||||||
|
}
|
||||||
|
if SPMUCStatusCodes.IsSet(args) {
|
||||||
|
statusCodes := SPMUCStatusCodes.Get(args).([]uint16)
|
||||||
|
for _, statusCode := range statusCodes {
|
||||||
|
userExt.Statuses = append(userExt.Statuses, extensions.PresenceXMucUserStatus{
|
||||||
|
Code: statusCode,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
presence.Extensions = append(presence.Extensions, userExt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return presence
|
return presence
|
||||||
}
|
}
|
||||||
|
@ -377,3 +500,13 @@ func SplitJID(from string) (string, string, bool) {
|
||||||
}
|
}
|
||||||
return fromJid.Bare(), fromJid.Resource, true
|
return fromJid.Bare(), fromJid.Resource, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func affilationToRole(affilation string) string {
|
||||||
|
switch affilation {
|
||||||
|
case "owner", "admin":
|
||||||
|
return "moderator"
|
||||||
|
case "member":
|
||||||
|
return "participant"
|
||||||
|
}
|
||||||
|
return "none"
|
||||||
|
}
|
||||||
|
|
333
xmpp/handlers.go
333
xmpp/handlers.go
|
@ -27,6 +27,12 @@ const (
|
||||||
)
|
)
|
||||||
const NodeVCard4 string = "urn:xmpp:vcard4"
|
const NodeVCard4 string = "urn:xmpp:vcard4"
|
||||||
|
|
||||||
|
type discoType int
|
||||||
|
const (
|
||||||
|
discoTypeInfo discoType = iota
|
||||||
|
discoTypeItems
|
||||||
|
)
|
||||||
|
|
||||||
func logPacketType(p stanza.Packet) {
|
func logPacketType(p stanza.Packet) {
|
||||||
log.Warnf("Ignoring packet: %T\n", p)
|
log.Warnf("Ignoring packet: %T\n", p)
|
||||||
}
|
}
|
||||||
|
@ -55,12 +61,12 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) {
|
||||||
}
|
}
|
||||||
_, ok = iq.Payload.(*stanza.DiscoInfo)
|
_, ok = iq.Payload.(*stanza.DiscoInfo)
|
||||||
if ok {
|
if ok {
|
||||||
go handleGetDiscoInfo(s, iq)
|
go handleGetDisco(discoTypeInfo, s, iq)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, ok = iq.Payload.(*stanza.DiscoItems)
|
_, ok = iq.Payload.(*stanza.DiscoItems)
|
||||||
if ok {
|
if ok {
|
||||||
go handleGetDiscoItems(s, iq)
|
go handleGetDisco(discoTypeItems, s, iq)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, ok = iq.Payload.(*extensions.QueryRegister)
|
_, ok = iq.Payload.(*extensions.QueryRegister)
|
||||||
|
@ -117,6 +123,26 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) {
|
||||||
|
|
||||||
toID, ok := toToID(msg.To)
|
toID, ok := toToID(msg.To)
|
||||||
if ok {
|
if ok {
|
||||||
|
toJid, err := stanza.NewJid(msg.To)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Invalid to JID!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isGroupchat := msg.Type == "groupchat"
|
||||||
|
|
||||||
|
if session.Session.MUC && toJid.Resource != "" {
|
||||||
|
chat, _, err := session.GetContactByID(toID, nil)
|
||||||
|
if err == nil && session.IsGroup(chat) {
|
||||||
|
if isGroupchat {
|
||||||
|
gateway.SendErrorMessageWithBody(msg.From, msg.To, msg.Body, "", msg.Id, 400, true, component)
|
||||||
|
} else {
|
||||||
|
gateway.SendErrorMessage(msg.From, msg.To, "PMing room members is not supported, use the real JID", 406, true, component)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var reply extensions.Reply
|
var reply extensions.Reply
|
||||||
var fallback extensions.Fallback
|
var fallback extensions.Fallback
|
||||||
var replace extensions.Replace
|
var replace extensions.Replace
|
||||||
|
@ -128,7 +154,6 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) {
|
||||||
log.Debugf("replace: %#v", replace)
|
log.Debugf("replace: %#v", replace)
|
||||||
|
|
||||||
var replyId int64
|
var replyId int64
|
||||||
var err error
|
|
||||||
text := msg.Body
|
text := msg.Body
|
||||||
if len(reply.Id) > 0 {
|
if len(reply.Id) > 0 {
|
||||||
chatId, msgId, err := gateway.IdsDB.GetByXmppId(session.Session.Login, bare, reply.Id)
|
chatId, msgId, err := gateway.IdsDB.GetByXmppId(session.Session.Login, bare, reply.Id)
|
||||||
|
@ -191,8 +216,8 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) {
|
||||||
|
|
||||||
session.SendMessageLock.Lock()
|
session.SendMessageLock.Lock()
|
||||||
defer session.SendMessageLock.Unlock()
|
defer session.SendMessageLock.Unlock()
|
||||||
tgMessageId := session.ProcessOutgoingMessage(toID, text, msg.From, replyId, replaceId)
|
tgMessage := session.ProcessOutgoingMessage(toID, text, msg.From, replyId, replaceId, isGroupchat)
|
||||||
if tgMessageId != 0 {
|
if tgMessage != nil {
|
||||||
if replaceId != 0 {
|
if replaceId != 0 {
|
||||||
// not needed (is it persistent among clients though?)
|
// not needed (is it persistent among clients though?)
|
||||||
/* err = gateway.IdsDB.ReplaceIdPair(session.Session.Login, bare, replace.Id, msg.Id, tgMessageId)
|
/* err = gateway.IdsDB.ReplaceIdPair(session.Session.Login, bare, replace.Id, msg.Id, tgMessageId)
|
||||||
|
@ -201,11 +226,23 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) {
|
||||||
} */
|
} */
|
||||||
session.AddToOutbox(replace.Id, resource)
|
session.AddToOutbox(replace.Id, resource)
|
||||||
} else {
|
} else {
|
||||||
err = gateway.IdsDB.Set(session.Session.Login, bare, toID, tgMessageId, msg.Id)
|
err = gateway.IdsDB.Set(session.Session.Login, bare, toID, tgMessage.Id, msg.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to save ids %v/%v %v", toID, tgMessageId, msg.Id)
|
log.Errorf("Failed to save ids %v/%v %v", toID, tgMessage.Id, msg.Id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pong groupchat messages back
|
||||||
|
if isGroupchat && toJid.Resource == "" {
|
||||||
|
session.SendMessageToGateway(
|
||||||
|
toID,
|
||||||
|
tgMessage,
|
||||||
|
msg.Id,
|
||||||
|
false,
|
||||||
|
msg.To + "/" + session.GetMUCNickname(session.GetSenderId(tgMessage)),
|
||||||
|
[]string{msg.From},
|
||||||
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
// if a message failed to edit on Telegram side, match new XMPP ID with old Telegram ID anyway
|
// if a message failed to edit on Telegram side, match new XMPP ID with old Telegram ID anyway
|
||||||
|
@ -281,7 +318,15 @@ func HandlePresence(s xmpp.Sender, p stanza.Packet) {
|
||||||
}
|
}
|
||||||
if prs.To == gateway.Jid.Bare() {
|
if prs.To == gateway.Jid.Bare() {
|
||||||
handlePresence(s, prs)
|
handlePresence(s, prs)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
var mucExt stanza.MucPresence
|
||||||
|
prs.Get(&mucExt)
|
||||||
|
if mucExt.XMLName.Space != "" {
|
||||||
|
handleMUCPresence(s, prs, mucExt)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tryHandleMUCNicknameChange(s, prs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSubscription(s xmpp.Sender, p stanza.Presence) {
|
func handleSubscription(s xmpp.Sender, p stanza.Presence) {
|
||||||
|
@ -391,6 +436,141 @@ func handlePresence(s xmpp.Sender, p stanza.Presence) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleMUCPresence(s xmpp.Sender, p stanza.Presence, mucExt stanza.MucPresence) {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"type": p.Type,
|
||||||
|
"from": p.From,
|
||||||
|
"to": p.To,
|
||||||
|
}).Warn("MUC presence")
|
||||||
|
log.Debugf("%#v", p)
|
||||||
|
|
||||||
|
if p.Type == "" {
|
||||||
|
toBare, nickname, ok := gateway.SplitJID(p.To)
|
||||||
|
if ok {
|
||||||
|
component, ok := s.(*xmpp.Component)
|
||||||
|
if !ok {
|
||||||
|
log.Error("Not a component")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// separate declaration is crucial for passing as pointer to defer
|
||||||
|
var reply *stanza.Presence
|
||||||
|
reply = &stanza.Presence{Attrs: stanza.Attrs{
|
||||||
|
From: toBare,
|
||||||
|
To: p.From,
|
||||||
|
Id: p.Id,
|
||||||
|
}}
|
||||||
|
defer gateway.ResumableSend(component, reply)
|
||||||
|
|
||||||
|
if nickname == "" {
|
||||||
|
presenceReplySetError(reply, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
chatId, ok := toToID(toBare)
|
||||||
|
if !ok {
|
||||||
|
presenceReplySetError(reply, 404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fromBare, fromResource, ok := gateway.SplitJID(p.From)
|
||||||
|
if !ok {
|
||||||
|
presenceReplySetError(reply, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session, ok := sessions[fromBare]
|
||||||
|
if !ok || !session.Session.MUC {
|
||||||
|
presenceReplySetError(reply, 407)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
chat, _, err := session.GetContactByID(chatId, nil)
|
||||||
|
if err != nil || !session.IsGroup(chat) {
|
||||||
|
presenceReplySetError(reply, 404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
limit, ok := mucExt.History.MaxStanzas.Get()
|
||||||
|
if !ok {
|
||||||
|
limit = 20
|
||||||
|
}
|
||||||
|
session.JoinMUC(chatId, fromResource, int32(limit))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryHandleMUCNicknameChange(s xmpp.Sender, p stanza.Presence) {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"type": p.Type,
|
||||||
|
"from": p.From,
|
||||||
|
"to": p.To,
|
||||||
|
}).Warn("Nickname change presence?")
|
||||||
|
log.Debugf("%#v", p)
|
||||||
|
|
||||||
|
if p.Type != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
toBare, nickname, ok := gateway.SplitJID(p.To)
|
||||||
|
if !ok || nickname == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fromBare, fromResource, ok := gateway.SplitJID(p.From)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session, ok := sessions[fromBare]
|
||||||
|
if !ok || !session.Session.MUC {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
chatId, ok := toToID(toBare)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
chat, _, err := session.GetContactByID(chatId, nil)
|
||||||
|
if err != nil || !session.IsGroup(chat) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !session.MUCHasResource(chatId, fromResource) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Warn("🗿 Yes")
|
||||||
|
|
||||||
|
component, ok := s.(*xmpp.Component)
|
||||||
|
if !ok {
|
||||||
|
log.Error("Not a component")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
from := toBare
|
||||||
|
nickname, ok = session.GetMyMUCNickname(chatId)
|
||||||
|
if ok {
|
||||||
|
from = from+"/"+nickname
|
||||||
|
}
|
||||||
|
reply := &stanza.Presence{
|
||||||
|
Attrs: stanza.Attrs{
|
||||||
|
From: from,
|
||||||
|
To: p.From,
|
||||||
|
Id: p.Id,
|
||||||
|
Type: stanza.PresenceTypeError,
|
||||||
|
},
|
||||||
|
Error: stanza.Err{
|
||||||
|
Code: 406,
|
||||||
|
Type: stanza.ErrorTypeModify,
|
||||||
|
Reason: "not-acceptable",
|
||||||
|
Text: "Telegram does not support changing nicknames per-chat. Issue a /setname command to the transport if you wish to change the global name",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
gateway.ResumableSend(component, reply)
|
||||||
|
}
|
||||||
|
|
||||||
func handleGetVcardIq(s xmpp.Sender, iq *stanza.IQ, typ byte) {
|
func handleGetVcardIq(s xmpp.Sender, iq *stanza.IQ, typ byte) {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"from": iq.From,
|
"from": iq.From,
|
||||||
|
@ -441,7 +621,7 @@ 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 handleGetDisco(dt discoType, s xmpp.Sender, iq *stanza.IQ) {
|
||||||
answer, err := stanza.NewIQ(stanza.Attrs{
|
answer, err := stanza.NewIQ(stanza.Attrs{
|
||||||
Type: stanza.IQTypeResult,
|
Type: stanza.IQTypeResult,
|
||||||
From: iq.To,
|
From: iq.To,
|
||||||
|
@ -454,15 +634,90 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
disco := answer.DiscoInfo()
|
if dt == discoTypeInfo {
|
||||||
_, ok := toToID(iq.To)
|
disco := answer.DiscoInfo()
|
||||||
if ok {
|
toID, toOk := toToID(iq.To)
|
||||||
disco.AddIdentity("", "account", "registered")
|
if !toOk {
|
||||||
} else {
|
disco.AddIdentity("Telegram Gateway", "gateway", "telegram")
|
||||||
disco.AddIdentity("Telegram Gateway", "gateway", "telegram")
|
disco.AddFeatures("jabber:iq:register")
|
||||||
disco.AddFeatures("jabber:iq:register")
|
}
|
||||||
|
|
||||||
|
var isMuc bool
|
||||||
|
bare, _, fromOk := gateway.SplitJID(iq.From)
|
||||||
|
if fromOk {
|
||||||
|
session, sessionOk := sessions[bare]
|
||||||
|
if sessionOk && session.Session.MUC {
|
||||||
|
if toOk {
|
||||||
|
chat, _, err := session.GetContactByID(toID, nil)
|
||||||
|
if err == nil && session.IsGroup(chat) {
|
||||||
|
isMuc = true
|
||||||
|
disco.AddIdentity(chat.Title, "conference", "text")
|
||||||
|
|
||||||
|
disco.AddFeatures(
|
||||||
|
"http://jabber.org/protocol/muc",
|
||||||
|
"muc_persistent",
|
||||||
|
"muc_hidden",
|
||||||
|
"muc_membersonly",
|
||||||
|
"muc_unmoderated",
|
||||||
|
"muc_nonanonymous",
|
||||||
|
"muc_unsecured",
|
||||||
|
"http://jabber.org/protocol/muc#stable_id",
|
||||||
|
)
|
||||||
|
fields := []*stanza.Field{
|
||||||
|
&stanza.Field{
|
||||||
|
Var: "FORM_TYPE",
|
||||||
|
Type: "hidden",
|
||||||
|
ValuesList: []string{"http://jabber.org/protocol/muc#roominfo"},
|
||||||
|
},
|
||||||
|
&stanza.Field{
|
||||||
|
Var: "muc#roominfo_description",
|
||||||
|
Label: "Description",
|
||||||
|
ValuesList: []string{session.GetChatDescription(chat)},
|
||||||
|
},
|
||||||
|
&stanza.Field{
|
||||||
|
Var: "muc#roominfo_occupants",
|
||||||
|
Label: "Number of occupants",
|
||||||
|
ValuesList: []string{strconv.FormatInt(int64(session.GetChatMemberCount(chat)), 10)},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
disco.Form = stanza.NewForm(fields, "result")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
disco.AddFeatures(
|
||||||
|
stanza.NSDiscoItems,
|
||||||
|
"http://jabber.org/protocol/muc#stable_id",
|
||||||
|
)
|
||||||
|
disco.AddIdentity("Telegram group chats", "conference", "text")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if toOk && !isMuc {
|
||||||
|
disco.AddIdentity("", "account", "registered")
|
||||||
|
}
|
||||||
|
answer.Payload = disco
|
||||||
|
} else if dt == discoTypeItems {
|
||||||
|
disco := answer.DiscoItems()
|
||||||
|
|
||||||
|
_, ok := toToID(iq.To)
|
||||||
|
if !ok {
|
||||||
|
bare, _, ok := gateway.SplitJID(iq.From)
|
||||||
|
if ok {
|
||||||
|
// raw access, no need to create a new instance if not connected
|
||||||
|
session, ok := sessions[bare]
|
||||||
|
if ok && session.Session.MUC {
|
||||||
|
bareJid := gateway.Jid.Bare()
|
||||||
|
disco.AddItem(bareJid, "", "Telegram group chats")
|
||||||
|
for _, chat := range session.GetGroupChats() {
|
||||||
|
jid := strconv.FormatInt(chat.Id, 10) + "@" + bareJid
|
||||||
|
disco.AddItem(jid, "", chat.Title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
answer.Payload = disco
|
||||||
}
|
}
|
||||||
answer.Payload = disco
|
|
||||||
|
|
||||||
log.Debugf("%#v", answer)
|
log.Debugf("%#v", answer)
|
||||||
|
|
||||||
|
@ -475,30 +730,6 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
|
||||||
_ = gateway.ResumableSend(component, answer)
|
_ = gateway.ResumableSend(component, answer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetDiscoItems(s xmpp.Sender, iq *stanza.IQ) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
answer.Payload = answer.DiscoItems()
|
|
||||||
|
|
||||||
component, ok := s.(*xmpp.Component)
|
|
||||||
if !ok {
|
|
||||||
log.Error("Not a component")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = gateway.ResumableSend(component, answer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleGetQueryRegister(s xmpp.Sender, iq *stanza.IQ) {
|
func handleGetQueryRegister(s xmpp.Sender, iq *stanza.IQ) {
|
||||||
component, ok := s.(*xmpp.Component)
|
component, ok := s.(*xmpp.Component)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -658,6 +889,28 @@ func iqAnswerSetError(answer *stanza.IQ, payload *extensions.QueryRegister, code
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func presenceReplySetError(reply *stanza.Presence, code int) {
|
||||||
|
reply.Type = stanza.PresenceTypeError
|
||||||
|
reply.Error = stanza.Err{
|
||||||
|
Code: code,
|
||||||
|
}
|
||||||
|
switch code {
|
||||||
|
case 400:
|
||||||
|
reply.Error.Type = stanza.ErrorTypeModify
|
||||||
|
reply.Error.Reason = "jid-malformed"
|
||||||
|
case 407:
|
||||||
|
reply.Error.Type = stanza.ErrorTypeAuth
|
||||||
|
reply.Error.Reason = "registration-required"
|
||||||
|
case 404:
|
||||||
|
reply.Error.Type = stanza.ErrorTypeCancel
|
||||||
|
reply.Error.Reason = "item-not-found"
|
||||||
|
default:
|
||||||
|
log.Error("Unknown error code, falling back with empty reason")
|
||||||
|
reply.Error.Type = stanza.ErrorTypeCancel
|
||||||
|
reply.Error.Reason = "undefined-condition"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func toToID(to string) (int64, bool) {
|
func toToID(to string) (int64, bool) {
|
||||||
toParts := strings.Split(to, "@")
|
toParts := strings.Split(to, "@")
|
||||||
if len(toParts) < 2 {
|
if len(toParts) < 2 {
|
||||||
|
|
Loading…
Reference in a new issue