Simulate incoming message/edit for incoming reactions

Stephen Paul Weber created

Allow retracting older reaction messages, do not allow editing any of them

Change summary

src/main/java/eu/siacs/conversations/entities/Conversation.java   | 25 
src/main/java/eu/siacs/conversations/entities/Message.java        |  9 
src/main/java/eu/siacs/conversations/parser/MessageParser.java    | 27 
src/main/java/eu/siacs/conversations/ui/ConversationFragment.java | 11 
src/main/java/eu/siacs/conversations/xml/LocalizedContent.java    |  2 
5 files changed, 61 insertions(+), 13 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/entities/Conversation.java 🔗

@@ -486,11 +486,11 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
             for (int i = this.messages.size() - 1; i >= 0; --i) {
                 final Message message = messages.get(i);
                 final Jid mcp = message.getCounterpart();
-                if (mcp == null) {
+                if (mcp == null && counterpart != null) {
                     continue;
                 }
-                if (mcp.equals(counterpart) || mcp.asBareJid().equals(counterpart)) {
-                    final boolean idMatch = id.equals(message.getRemoteMsgId()) || message.remoteMsgIdMatchInEdit(id) || (getMode() == MODE_MULTI && id.equals(message.getServerMsgId()));
+                if (counterpart == null || mcp.equals(counterpart) || mcp.asBareJid().equals(counterpart)) {
+                    final boolean idMatch = id.equals(message.getUuid()) || id.equals(message.getRemoteMsgId()) || message.remoteMsgIdMatchInEdit(id) || (getMode() == MODE_MULTI && id.equals(message.getServerMsgId()));
                     if (idMatch) return message;
                 }
             }
@@ -543,19 +543,26 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
         return false;
     }
 
-    public Set<String> findOwnReactionsTo(String id) {
-        Set<String> reactionEmoji = new HashSet<>();
-        Element reactions = null;
+    public Message findMessageReactingTo(String id, Jid reactor) {
         synchronized (this.messages) {
-            for (Message message : this.messages) {
-                if (message.getStatus() < Message.STATUS_SEND) continue;
+            for (int i = this.messages.size() - 1; i >= 0; --i) {
+                final Message message = messages.get(i);
+                if (reactor == null && message.getStatus() < Message.STATUS_SEND) continue;
+                if (reactor != null && !(message.getCounterpart().equals(reactor) || message.getCounterpart().asBareJid().equals(reactor))) continue;
 
                 final Element r = message.getReactions();
                 if (r != null && r.getAttribute("id") != null && id.equals(r.getAttribute("id"))) {
-                    reactions = r;
+                    return message;
                 }
             }
         }
+        return null;
+    }
+
+    public Set<String> findReactionsTo(String id, Jid reactor) {
+        Set<String> reactionEmoji = new HashSet<>();
+        Message reactM = findMessageReactingTo(id, reactor);
+        Element reactions = reactM == null ? null : reactM.getReactions();
         if (reactions != null) {
             for (Element el : reactions.getChildren()) {
                 if (el.getName().equals("reaction") && el.getNamespace().equals("urn:xmpp:reactions:0")) {

src/main/java/eu/siacs/conversations/entities/Message.java 🔗

@@ -403,7 +403,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
 
     public Message react(String emoji) {
         Set<String> emojis = new HashSet<>();
-        if (conversation instanceof Conversation) emojis = ((Conversation) conversation).findOwnReactionsTo(replyId());
+        if (conversation instanceof Conversation) emojis = ((Conversation) conversation).findReactionsTo(replyId(), null);
         emojis.add(emoji);
         final Message m = reply();
         m.appendBody(emoji);
@@ -418,6 +418,13 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
         return m;
     }
 
+    public void setReactions(Element reactions) {
+        if (this.payloads != null) {
+            this.payloads.remove(getReactions());
+        }
+        addPayload(reactions);
+    }
+
     public Element getReactions() {
         if (this.payloads == null) return null;
 

src/main/java/eu/siacs/conversations/parser/MessageParser.java 🔗

@@ -441,7 +441,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
                 }
             }
         }
-        final LocalizedContent body = packet.getBody();
+        LocalizedContent body = packet.getBody();
 
         final Element axolotlEncrypted = packet.findChildEnsureSingle(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
         int status;
@@ -500,6 +500,28 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
             }
         }
 
+        final Element reactions = packet.findChild("reactions", "urn:xmpp:reactions:0");
+        if (body == null && html == null) {
+            if (reactions != null && reactions.getAttribute("id") != null) {
+                final Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid());
+                if (conversation != null) {
+                    final Message reactionTo = conversation.findMessageWithRemoteIdAndCounterpart(reactions.getAttribute("id"), null);
+                    if (reactionTo != null) {
+                        String bodyS = reactionTo.reply().getBody();
+                        for (Element el : reactions.getChildren()) {
+                            if (el.getName().equals("reaction") && el.getNamespace().equals("urn:xmpp:reactions:0")) {
+                                bodyS += el.getContent();
+                            }
+                        }
+                        body = new LocalizedContent(bodyS, "en", 1);
+                        final Message previousReaction = conversation.findMessageReactingTo(reactions.getAttribute("id"), counterpart);
+Log.d("WUT", "" + previousReaction + "    " + counterpart);
+                        if (previousReaction != null) replacementId = previousReaction.replyId();
+                    }
+                }
+            }
+        }
+
         if ((body != null || pgpEncrypted != null || (axolotlEncrypted != null && axolotlEncrypted.hasChild("payload")) || !attachments.isEmpty() || html != null) && !isMucStatusMessage) {
             final boolean conversationIsProbablyMuc = isTypeGroupChat || mucUserElement != null || account.getXmppConnection().getMucServersWithholdAccount().contains(counterpart.getDomain().toEscapedString());
             final Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, counterpart.asBareJid(), conversationIsProbablyMuc, false, query, false);
@@ -637,6 +659,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
                 }
             }
             message.markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
+            if (reactions != null) message.addPayload(reactions);
             for (Element el : packet.getChildren()) {
                 if ((el.getName().equals("query") && el.getNamespace().equals("http://jabber.org/protocol/disco#items") && el.getAttribute("node").equals("http://jabber.org/protocol/commands")) ||
                     (el.getName().equals("fallback") && el.getNamespace().equals("urn:xmpp:fallback:0"))) {
@@ -693,7 +716,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
                             replacedMessage.setBody(message.getBody());
                             replacedMessage.putEdited(replacedMessage.getRemoteMsgId(), replacedMessage.getServerMsgId());
                             replacedMessage.setRemoteMsgId(remoteMsgId);
-                            if (!replaceElement.getName().equals("replace")) {
+                            if (replaceElement != null && !replaceElement.getName().equals("replace")) {
                                 mXmppConnectionService.getFileBackend().deleteFile(replacedMessage);
                                 mXmppConnectionService.evictPreview(message.getUuid());
                                 replacedMessage.clearPayloads();

src/main/java/eu/siacs/conversations/ui/ConversationFragment.java 🔗

@@ -1490,6 +1490,10 @@ public class ConversationFragment extends XmppFragment
                 correctMessage.setVisible(true);
                 if (!relevantForCorrection.getBody().equals("") && !relevantForCorrection.getBody().equals(" ")) retractMessage.setVisible(true);
             }
+            if (relevantForCorrection.getReactions() != null) {
+                correctMessage.setVisible(false);
+                retractMessage.setVisible(true);
+            }
             if (conversation.getMode() == Conversation.MODE_MULTI && m.getServerMsgId() != null && m.getModerated() == null && conversation.getMucOptions().getSelf().getRole().ranks(MucOptions.Role.MODERATOR) && conversation.getMucOptions().hasFeature("urn:xmpp:message-moderate:0")) {
                 moderateMessage.setVisible(true);
             }
@@ -1575,11 +1579,18 @@ public class ConversationFragment extends XmppFragment
                         }
                         Element reactions = message.getReactions();
                         if (reactions != null) {
+                            final Message previousReaction = conversation.findMessageReactingTo(reactions.getAttribute("id"), null);
+                            if (previousReaction != null) reactions = previousReaction.getReactions();
                             for (Element el : reactions.getChildren()) {
                                 if (message.getQuoteableBody().endsWith(el.getContent())) {
                                     reactions.removeChild(el);
                                 }
                             }
+                            message.setReactions(reactions);
+                            if (previousReaction != null) {
+                                previousReaction.setReactions(reactions);
+                                activity.xmppConnectionService.updateMessage(previousReaction);
+                            }
                         }
                         message.setBody(" ");
                         message.putEdited(message.getUuid(), message.getServerMsgId());

src/main/java/eu/siacs/conversations/xml/LocalizedContent.java 🔗

@@ -14,7 +14,7 @@ public class LocalizedContent {
     public final String language;
     public final int count;
 
-    private LocalizedContent(String content, String language, int count) {
+    public LocalizedContent(String content, String language, int count) {
         this.content = content;
         this.language = language;
         this.count = count;