|
|
|
@ -16,6 +16,7 @@ import (
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
"unicode/utf8"
|
|
|
|
|
|
|
|
|
|
"dev.narayana.im/narayana/telegabber/telegram/cache"
|
|
|
|
|
"dev.narayana.im/narayana/telegabber/telegram/formatter"
|
|
|
|
@ -36,13 +37,21 @@ type VCardInfo struct {
|
|
|
|
|
Info string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type messageStub struct {
|
|
|
|
|
MessageId int64
|
|
|
|
|
ChatId int64
|
|
|
|
|
Sender string
|
|
|
|
|
Date int32
|
|
|
|
|
Text string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var errOffline = errors.New("TDlib instance is offline")
|
|
|
|
|
|
|
|
|
|
var spaceRegex = regexp.MustCompile(`\s+`)
|
|
|
|
|
var replyRegex = regexp.MustCompile("\\A>>? ?([0-9]+)\\n")
|
|
|
|
|
|
|
|
|
|
const newlineChar string = "\n"
|
|
|
|
|
const messageHeaderSeparator string = " | "
|
|
|
|
|
const messageHeaderSeparator string = " | " // no hrunicode allowed here yet
|
|
|
|
|
|
|
|
|
|
// GetContactByUsername resolves username to user id retrieves user and chat information
|
|
|
|
|
func (c *Client) GetContactByUsername(username string) (*client.Chat, *client.User, error) {
|
|
|
|
@ -272,22 +281,17 @@ func (c *Client) ProcessStatusUpdate(chatID int64, status string, show string, o
|
|
|
|
|
c.cache.SetStatus(chatID, cacheShow, status)
|
|
|
|
|
|
|
|
|
|
newArgs := []args.V{
|
|
|
|
|
gateway.SPFrom(strconv.FormatInt(chatID, 10)),
|
|
|
|
|
gateway.SPShow(show),
|
|
|
|
|
gateway.SPStatus(status),
|
|
|
|
|
gateway.SPPhoto(photo),
|
|
|
|
|
gateway.SPResource(gateway.Jid.Resource),
|
|
|
|
|
gateway.SPImmed(gateway.SPImmed.Get(oldArgs)),
|
|
|
|
|
}
|
|
|
|
|
newArgs = gateway.SPAppendFrom(newArgs, chatID)
|
|
|
|
|
if presenceType != "" {
|
|
|
|
|
newArgs = append(newArgs, gateway.SPType(presenceType))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return gateway.SendPresence(
|
|
|
|
|
c.xmpp,
|
|
|
|
|
c.jid,
|
|
|
|
|
newArgs...,
|
|
|
|
|
)
|
|
|
|
|
return c.sendPresence(newArgs...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) formatContact(chatID int64) string {
|
|
|
|
@ -341,25 +345,74 @@ func (c *Client) formatSender(message *client.Message) string {
|
|
|
|
|
return c.formatContact(c.getSenderId(message))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) getMessageReply(message *client.Message) (reply *gateway.Reply, replyMsg *client.Message) {
|
|
|
|
|
if message.ReplyToMessageId != 0 {
|
|
|
|
|
var err error
|
|
|
|
|
replyMsg, err = c.client.GetMessage(&client.GetMessageRequest{
|
|
|
|
|
ChatId: message.ChatId,
|
|
|
|
|
MessageId: message.ReplyToMessageId,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorf("<error fetching message: %s>", err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
func (c *Client) messageToStub(message *client.Message, preview bool, text string) *messageStub {
|
|
|
|
|
if text == "" {
|
|
|
|
|
text = c.messageContentToText(message.Content, message.ChatId, preview)
|
|
|
|
|
}
|
|
|
|
|
return &messageStub{
|
|
|
|
|
MessageId: message.Id,
|
|
|
|
|
ChatId: message.ChatId,
|
|
|
|
|
Sender: c.formatSender(message),
|
|
|
|
|
Date: message.Date,
|
|
|
|
|
Text: text,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
replyId, err := gateway.IdsDB.GetByTgIds(c.Session.Login, c.jid, message.ChatId, message.ReplyToMessageId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
replyId = strconv.FormatInt(message.ReplyToMessageId, 10)
|
|
|
|
|
}
|
|
|
|
|
reply = &gateway.Reply{
|
|
|
|
|
Author: fmt.Sprintf("%v@%s", c.getSenderId(replyMsg), gateway.Jid.Full()),
|
|
|
|
|
Id: replyId,
|
|
|
|
|
func (c *Client) getMessageReply(message *client.Message, preview bool, noContent bool) (gatewayReply *gateway.Reply, tgReply *messageStub) {
|
|
|
|
|
if message.ReplyTo != nil && message.ReplyTo.MessageReplyToType() == client.TypeMessageReplyToMessage {
|
|
|
|
|
replyTo, _ := message.ReplyTo.(*client.MessageReplyToMessage)
|
|
|
|
|
var text string
|
|
|
|
|
if replyTo.Quote != nil && replyTo.Quote.Text != nil && !noContent {
|
|
|
|
|
text = formatter.Format(
|
|
|
|
|
replyTo.Quote.Text.Text,
|
|
|
|
|
replyTo.Quote.Text.Entities,
|
|
|
|
|
c.getFormatter(),
|
|
|
|
|
)
|
|
|
|
|
// make the whole quote fit one line
|
|
|
|
|
text = strings.ReplaceAll(text, "\n", " ")
|
|
|
|
|
}
|
|
|
|
|
if message.ChatId == replyTo.ChatId {
|
|
|
|
|
// obtain message from this chat
|
|
|
|
|
replyMsg, err := c.client.GetMessage(&client.GetMessageRequest{
|
|
|
|
|
ChatId: message.ChatId,
|
|
|
|
|
MessageId: replyTo.MessageId,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorf("<error fetching message: %s>", err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !noContent {
|
|
|
|
|
tgReply = c.messageToStub(replyMsg, preview, text)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
replyId, err := gateway.IdsDB.GetByTgIds(c.Session.Login, c.jid, message.ChatId, replyTo.MessageId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
replyId = strconv.FormatInt(replyTo.MessageId, 10)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gatewayReply = &gateway.Reply{
|
|
|
|
|
Author: fmt.Sprintf("%v@%s", c.getSenderId(replyMsg), gateway.Jid.Full()),
|
|
|
|
|
Id: replyId,
|
|
|
|
|
}
|
|
|
|
|
} else if !noContent {
|
|
|
|
|
// it's safe to assume there's no need to pass ChatId here
|
|
|
|
|
// as it's needed only for pin messages which are not allowed in replies
|
|
|
|
|
if text == "" && replyTo.Content != nil {
|
|
|
|
|
text = c.messageContentToText(replyTo.Content, 0, preview)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if text == "" {
|
|
|
|
|
log.Error("Empty reply from other/unknown chat")
|
|
|
|
|
log.Debugf("replyTo: %#v", replyTo)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tgReply = &messageStub{
|
|
|
|
|
Sender: c.formatOrigin(replyTo.Origin) + " @ " + c.formatContact(replyTo.ChatId),
|
|
|
|
|
Date: replyTo.OriginSendDate,
|
|
|
|
|
Text: text,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -382,9 +435,16 @@ func (c *Client) formatMessage(chatID int64, messageID int64, preview bool, mess
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return c.formatMessageContent(preview, c.messageToStub(message, preview, ""))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) formatMessageContent(preview bool, message *messageStub) string {
|
|
|
|
|
var str strings.Builder
|
|
|
|
|
// add messageid and sender
|
|
|
|
|
str.WriteString(fmt.Sprintf("%v | %s | ", message.Id, c.formatSender(message)))
|
|
|
|
|
if message.MessageId != 0 {
|
|
|
|
|
str.WriteString(fmt.Sprintf("%v | ", message.MessageId))
|
|
|
|
|
}
|
|
|
|
|
str.WriteString(fmt.Sprintf("%s | ", message.Sender))
|
|
|
|
|
// add date
|
|
|
|
|
if !preview {
|
|
|
|
|
str.WriteString(
|
|
|
|
@ -395,10 +455,7 @@ func (c *Client) formatMessage(chatID int64, messageID int64, preview bool, mess
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// text message
|
|
|
|
|
var text string
|
|
|
|
|
if message.Content != nil {
|
|
|
|
|
text = c.messageToText(message, preview)
|
|
|
|
|
}
|
|
|
|
|
text := message.Text
|
|
|
|
|
if text != "" {
|
|
|
|
|
if !preview {
|
|
|
|
|
str.WriteString(text)
|
|
|
|
@ -415,33 +472,33 @@ func (c *Client) formatMessage(chatID int64, messageID int64, preview bool, mess
|
|
|
|
|
return str.String()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) formatForward(fwd *client.MessageForwardInfo) string {
|
|
|
|
|
switch fwd.Origin.MessageForwardOriginType() {
|
|
|
|
|
case client.TypeMessageForwardOriginUser:
|
|
|
|
|
originUser := fwd.Origin.(*client.MessageForwardOriginUser)
|
|
|
|
|
func (c *Client) formatOrigin(origin client.MessageOrigin) string {
|
|
|
|
|
if origin == nil {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
switch origin.MessageOriginType() {
|
|
|
|
|
case client.TypeMessageOriginUser:
|
|
|
|
|
originUser := origin.(*client.MessageOriginUser)
|
|
|
|
|
return c.formatContact(originUser.SenderUserId)
|
|
|
|
|
case client.TypeMessageForwardOriginChat:
|
|
|
|
|
originChat := fwd.Origin.(*client.MessageForwardOriginChat)
|
|
|
|
|
case client.TypeMessageOriginChat:
|
|
|
|
|
originChat := origin.(*client.MessageOriginChat)
|
|
|
|
|
var signature string
|
|
|
|
|
if originChat.AuthorSignature != "" {
|
|
|
|
|
signature = fmt.Sprintf(" (%s)", originChat.AuthorSignature)
|
|
|
|
|
}
|
|
|
|
|
return c.formatContact(originChat.SenderChatId) + signature
|
|
|
|
|
case client.TypeMessageForwardOriginHiddenUser:
|
|
|
|
|
originUser := fwd.Origin.(*client.MessageForwardOriginHiddenUser)
|
|
|
|
|
case client.TypeMessageOriginHiddenUser:
|
|
|
|
|
originUser := origin.(*client.MessageOriginHiddenUser)
|
|
|
|
|
return originUser.SenderName
|
|
|
|
|
case client.TypeMessageForwardOriginChannel:
|
|
|
|
|
channel := fwd.Origin.(*client.MessageForwardOriginChannel)
|
|
|
|
|
case client.TypeMessageOriginChannel:
|
|
|
|
|
channel := origin.(*client.MessageOriginChannel)
|
|
|
|
|
var signature string
|
|
|
|
|
if channel.AuthorSignature != "" {
|
|
|
|
|
signature = fmt.Sprintf(" (%s)", channel.AuthorSignature)
|
|
|
|
|
}
|
|
|
|
|
return c.formatContact(channel.ChatId) + signature
|
|
|
|
|
case client.TypeMessageForwardOriginMessageImport:
|
|
|
|
|
originImport := fwd.Origin.(*client.MessageForwardOriginMessageImport)
|
|
|
|
|
return originImport.SenderName
|
|
|
|
|
}
|
|
|
|
|
return "Unknown forward type"
|
|
|
|
|
return "Unknown origin type"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) formatFile(file *client.File, compact bool) (string, string) {
|
|
|
|
@ -587,20 +644,24 @@ func (c *Client) messageToText(message *client.Message, preview bool) string {
|
|
|
|
|
return "<empty message>"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
markupFunction := c.getFormatter()
|
|
|
|
|
switch message.Content.MessageContentType() {
|
|
|
|
|
return c.messageContentToText(message.Content, message.ChatId, preview)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) messageContentToText(content client.MessageContent, chatId int64, preview bool) string {
|
|
|
|
|
markupMode := c.getFormatter()
|
|
|
|
|
switch content.MessageContentType() {
|
|
|
|
|
case client.TypeMessageSticker:
|
|
|
|
|
sticker, _ := message.Content.(*client.MessageSticker)
|
|
|
|
|
sticker, _ := content.(*client.MessageSticker)
|
|
|
|
|
return sticker.Sticker.Emoji
|
|
|
|
|
case client.TypeMessageAnimatedEmoji:
|
|
|
|
|
animatedEmoji, _ := message.Content.(*client.MessageAnimatedEmoji)
|
|
|
|
|
animatedEmoji, _ := content.(*client.MessageAnimatedEmoji)
|
|
|
|
|
return animatedEmoji.Emoji
|
|
|
|
|
case client.TypeMessageBasicGroupChatCreate, client.TypeMessageSupergroupChatCreate:
|
|
|
|
|
return "has created chat"
|
|
|
|
|
case client.TypeMessageChatJoinByLink:
|
|
|
|
|
return "joined chat via invite link"
|
|
|
|
|
case client.TypeMessageChatAddMembers:
|
|
|
|
|
addMembers, _ := message.Content.(*client.MessageChatAddMembers)
|
|
|
|
|
addMembers, _ := content.(*client.MessageChatAddMembers)
|
|
|
|
|
|
|
|
|
|
text := "invited "
|
|
|
|
|
if len(addMembers.MemberUserIds) > 0 {
|
|
|
|
@ -609,19 +670,19 @@ func (c *Client) messageToText(message *client.Message, preview bool) string {
|
|
|
|
|
|
|
|
|
|
return text
|
|
|
|
|
case client.TypeMessageChatDeleteMember:
|
|
|
|
|
deleteMember, _ := message.Content.(*client.MessageChatDeleteMember)
|
|
|
|
|
deleteMember, _ := content.(*client.MessageChatDeleteMember)
|
|
|
|
|
return "kicked " + c.formatContact(deleteMember.UserId)
|
|
|
|
|
case client.TypeMessagePinMessage:
|
|
|
|
|
pinMessage, _ := message.Content.(*client.MessagePinMessage)
|
|
|
|
|
return "pinned message: " + c.formatMessage(message.ChatId, pinMessage.MessageId, preview, nil)
|
|
|
|
|
pinMessage, _ := content.(*client.MessagePinMessage)
|
|
|
|
|
return "pinned message: " + c.formatMessage(chatId, pinMessage.MessageId, preview, nil)
|
|
|
|
|
case client.TypeMessageChatChangeTitle:
|
|
|
|
|
changeTitle, _ := message.Content.(*client.MessageChatChangeTitle)
|
|
|
|
|
changeTitle, _ := content.(*client.MessageChatChangeTitle)
|
|
|
|
|
return "chat title set to: " + changeTitle.Title
|
|
|
|
|
case client.TypeMessageLocation:
|
|
|
|
|
location, _ := message.Content.(*client.MessageLocation)
|
|
|
|
|
location, _ := content.(*client.MessageLocation)
|
|
|
|
|
return c.formatLocation(location.Location)
|
|
|
|
|
case client.TypeMessageVenue:
|
|
|
|
|
venue, _ := message.Content.(*client.MessageVenue)
|
|
|
|
|
venue, _ := content.(*client.MessageVenue)
|
|
|
|
|
if preview {
|
|
|
|
|
return venue.Venue.Title
|
|
|
|
|
} else {
|
|
|
|
@ -633,86 +694,86 @@ func (c *Client) messageToText(message *client.Message, preview bool) string {
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
case client.TypeMessagePhoto:
|
|
|
|
|
photo, _ := message.Content.(*client.MessagePhoto)
|
|
|
|
|
photo, _ := content.(*client.MessagePhoto)
|
|
|
|
|
if preview {
|
|
|
|
|
return photo.Caption.Text
|
|
|
|
|
} else {
|
|
|
|
|
return formatter.Format(
|
|
|
|
|
photo.Caption.Text,
|
|
|
|
|
photo.Caption.Entities,
|
|
|
|
|
markupFunction,
|
|
|
|
|
markupMode,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
case client.TypeMessageAudio:
|
|
|
|
|
audio, _ := message.Content.(*client.MessageAudio)
|
|
|
|
|
audio, _ := content.(*client.MessageAudio)
|
|
|
|
|
if preview {
|
|
|
|
|
return audio.Caption.Text
|
|
|
|
|
} else {
|
|
|
|
|
return formatter.Format(
|
|
|
|
|
audio.Caption.Text,
|
|
|
|
|
audio.Caption.Entities,
|
|
|
|
|
markupFunction,
|
|
|
|
|
markupMode,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
case client.TypeMessageVideo:
|
|
|
|
|
video, _ := message.Content.(*client.MessageVideo)
|
|
|
|
|
video, _ := content.(*client.MessageVideo)
|
|
|
|
|
if preview {
|
|
|
|
|
return video.Caption.Text
|
|
|
|
|
} else {
|
|
|
|
|
return formatter.Format(
|
|
|
|
|
video.Caption.Text,
|
|
|
|
|
video.Caption.Entities,
|
|
|
|
|
markupFunction,
|
|
|
|
|
markupMode,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
case client.TypeMessageDocument:
|
|
|
|
|
document, _ := message.Content.(*client.MessageDocument)
|
|
|
|
|
document, _ := content.(*client.MessageDocument)
|
|
|
|
|
if preview {
|
|
|
|
|
return document.Caption.Text
|
|
|
|
|
} else {
|
|
|
|
|
return formatter.Format(
|
|
|
|
|
document.Caption.Text,
|
|
|
|
|
document.Caption.Entities,
|
|
|
|
|
markupFunction,
|
|
|
|
|
markupMode,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
case client.TypeMessageText:
|
|
|
|
|
text, _ := message.Content.(*client.MessageText)
|
|
|
|
|
text, _ := content.(*client.MessageText)
|
|
|
|
|
if preview {
|
|
|
|
|
return text.Text.Text
|
|
|
|
|
} else {
|
|
|
|
|
return formatter.Format(
|
|
|
|
|
text.Text.Text,
|
|
|
|
|
text.Text.Entities,
|
|
|
|
|
markupFunction,
|
|
|
|
|
markupMode,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
case client.TypeMessageVoiceNote:
|
|
|
|
|
voice, _ := message.Content.(*client.MessageVoiceNote)
|
|
|
|
|
voice, _ := content.(*client.MessageVoiceNote)
|
|
|
|
|
if preview {
|
|
|
|
|
return voice.Caption.Text
|
|
|
|
|
} else {
|
|
|
|
|
return formatter.Format(
|
|
|
|
|
voice.Caption.Text,
|
|
|
|
|
voice.Caption.Entities,
|
|
|
|
|
markupFunction,
|
|
|
|
|
markupMode,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
case client.TypeMessageVideoNote:
|
|
|
|
|
return ""
|
|
|
|
|
case client.TypeMessageAnimation:
|
|
|
|
|
animation, _ := message.Content.(*client.MessageAnimation)
|
|
|
|
|
animation, _ := content.(*client.MessageAnimation)
|
|
|
|
|
if preview {
|
|
|
|
|
return animation.Caption.Text
|
|
|
|
|
} else {
|
|
|
|
|
return formatter.Format(
|
|
|
|
|
animation.Caption.Text,
|
|
|
|
|
animation.Caption.Entities,
|
|
|
|
|
markupFunction,
|
|
|
|
|
markupMode,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
case client.TypeMessageContact:
|
|
|
|
|
contact, _ := message.Content.(*client.MessageContact)
|
|
|
|
|
contact, _ := content.(*client.MessageContact)
|
|
|
|
|
if preview {
|
|
|
|
|
return contact.Contact.FirstName + " " + contact.Contact.LastName
|
|
|
|
|
} else {
|
|
|
|
@ -730,10 +791,10 @@ func (c *Client) messageToText(message *client.Message, preview bool) string {
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
case client.TypeMessageDice:
|
|
|
|
|
dice, _ := message.Content.(*client.MessageDice)
|
|
|
|
|
dice, _ := content.(*client.MessageDice)
|
|
|
|
|
return fmt.Sprintf("%s 1d6: [%v]", dice.Emoji, dice.Value)
|
|
|
|
|
case client.TypeMessagePoll:
|
|
|
|
|
poll, _ := message.Content.(*client.MessagePoll)
|
|
|
|
|
poll, _ := content.(*client.MessagePoll)
|
|
|
|
|
|
|
|
|
|
if preview {
|
|
|
|
|
return poll.Poll.Question
|
|
|
|
@ -759,7 +820,7 @@ func (c *Client) messageToText(message *client.Message, preview bool) string {
|
|
|
|
|
return strings.Join(rows, "\n")
|
|
|
|
|
}
|
|
|
|
|
case client.TypeMessageChatSetMessageAutoDeleteTime:
|
|
|
|
|
ttl, _ := message.Content.(*client.MessageChatSetMessageAutoDeleteTime)
|
|
|
|
|
ttl, _ := content.(*client.MessageChatSetMessageAutoDeleteTime)
|
|
|
|
|
name := c.formatContact(ttl.FromUserId)
|
|
|
|
|
if name == "" {
|
|
|
|
|
if ttl.MessageAutoDeleteTime == 0 {
|
|
|
|
@ -776,7 +837,7 @@ func (c *Client) messageToText(message *client.Message, preview bool) string {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return fmt.Sprintf("unknown message (%s)", message.Content.MessageContentType())
|
|
|
|
|
return fmt.Sprintf("unknown message (%s)", content.MessageContentType())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) contentToFile(content client.MessageContent) (*client.File, *client.File) {
|
|
|
|
@ -845,21 +906,23 @@ func (c *Client) contentToFile(content client.MessageContent) (*client.File, *cl
|
|
|
|
|
|
|
|
|
|
func (c *Client) countCharsInLines(lines *[]string) (count int) {
|
|
|
|
|
for _, line := range *lines {
|
|
|
|
|
count += len(line)
|
|
|
|
|
count += utf8.RuneCountInString(line)
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) messageToPrefix(message *client.Message, previewString string, fileString string, replyMsg *client.Message) (string, int, int) {
|
|
|
|
|
func (c *Client) isCarbonsEnabled() bool {
|
|
|
|
|
return gateway.MessageOutgoingPermissionVersion > 0 && c.Session.Carbons
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) messageToPrefix(message *client.Message, previewString string, fileString string, suppressReply bool) (string, *gateway.Reply) {
|
|
|
|
|
isPM, err := c.IsPM(message.ChatId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorf("Could not determine if chat is PM: %v", err)
|
|
|
|
|
}
|
|
|
|
|
isCarbonsEnabled := gateway.MessageOutgoingPermissionVersion > 0 && c.Session.Carbons
|
|
|
|
|
// with carbons, hide for all messages in PM and only for outgoing in group chats
|
|
|
|
|
hideSender := isCarbonsEnabled && (message.IsOutgoing || isPM)
|
|
|
|
|
hideSender := c.isCarbonsEnabled() && (message.IsOutgoing || isPM)
|
|
|
|
|
|
|
|
|
|
var replyStart, replyEnd int
|
|
|
|
|
prefix := []string{}
|
|
|
|
|
// message direction
|
|
|
|
|
var directionChar string
|
|
|
|
@ -888,20 +951,39 @@ func (c *Client) messageToPrefix(message *client.Message, previewString string,
|
|
|
|
|
prefix = append(prefix, sender)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// reply to
|
|
|
|
|
if message.ReplyToMessageId != 0 {
|
|
|
|
|
if len(prefix) > 0 {
|
|
|
|
|
replyStart = c.countCharsInLines(&prefix) + (len(prefix)-1)*len(messageHeaderSeparator)
|
|
|
|
|
}
|
|
|
|
|
replyLine := "reply: " + c.formatMessage(message.ChatId, message.ReplyToMessageId, true, replyMsg)
|
|
|
|
|
prefix = append(prefix, replyLine)
|
|
|
|
|
replyEnd = replyStart + len(replyLine)
|
|
|
|
|
if len(prefix) > 0 {
|
|
|
|
|
replyEnd += len(messageHeaderSeparator)
|
|
|
|
|
var reply *gateway.Reply
|
|
|
|
|
if !suppressReply {
|
|
|
|
|
preview := true
|
|
|
|
|
gwReply, tgReply := c.getMessageReply(message, preview, false)
|
|
|
|
|
|
|
|
|
|
if tgReply != nil {
|
|
|
|
|
reply = gwReply
|
|
|
|
|
|
|
|
|
|
var replyStart, replyEnd int
|
|
|
|
|
|
|
|
|
|
if len(prefix) > 0 {
|
|
|
|
|
replyStart = c.countCharsInLines(&prefix) + (len(prefix)-1)*len(messageHeaderSeparator)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
replyLine := "reply: " + c.formatMessageContent(preview, tgReply)
|
|
|
|
|
prefix = append(prefix, replyLine)
|
|
|
|
|
|
|
|
|
|
replyEnd = replyStart + utf8.RuneCountInString(replyLine)
|
|
|
|
|
if len(prefix) > 0 {
|
|
|
|
|
replyEnd += len(messageHeaderSeparator)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if reply != nil {
|
|
|
|
|
reply.Start = uint64(replyStart)
|
|
|
|
|
reply.End = uint64(replyEnd)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if message.ForwardInfo != nil {
|
|
|
|
|
prefix = append(prefix, "fwd: "+c.formatForward(message.ForwardInfo))
|
|
|
|
|
prefix = append(prefix, "fwd: "+c.formatOrigin(message.ForwardInfo.Origin))
|
|
|
|
|
}
|
|
|
|
|
// preview
|
|
|
|
|
if previewString != "" {
|
|
|
|
@ -912,7 +994,7 @@ func (c *Client) messageToPrefix(message *client.Message, previewString string,
|
|
|
|
|
prefix = append(prefix, "file: "+fileString)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return strings.Join(prefix, messageHeaderSeparator), replyStart, replyEnd
|
|
|
|
|
return strings.Join(prefix, messageHeaderSeparator), reply
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) ensureDownloadFile(file *client.File) *client.File {
|
|
|
|
@ -931,14 +1013,25 @@ func (c *Client) ensureDownloadFile(file *client.File) *client.File {
|
|
|
|
|
return file
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// \n if it is groupchat and message is not empty
|
|
|
|
|
func (c *Client) getPrefixSeparator(chatId int64) string {
|
|
|
|
|
var separator string
|
|
|
|
|
if chatId < 0 {
|
|
|
|
|
separator = "\n"
|
|
|
|
|
} else if chatId > 0 {
|
|
|
|
|
separator = " | "
|
|
|
|
|
}
|
|
|
|
|
return separator
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ProcessIncomingMessage transfers a message to XMPP side and marks it as read on Telegram side
|
|
|
|
|
func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) {
|
|
|
|
|
isCarbon := gateway.MessageOutgoingPermissionVersion > 0 && c.Session.Carbons && message.IsOutgoing
|
|
|
|
|
isCarbon := c.isCarbonsEnabled() && message.IsOutgoing
|
|
|
|
|
jids := c.getCarbonFullJids(isCarbon, "")
|
|
|
|
|
|
|
|
|
|
var text, oob, auxText string
|
|
|
|
|
|
|
|
|
|
reply, replyMsg := c.getMessageReply(message)
|
|
|
|
|
var reply *gateway.Reply
|
|
|
|
|
var replyObtained bool
|
|
|
|
|
|
|
|
|
|
content := message.Content
|
|
|
|
|
if content != nil && content.MessageContentType() == client.TypeMessageChatChangePhoto {
|
|
|
|
@ -974,47 +1067,50 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) {
|
|
|
|
|
} else if !c.Session.RawMessages {
|
|
|
|
|
var newText strings.Builder
|
|
|
|
|
|
|
|
|
|
prefix, replyStart, replyEnd := c.messageToPrefix(message, previewName, fileName, replyMsg)
|
|
|
|
|
prefix, prefixReply := c.messageToPrefix(message, previewName, fileName, false)
|
|
|
|
|
reply = prefixReply
|
|
|
|
|
replyObtained = true
|
|
|
|
|
newText.WriteString(prefix)
|
|
|
|
|
if reply != nil {
|
|
|
|
|
reply.Start = uint64(replyStart)
|
|
|
|
|
reply.End = uint64(replyEnd)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if text != "" {
|
|
|
|
|
// \n if it is groupchat and message is not empty
|
|
|
|
|
if prefix != "" {
|
|
|
|
|
if chatId < 0 {
|
|
|
|
|
newText.WriteString("\n")
|
|
|
|
|
} else if chatId > 0 {
|
|
|
|
|
newText.WriteString(" | ")
|
|
|
|
|
}
|
|
|
|
|
newText.WriteString(c.getPrefixSeparator(chatId))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
newText.WriteString(text)
|
|
|
|
|
}
|
|
|
|
|
text = newText.String()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if !replyObtained {
|
|
|
|
|
reply, _ = c.getMessageReply(message, false, true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// mark message as read
|
|
|
|
|
c.client.ViewMessages(&client.ViewMessagesRequest{
|
|
|
|
|
ChatId: chatId,
|
|
|
|
|
MessageIds: []int64{message.Id},
|
|
|
|
|
ForceRead: true,
|
|
|
|
|
})
|
|
|
|
|
if !c.Session.Receipts {
|
|
|
|
|
c.MarkAsRead(chatId, message.Id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// forward message to XMPP
|
|
|
|
|
sId := strconv.FormatInt(message.Id, 10)
|
|
|
|
|
sChatId := strconv.FormatInt(chatId, 10)
|
|
|
|
|
|
|
|
|
|
for _, jid := range jids {
|
|
|
|
|
gateway.SendMessageWithOOB(jid, sChatId, text, sId, c.xmpp, reply, oob, isCarbon)
|
|
|
|
|
gateway.SendMessageWithOOB(jid, sChatId, text, sId, c.xmpp, reply, oob, "", isCarbon, c.Session.Receipts)
|
|
|
|
|
if auxText != "" {
|
|
|
|
|
gateway.SendMessage(jid, sChatId, auxText, sId, c.xmpp, reply, isCarbon)
|
|
|
|
|
gateway.SendMessage(jid, sChatId, auxText, sId, c.xmpp, reply, "", isCarbon, c.Session.Receipts)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
c.UpdateLastChatMessageId(chatId, sId)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MarkAsRead marks a message as read
|
|
|
|
|
func (c *Client) MarkAsRead(chatId, messageId int64) {
|
|
|
|
|
c.client.ViewMessages(&client.ViewMessagesRequest{
|
|
|
|
|
ChatId: chatId,
|
|
|
|
|
MessageIds: []int64{messageId},
|
|
|
|
|
ForceRead: true,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PrepareMessageContent creates a simple text message
|
|
|
|
@ -1115,7 +1211,7 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str
|
|
|
|
|
|
|
|
|
|
tgMessage, err := c.client.SendMessage(&client.SendMessageRequest{
|
|
|
|
|
ChatId: chatID,
|
|
|
|
|
ReplyToMessageId: reply,
|
|
|
|
|
ReplyTo: &client.InputMessageReplyToMessage{MessageId: reply},
|
|
|
|
|
InputMessageContent: content,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
@ -1212,7 +1308,7 @@ func (c *Client) roster(resource string) {
|
|
|
|
|
c.ProcessStatusUpdate(chat, "", "")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gateway.SendPresence(c.xmpp, c.jid, gateway.SPStatus("Logged in as: "+c.Session.Login))
|
|
|
|
|
c.sendPresence(gateway.SPStatus("Logged in as: " + c.Session.Login))
|
|
|
|
|
|
|
|
|
|
c.addResource(resource)
|
|
|
|
|
}
|
|
|
|
@ -1313,9 +1409,7 @@ func (c *Client) GetChatDescription(chat *client.Chat) string {
|
|
|
|
|
|
|
|
|
|
// subscribe to a Telegram ID
|
|
|
|
|
func (c *Client) subscribeToID(id int64, chat *client.Chat) {
|
|
|
|
|
var args []args.V
|
|
|
|
|
args = append(args, gateway.SPFrom(strconv.FormatInt(id, 10)))
|
|
|
|
|
args = append(args, gateway.SPType("subscribe"))
|
|
|
|
|
args := gateway.SimplePresence(id, "subscribe")
|
|
|
|
|
|
|
|
|
|
if chat == nil {
|
|
|
|
|
chat, _, _ = c.GetContactByID(id, nil)
|
|
|
|
@ -1326,11 +1420,11 @@ func (c *Client) subscribeToID(id int64, chat *client.Chat) {
|
|
|
|
|
gateway.SetNickname(c.jid, strconv.FormatInt(id, 10), chat.Title, c.xmpp)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gateway.SendPresence(
|
|
|
|
|
c.xmpp,
|
|
|
|
|
c.jid,
|
|
|
|
|
args...,
|
|
|
|
|
)
|
|
|
|
|
c.sendPresence(args...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) sendPresence(args ...args.V) error {
|
|
|
|
|
return gateway.SendPresence(c.xmpp, c.jid, args...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) prepareDiskSpace(size uint64) {
|
|
|
|
@ -1379,9 +1473,9 @@ func (c *Client) UpdateChatNicknames() {
|
|
|
|
|
chat, ok := c.cache.GetChat(id)
|
|
|
|
|
if ok {
|
|
|
|
|
newArgs := []args.V{
|
|
|
|
|
gateway.SPFrom(strconv.FormatInt(id, 10)),
|
|
|
|
|
gateway.SPNickname(chat.Title),
|
|
|
|
|
}
|
|
|
|
|
newArgs = gateway.SPAppendFrom(newArgs, id)
|
|
|
|
|
|
|
|
|
|
cachedStatus, ok := c.cache.GetStatus(id)
|
|
|
|
|
if ok {
|
|
|
|
@ -1392,17 +1486,34 @@ func (c *Client) UpdateChatNicknames() {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gateway.SendPresence(
|
|
|
|
|
c.xmpp,
|
|
|
|
|
c.jid,
|
|
|
|
|
newArgs...,
|
|
|
|
|
)
|
|
|
|
|
c.sendPresence(newArgs...)
|
|
|
|
|
|
|
|
|
|
gateway.SetNickname(c.jid, strconv.FormatInt(id, 10), chat.Title, c.xmpp)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// AddToEditOutbox temporarily store the resource from which a replace message with given ID was sent
|
|
|
|
|
func (c *Client) AddToEditOutbox(xmppId, resource string) {
|
|
|
|
|
c.locks.editOutboxLock.Lock()
|
|
|
|
|
defer c.locks.editOutboxLock.Unlock()
|
|
|
|
|
|
|
|
|
|
c.editOutbox[xmppId] = resource
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) popFromEditOutbox(xmppId string) string {
|
|
|
|
|
c.locks.editOutboxLock.Lock()
|
|
|
|
|
defer c.locks.editOutboxLock.Unlock()
|
|
|
|
|
|
|
|
|
|
resource, ok := c.editOutbox[xmppId]
|
|
|
|
|
if ok {
|
|
|
|
|
delete(c.editOutbox, xmppId)
|
|
|
|
|
} else {
|
|
|
|
|
log.Warnf("No %v xmppId in edit outbox", xmppId)
|
|
|
|
|
}
|
|
|
|
|
return resource
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// AddToOutbox remembers the resource from which a message with given ID was sent
|
|
|
|
|
func (c *Client) AddToOutbox(xmppId, resource string) {
|
|
|
|
|
c.locks.outboxLock.Lock()
|
|
|
|
@ -1411,14 +1522,12 @@ func (c *Client) AddToOutbox(xmppId, resource string) {
|
|
|
|
|
c.outbox[xmppId] = resource
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) popFromOutbox(xmppId string) string {
|
|
|
|
|
func (c *Client) getFromOutbox(xmppId string) string {
|
|
|
|
|
c.locks.outboxLock.Lock()
|
|
|
|
|
defer c.locks.outboxLock.Unlock()
|
|
|
|
|
|
|
|
|
|
resource, ok := c.outbox[xmppId]
|
|
|
|
|
if ok {
|
|
|
|
|
delete(c.outbox, xmppId)
|
|
|
|
|
} else {
|
|
|
|
|
if !ok {
|
|
|
|
|
log.Warnf("No %v xmppId in outbox", xmppId)
|
|
|
|
|
}
|
|
|
|
|
return resource
|
|
|
|
@ -1493,8 +1602,23 @@ func (c *Client) hasLastMessageHashChanged(chatId, messageId int64, content clie
|
|
|
|
|
return !ok || oldHash != newHash
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) getFormatter() func(*client.TextEntity) (*formatter.Insertion, *formatter.Insertion) {
|
|
|
|
|
return formatter.EntityToXEP0393
|
|
|
|
|
func (c *Client) UpdateLastChatMessageId(chatId int64, messageId string) {
|
|
|
|
|
c.locks.lastMsgIdsLock.Lock()
|
|
|
|
|
defer c.locks.lastMsgIdsLock.Unlock()
|
|
|
|
|
|
|
|
|
|
c.lastMsgIds[chatId] = messageId
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) getLastChatMessageId(chatId int64) (string, bool) {
|
|
|
|
|
c.locks.lastMsgIdsLock.RLock()
|
|
|
|
|
defer c.locks.lastMsgIdsLock.RUnlock()
|
|
|
|
|
|
|
|
|
|
xmppId, ok := c.lastMsgIds[chatId]
|
|
|
|
|
return xmppId, ok
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) getFormatter() formatter.MarkupModeType {
|
|
|
|
|
return formatter.MarkupModeXEP0393
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) usernamesToString(usernames []string) string {
|
|
|
|
|