From ea004b7f7c11fa0ddf560317fd9d6f9b2869144a Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Mon, 29 Jan 2024 04:28:15 -0500 Subject: [PATCH] Reflect Telegram edits natively by nativeedits option --- persistence/sessions.go | 11 +++++++++ telegram/client.go | 3 +++ telegram/commands.go | 14 ++++++++++- telegram/handlers.go | 51 +++++++++++++++++++++++++++++++++-------- telegram/utils.go | 29 +++++++++++++++++++---- xmpp/gateway/gateway.go | 19 ++++++++------- xmpp/handlers.go | 1 + 7 files changed, 104 insertions(+), 24 deletions(-) diff --git a/persistence/sessions.go b/persistence/sessions.go index 56ff152..29c4918 100644 --- a/persistence/sessions.go +++ b/persistence/sessions.go @@ -43,6 +43,7 @@ type Session struct { Carbons bool `yaml:":carbons"` HideIds bool `yaml:":hideids"` Receipts bool `yaml:":receipts"` + NativeEdits bool `yaml:":nativeedits"` } var configKeys = []string{ @@ -54,6 +55,7 @@ var configKeys = []string{ "carbons", "hideids", "receipts", + "nativeedits", } var sessionDB *SessionsYamlDB @@ -134,6 +136,8 @@ func (s *Session) Get(key string) (string, error) { return fromBool(s.HideIds), nil case "receipts": return fromBool(s.Receipts), nil + case "nativeedits": + return fromBool(s.NativeEdits), nil } return "", errors.New("Unknown session property") @@ -205,6 +209,13 @@ func (s *Session) Set(key string, value string) (string, error) { } s.Receipts = b return value, nil + case "nativeedits": + b, err := toBool(value) + if err != nil { + return "", err + } + s.NativeEdits = b + return value, nil } return "", errors.New("Unknown session property") diff --git a/telegram/client.go b/telegram/client.go index 38dff4c..79f27d5 100644 --- a/telegram/client.go +++ b/telegram/client.go @@ -45,6 +45,7 @@ type Client struct { DelayedStatusesLock sync.Mutex lastMsgHashes map[int64]uint64 + lastMsgIds map[int64]string msgHashSeed maphash.Seed locks clientLocks @@ -58,6 +59,7 @@ type clientLocks struct { outboxLock sync.Mutex editOutboxLock sync.Mutex lastMsgHashesLock sync.Mutex + lastMsgIdsLock sync.RWMutex authorizerReadLock sync.Mutex authorizerWriteLock sync.Mutex @@ -119,6 +121,7 @@ func NewClient(conf config.TelegramConfig, jid string, component *xmpp.Component options: options, DelayedStatuses: make(map[int64]*DelayedStatus), lastMsgHashes: make(map[int64]uint64), + lastMsgIds: make(map[int64]string), msgHashSeed: maphash.MakeSeed(), locks: clientLocks{ chatMessageLocks: make(map[int64]*sync.Mutex), diff --git a/telegram/commands.go b/telegram/commands.go index b5c856e..9251ebb 100644 --- a/telegram/commands.go +++ b/telegram/commands.go @@ -201,6 +201,7 @@ func (c *Client) sendMessagesReverse(chatID int64, messages []*client.Message) { strconv.FormatInt(message.Id, 10), c.xmpp, reply, + "", false, false, ) @@ -380,9 +381,20 @@ func (c *Client) ProcessTransportCommand(cmdline string, resource string) string } case "config": if len(args) > 1 { + var msg string if gateway.MessageOutgoingPermissionVersion == 0 && args[0] == "carbons" && args[1] == "true" { return "The server did not allow to enable carbons" } + if !c.Session.RawMessages && args[0] == "nativeedits" && args[1] == "true" { + return "nativeedits only works with rawmessages as of yet, enable it first" + } + if c.Session.NativeEdits && args[0] == "rawmessages" && args[1] == "false" { + _, err := c.Session.Set("nativeedits", "false") + if err != nil { + return err.Error() + } + msg = "Automatically disabling nativeedits too...\n" + } value, err := c.Session.Set(args[0], args[1]) if err != nil { @@ -390,7 +402,7 @@ func (c *Client) ProcessTransportCommand(cmdline string, resource string) string } gateway.DirtySessions = true - return fmt.Sprintf("%s set to %s", args[0], value) + return fmt.Sprintf("%s%s set to %s", msg, args[0], value) } else if len(args) > 0 { value, err := c.Session.Get(args[0]) if err != nil { diff --git a/telegram/handlers.go b/telegram/handlers.go index c715932..dfdd3d5 100644 --- a/telegram/handlers.go +++ b/telegram/handlers.go @@ -269,9 +269,9 @@ func (c *Client) updateMessageContent(update *client.UpdateMessageContent) { c.SendMessageLock.Lock() c.SendMessageLock.Unlock() - xmppId, err := gateway.IdsDB.GetByTgIds(c.Session.Login, c.jid, update.ChatId, update.MessageId) + xmppId, xmppIdErr := gateway.IdsDB.GetByTgIds(c.Session.Login, c.jid, update.ChatId, update.MessageId) var ignoredResource string - if err == nil { + if xmppIdErr == nil { ignoredResource = c.popFromEditOutbox(xmppId) } else { log.Infof("Couldn't retrieve XMPP message ids for %v, an echo may happen", update.MessageId) @@ -286,19 +286,50 @@ func (c *Client) updateMessageContent(update *client.UpdateMessageContent) { if update.NewContent.MessageContentType() == client.TypeMessageText && c.hasLastMessageHashChanged(update.ChatId, update.MessageId, update.NewContent) { textContent := update.NewContent.(*client.MessageText) - var editChar string - if c.Session.AsciiArrows { - editChar = "e " - } else { - editChar = "✎ " + var replaceId string + sId := strconv.FormatInt(update.MessageId, 10) + var isCarbon bool + + // use XEP-0308 edits only if the last message is edited for sure, fallback otherwise + if c.Session.NativeEdits { + lastXmppId, ok := c.getLastChatMessageId(update.ChatId) + if xmppIdErr != nil { + xmppId = sId + } + if ok && lastXmppId == xmppId { + replaceId = xmppId + message, err := c.client.GetMessage(&client.GetMessageRequest{ + ChatId: update.ChatId, + MessageId: update.MessageId, + }) + if err == nil { + isCarbon = c.isCarbonsEnabled() && message.IsOutgoing + } else { + log.Errorf("No message %v/%v found, cannot reliably determine if it's a carbon", update.ChatId, update.MessageId) + } + } else { + log.Infof("Mismatching message ids: %v %v, falling back to separate edit message", lastXmppId, xmppId) + } } - text := editChar + fmt.Sprintf("%v | %s", update.MessageId, formatter.Format( + + text := formatter.Format( textContent.Text.Text, textContent.Text.Entities, markupFunction, - )) + ) + + if replaceId == "" { + var editChar string + if c.Session.AsciiArrows { + editChar = "e " + } else { + editChar = "✎ " + } + text = editChar + fmt.Sprintf("%v | %s", update.MessageId, text) + } + for _, jid := range jids { - gateway.SendMessage(jid, strconv.FormatInt(update.ChatId, 10), text, "e"+strconv.FormatInt(update.MessageId, 10), c.xmpp, nil, false, false) + gateway.SendMessage(jid, strconv.FormatInt(update.ChatId, 10), text, "e"+sId, c.xmpp, nil, replaceId, isCarbon, false) } } } diff --git a/telegram/utils.go b/telegram/utils.go index 966c5c2..2082645 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -911,14 +911,17 @@ func (c *Client) countCharsInLines(lines *[]string) (count int) { return } +func (c *Client) isCarbonsEnabled() bool { + return gateway.MessageOutgoingPermissionVersion > 0 && c.Session.Carbons +} + func (c *Client) messageToPrefix(message *client.Message, previewString string, fileString string) (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) prefix := []string{} // message direction @@ -1007,7 +1010,7 @@ func (c *Client) ensureDownloadFile(file *client.File) *client.File { // 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 @@ -1083,11 +1086,12 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) { sChatId := strconv.FormatInt(chatId, 10) for _, jid := range jids { - gateway.SendMessageWithOOB(jid, sChatId, text, sId, c.xmpp, reply, oob, isCarbon, c.Session.Receipts) + 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, c.Session.Receipts) + gateway.SendMessage(jid, sChatId, auxText, sId, c.xmpp, reply, "", isCarbon, c.Session.Receipts) } } + c.UpdateLastChatMessageId(chatId, sId) } // MarkAsRead marks a message as read @@ -1588,6 +1592,21 @@ func (c *Client) hasLastMessageHashChanged(chatId, messageId int64, content clie return !ok || oldHash != newHash } +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 } diff --git a/xmpp/gateway/gateway.go b/xmpp/gateway/gateway.go index b1bcd69..736f760 100644 --- a/xmpp/gateway/gateway.go +++ b/xmpp/gateway/gateway.go @@ -54,23 +54,23 @@ var DirtySessions = false var MessageOutgoingPermissionVersion = 0 // SendMessage creates and sends a message stanza -func SendMessage(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, isCarbon, requestReceipt bool) { - sendMessageWrapper(to, from, body, id, component, reply, nil, "", isCarbon, requestReceipt) +func SendMessage(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, replaceId string, isCarbon, requestReceipt bool) { + sendMessageWrapper(to, from, body, id, component, reply, nil, "", replaceId, isCarbon, requestReceipt) } // SendServiceMessage creates and sends a simple message stanza from transport func SendServiceMessage(to string, body string, component *xmpp.Component) { - sendMessageWrapper(to, "", body, "", component, nil, nil, "", false, false) + sendMessageWrapper(to, "", body, "", component, nil, nil, "", "", false, false) } // SendTextMessage creates and sends a simple message stanza func SendTextMessage(to string, from string, body string, component *xmpp.Component) { - sendMessageWrapper(to, from, body, "", component, nil, nil, "", false, false) + sendMessageWrapper(to, from, body, "", component, nil, nil, "", "", false, false) } // SendMessageWithOOB creates and sends a message stanza with OOB URL -func SendMessageWithOOB(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, oob string, isCarbon, requestReceipt bool) { - sendMessageWrapper(to, from, body, id, component, reply, nil, oob, isCarbon, requestReceipt) +func SendMessageWithOOB(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, oob, replaceId string, isCarbon, requestReceipt bool) { + sendMessageWrapper(to, from, body, id, component, reply, nil, oob, replaceId, isCarbon, requestReceipt) } // SendMessageMarker creates and sends a message stanza with a XEP-0333 marker @@ -78,10 +78,10 @@ func SendMessageMarker(to string, from string, component *xmpp.Component, marker sendMessageWrapper(to, from, "", "", component, nil, &marker{ Type: markerType, Id: markerId, - }, "", false, false) + }, "", "", false, false) } -func sendMessageWrapper(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, marker *marker, oob string, isCarbon, requestReceipt bool) { +func sendMessageWrapper(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, marker *marker, oob, replaceId string, isCarbon, requestReceipt bool) { toJid, err := stanza.NewJid(to) if err != nil { log.WithFields(log.Fields{ @@ -153,6 +153,9 @@ func sendMessageWrapper(to string, from string, body string, id string, componen if requestReceipt { message.Extensions = append(message.Extensions, stanza.Markable{}) } + if replaceId != "" { + message.Extensions = append(message.Extensions, extensions.Replace{Id: replaceId}) + } if isCarbon { carbonMessage := extensions.ClientMessage{ diff --git a/xmpp/handlers.go b/xmpp/handlers.go index 9caf886..8c6ba37 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -204,6 +204,7 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) { err = gateway.IdsDB.Set(session.Session.Login, bare, toID, tgMessageId, msg.Id) if err == nil { session.AddToOutbox(msg.Id, resource) + session.UpdateLastChatMessageId(toID, msg.Id) } else { log.Errorf("Failed to save ids %v/%v %v", toID, tgMessageId, msg.Id) }