diff --git a/cheogram.doap b/cheogram.doap
index b1d82524d56649e8550dbdf2cdd4261105997dcf..a410ac780f657a1be87494ef3b5a368110bbdd28 100644
--- a/cheogram.doap
+++ b/cheogram.doap
@@ -60,6 +60,13 @@
+
+
+
+ complete
+ 0.2.1
+
+
diff --git a/src/cheogram/res/values/strings.xml b/src/cheogram/res/values/strings.xml
index 3ea8c47d153ddcf67d8bc2204ab142fc1e442fa7..b5836c46ec07f29f862c3b873adb0beb89230563 100644
--- a/src/cheogram/res/values/strings.xml
+++ b/src/cheogram/res/values/strings.xml
@@ -31,4 +31,7 @@
Incoming calls from phone numbers may ring with your system dialler instead of this app\'s notification settings
Save as Sticker
Sticker Name
+ Moderate
+ Moderation Resaon
+ Unable to Moderate
diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java
index 37bfb1529fbf56c91c9d29cdef4775de10490048..d3244ae612a10b423265f0b119ba41c4ba1ff88a 100644
--- a/src/main/java/eu/siacs/conversations/entities/Conversation.java
+++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java
@@ -478,7 +478,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
return null;
}
- public Message findMessageWithRemoteIdAndCounterpart(String id, Jid counterpart, boolean received, boolean carbon) {
+ public Message findMessageWithRemoteIdAndCounterpart(String id, Jid counterpart) {
synchronized (this.messages) {
for (int i = this.messages.size() - 1; i >= 0; --i) {
final Message message = messages.get(i);
@@ -486,14 +486,9 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
if (mcp == null) {
continue;
}
- if (mcp.equals(counterpart) && ((message.getStatus() == Message.STATUS_RECEIVED) == received)
- && (carbon == message.isCarbon() || received)) {
- final boolean idMatch = id.equals(message.getRemoteMsgId()) || message.remoteMsgIdMatchInEdit(id);
- if (idMatch && !message.isFileOrImage() && !message.treatAsDownloadable()) {
- return message;
- } else {
- return null;
- }
+ 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 (idMatch) return message;
}
}
}
diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java
index 62e2001c9a059e38ab1516562e644a507737c756..8b940a440ec97e39060268e3909f6ab26d05fe8f 100644
--- a/src/main/java/eu/siacs/conversations/entities/Message.java
+++ b/src/main/java/eu/siacs/conversations/entities/Message.java
@@ -981,6 +981,10 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
}
}
+ public void clearPayloads() {
+ this.payloads.clear();
+ }
+
public void addPayload(Element el) {
if (el == null) return;
@@ -1080,7 +1084,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
}
public synchronized void setFileParams(FileParams fileParams) {
- if (this.fileParams != null && this.fileParams.sims != null && fileParams.sims == null) {
+ if (fileParams != null && this.fileParams != null && this.fileParams.sims != null && fileParams.sims == null) {
fileParams.sims = this.fileParams.sims;
}
this.fileParams = fileParams;
diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
index ee8f4adb12c3d2f0de0ccb808e3b0809f80fa10c..bd4cd01750a8084779316f639d763f3f9fde0e75 100644
--- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
+++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
@@ -36,6 +36,7 @@ import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Bookmark;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.DownloadableFile;
+import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.services.MessageArchiveService;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xml.Element;
@@ -410,6 +411,19 @@ public class IqGenerator extends AbstractGenerator {
return packet;
}
+ public IqPacket moderateMessage(Account account, Message m, String reason) {
+ IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
+ packet.setTo(m.getConversation().getJid().asBareJid());
+ packet.setFrom(account.getJid());
+ Element moderate =
+ packet.addChild("apply-to", "urn:xmpp:fasten:0")
+ .setAttribute("id", m.getServerMsgId())
+ .addChild("moderate", "urn:xmpp:message-moderate:0");
+ moderate.addChild("retract", "urn:xmpp:message-retract:0");
+ moderate.addChild("reason", "urn:xmpp:message-moderate:0").setContent(reason);
+ return packet;
+ }
+
public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file, String mime) {
IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
packet.setTo(host);
diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java
index 68fc3e714be4528c692f13304157767132e0ed9a..961e016f96fdae6ce27f23a21b03a681be90be1a 100644
--- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java
@@ -429,11 +429,13 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
}
}
String replacementId = replaceElement == null ? null : replaceElement.getAttribute("id");
+ boolean replaceAsRetraction = false;
if (replacementId == null) {
Element fasten = packet.findChild("apply-to", "urn:xmpp:fasten:0");
- if (fasten != null && (fasten.findChild("retract", "urn:xmpp:message-retract:0") != null || fasten.findChild("urn:xmpp:message-moderate:0") != null)) {
+ if (fasten != null && (fasten.findChild("retract", "urn:xmpp:message-retract:0") != null || fasten.findChild("moderated", "urn:xmpp:message-moderate:0") != null)) {
replacementId = fasten.getAttribute("id");
packet.setBody("");
+ replaceAsRetraction = true;
}
}
final LocalizedContent body = packet.getBody();
@@ -671,10 +673,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
}
if (replacementId != null && mXmppConnectionService.allowMessageCorrection()) {
- final Message replacedMessage = conversation.findMessageWithRemoteIdAndCounterpart(replacementId,
- counterpart,
- message.getStatus() == Message.STATUS_RECEIVED,
- message.isCarbon());
+ final Message replacedMessage = conversation.findMessageWithRemoteIdAndCounterpart(replacementId, counterpart);
if (replacedMessage != null) {
final boolean fingerprintsMatch = replacedMessage.getFingerprint() == null
|| replacedMessage.getFingerprint().equals(message.getFingerprint());
@@ -683,7 +682,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
&& replacedMessage.getTrueCounterpart().asBareJid().equals(message.getTrueCounterpart().asBareJid());
final boolean mucUserMatches = query == null && replacedMessage.sameMucUser(message); //can not be checked when using mam
final boolean duplicate = conversation.hasDuplicateMessage(message);
- if (fingerprintsMatch && (trueCountersMatch || !conversationMultiMode || mucUserMatches) && !duplicate) {
+ if (fingerprintsMatch && (trueCountersMatch || !conversationMultiMode || mucUserMatches || counterpart.isBareJid()) && !duplicate) {
Log.d(Config.LOGTAG, "replaced message '" + replacedMessage.getBody() + "' with '" + message.getBody() + "'");
synchronized (replacedMessage) {
final String uuid = replacedMessage.getUuid();
@@ -691,6 +690,13 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
replacedMessage.setBody(message.getBody());
replacedMessage.putEdited(replacedMessage.getRemoteMsgId(), replacedMessage.getServerMsgId());
replacedMessage.setRemoteMsgId(remoteMsgId);
+ if (replaceAsRetraction) {
+ mXmppConnectionService.getFileBackend().deleteFile(replacedMessage);
+ mXmppConnectionService.evictPreview(message.getUuid());
+ replacedMessage.clearPayloads();
+ replacedMessage.setFileParams(null);
+ replacedMessage.setDeleted(true);
+ }
if (replacedMessage.getServerMsgId() == null || message.getServerMsgId() != null) {
replacedMessage.setServerMsgId(message.getServerMsgId());
}
diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
index 028b6b8168d0f14147fb56cb6e8ab4de2cd0eb90..a5f827b1b1120dccf381c55edd65562431b38dd3 100644
--- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
@@ -3555,6 +3555,18 @@ public class XmppConnectionService extends Service {
});
}
+ public void moderateMessage(final Account account, final Message m, final String reason) {
+ IqPacket request = this.mIqGenerator.moderateMessage(account, m, reason);
+ Log.d(Config.LOGTAG, "moderate: " + request);
+ sendIqPacket(account, request, (a, packet) -> {
+ Log.d(Config.LOGTAG, "moderate1: " + packet);
+ if (packet.getType() != IqPacket.TYPE.RESULT) {
+ showErrorToastInUi(R.string.unable_to_moderate);
+ Log.d(Config.LOGTAG, a.getJid().asBareJid() + " unable to moderate: " + packet);
+ }
+ });
+ }
+
public void destroyRoom(final Conversation conversation, final OnRoomDestroy callback) {
IqPacket request = new IqPacket(IqPacket.TYPE.SET);
request.setTo(conversation.getJid().asBareJid());
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
index 50721d9dfeff1defbae6dba280f6b20445ab97c2..30f43b3d4bee3758dfb952872dd09bf0e4e01a30 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
@@ -1002,7 +1002,7 @@ public class ConversationFragment extends XmppFragment
final String existingName = savingAsStickerName;
savingAsSticker = null;
savingAsStickerName = null;
- activity.quickEdit(existingName, R.string.sticker_name, (name) -> {
+ activity.quickEdit(existingName, (name) -> {
try {
activity.xmppConnectionService.getFileBackend().copyFileToDocumentFile(activity, f, df, name);
} catch (final FileBackend.FileCopyException e) {
@@ -1012,7 +1012,7 @@ public class ConversationFragment extends XmppFragment
Toast.makeText(activity, "Sticker saved", Toast.LENGTH_SHORT).show();
return null;
- });
+ }, R.string.sticker_name, false, false, true);
break;
case REQUEST_TRUST_KEYS_TEXT:
sendMessage();
@@ -1431,6 +1431,7 @@ public class ConversationFragment extends XmppFragment
MenuItem retryDecryption = menu.findItem(R.id.retry_decryption);
MenuItem correctMessage = menu.findItem(R.id.correct_message);
MenuItem retractMessage = menu.findItem(R.id.retract_message);
+ MenuItem moderateMessage = menu.findItem(R.id.moderate_message);
MenuItem onlyThisThread = menu.findItem(R.id.only_this_thread);
MenuItem shareWith = menu.findItem(R.id.share_with);
MenuItem sendAgain = menu.findItem(R.id.send_again);
@@ -1461,6 +1462,9 @@ public class ConversationFragment extends XmppFragment
correctMessage.setVisible(true);
if (!relevantForCorrection.getBody().equals("") && !relevantForCorrection.getBody().equals(" ")) retractMessage.setVisible(true);
}
+ if (conversation.getMode() == Conversation.MODE_MULTI && conversation.getMucOptions().getSelf().getRole().ranks(MucOptions.Role.MODERATOR) && conversation.getMucOptions().hasFeature("urn:xmpp:message-moderate:0")) {
+ moderateMessage.setVisible(true);
+ }
if ((m.isFileOrImage() && !deleted && !receiving)
|| (m.getType() == Message.TYPE_TEXT && !m.treatAsDownloadable())
&& !unInitiatedButKnownSize
@@ -1543,6 +1547,12 @@ public class ConversationFragment extends XmppFragment
})
.setNegativeButton(R.string.no, null).show();
return true;
+ case R.id.moderate_message:
+ activity.quickEdit("Spam", (reason) -> {
+ activity.xmppConnectionService.moderateMessage(conversation.getAccount(), selectedMessage, reason);
+ return null;
+ }, R.string.moderate_reason, false, false, true);
+ return true;
case R.id.copy_message:
ShareUtil.copyToClipboard(activity, selectedMessage);
return true;
diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
index fb81d1759e67c9ddc43cb101215a40e7ed9bc7cc..e99408e2507f348f331d9e45717d77c9c2d46d4b 100644
--- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
@@ -722,12 +722,17 @@ public abstract class XmppActivity extends ActionBarActivity {
quickEdit(previousValue, callback, R.string.password, true, false);
}
+ protected void quickEdit(final String previousValue, final OnValueEdited callback, final @StringRes int hint, boolean password, boolean permitEmpty) {
+ quickEdit(previousValue, callback, hint, password, permitEmpty, false);
+ }
+
@SuppressLint("InflateParams")
- private void quickEdit(final String previousValue,
+ protected void quickEdit(final String previousValue,
final OnValueEdited callback,
final @StringRes int hint,
boolean password,
- boolean permitEmpty) {
+ boolean permitEmpty,
+ boolean alwaysCallback) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
DialogQuickeditBinding binding = DataBindingUtil.inflate(getLayoutInflater(), R.layout.dialog_quickedit, null, false);
if (password) {
@@ -748,7 +753,7 @@ public abstract class XmppActivity extends ActionBarActivity {
dialog.show();
View.OnClickListener clickListener = v -> {
String value = binding.inputEditText.getText().toString();
- if (!value.equals(previousValue) && (!value.trim().isEmpty() || permitEmpty)) {
+ if ((alwaysCallback || !value.equals(previousValue)) && (!value.trim().isEmpty() || permitEmpty)) {
String error = callback.onValueEdited(value);
if (error != null) {
binding.inputLayout.setError(error);
diff --git a/src/main/res/menu/message_context.xml b/src/main/res/menu/message_context.xml
index 693674571af7e1dc7464f6936bc3011c9039410e..b845c444f7c929cd0adaf1d3c10a76288083fe4f 100644
--- a/src/main/res/menu/message_context.xml
+++ b/src/main/res/menu/message_context.xml
@@ -38,6 +38,10 @@
android:id="@+id/retract_message"
android:title="@string/retract_message"
android:visible="false" />
+