Snackbar to deal with spam MUC invites

Stephen Paul Weber created

Change summary

src/cheogram/res/menu/block_muc.xml                               | 12 
src/cheogram/res/values/strings.xml                               |  3 
src/main/java/eu/siacs/conversations/entities/Conversation.java   |  7 
src/main/java/eu/siacs/conversations/parser/MessageParser.java    |  1 
src/main/java/eu/siacs/conversations/ui/ConversationFragment.java | 43 +
5 files changed, 66 insertions(+)

Detailed changes

src/cheogram/res/menu/block_muc.xml 🔗

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:id="@+id/reject"
+        android:title="@string/delete_and_close"/>
+    <item
+        android:id="@+id/block_contact"
+        android:title="@string/block_inviter" />
+    <item
+        android:id="@+id/add_bookmark"
+        android:title="@string/add_bookmark" />
+</menu>

src/cheogram/res/values/strings.xml 🔗

@@ -45,4 +45,7 @@
     <string name="pref_message_autocomplete_summary">For example, complete emoji starting with :</string>
     <string name="notify_only_when_highlighted_or_replied">Notify for mentions and replies</string>
     <string name="moderate_recent">Moderate messages?</string>
+    <string name="block_inviter">Block inviter</string>
+    <string name="add_bookmark">Add Chat</string>
+    <string name="received_invite_from_stranger">Received invite from stranger</string>
 </resources>

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

@@ -1365,6 +1365,13 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
                 && sentMessagesCount() == 0;
     }
 
+    public boolean strangerInvited() {
+        final var inviterS = getAttribute("inviter");
+        if (inviterS == null) return false;
+        final var inviter = account.getRoster().getContact(Jid.of(inviterS));
+        return getBookmark() == null && !inviter.showInContactList() && !inviter.isSelf() && sentMessagesCount() == 0;
+    }
+
     public int getReceivedMessagesCountSinceUuid(String uuid) {
         if (uuid == null) {
             return 0;

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

@@ -1348,6 +1348,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
                 return false;
             }
             final Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, jid, true, false);
+            conversation.setAttribute("inviter", inviter.toEscapedString());
             if (conversation.getMucOptions().online()) {
                 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received invite to " + jid + " but muc is considered to be online");
                 mXmppConnectionService.mucSelfPingAndRejoin(conversation);

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

@@ -3508,6 +3508,41 @@ public class ConversationFragment extends XmppFragment
         return true;
     }
 
+    private boolean showBlockMucSubmenu(View view) {
+        final var jid = conversation.getJid();
+        final var popupMenu = new PopupMenu(getActivity(), view);
+        popupMenu.inflate(R.menu.block_muc);
+        popupMenu.getMenu().findItem(R.id.block_contact).setVisible(jid.getLocal() != null);
+        popupMenu.setOnMenuItemClickListener(
+                menuItem -> {
+                    Blockable blockable;
+                    switch (menuItem.getItemId()) {
+                        case R.id.reject:
+                            activity.xmppConnectionService.clearConversationHistory(conversation);
+                            activity.xmppConnectionService.archiveConversation(conversation);
+                            return true;
+                        case R.id.add_bookmark:
+                            activity.xmppConnectionService.saveConversationAsBookmark(conversation, "");
+                            updateSnackBar(conversation);
+                            return true;
+                        case R.id.block_contact:
+                            blockable =
+                                    conversation
+                                            .getAccount()
+                                            .getRoster()
+                                            .getContact(Jid.of(conversation.getAttribute("inviter")));
+                            break;
+                        default:
+                            blockable = conversation;
+                    }
+                    BlockContactDialog.show(activity, blockable);
+                    activity.xmppConnectionService.archiveConversation(conversation);
+                    return true;
+                });
+        popupMenu.show();
+        return true;
+    }
+
     private void updateSnackBar(final Conversation conversation) {
         final Account account = conversation.getAccount();
         final XmppConnection connection = account.getXmppConnection();
@@ -3609,6 +3644,14 @@ public class ConversationFragment extends XmppFragment
             }
         } else if (account.hasPendingPgpIntent(conversation)) {
             showSnackbar(R.string.openpgp_messages_found, R.string.decrypt, clickToDecryptListener);
+        } else if (connection != null
+                && connection.getFeatures().blocking()
+                && conversation.strangerInvited()) {
+            showSnackbar(
+                    R.string.received_invite_from_stranger,
+                    R.string.options,
+                    (v) -> showBlockMucSubmenu(v),
+                    (v) -> showBlockMucSubmenu(v));
         } else if (connection != null
                 && connection.getFeatures().blocking()
                 && conversation.countMessages() != 0