Register with MUC when joining, deregister when leaving

Stephen Paul Weber created

Where allowed, if not allowed just do nothing.
Complex registration forms (requiring more than just nickname) are not
supported for now, we could render them in the UI to allow for "apply
for membership" flows, but no one supports that yet so this gets us the
basics for now.

Change summary

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java | 70 
1 file changed, 70 insertions(+)

Detailed changes

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -2717,6 +2717,71 @@ public class XmppConnectionService extends Service {
         return false;
     }
 
+    public void maybeRegisterWithMuc(Conversation c, String nickArg) {
+        final var nick = nickArg == null ? c.getMucOptions().getSelf().getFullJid().getResource() : nickArg;
+        final IqPacket register = new IqPacket(IqPacket.TYPE.GET);
+        register.query(Namespace.REGISTER);
+        register.setTo(c.getJid().asBareJid());
+        sendIqPacket(c.getAccount(), register, (a, response) -> {
+            if (response.getType() == IqPacket.TYPE.RESULT) {
+                final Element query = response.query(Namespace.REGISTER);
+                String username = query.findChildContent("username", Namespace.REGISTER);
+                if (username == null) username = query.findChildContent("nick", Namespace.REGISTER);
+                if (username != null && username.equals(nick)) {
+                    // Already registered with this nick, done
+                    Log.d(Config.LOGTAG, "Already registered with " + c.getJid().asBareJid() + " as " + username);
+                    return;
+                }
+                Data form = Data.parse(query.findChild("x", Namespace.DATA));
+                if (form != null) {
+                    final var field = form.getFieldByName("muc#register_roomnick");
+                    if (field != null && nick.equals(field.getValue())) {
+                        Log.d(Config.LOGTAG, "Already registered with " + c.getJid().asBareJid() + " as " + field.getValue());
+                        return;
+                    }
+                }
+                if (form == null || !"form".equals(form.getFormType()) || !form.getFields().stream().anyMatch(f -> f.isRequired() && !"muc#register_roomnick".equals(f.getFieldName()))) {
+                    // No form, result form, or no required fields other than nickname, let's just send nickname
+                    if (form == null || !"form".equals(form.getFormType())) {
+                        form = new Data();
+                        form.put("FORM_TYPE", "http://jabber.org/protocol/muc#register");
+                    }
+                    form.put("muc#register_roomnick", nick);
+                    form.submit();
+                    final IqPacket finish = new IqPacket(IqPacket.TYPE.SET);
+                    finish.query(Namespace.REGISTER).addChild(form);
+                    finish.setTo(c.getJid().asBareJid());
+                    sendIqPacket(c.getAccount(), finish, (a2, response2) -> {
+                        if (response.getType() == IqPacket.TYPE.RESULT) {
+                            Log.w(Config.LOGTAG, "Success registering with channel " + c.getJid().asBareJid() + "/" + nick);
+                        } else {
+                            Log.w(Config.LOGTAG, "Error registering with channel: " + response2);
+                        }
+                    });
+                } else {
+                    // TODO: offer registration form to user
+                    Log.d(Config.LOGTAG, "Complex registration form for " + c.getJid().asBareJid() + ": " + response);
+                }
+            } else {
+                // We said maybe. Guess not
+                Log.d(Config.LOGTAG, "Could not register with " + c.getJid().asBareJid() + ": " + response);
+            }
+        });
+    }
+
+    public void deregisterWithMuc(Conversation c) {
+        final IqPacket register = new IqPacket(IqPacket.TYPE.GET);
+        register.query(Namespace.REGISTER).addChild("remove");
+        register.setTo(c.getJid().asBareJid());
+        sendIqPacket(c.getAccount(), register, (a, response) -> {
+            if (response.getType() == IqPacket.TYPE.RESULT) {
+                Log.d(Config.LOGTAG, "deregistered with " + c.getJid().asBareJid());
+            } else {
+                Log.w(Config.LOGTAG, "Could not deregister with " + c.getJid().asBareJid() + ": " + response);
+            }
+        });
+    }
+
     public Conversation findOrCreateConversation(Account account, Jid jid, boolean muc, final boolean async) {
         return this.findOrCreateConversation(account, jid, muc, false, async);
     }
@@ -2829,6 +2894,7 @@ public class XmppConnectionService extends Service {
                         }
                     }
                 }
+                deregisterWithMuc(conversation);
                 leaveMuc(conversation);
             } else {
                 if (conversation.getContact().getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
@@ -3455,6 +3521,8 @@ public class XmppConnectionService extends Service {
                         databaseBackend.updateConversation(conversation);
                     }
 
+                    maybeRegisterWithMuc(conversation, null);
+
                     if (mucOptions.mamSupport()) {
                         getMessageArchiveService().catchupMUC(conversation);
                     }
@@ -3732,6 +3800,8 @@ public class XmppConnectionService extends Service {
             return false;
         }
         if (options.online()) {
+            maybeRegisterWithMuc(conversation, nick);
+
             Account account = conversation.getAccount();
             options.setOnRenameListener(new OnRenameListener() {