diff --git a/src/androidTest/java/im/conversations/android/xmpp/TransformationTest.java b/src/androidTest/java/im/conversations/android/xmpp/TransformationTest.java index 28da6bc12..f824f7399 100644 --- a/src/androidTest/java/im/conversations/android/xmpp/TransformationTest.java +++ b/src/androidTest/java/im/conversations/android/xmpp/TransformationTest.java @@ -11,6 +11,7 @@ import im.conversations.android.database.ConversationsDatabase; import im.conversations.android.database.entity.AccountEntity; import im.conversations.android.database.model.MessageEmbedded; import im.conversations.android.database.model.Modification; +import im.conversations.android.database.model.PartType; import im.conversations.android.transformer.Transformation; import im.conversations.android.transformer.Transformer; import im.conversations.android.xmpp.model.correction.Replace; @@ -19,6 +20,7 @@ import im.conversations.android.xmpp.model.reactions.Reaction; import im.conversations.android.xmpp.model.reactions.Reactions; import im.conversations.android.xmpp.model.receipts.Received; import im.conversations.android.xmpp.model.reply.Reply; +import im.conversations.android.xmpp.model.retract.Retract; import im.conversations.android.xmpp.model.stanza.Message; import java.time.Instant; import java.util.concurrent.ExecutionException; @@ -430,4 +432,27 @@ public class TransformationTest { Assert.assertEquals(1L, message.states.size()); } + + @Test + public void messageAndRetraction() { + final var m1 = new Message(); + m1.setTo(ACCOUNT); + m1.setFrom(REMOTE.withResource("junit")); + m1.setId("m1"); + m1.addExtension(new Body("It is raining outside")); + + this.transformer.transform(Transformation.of(m1, Instant.now(), REMOTE, null, null)); + + final var m2 = new Message(); + m2.setTo(ACCOUNT); + m2.setFrom(REMOTE.withResource("junit")); + m2.addExtension(new Retract()).setId("m1"); + + this.transformer.transform(Transformation.of(m2, Instant.now(), REMOTE, null, null)); + + final var messages = database.messageDao().getMessages(1L); + final var message = Iterables.getOnlyElement(messages); + Assert.assertEquals(Modification.RETRACTION, message.modification); + Assert.assertEquals(PartType.RETRACTION, Iterables.getOnlyElement(message.contents).type); + } } diff --git a/src/main/java/im/conversations/android/database/model/MessageContent.java b/src/main/java/im/conversations/android/database/model/MessageContent.java index 98ceb3ffc..095f324a7 100644 --- a/src/main/java/im/conversations/android/database/model/MessageContent.java +++ b/src/main/java/im/conversations/android/database/model/MessageContent.java @@ -1,5 +1,8 @@ package im.conversations.android.database.model; +import com.google.common.collect.ImmutableList; +import java.util.List; + public class MessageContent { public final String language; @@ -24,4 +27,7 @@ public class MessageContent { public static MessageContent file(final String url) { return new MessageContent(null, PartType.FILE, null, url); } + + public static final List RETRACTION = + ImmutableList.of(new MessageContent(null, PartType.RETRACTION, null, null)); } diff --git a/src/main/java/im/conversations/android/transformer/Transformation.java b/src/main/java/im/conversations/android/transformer/Transformation.java index 183b08bd6..f99184cdf 100644 --- a/src/main/java/im/conversations/android/transformer/Transformation.java +++ b/src/main/java/im/conversations/android/transformer/Transformation.java @@ -18,6 +18,7 @@ import im.conversations.android.xmpp.model.muc.user.MultiUserChat; import im.conversations.android.xmpp.model.oob.OutOfBandData; import im.conversations.android.xmpp.model.reactions.Reactions; import im.conversations.android.xmpp.model.reply.Reply; +import im.conversations.android.xmpp.model.retract.Retract; import im.conversations.android.xmpp.model.stanza.Message; import java.time.Instant; import java.util.Arrays; @@ -38,7 +39,8 @@ public class Transformation { Displayed.class, Replace.class, Reactions.class, - Reply.class); + Reply.class, + Retract.class); public final Instant receivedAt; public final Jid to; diff --git a/src/main/java/im/conversations/android/transformer/Transformer.java b/src/main/java/im/conversations/android/transformer/Transformer.java index 11190bc59..3660195e2 100644 --- a/src/main/java/im/conversations/android/transformer/Transformer.java +++ b/src/main/java/im/conversations/android/transformer/Transformer.java @@ -21,6 +21,7 @@ import im.conversations.android.xmpp.model.muc.user.MultiUserChat; import im.conversations.android.xmpp.model.oob.OutOfBandData; import im.conversations.android.xmpp.model.reactions.Reactions; import im.conversations.android.xmpp.model.reply.Reply; +import im.conversations.android.xmpp.model.retract.Retract; import im.conversations.android.xmpp.model.stanza.Message; import java.util.Arrays; import java.util.Collection; @@ -74,6 +75,8 @@ public class Transformer { } final Replace messageCorrection = transformation.getExtension(Replace.class); final Reactions reactions = transformation.getExtension(Reactions.class); + final Retract retract = transformation.getExtension(Retract.class); + // TODO we need to remove fallbacks for reactions, retractions and potentially other things final List contents = parseContent(transformation); final boolean identifiableSender = @@ -85,10 +88,21 @@ public class Transformer { && identifiableSender; final boolean isMessageCorrection = Objects.nonNull(messageCorrection) - && messageCorrection.getId() != null + && Objects.nonNull(messageCorrection.getId()) && identifiableSender; - - if (contents.isEmpty()) { + final boolean isRetraction = + Objects.nonNull(retract) && Objects.nonNull(retract.getId()) && identifiableSender; + // TODO in a way it would be more appropriate to move this into the contents.isEmpty block + // but for that to work we would need to properly ignore the fallback body + if (isRetraction) { + final var messageIdentifier = + database.messageDao() + .getOrCreateVersion( + chat, transformation, retract.getId(), Modification.RETRACTION); + database.messageDao() + .insertMessageContent(messageIdentifier.version, MessageContent.RETRACTION); + return true; + } else if (contents.isEmpty()) { LOGGER.info("Received message from {} w/o contents", transformation.from); transformMessageState(chat, transformation); if (isReaction) { diff --git a/src/main/java/im/conversations/android/xmpp/model/Extension.java b/src/main/java/im/conversations/android/xmpp/model/Extension.java index 2739e5a0b..07e7e5cbd 100644 --- a/src/main/java/im/conversations/android/xmpp/model/Extension.java +++ b/src/main/java/im/conversations/android/xmpp/model/Extension.java @@ -1,5 +1,6 @@ package im.conversations.android.xmpp.model; +import com.google.common.base.Preconditions; import eu.siacs.conversations.xml.Element; import im.conversations.android.xmpp.ExtensionFactory; @@ -10,6 +11,11 @@ public class Extension extends Element { } public Extension(final Class clazz) { - this(ExtensionFactory.id(clazz)); + this( + Preconditions.checkNotNull( + ExtensionFactory.id(clazz), + String.format( + "%s does not seem to be annotated with @XmlElement", + clazz.getName()))); } } diff --git a/src/main/java/im/conversations/android/xmpp/model/retract/Retract.java b/src/main/java/im/conversations/android/xmpp/model/retract/Retract.java index 8e7daf24a..0376155c8 100644 --- a/src/main/java/im/conversations/android/xmpp/model/retract/Retract.java +++ b/src/main/java/im/conversations/android/xmpp/model/retract/Retract.java @@ -1,10 +1,20 @@ package im.conversations.android.xmpp.model.retract; +import im.conversations.android.annotation.XmlElement; import im.conversations.android.xmpp.model.Extension; +@XmlElement public class Retract extends Extension { public Retract() { super(Retract.class); } + + public String getId() { + return this.getAttribute("id"); + } + + public void setId(final String id) { + this.setAttribute("id", id); + } } diff --git a/src/main/java/im/conversations/android/xmpp/model/retract/Retracted.java b/src/main/java/im/conversations/android/xmpp/model/retract/Retracted.java index 1df163f7b..4056ae9e2 100644 --- a/src/main/java/im/conversations/android/xmpp/model/retract/Retracted.java +++ b/src/main/java/im/conversations/android/xmpp/model/retract/Retracted.java @@ -1,7 +1,9 @@ package im.conversations.android.xmpp.model.retract; +import im.conversations.android.annotation.XmlElement; import im.conversations.android.xmpp.model.Extension; +@XmlElement public class Retracted extends Extension { public Retracted() {