mirror of
https://dev.narayana.im/narayana/telegabber.git
synced 2026-08-05 12:17:06 +00:00
MAM support in MUCs
This commit is contained in:
parent
47179e9c77
commit
12ac2fb5dd
11 changed files with 973 additions and 56 deletions
|
|
@ -7,6 +7,7 @@
|
||||||
:user: 'www-data' # owner of content files
|
:user: 'www-data' # owner of content files
|
||||||
:quota: '256MB' # maximum storage size
|
:quota: '256MB' # maximum storage size
|
||||||
:tdlib_verbosity: 1
|
:tdlib_verbosity: 1
|
||||||
|
:mam_threshold: 7 # in days
|
||||||
:tdlib:
|
:tdlib:
|
||||||
:datadir: './sessions/'
|
:datadir: './sessions/'
|
||||||
:client:
|
:client:
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ type TelegramConfig struct {
|
||||||
Loglevel string `yaml:":loglevel"`
|
Loglevel string `yaml:":loglevel"`
|
||||||
Content TelegramContentConfig `yaml:":content"`
|
Content TelegramContentConfig `yaml:":content"`
|
||||||
Verbosity uint8 `yaml:":tdlib_verbosity"`
|
Verbosity uint8 `yaml:":tdlib_verbosity"`
|
||||||
|
MAMThreshold uint32 `yaml:":mam_threshold"`
|
||||||
Tdlib TelegramTdlibConfig `yaml:":tdlib"`
|
Tdlib TelegramTdlibConfig `yaml:":tdlib"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,9 @@
|
||||||
":tdlib_verbosity": {
|
":tdlib_verbosity": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
|
":mam_threshold": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
":tdlib": {
|
":tdlib": {
|
||||||
"required": [":client"],
|
"required": [":client"],
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
|
||||||
|
|
@ -374,7 +374,7 @@ func (c *Client) updateMessageContent(update *client.UpdateMessageContent) {
|
||||||
}
|
}
|
||||||
for _, jid := range jids {
|
for _, jid := range jids {
|
||||||
if safeToSend {
|
if safeToSend {
|
||||||
gateway.SendMessage(jid, from, text.String(), id, c.xmpp, nil, 0, replaceId, isCarbon, isMUC, false, originalFrom, "")
|
gateway.SendMessage(jid, from, text.String(), id, c.xmpp, nil, 0, replaceId, isCarbon, isMUC, false, originalFrom, "", "", "", nil)
|
||||||
} else {
|
} else {
|
||||||
gateway.SendMUCAnnouncement(jid, from, text.String(), nickname, id, c.xmpp)
|
gateway.SendMUCAnnouncement(jid, from, text.String(), nickname, id, c.xmpp)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import (
|
||||||
osUser "os/user"
|
osUser "os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -1844,7 +1845,7 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) {
|
||||||
}
|
}
|
||||||
log.Debugf("groupChatFrom: %v groupChatTos: %#v, safeToSend: %v", groupChatFrom, groupChatTos, safeToSend)
|
log.Debugf("groupChatFrom: %v groupChatTos: %#v, safeToSend: %v", groupChatFrom, groupChatTos, safeToSend)
|
||||||
if safeToSend {
|
if safeToSend {
|
||||||
c.SendMessageToGateway(chatId, message, "", false, groupChatFrom, groupChatTos)
|
c.SendMessageToGateway(chatId, message, "", false, groupChatFrom, groupChatTos, "")
|
||||||
} else {
|
} else {
|
||||||
mucJID := gateway.MUCJID(chatId)
|
mucJID := gateway.MUCJID(chatId)
|
||||||
gateway.SendErrorMessage(c.jid, mucJID, "Cannot show a message", 500, true, c.xmpp)
|
gateway.SendErrorMessage(c.jid, mucJID, "Cannot show a message", 500, true, c.xmpp)
|
||||||
|
|
@ -1852,7 +1853,7 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendMessageToGateway transfers a message to XMPP side and marks it as read on Telegram side
|
// 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) {
|
func (c *Client) SendMessageToGateway(chatId int64, message *client.Message, id string, delay bool, groupChatFrom string, groupChatTos []string, mamQueryId string) {
|
||||||
var isCarbon bool
|
var isCarbon bool
|
||||||
var jids []string
|
var jids []string
|
||||||
var isGroupchat bool
|
var isGroupchat bool
|
||||||
|
|
@ -1998,15 +1999,52 @@ func (c *Client) SendMessageToGateway(chatId int64, message *client.Message, id
|
||||||
timestamp = int64(message.Date)
|
timestamp = int64(message.Date)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mucUserItem *gateway.MUCUserItem
|
||||||
|
var mucJID string
|
||||||
|
if mamQueryId != "" {
|
||||||
|
chatMember, err := c.client.GetChatMember(&client.GetChatMemberRequest{
|
||||||
|
ChatId: chatId,
|
||||||
|
MemberId: message.SenderId,
|
||||||
|
})
|
||||||
|
var status client.ChatMemberStatus
|
||||||
|
if err == nil {
|
||||||
|
status = chatMember.Status
|
||||||
|
}
|
||||||
|
chat, err := c.GetChatByID(chatId, nil)
|
||||||
|
if err == nil {
|
||||||
|
affiliation, role := c.memberStatusToAffiliationAndRole(status, chat)
|
||||||
|
mucUserItem = &gateway.MUCUserItem{
|
||||||
|
Affiliation: affiliation,
|
||||||
|
Jid: gateway.CHATJID(chatId, false),
|
||||||
|
Role: role,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mucJID = gateway.MUCJID(chatId)
|
||||||
|
}
|
||||||
|
|
||||||
for _, jid := range jids {
|
for _, jid := range jids {
|
||||||
gateway.SendMessageWithOOB(jid, from, text, sId, c.xmpp, reply, timestamp, oob, "", isCarbon, isGroupchat, c.Session.Receipts, originalFrom, stanzaId)
|
gateway.SendMessageWithOOB(jid, from, text, sId, c.xmpp, reply, timestamp, oob, "", isCarbon, isGroupchat, c.Session.Receipts, originalFrom, stanzaId, mamQueryId, mucJID, mucUserItem)
|
||||||
if auxText != "" {
|
if auxText != "" {
|
||||||
gateway.SendMessage(jid, from, auxText, sId, c.xmpp, reply, timestamp, "", isCarbon, isGroupchat, c.Session.Receipts, originalFrom, stanzaId)
|
gateway.SendMessage(jid, from, auxText, sId, c.xmpp, reply, timestamp, "", isCarbon, isGroupchat, c.Session.Receipts, originalFrom, stanzaId, mamQueryId, mucJID, mucUserItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.UpdateLastChatMessageId(chatId, sId)
|
c.UpdateLastChatMessageId(chatId, sId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendDelayedMUCMessage is used to send MUC history via the legacy method or MAM
|
||||||
|
func (c *Client) SendDelayedMUCMessage(chatId int64, message *client.Message, toJid string, mamQueryId string) {
|
||||||
|
msgId, _ := gateway.IdsDB.GetByTgIds(c.Session.Login, c.jid, chatId, message.Id)
|
||||||
|
c.SendMessageToGateway(
|
||||||
|
chatId,
|
||||||
|
message,
|
||||||
|
msgId,
|
||||||
|
true,
|
||||||
|
gateway.MUCJID(chatId) + "/" + c.GetMUCNickname(c.getMessageSenderId(message)),
|
||||||
|
[]string{toJid},
|
||||||
|
mamQueryId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// MarkAsRead marks a message as read
|
// MarkAsRead marks a message as read
|
||||||
func (c *Client) MarkAsRead(chatId, messageId int64) {
|
func (c *Client) MarkAsRead(chatId, messageId int64) {
|
||||||
c.client.ViewMessages(&client.ViewMessagesRequest{
|
c.client.ViewMessages(&client.ViewMessagesRequest{
|
||||||
|
|
@ -2330,6 +2368,73 @@ func (c *Client) getNLastMessages(chatID int64, limit *MessageLimit) ([]*client.
|
||||||
return messages, nil
|
return messages, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMessagesBetween lazily fetches message history between given ids (from exclusive, last inclusive), also calculating completeness flag; negative limit means messages from the end
|
||||||
|
func (c *Client) GetMessagesBetween(chatID, fromMessageId, lastMessageId int64, limit int32) (messages []*client.Message, complete bool, err error) {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"chat_id": chatID,
|
||||||
|
"from": fromMessageId,
|
||||||
|
"last": lastMessageId,
|
||||||
|
"limit": limit,
|
||||||
|
}).Debug("messages between")
|
||||||
|
|
||||||
|
var newMessages *client.Messages
|
||||||
|
if limit == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var reqFromMessageId int64
|
||||||
|
var reqOffset, reqLimit int32
|
||||||
|
if limit < 0 {
|
||||||
|
reqFromMessageId = lastMessageId
|
||||||
|
reqOffset = -1
|
||||||
|
reqLimit = limit
|
||||||
|
} else {
|
||||||
|
reqFromMessageId = fromMessageId
|
||||||
|
reqOffset = -limit
|
||||||
|
reqLimit = limit
|
||||||
|
}
|
||||||
|
|
||||||
|
newMessages, err = c.client.GetChatHistory(&client.GetChatHistoryRequest{
|
||||||
|
ChatId: chatID,
|
||||||
|
FromMessageId: reqFromMessageId,
|
||||||
|
Offset: reqOffset,
|
||||||
|
Limit: reqLimit,
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
if len(newMessages.Messages) == 0 {
|
||||||
|
complete = true
|
||||||
|
}
|
||||||
|
if limit < 0 {
|
||||||
|
fromPos := -1
|
||||||
|
for i, message := range newMessages.Messages {
|
||||||
|
if message.Id == fromMessageId {
|
||||||
|
fromPos = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if fromPos > -1 {
|
||||||
|
messages = newMessages.Messages[fromPos:]
|
||||||
|
} else {
|
||||||
|
messages = newMessages.Messages
|
||||||
|
}
|
||||||
|
complete = true
|
||||||
|
} else {
|
||||||
|
for _, message := range newMessages.Messages {
|
||||||
|
if lastMessageId != 0 {
|
||||||
|
if message.Id > lastMessageId {
|
||||||
|
break
|
||||||
|
complete = true
|
||||||
|
} else if message.Id == lastMessageId {
|
||||||
|
complete = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
messages = append(messages, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// GetFile retrieves a file object by id given by TDlib
|
// GetFile retrieves a file object by id given by TDlib
|
||||||
func (c *Client) GetFile(id int32) (*client.File, error) {
|
func (c *Client) GetFile(id int32) (*client.File, error) {
|
||||||
return c.client.GetFile(&client.GetFileRequest{
|
return c.client.GetFile(&client.GetFileRequest{
|
||||||
|
|
@ -2751,18 +2856,13 @@ func (c *Client) sendMessagesReverse(chatID int64, messages []*client.Message, p
|
||||||
false,
|
false,
|
||||||
originalFrom,
|
originalFrom,
|
||||||
"",
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
msgId, _ := gateway.IdsDB.GetByTgIds(c.Session.Login, c.jid, chatID, message.Id)
|
c.SendDelayedMUCMessage(chatID, message, toJid, "")
|
||||||
c.SendMessageToGateway(
|
|
||||||
chatID,
|
|
||||||
message,
|
|
||||||
msgId,
|
|
||||||
true,
|
|
||||||
mucJid + "/" + c.GetMUCNickname(c.getMessageSenderId(message)),
|
|
||||||
[]string{toJid},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3072,6 +3172,22 @@ func (c *Client) DeleteChat(chatID int64) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMessage is a handy wrapper for the following TDLib method
|
||||||
|
func (c *Client) GetMessage(chatID, messageId int64) (*client.Message, error) {
|
||||||
|
return c.client.GetMessage(&client.GetMessageRequest{
|
||||||
|
ChatId: chatID,
|
||||||
|
MessageId: messageId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetChatMessagePosition is a handy wrapper for the following TDLib method
|
||||||
|
func (c *Client) GetChatMessagePosition(chatID, messageId int64) (*client.Count, error) {
|
||||||
|
return c.client.GetChatMessagePosition(&client.GetChatMessagePositionRequest{
|
||||||
|
ChatId: chatID,
|
||||||
|
MessageId: messageId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// CloneChatPermissions makes a copy of ChatPermissions structure
|
// CloneChatPermissions makes a copy of ChatPermissions structure
|
||||||
func CloneChatPermissions(permissions *client.ChatPermissions) *client.ChatPermissions {
|
func CloneChatPermissions(permissions *client.ChatPermissions) *client.ChatPermissions {
|
||||||
return &client.ChatPermissions{
|
return &client.ChatPermissions{
|
||||||
|
|
@ -3219,6 +3335,46 @@ func (c *Client) GetMUCNicknameByUsername(username string) (string, error) {
|
||||||
return c.GetMUCNickname(chat.Id), nil
|
return c.GetMUCNickname(chat.Id), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindMessageByTime retrieves the closest message before the given timestamp
|
||||||
|
func (c *Client) FindMessageByTime(chatID int64, ts time.Time) (*client.Message, error) {
|
||||||
|
if ts.IsZero() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.client.GetChatMessageByDate(&client.GetChatMessageByDateRequest{
|
||||||
|
ChatId: chatID,
|
||||||
|
Date: int32(ts.Unix()), // DUROV!!!!!!!!!!!!!!!1111111
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNextMessage attempts to obtain the next message in the history
|
||||||
|
func (c *Client) GetNextMessage(chatID, messageId int64) (*client.Message, error) {
|
||||||
|
messages, err := c.client.GetChatHistory(&client.GetChatHistoryRequest{
|
||||||
|
ChatId: chatID,
|
||||||
|
FromMessageId: messageId,
|
||||||
|
Limit: 1,
|
||||||
|
Offset: -1,
|
||||||
|
})
|
||||||
|
if err == nil && len(messages.Messages) == 1 && messages.Messages[0] != nil && messages.Messages[0].Id != messageId {
|
||||||
|
return messages.Messages[0], nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPreviousMessage attempts to obtain the previous message in the history
|
||||||
|
func (c *Client) GetPreviousMessage(chatID, messageId int64) (*client.Message, error) {
|
||||||
|
messages, err := c.client.GetChatHistory(&client.GetChatHistoryRequest{
|
||||||
|
ChatId: chatID,
|
||||||
|
FromMessageId: messageId,
|
||||||
|
Limit: 1,
|
||||||
|
Offset: 0,
|
||||||
|
})
|
||||||
|
if err == nil && len(messages.Messages) == 1 && messages.Messages[0] != nil && messages.Messages[0].Id != messageId {
|
||||||
|
return messages.Messages[0], nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// GetErrorCode obtains an error code from a Telegram response error
|
// GetErrorCode obtains an error code from a Telegram response error
|
||||||
func GetErrorCode(err error) (int32, bool) {
|
func GetErrorCode(err error) (int32, bool) {
|
||||||
responseError, ok := err.(client.ResponseError)
|
responseError, ok := err.(client.ResponseError)
|
||||||
|
|
@ -3227,3 +3383,20 @@ func GetErrorCode(err error) (int32, bool) {
|
||||||
}
|
}
|
||||||
return responseError.Err.Code, true
|
return responseError.Err.Code, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ChronologicallySortMessages is… self-explanatory (Achtung: destructive)
|
||||||
|
func ChronologicallySortMessages(messages []*client.Message) []*client.Message {
|
||||||
|
sort.Slice(messages, func(i int, j int) bool {
|
||||||
|
msg1 := messages[i]
|
||||||
|
msg2 := messages[j]
|
||||||
|
return msg1.Date < msg2.Date
|
||||||
|
})
|
||||||
|
return messages
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReverseMessagesSlice efficiently reverses a messages slice in-place
|
||||||
|
func ReverseMessagesSlice(s []*client.Message) {
|
||||||
|
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
s[i], s[j] = s[j], s[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
:link: 'http://tlgrm.localhost/content' # webserver public address
|
:link: 'http://tlgrm.localhost/content' # webserver public address
|
||||||
:upload: 'https:///xmppfiles.localhost' # xmpp http upload address
|
:upload: 'https:///xmppfiles.localhost' # xmpp http upload address
|
||||||
:tdlib_verbosity: 1
|
:tdlib_verbosity: 1
|
||||||
|
:mam_threshold: 7 # in days
|
||||||
:tdlib:
|
:tdlib:
|
||||||
:client:
|
:client:
|
||||||
:api_id: '17349'
|
:api_id: '17349'
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
:link: '' # webserver public address
|
:link: '' # webserver public address
|
||||||
:upload: '' # xmpp http upload address
|
:upload: '' # xmpp http upload address
|
||||||
:tdlib_verbosity: 1
|
:tdlib_verbosity: 1
|
||||||
|
:tdlib_verbosity: 7 # in days
|
||||||
:tdlib:
|
:tdlib:
|
||||||
:client:
|
:client:
|
||||||
:api_id: '17349'
|
:api_id: '17349'
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,8 @@ func NewComponent(conf config.XMPPConfig, tc config.TelegramConfig, idsPath stri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gateway.MAMThreshold = tc.MAMThreshold
|
||||||
|
|
||||||
options := xmpp.ComponentOptions{
|
options := xmpp.ComponentOptions{
|
||||||
TransportConfiguration: xmpp.TransportConfiguration{
|
TransportConfiguration: xmpp.TransportConfiguration{
|
||||||
Address: conf.Host + ":" + conf.Port,
|
Address: conf.Host + ":" + conf.Port,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package extensions
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gosrc.io/xmpp/stanza"
|
"gosrc.io/xmpp/stanza"
|
||||||
)
|
)
|
||||||
|
|
@ -224,6 +225,7 @@ type MessageXMucUserExtension struct {
|
||||||
XMLName xml.Name `xml:"http://jabber.org/protocol/muc#user x"`
|
XMLName xml.Name `xml:"http://jabber.org/protocol/muc#user x"`
|
||||||
Invite *MessageXMucUserInvite `xml:"invite,omitempty"`
|
Invite *MessageXMucUserInvite `xml:"invite,omitempty"`
|
||||||
Status *MessageXMucUserStatus `xml:"status,omitempty"`
|
Status *MessageXMucUserStatus `xml:"status,omitempty"`
|
||||||
|
Item PresenceXMucUserItem `xml:"item,omitempty"`
|
||||||
Password string `xml:"password,omitempty"`
|
Password string `xml:"password,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -273,10 +275,21 @@ type PresenceXMucUserStatus struct {
|
||||||
// MessageDelay is from XEP-0203
|
// MessageDelay is from XEP-0203
|
||||||
type MessageDelay struct {
|
type MessageDelay struct {
|
||||||
XMLName xml.Name `xml:"urn:xmpp:delay delay"`
|
XMLName xml.Name `xml:"urn:xmpp:delay delay"`
|
||||||
From string `xml:"from,attr"`
|
From string `xml:"from,attr,omitempty"`
|
||||||
Stamp string `xml:"stamp,attr"`
|
Stamp string `xml:"stamp,attr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewMessageDelay(timestamp int64, from string) MessageDelay {
|
||||||
|
return MessageDelay{
|
||||||
|
From: from,
|
||||||
|
Stamp: TimestampToRFC3339(timestamp),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TimestampToRFC3339(timestamp int64) string {
|
||||||
|
return time.Unix(timestamp, 0).UTC().Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
|
||||||
// MessageDelayLegacy is from XEP-0203
|
// MessageDelayLegacy is from XEP-0203
|
||||||
type MessageDelayLegacy struct {
|
type MessageDelayLegacy struct {
|
||||||
XMLName xml.Name `xml:"jabber:x:delay x"`
|
XMLName xml.Name `xml:"jabber:x:delay x"`
|
||||||
|
|
@ -284,6 +297,13 @@ type MessageDelayLegacy struct {
|
||||||
Stamp string `xml:"stamp,attr"`
|
Stamp string `xml:"stamp,attr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewMessageDelayLegacy(timestamp int64, from string) MessageDelayLegacy {
|
||||||
|
return MessageDelayLegacy{
|
||||||
|
From: from,
|
||||||
|
Stamp: time.Unix(timestamp, 0).UTC().Format("20060102T15:04:05"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MessageAddresses is from XEP-0033
|
// MessageAddresses is from XEP-0033
|
||||||
type MessageAddresses struct {
|
type MessageAddresses struct {
|
||||||
XMLName xml.Name `xml:"http://jabber.org/protocol/address addresses"`
|
XMLName xml.Name `xml:"http://jabber.org/protocol/address addresses"`
|
||||||
|
|
@ -347,6 +367,65 @@ type MucDestroy struct {
|
||||||
Reason string `xml:"reason,omitempty"`
|
Reason string `xml:"reason,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MAM2Query is from XEP-0313
|
||||||
|
type MAM2Query struct {
|
||||||
|
XMLName xml.Name `xml:"urn:xmpp:mam:2 query"`
|
||||||
|
Form *stanza.Form `xml:"jabber:x:data x"`
|
||||||
|
QueryId string `xml:"queryid,attr,omitempty"`
|
||||||
|
ResultSet *stanza.ResultSet `xml:"set,omitempty"`
|
||||||
|
FlipPage *FlipPage `xml:"flip-page"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlipPage is an extended element from XEP-0313
|
||||||
|
type FlipPage struct {
|
||||||
|
XMLName xml.Name `xml:"flip-page"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForwardedMessage is from XEP-0297 (go-xmpp lacks Delay)
|
||||||
|
type ForwardedMessage struct {
|
||||||
|
XMLName xml.Name `xml:"urn:xmpp:forward:0 forwarded"`
|
||||||
|
Message *stanza.Message `xml:"message"`
|
||||||
|
Delay *MessageDelay `xml:"urn:xmpp:delay delay,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MAM2MessageResult is from XEP-0313
|
||||||
|
type MAM2MessageResult struct {
|
||||||
|
XMLName xml.Name `xml:"urn:xmpp:mam:2 result"`
|
||||||
|
Forwarded *ForwardedMessage `xml:"urn:xmpp:forward:0 forwarded,omitempty"`
|
||||||
|
QueryId string `xml:"queryid,attr,omitempty"`
|
||||||
|
Id string `xml:"id,attr,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MAM2Fin is from XEP-0313
|
||||||
|
type MAM2Fin struct {
|
||||||
|
XMLName xml.Name `xml:"urn:xmpp:mam:2 fin"`
|
||||||
|
ResultSet *stanza.ResultSet `xml:"set,omitempty"`
|
||||||
|
Complete bool `xml:"complete,attr,omitempty"`
|
||||||
|
Stable bool `xml:"stable,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MAM2Metadata is from XEP-0313
|
||||||
|
type MAM2Metadata struct {
|
||||||
|
XMLName xml.Name `xml:"urn:xmpp:mam:2 metadata"`
|
||||||
|
Start *MAM2MetadataStart `xml:"start"`
|
||||||
|
End *MAM2MetadataEnd `xml:"end"`
|
||||||
|
ResultSet *stanza.ResultSet `xml:"set,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MAM2MetadataStart is from XEP-0313
|
||||||
|
type MAM2MetadataStart struct {
|
||||||
|
XMLName xml.Name `xml:"start"`
|
||||||
|
Id string `xml:"id,attr,omitempty"`
|
||||||
|
Timestamp string `xml:"timestamp,attr,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MAM2MetadataEnd is from XEP-0313
|
||||||
|
type MAM2MetadataEnd struct {
|
||||||
|
XMLName xml.Name `xml:"end"`
|
||||||
|
Id string `xml:"id,attr,omitempty"`
|
||||||
|
Timestamp string `xml:"timestamp,attr,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
|
@ -452,6 +531,36 @@ func (c QueryMucOwner) GetSet() *stanza.ResultSet {
|
||||||
return c.ResultSet
|
return c.ResultSet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Namespace is a namespace!
|
||||||
|
func (c MAM2Query) Namespace() string {
|
||||||
|
return c.XMLName.Space
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSet getsets!
|
||||||
|
func (c MAM2Query) GetSet() *stanza.ResultSet {
|
||||||
|
return c.ResultSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Namespace is a namespace!
|
||||||
|
func (c MAM2Fin) Namespace() string {
|
||||||
|
return c.XMLName.Space
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSet getsets!
|
||||||
|
func (c MAM2Fin) GetSet() *stanza.ResultSet {
|
||||||
|
return c.ResultSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Namespace is a namespace!
|
||||||
|
func (c MAM2Metadata) Namespace() string {
|
||||||
|
return c.XMLName.Space
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSet getsets!
|
||||||
|
func (c MAM2Metadata) GetSet() *stanza.ResultSet {
|
||||||
|
return c.ResultSet
|
||||||
|
}
|
||||||
|
|
||||||
// NewReplyFallback initializes a fallback range
|
// NewReplyFallback initializes a fallback range
|
||||||
func NewReplyFallback(start uint64, end uint64) Fallback {
|
func NewReplyFallback(start uint64, end uint64) Fallback {
|
||||||
return Fallback{
|
return Fallback{
|
||||||
|
|
@ -585,4 +694,28 @@ func init() {
|
||||||
"http://jabber.org/protocol/muc#owner",
|
"http://jabber.org/protocol/muc#owner",
|
||||||
"query",
|
"query",
|
||||||
}, QueryMucOwner{})
|
}, QueryMucOwner{})
|
||||||
|
|
||||||
|
// MAM2 query
|
||||||
|
stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{
|
||||||
|
"urn:xmpp:mam:2",
|
||||||
|
"query",
|
||||||
|
}, MAM2Query{})
|
||||||
|
|
||||||
|
// MAM2 message result
|
||||||
|
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{
|
||||||
|
"urn:xmpp:mam:2",
|
||||||
|
"result",
|
||||||
|
}, MAM2MessageResult{})
|
||||||
|
|
||||||
|
// MAM2 fin
|
||||||
|
stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{
|
||||||
|
"urn:xmpp:mam:2",
|
||||||
|
"fin",
|
||||||
|
}, MAM2Fin{})
|
||||||
|
|
||||||
|
// MAM2 metadata
|
||||||
|
stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{
|
||||||
|
"urn:xmpp:mam:2",
|
||||||
|
"metadata",
|
||||||
|
}, MAM2Metadata{})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"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"
|
||||||
|
|
@ -38,6 +37,12 @@ type marker struct {
|
||||||
Id string
|
Id string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MUCUserItem struct {
|
||||||
|
Affiliation string
|
||||||
|
Jid string
|
||||||
|
Role string
|
||||||
|
}
|
||||||
|
|
||||||
const NSNick string = "http://jabber.org/protocol/nick"
|
const NSNick string = "http://jabber.org/protocol/nick"
|
||||||
const NodeVCard4 string = "urn:xmpp:vcard4"
|
const NodeVCard4 string = "urn:xmpp:vcard4"
|
||||||
const NodeAvatarMetadata string = "urn:xmpp:avatar:metadata"
|
const NodeAvatarMetadata string = "urn:xmpp:avatar:metadata"
|
||||||
|
|
@ -62,6 +67,9 @@ var DirtySessions = false
|
||||||
// MessageOutgoingPermissionVersion contains a XEP-0356 version to fake outgoing messages by foreign JIDs
|
// MessageOutgoingPermissionVersion contains a XEP-0356 version to fake outgoing messages by foreign JIDs
|
||||||
var MessageOutgoingPermissionVersion = 0
|
var MessageOutgoingPermissionVersion = 0
|
||||||
|
|
||||||
|
// MAMThreshold specifies a day limit behind which history should not be requested to avoid abuse detection and storage overload
|
||||||
|
var MAMThreshold uint32
|
||||||
|
|
||||||
// CHATNODE converts numeric id to node part of 1-1 chat JID
|
// CHATNODE converts numeric id to node part of 1-1 chat JID
|
||||||
func CHATNODE(chatId int64) string {
|
func CHATNODE(chatId int64) string {
|
||||||
return strconv.FormatInt(chatId, 10)
|
return strconv.FormatInt(chatId, 10)
|
||||||
|
|
@ -114,8 +122,8 @@ func ResourcePrep(resource string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendMessage creates and sends a message stanza
|
// SendMessage creates and sends a message stanza
|
||||||
func SendMessage(to, from, body, id string, component *xmpp.Component, reply *Reply, timestamp int64, replaceId string, isCarbon, isGroupchat, requestReceipt bool, originalFrom, stanzaId string) {
|
func SendMessage(to, from, body, id string, component *xmpp.Component, reply *Reply, timestamp int64, replaceId string, isCarbon, isGroupchat, requestReceipt bool, originalFrom, stanzaId, mamQueryId, mucJID string, mucUserItem *MUCUserItem) {
|
||||||
sendMessageWrapper(to, from, body, "", "", id, component, reply, nil, timestamp, "", replaceId, isCarbon, isGroupchat, false, requestReceipt, originalFrom, 0, "", stanzaId, 0)
|
sendMessageWrapper(to, from, body, "", "", id, component, reply, nil, timestamp, "", replaceId, isCarbon, isGroupchat, false, requestReceipt, originalFrom, 0, "", stanzaId, 0, mamQueryId, mucJID, mucUserItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendServiceMessage creates and sends a simple message stanza from transport
|
// SendServiceMessage creates and sends a simple message stanza from transport
|
||||||
|
|
@ -124,7 +132,7 @@ func SendServiceMessage(to, body string, component *xmpp.Component) {
|
||||||
if uuid, err := uuid.NewRandom(); err == nil {
|
if uuid, err := uuid.NewRandom(); err == nil {
|
||||||
id = uuid.String()
|
id = uuid.String()
|
||||||
}
|
}
|
||||||
sendMessageWrapper(to, "", body, "", "", id, component, nil, nil, 0, "", "", false, false, false, false, "", 0, "", "", 0)
|
sendMessageWrapper(to, "", body, "", "", id, component, nil, nil, 0, "", "", false, false, false, false, "", 0, "", "", 0, "", "", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendTextMessage creates and sends a simple message stanza
|
// SendTextMessage creates and sends a simple message stanza
|
||||||
|
|
@ -133,7 +141,7 @@ func SendTextMessage(to, from, body string, component *xmpp.Component, isGroupch
|
||||||
if uuid, err := uuid.NewRandom(); err == nil {
|
if uuid, err := uuid.NewRandom(); err == nil {
|
||||||
id = uuid.String()
|
id = uuid.String()
|
||||||
}
|
}
|
||||||
sendMessageWrapper(to, from, body, "", "", id, component, nil, nil, 0, "", "", false, isGroupchat, false, false, "", 0, "", "", 0)
|
sendMessageWrapper(to, from, body, "", "", id, component, nil, nil, 0, "", "", false, isGroupchat, false, false, "", 0, "", "", 0, "", "", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendMUCAnnouncement creates and sends a message by a temporary occupant
|
// SendMUCAnnouncement creates and sends a message by a temporary occupant
|
||||||
|
|
@ -158,7 +166,7 @@ func SendMUCAnnouncement(to, from, body, nickname, id string, component *xmpp.Co
|
||||||
id = uuid.String()
|
id = uuid.String()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sendMessageWrapper(to, fullFrom, body, "", "", id, component, nil, nil, 0, "", "", false, true, false, false, "", 0, "", "", 0)
|
sendMessageWrapper(to, fullFrom, body, "", "", id, component, nil, nil, 0, "", "", false, true, false, false, "", 0, "", "", 0, "", "", nil)
|
||||||
|
|
||||||
SendPresence(
|
SendPresence(
|
||||||
component,
|
component,
|
||||||
|
|
@ -173,22 +181,22 @@ func SendMUCAnnouncement(to, from, body, nickname, id string, component *xmpp.Co
|
||||||
|
|
||||||
// SendErrorMessage creates and sends an error message stanza
|
// SendErrorMessage creates and sends an error message stanza
|
||||||
func SendErrorMessage(to, from, text string, code int, isGroupchat bool, component *xmpp.Component) {
|
func SendErrorMessage(to, from, text string, code int, isGroupchat bool, component *xmpp.Component) {
|
||||||
sendMessageWrapper(to, from, "", "", text, "", component, nil, nil, 0, "", "", false, isGroupchat, false, false, "", code, "", "", 0)
|
sendMessageWrapper(to, from, "", "", text, "", component, nil, nil, 0, "", "", false, isGroupchat, false, false, "", code, "", "", 0, "", "", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendErrorMessageWithBody creates and sends an error message stanza with body payload
|
// 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) {
|
func SendErrorMessageWithBody(to, from, body, errorText, id string, code int, isGroupchat bool, component *xmpp.Component) {
|
||||||
sendMessageWrapper(to, from, body, "", errorText, id, component, nil, nil, 0, "", "", false, isGroupchat, false, false, "", code, "", "", 0)
|
sendMessageWrapper(to, from, body, "", errorText, id, component, nil, nil, 0, "", "", false, isGroupchat, false, false, "", code, "", "", 0, "", "", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendMessageWithOOB creates and sends a message stanza with OOB URL
|
// SendMessageWithOOB creates and sends a message stanza with OOB URL
|
||||||
func SendMessageWithOOB(to, from, body, id string, component *xmpp.Component, reply *Reply, timestamp int64, oob, replaceId string, isCarbon, isGroupchat, requestReceipt bool, originalFrom, stanzaId string) {
|
func SendMessageWithOOB(to, from, body, id string, component *xmpp.Component, reply *Reply, timestamp int64, oob, replaceId string, isCarbon, isGroupchat, requestReceipt bool, originalFrom, stanzaId, mamQueryId, mucJID string, mucUserItem *MUCUserItem) {
|
||||||
sendMessageWrapper(to, from, body, "", "", id, component, reply, nil, timestamp, oob, replaceId, isCarbon, isGroupchat, false, requestReceipt, originalFrom, 0, "", stanzaId, 0)
|
sendMessageWrapper(to, from, body, "", "", id, component, reply, nil, timestamp, oob, replaceId, isCarbon, isGroupchat, false, requestReceipt, originalFrom, 0, "", stanzaId, 0, mamQueryId, mucJID, mucUserItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendSubjectMessage creates and sends a MUC subject
|
// SendSubjectMessage creates and sends a MUC subject
|
||||||
func SendSubjectMessage(to, from, subject, id string, component *xmpp.Component, timestamp int64) {
|
func SendSubjectMessage(to, from, subject, id string, component *xmpp.Component, timestamp int64) {
|
||||||
sendMessageWrapper(to, from, "", subject, "", id, component, nil, nil, timestamp, "", "", false, true, true, false, "", 0, "", "", 0)
|
sendMessageWrapper(to, from, "", subject, "", id, component, nil, nil, timestamp, "", "", false, true, true, false, "", 0, "", "", 0, "", "", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendMessageMarker creates and sends a message stanza with a XEP-0333 marker
|
// SendMessageMarker creates and sends a message stanza with a XEP-0333 marker
|
||||||
|
|
@ -196,20 +204,20 @@ func SendMessageMarker(to string, from string, component *xmpp.Component, marker
|
||||||
sendMessageWrapper(to, from, "", "", "", "", component, nil, &marker{
|
sendMessageWrapper(to, from, "", "", "", "", component, nil, &marker{
|
||||||
Type: markerType,
|
Type: markerType,
|
||||||
Id: markerId,
|
Id: markerId,
|
||||||
}, 0, "", "", false, false, false, false, "", 0, "", "", 0)
|
}, 0, "", "", false, false, false, false, "", 0, "", "", 0, "", "", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendMUCInvite creates and send a MUC invitation message
|
// SendMUCInvite creates and send a MUC invitation message
|
||||||
func SendMUCInvite(to string, from string, component *xmpp.Component, inviteFrom string) {
|
func SendMUCInvite(to string, from string, component *xmpp.Component, inviteFrom string) {
|
||||||
sendMessageWrapper(to, from, "", "", "", "", component, nil, nil, 0, "", "", false, false, false, false, "", 0, inviteFrom, "", 0)
|
sendMessageWrapper(to, from, "", "", "", "", component, nil, nil, 0, "", "", false, false, false, false, "", 0, inviteFrom, "", 0, "", "", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendMUCStatusCode creates a groupchat message with a muc#user status code
|
// SendMUCStatusCode creates a groupchat message with a muc#user status code
|
||||||
func SendMUCStatusCode(to string, from string, component *xmpp.Component, statusCode int64) {
|
func SendMUCStatusCode(to string, from string, component *xmpp.Component, statusCode int64) {
|
||||||
sendMessageWrapper(to, from, "", "", "", "", component, nil, nil, 0, "", "", false, true, false, false, "", 0, "", "", statusCode)
|
sendMessageWrapper(to, from, "", "", "", "", component, nil, nil, 0, "", "", false, true, false, false, "", 0, "", "", statusCode, "", "", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendMessageWrapper(to, from, body, subject, errorText, id string, component *xmpp.Component, reply *Reply, marker *marker, timestamp int64, oob, replaceId string, isCarbon, isGroupchat, forceSubject, requestReceipt bool, originalFrom string, errorCode int, inviteFrom, stanzaId string, statusCode int64) {
|
func sendMessageWrapper(to, from, body, subject, errorText, id string, component *xmpp.Component, reply *Reply, marker *marker, timestamp int64, oob, replaceId string, isCarbon, isGroupchat, forceSubject, requestReceipt bool, originalFrom string, errorCode int, inviteFrom, stanzaId string, statusCode int64, mamQueryId, mucJID string, mucUserItem *MUCUserItem) {
|
||||||
toJid, err := stanza.NewJid(to)
|
toJid, err := stanza.NewJid(to)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
|
|
@ -247,6 +255,8 @@ func sendMessageWrapper(to, from, body, subject, errorText, id string, component
|
||||||
if isCarbon {
|
if isCarbon {
|
||||||
messageTo = messageFrom
|
messageTo = messageFrom
|
||||||
messageFrom = bareTo + "/" + Jid.Resource
|
messageFrom = bareTo + "/" + Jid.Resource
|
||||||
|
} else if mucJID != "" {
|
||||||
|
messageTo = mucJID
|
||||||
} else {
|
} else {
|
||||||
messageTo = to
|
messageTo = to
|
||||||
}
|
}
|
||||||
|
|
@ -322,19 +332,13 @@ func sendMessageWrapper(to, from, body, subject, errorText, id string, component
|
||||||
if !isGroupchat && !isCarbon && toJid.Resource != "" && inviteFrom == "" {
|
if !isGroupchat && !isCarbon && toJid.Resource != "" && inviteFrom == "" {
|
||||||
message.Extensions = append(message.Extensions, stanza.HintNoCopy{})
|
message.Extensions = append(message.Extensions, stanza.HintNoCopy{})
|
||||||
}
|
}
|
||||||
if timestamp != 0 {
|
if timestamp != 0 && mamQueryId == "" {
|
||||||
var delayFrom string
|
var delayFrom string
|
||||||
if isGroupchat {
|
if isGroupchat {
|
||||||
delayFrom = bareFrom
|
delayFrom = bareFrom
|
||||||
}
|
}
|
||||||
message.Extensions = append(message.Extensions, extensions.MessageDelay{
|
message.Extensions = append(message.Extensions, extensions.NewMessageDelay(timestamp, delayFrom))
|
||||||
From: delayFrom,
|
message.Extensions = append(message.Extensions, extensions.NewMessageDelayLegacy(timestamp, 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 != "" {
|
if originalFrom != "" {
|
||||||
message.Extensions = append(message.Extensions, extensions.MessageAddresses{
|
message.Extensions = append(message.Extensions, extensions.MessageAddresses{
|
||||||
|
|
@ -377,7 +381,14 @@ func sendMessageWrapper(to, from, body, subject, errorText, id string, component
|
||||||
Code: strconv.FormatInt(statusCode, 10),
|
Code: strconv.FormatInt(statusCode, 10),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if inviteFrom != "" || statusCode != 0 {
|
if mucUserItem != nil {
|
||||||
|
userExt.Item = extensions.PresenceXMucUserItem{
|
||||||
|
Affiliation: mucUserItem.Affiliation,
|
||||||
|
Jid: &mucUserItem.Jid,
|
||||||
|
Role: mucUserItem.Role,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if inviteFrom != "" || statusCode != 0 || mucUserItem != nil {
|
||||||
message.Extensions = append(message.Extensions, userExt)
|
message.Extensions = append(message.Extensions, userExt)
|
||||||
}
|
}
|
||||||
if stanzaId != "" {
|
if stanzaId != "" {
|
||||||
|
|
@ -425,6 +436,26 @@ func sendMessageWrapper(to, from, body, subject, errorText, id string, component
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
sendMessage(&privilegeMessage, component)
|
sendMessage(&privilegeMessage, component)
|
||||||
|
} else if mamQueryId != "" {
|
||||||
|
delay := extensions.NewMessageDelay(timestamp, "")
|
||||||
|
mamMessage := stanza.Message{
|
||||||
|
Attrs: stanza.Attrs{
|
||||||
|
From: mucJID,
|
||||||
|
To: to,
|
||||||
|
Type: messageType,
|
||||||
|
},
|
||||||
|
Extensions: []stanza.MsgExtension{
|
||||||
|
extensions.MAM2MessageResult{
|
||||||
|
Id: stanzaId,
|
||||||
|
QueryId: mamQueryId,
|
||||||
|
Forwarded: &extensions.ForwardedMessage{
|
||||||
|
Delay: &delay,
|
||||||
|
Message: &message,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
sendMessage(&mamMessage, component)
|
||||||
} else {
|
} else {
|
||||||
sendMessage(&message, component)
|
sendMessage(&message, component)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
595
xmpp/handlers.go
595
xmpp/handlers.go
|
|
@ -83,6 +83,16 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) {
|
||||||
go handleGetQueryMucOwner(s, iq)
|
go handleGetQueryMucOwner(s, iq)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
queryMAM2, ok := iq.Payload.(*extensions.MAM2Query)
|
||||||
|
if ok {
|
||||||
|
go handleGetQueryMAM2(s, iq, queryMAM2)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, ok = iq.Payload.(*extensions.MAM2Metadata)
|
||||||
|
if ok {
|
||||||
|
go handleGetMetadataMAM2(s, iq)
|
||||||
|
return
|
||||||
|
}
|
||||||
} else if iq.Type == stanza.IQTypeSet {
|
} else if iq.Type == stanza.IQTypeSet {
|
||||||
queryRegister, ok := iq.Payload.(*extensions.QueryRegister)
|
queryRegister, ok := iq.Payload.(*extensions.QueryRegister)
|
||||||
if ok {
|
if ok {
|
||||||
|
|
@ -104,6 +114,11 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) {
|
||||||
go handleSetQueryMucOwner(s, iq, queryMucOwner)
|
go handleSetQueryMucOwner(s, iq, queryMucOwner)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
queryMAM2, ok := iq.Payload.(*extensions.MAM2Query)
|
||||||
|
if ok {
|
||||||
|
go handleSetQueryMAM2(s, iq, queryMAM2)
|
||||||
|
return
|
||||||
|
}
|
||||||
} else if iq.Type == stanza.IQTypeResult {
|
} else if iq.Type == stanza.IQTypeResult {
|
||||||
discoInfo, ok := iq.Payload.(*stanza.DiscoInfo)
|
discoInfo, ok := iq.Payload.(*stanza.DiscoInfo)
|
||||||
if ok {
|
if ok {
|
||||||
|
|
@ -202,17 +217,7 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) {
|
||||||
log.Debugf("replace tg: %#v %#v", chatId, msgId)
|
log.Debugf("replace tg: %#v %#v", chatId, msgId)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
id := reply.Id
|
replyId, _ = parseMessageId(reply.Id)
|
||||||
if id[0] == 'e' {
|
|
||||||
idParts := strings.Split(id[1:], ":")
|
|
||||||
if len(idParts) >= 1 {
|
|
||||||
id = idParts[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
replyId, err = strconv.ParseInt(id, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn(errors.Wrap(err, "Failed to parse message ID!"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if replyId != 0 && fallback.For == "urn:xmpp:reply:0" && len(fallback.Body) > 0 {
|
if replyId != 0 && fallback.For == "urn:xmpp:reply:0" && len(fallback.Body) > 0 {
|
||||||
|
|
@ -292,11 +297,12 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) {
|
||||||
false,
|
false,
|
||||||
msg.To + "/" + session.GetMUCNickname(session.GetSenderId(tgMessage.SenderId)),
|
msg.To + "/" + session.GetMUCNickname(session.GetSenderId(tgMessage.SenderId)),
|
||||||
[]string{msg.From},
|
[]string{msg.From},
|
||||||
|
"",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if isCommand && isGroupchat && session.Session.MUC {
|
} else if isCommand && isGroupchat && session.Session.MUC {
|
||||||
// pong outgoing commands back to groupchats
|
// pong outgoing commands back to groupchats
|
||||||
gateway.SendMessage(msg.From, msg.To + "/" + session.GetMUCNickname(0), text, "", component, nil, 0, "", false, isGroupchat, false, "", "")
|
gateway.SendMessage(msg.From, msg.To + "/" + session.GetMUCNickname(0), text, "", component, nil, 0, "", false, isGroupchat, false, "", "", "", "", nil)
|
||||||
} 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
|
||||||
|
|
@ -970,6 +976,8 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ, di *stanza.DiscoInfo) {
|
||||||
"muc_unsecured",
|
"muc_unsecured",
|
||||||
"http://jabber.org/protocol/muc#stable_id",
|
"http://jabber.org/protocol/muc#stable_id",
|
||||||
"jabber:iq:register",
|
"jabber:iq:register",
|
||||||
|
"urn:xmpp:mam:2",
|
||||||
|
"urn:xmpp:mam:2#extended",
|
||||||
"urn:xmpp:sid:0",
|
"urn:xmpp:sid:0",
|
||||||
"vcard-temp",
|
"vcard-temp",
|
||||||
)
|
)
|
||||||
|
|
@ -1374,6 +1382,113 @@ func handleGetQueryMucOwner(s xmpp.Sender, iq *stanza.IQ) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleGetQueryMAM2(s xmpp.Sender, iq *stanza.IQ, query *extensions.MAM2Query) {
|
||||||
|
component, answer, ok := iqResultStub(s, iq)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer gateway.ResumableSend(component, answer)
|
||||||
|
|
||||||
|
payload := &extensions.MAM2Query{}
|
||||||
|
answer.Payload = payload
|
||||||
|
|
||||||
|
payload.Form = &stanza.Form{
|
||||||
|
Type: stanza.FormTypeForm,
|
||||||
|
Fields: []*stanza.Field{
|
||||||
|
&stanza.Field{
|
||||||
|
Var: "FORM_TYPE",
|
||||||
|
Type: stanza.FieldTypeHidden,
|
||||||
|
ValuesList: []string{"urn:xmpp:mam:2"},
|
||||||
|
},
|
||||||
|
&stanza.Field{
|
||||||
|
Var: "with",
|
||||||
|
Type: stanza.FieldTypeJidSingle,
|
||||||
|
},
|
||||||
|
&stanza.Field{
|
||||||
|
Var: "start",
|
||||||
|
Type: stanza.FieldTypeTextSingle,
|
||||||
|
},
|
||||||
|
&stanza.Field{
|
||||||
|
Var: "end",
|
||||||
|
Type: stanza.FieldTypeTextSingle,
|
||||||
|
},
|
||||||
|
&stanza.Field{
|
||||||
|
Var: "before-id",
|
||||||
|
Type: stanza.FieldTypeTextSingle,
|
||||||
|
},
|
||||||
|
&stanza.Field{
|
||||||
|
Var: "after-id",
|
||||||
|
Type: stanza.FieldTypeTextSingle,
|
||||||
|
},
|
||||||
|
&stanza.Field{
|
||||||
|
Var: "ids",
|
||||||
|
Type: stanza.FieldTypeListMulti,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
log.Debugf("MAM info request: %#v", query)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleGetMetadataMAM2(s xmpp.Sender, iq *stanza.IQ) {
|
||||||
|
component, answer, ok := iqResultStub(s, iq)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer gateway.ResumableSend(component, answer)
|
||||||
|
|
||||||
|
bare, _, fromOk := gateway.SplitJID(iq.From)
|
||||||
|
if !fromOk {
|
||||||
|
iqAnswerSetError(answer, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session, sessionOk := sessions[bare]
|
||||||
|
if !sessionOk || !session.Session.MUC {
|
||||||
|
iqAnswerSetError(answer, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
toID, toOk, toIsGroup := toToID(iq.To)
|
||||||
|
if !toOk || !toIsGroup {
|
||||||
|
iqAnswerSetError(answer, 405)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
chat, _, err := session.GetContactByID(toID, nil)
|
||||||
|
if err != nil || chat == nil || !session.IsGroup(chat) {
|
||||||
|
iqAnswerSetError(answer, 405)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := &extensions.MAM2Metadata{}
|
||||||
|
answer.Payload = payload
|
||||||
|
|
||||||
|
quotaTs := time.Now().AddDate(0, 0, -int(gateway.MAMThreshold))
|
||||||
|
preFirstMessage, preFirstMessageErr := session.FindMessageByTime(toID, quotaTs)
|
||||||
|
var preFirstMessageId int64
|
||||||
|
if preFirstMessageErr == nil && preFirstMessage != nil {
|
||||||
|
preFirstMessageId = preFirstMessage.Id
|
||||||
|
} else {
|
||||||
|
preFirstMessageId = 1
|
||||||
|
}
|
||||||
|
startMessage, startMessageErr := session.GetNextMessage(toID, preFirstMessageId)
|
||||||
|
if startMessageErr == nil && startMessage != nil {
|
||||||
|
payload.Start = &extensions.MAM2MetadataStart{
|
||||||
|
Id: strconv.FormatInt(startMessage.Id, 10),
|
||||||
|
Timestamp: extensions.TimestampToRFC3339(int64(startMessage.Date)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
endMessage, endMessageErr := session.GetPreviousMessage(toID, 0)
|
||||||
|
if endMessageErr == nil && endMessage != nil {
|
||||||
|
payload.End = &extensions.MAM2MetadataEnd{
|
||||||
|
Id: strconv.FormatInt(endMessage.Id, 10),
|
||||||
|
Timestamp: extensions.TimestampToRFC3339(int64(endMessage.Date)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Debugf("MAM metadata: %#v", payload)
|
||||||
|
}
|
||||||
|
|
||||||
func handleSetQueryRegister(s xmpp.Sender, iq *stanza.IQ, query *extensions.QueryRegister) {
|
func handleSetQueryRegister(s xmpp.Sender, iq *stanza.IQ, query *extensions.QueryRegister) {
|
||||||
component, answer, ok := iqResultStub(s, iq)
|
component, answer, ok := iqResultStub(s, iq)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
@ -2039,6 +2154,442 @@ func handleSetQueryMucOwner(s xmpp.Sender, iq *stanza.IQ, query *extensions.Quer
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleSetQueryMAM2(s xmpp.Sender, iq *stanza.IQ, query *extensions.MAM2Query) {
|
||||||
|
component, answer, ok := iqResultStub(s, iq)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer gateway.ResumableSend(component, answer)
|
||||||
|
|
||||||
|
bare, _, fromOk := gateway.SplitJID(iq.From)
|
||||||
|
if !fromOk {
|
||||||
|
iqAnswerSetError(answer, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session, sessionOk := sessions[bare]
|
||||||
|
if !sessionOk || !session.Session.MUC {
|
||||||
|
iqAnswerSetError(answer, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
toID, toOk, toIsGroup := toToID(iq.To)
|
||||||
|
if !toOk || !toIsGroup {
|
||||||
|
iqAnswerSetError(answer, 405)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
chat, _, err := session.GetContactByID(toID, nil)
|
||||||
|
if err != nil || chat == nil || !session.IsGroup(chat) {
|
||||||
|
iqAnswerSetError(answer, 405)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var startTime, endTime time.Time
|
||||||
|
var beforeId, afterId int64
|
||||||
|
var ids []string
|
||||||
|
|
||||||
|
var rsmBefore, rsmAfter int64
|
||||||
|
var rsmLastPage bool
|
||||||
|
var rsmLimit int32
|
||||||
|
var justCount bool
|
||||||
|
|
||||||
|
log.Debugf("MAM query to %v: %#v %#v %#v", toID, query, query.Form, query.ResultSet)
|
||||||
|
if query.Form != nil && query.Form.Type == stanza.FormTypeSubmit {
|
||||||
|
for _, field := range query.Form.Fields {
|
||||||
|
if len(field.ValuesList) < 1 {
|
||||||
|
iqAnswerSetError(answer, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
value := field.ValuesList[0]
|
||||||
|
log.Debugf("MAM query field: %v %v", field.Var, value)
|
||||||
|
|
||||||
|
switch field.Var {
|
||||||
|
case "FORM_TYPE":
|
||||||
|
if value != "urn:xmpp:mam:2" {
|
||||||
|
iqAnswerSetError(answer, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "with": // okay, and?
|
||||||
|
case "start", "end":
|
||||||
|
timestamp, err := time.Parse(time.RFC3339, value)
|
||||||
|
if err != nil {
|
||||||
|
iqAnswerSetError(answer, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch field.Var {
|
||||||
|
case "start":
|
||||||
|
startTime = timestamp
|
||||||
|
case "end":
|
||||||
|
endTime = timestamp
|
||||||
|
}
|
||||||
|
case "before-id":
|
||||||
|
beforeId, ok = parseMessageId(value)
|
||||||
|
if !ok {
|
||||||
|
iqAnswerSetError(answer, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "after-id":
|
||||||
|
afterId, ok = parseMessageId(value)
|
||||||
|
if !ok {
|
||||||
|
iqAnswerSetError(answer, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ids":
|
||||||
|
ids = field.ValuesList
|
||||||
|
default:
|
||||||
|
iqAnswerSetError(answer, 501)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if query.ResultSet != nil {
|
||||||
|
if query.ResultSet.After != nil {
|
||||||
|
rsmAfter, ok = parseMessageId(*query.ResultSet.After)
|
||||||
|
if !ok {
|
||||||
|
iqAnswerSetError(answer, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debugf("MAM RSM after: %v", rsmAfter)
|
||||||
|
}
|
||||||
|
if query.ResultSet.Before != nil {
|
||||||
|
before := *query.ResultSet.Before
|
||||||
|
if before == "" {
|
||||||
|
rsmLastPage = true
|
||||||
|
} else {
|
||||||
|
rsmBefore, ok = parseMessageId(*query.ResultSet.Before)
|
||||||
|
if !ok {
|
||||||
|
iqAnswerSetError(answer, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debugf("MAM RSM after: %v", rsmBefore)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if query.ResultSet.Max != nil {
|
||||||
|
rsmLimit = int32(*query.ResultSet.Max)
|
||||||
|
if rsmLimit == 0 {
|
||||||
|
justCount = true
|
||||||
|
}
|
||||||
|
log.Debugf("MAM RSM max: %v", rsmLimit)
|
||||||
|
}
|
||||||
|
|
||||||
|
if query.ResultSet.First != nil {
|
||||||
|
iqAnswerSetError(answer, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if query.ResultSet.Index != nil {
|
||||||
|
iqAnswerSetError(answer, 501)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if query.ResultSet.Last != nil {
|
||||||
|
iqAnswerSetError(answer, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rsmLimit == 0 && !justCount {
|
||||||
|
rsmLimit = 100
|
||||||
|
}
|
||||||
|
|
||||||
|
if rsmLastPage {
|
||||||
|
rsmLimit = -rsmLimit // hacky, I know, and? :P
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for mutual parameter compatibility, there's a lot of them, nah?
|
||||||
|
if ((!startTime.IsZero() || !endTime.IsZero()) && (beforeId != 0 || afterId != 0 || ids != nil)) ||
|
||||||
|
((beforeId != 0 || afterId != 0) && (!startTime.IsZero() || !endTime.IsZero() || ids != nil)) ||
|
||||||
|
(ids != nil && (!startTime.IsZero() || !endTime.IsZero() || beforeId != 0 || afterId != 0)) {
|
||||||
|
iqAnswerSetError(answer, 501)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// dummy call to circumvent unexported type
|
||||||
|
messages, _, err := session.GetMessagesBetween(toID, 0, 0, 0)
|
||||||
|
|
||||||
|
quotaTs := time.Now().AddDate(0, 0, -int(gateway.MAMThreshold))
|
||||||
|
var beyond, complete bool
|
||||||
|
canBeComplete := true
|
||||||
|
fromStart := true
|
||||||
|
var overallyFirstMessageId, overallyLastMessageId int64
|
||||||
|
|
||||||
|
if ids != nil {
|
||||||
|
for _, sId := range ids {
|
||||||
|
id, ok := parseMessageId(sId)
|
||||||
|
if !ok {
|
||||||
|
iqAnswerSetError(answer, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg, err := session.GetMessage(toID, id)
|
||||||
|
if err != nil {
|
||||||
|
iqAnswerSetError(answer, 404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
messages = append(messages, msg)
|
||||||
|
}
|
||||||
|
messages = telegram.ChronologicallySortMessages(messages)
|
||||||
|
complete = true
|
||||||
|
} else if beforeId != 0 || afterId != 0 {
|
||||||
|
if (beforeId != 0 && afterId != 0) && beforeId > afterId {
|
||||||
|
iqAnswerSetError(answer, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't allow to fetch far beyond the quota
|
||||||
|
var replaceWithQuotaTs bool
|
||||||
|
if afterId != 0 {
|
||||||
|
afterMessage, afterMessageErr := session.GetMessage(toID, afterId)
|
||||||
|
if afterMessageErr == nil && afterMessage != nil {
|
||||||
|
if int64(afterMessage.Date) < quotaTs.Unix() {
|
||||||
|
replaceWithQuotaTs = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
iqAnswerSetError(answer, 404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
replaceWithQuotaTs = true
|
||||||
|
}
|
||||||
|
if replaceWithQuotaTs {
|
||||||
|
preFirstMessage, preFirstMessageErr := session.FindMessageByTime(toID, quotaTs)
|
||||||
|
if preFirstMessageErr == nil && preFirstMessage != nil {
|
||||||
|
afterId = preFirstMessage.Id
|
||||||
|
} else {
|
||||||
|
// there seem to be no messages older than quota, it's safe to fetch from the very start
|
||||||
|
afterId = 1 // https://github.com/tdlib/td/issues/195#issuecomment-380836359
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if beforeId != 0 {
|
||||||
|
beforeMessage, beforeMessageErr := session.GetMessage(toID, beforeId)
|
||||||
|
if beforeMessageErr != nil || beforeMessage == nil {
|
||||||
|
iqAnswerSetError(answer, 404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rsmAfter != 0 {
|
||||||
|
if rsmAfter < afterId || (beforeId != 0 && rsmAfter > beforeId) {
|
||||||
|
iqAnswerSetError(answer, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if rsmAfter != afterId {
|
||||||
|
fromStart = false
|
||||||
|
overallyFirstMessage, overallyFirstMessageErr := session.GetNextMessage(toID, afterId)
|
||||||
|
if overallyFirstMessageErr == nil && overallyFirstMessage != nil {
|
||||||
|
overallyFirstMessageId = overallyFirstMessage.Id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
afterId = rsmAfter
|
||||||
|
}
|
||||||
|
|
||||||
|
if rsmBefore != 0 {
|
||||||
|
if rsmBefore < afterId || (beforeId != 0 && rsmBefore > beforeId) {
|
||||||
|
iqAnswerSetError(answer, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if rsmBefore < beforeId {
|
||||||
|
canBeComplete = false
|
||||||
|
overallyLastMessageId = beforeId
|
||||||
|
}
|
||||||
|
beforeId = rsmBefore
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastMessageId int64
|
||||||
|
// should be fine even with afterId=0 as it would mean the last as needed
|
||||||
|
lastMessage, lastMessageErr := session.GetPreviousMessage(toID, afterId)
|
||||||
|
if lastMessageErr == nil && lastMessage != nil {
|
||||||
|
lastMessageId = lastMessage.Id
|
||||||
|
} else {
|
||||||
|
beyond = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !beyond {
|
||||||
|
var newComplete bool
|
||||||
|
messages, newComplete, err = session.GetMessagesBetween(toID, afterId, lastMessageId, rsmLimit)
|
||||||
|
if canBeComplete {
|
||||||
|
complete = newComplete
|
||||||
|
if !complete {
|
||||||
|
if lastMessageId != 0 {
|
||||||
|
overallyLastMessageId = lastMessageId
|
||||||
|
} else {
|
||||||
|
newestMessage, newestMessageErr := session.GetPreviousMessage(toID, 0)
|
||||||
|
if newestMessageErr == nil && newestMessage != nil {
|
||||||
|
overallyLastMessageId = newestMessage.Id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // time limit or no limits at all
|
||||||
|
// don't allow to fetch far beyond the quota
|
||||||
|
if !endTime.IsZero() && endTime.Before(quotaTs) {
|
||||||
|
beyond = true
|
||||||
|
}
|
||||||
|
if (!startTime.IsZero() && startTime.Before(quotaTs)) || startTime.IsZero() {
|
||||||
|
startTime = quotaTs
|
||||||
|
}
|
||||||
|
|
||||||
|
if !beyond {
|
||||||
|
var fromMessageId int64
|
||||||
|
var lastMessageId int64
|
||||||
|
fromMessage, fromMessageErr := session.FindMessageByTime(toID, startTime)
|
||||||
|
if fromMessageErr == nil && fromMessage != nil {
|
||||||
|
fromMessageId = fromMessage.Id
|
||||||
|
} else {
|
||||||
|
// there seem to be no messages older than quota, it's safe to fetch from the very start
|
||||||
|
fromMessageId = 1 // https://github.com/tdlib/td/issues/195#issuecomment-380836359
|
||||||
|
}
|
||||||
|
|
||||||
|
if !endTime.IsZero() {
|
||||||
|
endMsg, endMsgErr := session.FindMessageByTime(toID, endTime)
|
||||||
|
if endMsgErr == nil && endMsg != nil {
|
||||||
|
lastMessageId = endMsg.Id
|
||||||
|
} else {
|
||||||
|
beyond = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rsmAfter != 0 {
|
||||||
|
rsmAfterMessage, rsmAfterMessageErr := session.GetMessage(toID, rsmAfter)
|
||||||
|
if rsmAfterMessageErr == nil && rsmAfterMessage != nil {
|
||||||
|
if int64(rsmAfterMessage.Date) < startTime.Unix() || (!endTime.IsZero() && int64(rsmAfterMessage.Date) > endTime.Unix()) {
|
||||||
|
iqAnswerSetError(answer, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if rsmAfterMessage.Id > fromMessageId {
|
||||||
|
fromStart = false
|
||||||
|
overallyFirstMessage, overallyFirstMessageErr := session.GetNextMessage(toID, fromMessageId)
|
||||||
|
if overallyFirstMessageErr == nil && overallyFirstMessage != nil {
|
||||||
|
overallyFirstMessageId = overallyFirstMessage.Id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fromMessageId = rsmAfterMessage.Id
|
||||||
|
} else {
|
||||||
|
iqAnswerSetError(answer, 404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rsmBefore != 0 {
|
||||||
|
rsmBeforeMessage, rsmBeforeMessageErr := session.GetMessage(toID, rsmBefore)
|
||||||
|
if rsmBeforeMessageErr == nil && rsmBeforeMessage != nil {
|
||||||
|
if int64(rsmBeforeMessage.Date) < startTime.Unix() || (!endTime.IsZero() && int64(rsmBeforeMessage.Date) > endTime.Unix()) {
|
||||||
|
iqAnswerSetError(answer, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newLastMessage, newLastMessageErr := session.GetPreviousMessage(toID, rsmBeforeMessage.Id)
|
||||||
|
if newLastMessageErr == nil && newLastMessage != nil {
|
||||||
|
if lastMessageId == 0 || lastMessageId != newLastMessage.Id {
|
||||||
|
canBeComplete = false
|
||||||
|
if lastMessageId != 0 {
|
||||||
|
overallyLastMessageId = lastMessageId
|
||||||
|
} else {
|
||||||
|
newestMessage, newestMessageErr := session.GetPreviousMessage(toID, 0)
|
||||||
|
if newestMessageErr == nil && newestMessage != nil {
|
||||||
|
overallyLastMessageId = newestMessage.Id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastMessageId = newLastMessage.Id
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// nothing?.. not complete, just empty
|
||||||
|
beyond = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
iqAnswerSetError(answer, 404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !beyond { // yes🗿, twice
|
||||||
|
var newComplete bool
|
||||||
|
messages, newComplete, err = session.GetMessagesBetween(toID, fromMessageId, lastMessageId, rsmLimit)
|
||||||
|
if canBeComplete {
|
||||||
|
complete = newComplete
|
||||||
|
if !complete {
|
||||||
|
if lastMessageId != 0 {
|
||||||
|
overallyLastMessageId = lastMessageId
|
||||||
|
} else {
|
||||||
|
newestMessage, newestMessageErr := session.GetPreviousMessage(toID, 0)
|
||||||
|
if newestMessageErr == nil && newestMessage != nil {
|
||||||
|
overallyLastMessageId = newestMessage.Id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if query.FlipPage != nil {
|
||||||
|
telegram.ReverseMessagesSlice(messages)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("obtained %v messages", len(messages))
|
||||||
|
for _, message := range messages {
|
||||||
|
session.SendDelayedMUCMessage(toID, message, iq.From, query.QueryId)
|
||||||
|
}
|
||||||
|
|
||||||
|
rs := stanza.ResultSet{}
|
||||||
|
answer.Payload = &extensions.MAM2Fin{
|
||||||
|
ResultSet: &rs,
|
||||||
|
Complete: complete,
|
||||||
|
Stable: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
if beyond {
|
||||||
|
count := 0
|
||||||
|
rs.Count = &count
|
||||||
|
} else {
|
||||||
|
if len(messages) > 0 {
|
||||||
|
if fromStart {
|
||||||
|
overallyFirstMessageId = messages[0].Id
|
||||||
|
}
|
||||||
|
if complete {
|
||||||
|
overallyLastMessageId = messages[len(messages)-1].Id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstMsgPositionCount, lastMsgPositionCount int32
|
||||||
|
// estimate overall count
|
||||||
|
if overallyFirstMessageId != 0 && overallyLastMessageId != 0 {
|
||||||
|
firstMsgPosition, firstMsgPositionErr := session.GetChatMessagePosition(toID, overallyFirstMessageId)
|
||||||
|
if firstMsgPositionErr == nil && firstMsgPosition != nil {
|
||||||
|
firstMsgPositionCount = firstMsgPosition.Count
|
||||||
|
}
|
||||||
|
lastMsgPosition, lastMsgPositionErr := session.GetChatMessagePosition(toID, overallyLastMessageId)
|
||||||
|
if lastMsgPositionErr == nil && lastMsgPosition != nil {
|
||||||
|
lastMsgPositionCount = lastMsgPosition.Count
|
||||||
|
}
|
||||||
|
if firstMsgPositionCount != 0 && lastMsgPositionCount != 0 {
|
||||||
|
count := int(lastMsgPositionCount - firstMsgPositionCount + 1)
|
||||||
|
rs.Count = &count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(messages) > 0 {
|
||||||
|
rs.First = &stanza.First{
|
||||||
|
Content: strconv.FormatInt(messages[0].Id, 10),
|
||||||
|
}
|
||||||
|
if firstMsgPositionCount != 0 {
|
||||||
|
rsmFirstMsgPosition, rsmFirstMsgPositionErr := session.GetChatMessagePosition(toID, messages[0].Id)
|
||||||
|
if rsmFirstMsgPositionErr == nil && rsmFirstMsgPosition != nil {
|
||||||
|
index := int(rsmFirstMsgPosition.Count - firstMsgPositionCount)
|
||||||
|
rs.First.Index = &index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
last := strconv.FormatInt(messages[len(messages)-1].Id, 10)
|
||||||
|
rs.Last = &last
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Debugf("MAM fin: %#v", answer.Payload)
|
||||||
|
}
|
||||||
|
|
||||||
func iqAnswerSetError(answer *stanza.IQ, code int) {
|
func iqAnswerSetError(answer *stanza.IQ, code int) {
|
||||||
iqAnswerSetErrorInternal(answer, code, false)
|
iqAnswerSetErrorInternal(answer, code, false)
|
||||||
}
|
}
|
||||||
|
|
@ -2086,6 +2637,11 @@ func iqAnswerSetErrorInternal(answer *stanza.IQ, code int, registerMode bool) {
|
||||||
Type: stanza.ErrorTypeWait,
|
Type: stanza.ErrorTypeWait,
|
||||||
Reason: "internal-server-error",
|
Reason: "internal-server-error",
|
||||||
}
|
}
|
||||||
|
case 501:
|
||||||
|
answer.Error = &stanza.Err{
|
||||||
|
Type: stanza.ErrorTypeCancel,
|
||||||
|
Reason: "feature-not-implemented",
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
log.Error("Unknown error code, falling back with empty reason")
|
log.Error("Unknown error code, falling back with empty reason")
|
||||||
answer.Error = &stanza.Err{
|
answer.Error = &stanza.Err{
|
||||||
|
|
@ -2251,6 +2807,21 @@ func toToID(to string) (int64, bool, bool) {
|
||||||
return toID, true, isGroup
|
return toID, true, isGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseMessageId(sId string) (int64, bool) {
|
||||||
|
if sId[0] == 'e' {
|
||||||
|
idParts := strings.Split(sId[1:], ":")
|
||||||
|
if len(idParts) >= 1 {
|
||||||
|
sId = idParts[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
id, err := strconv.ParseInt(sId, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn(errors.Wrap(err, "Failed to parse message ID!"))
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return id, true
|
||||||
|
}
|
||||||
|
|
||||||
func makeVCardPayload(typ byte, id string, info telegram.VCardInfo, session *telegram.Client) stanza.IQPayload {
|
func makeVCardPayload(typ byte, id string, info telegram.VCardInfo, session *telegram.Client) stanza.IQPayload {
|
||||||
var base64Photo string
|
var base64Photo string
|
||||||
if info.Photo != nil {
|
if info.Photo != nil {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue