Bug 1281722 - Implement Direct MUC Invitations (XEP-0249). r=aleth
authorAbdelrhman Ahmed <ab@abahmed.com>
Fri, 01 Jul 2016 21:23:40 +0200
changeset 19545 8ed9ff2cedd388da363ccf801835442459702866
parent 19544 3e4a05ed0bdb6082df925f8a25e5485e2d2e037c
child 19546 123f1bb494b7b254369fd549a764e77af9d09df5
push id12046
push userab@abahmed.com
push dateFri, 01 Jul 2016 19:26:05 +0000
treeherdercomm-central@8ed9ff2cedd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaleth
bugs1281722
Bug 1281722 - Implement Direct MUC Invitations (XEP-0249). r=aleth
chat/locales/en-US/xmpp.properties
chat/protocols/xmpp/xmpp-commands.jsm
chat/protocols/xmpp/xmpp.jsm
--- a/chat/locales/en-US/xmpp.properties
+++ b/chat/locales/en-US/xmpp.properties
@@ -117,17 +117,23 @@ chatRoomField.password=_Password
 # LOCALIZATION NOTE (conversation.muc.*):
 #   These are displayed as a system message when a chatroom invitation is
 #   received.
 #   %1$S is the inviter.
 #   %2$S is the room.
 #   %3$S is the reason which is a message provided by the person sending the
 #   invitation.
 conversation.muc.invitationWithReason=%1$S has invited you to join %2$S: %3$S
+#   %3$S is the password of the room.
+#   %4$S is the reason which is a message provided by the person sending the
+#   invitation.
+conversation.muc.invitationWithReason.password==%1$S has invited you to join %2$S with password %3$S: %4$S
 conversation.muc.invitationWithoutReason=%1$S has invited you to join %2$S
+#   %3$S is the password of the room.
+conversation.muc.invitationWithoutReason.password=%1$S has invited you to join %2$S with password %3$S
 
 # LOCALIZATION NOTE (conversation.muc.join):
 #   This is displayed as a system message when a participant joins room.
 #   %S is the nick of the participant.
 conversation.message.join=%S entered the room.
 
 # LOCALIZATION NOTE (conversation.muc.rejoined):
 #   This is displayed as a system message when a participant rejoins room after
@@ -240,11 +246,12 @@ odnoklassniki.usernameHint=Profile ID
 # LOCALZIATION NOTE (command.*):
 #  These are the help messages for each command.
 command.join3=%S [&lt;room&gt;[@&lt;server&gt;][/&lt;nick&gt;]] [&lt;password&gt;]: Join a room, optionally providing a different server, or nickname, or the room password.
 command.part2=%S [&lt;message&gt;]: Leave the current room with an optional message.
 command.topic=%S [&lt;new topic&gt;]: Set this room's topic.
 command.ban=%S &lt;nick&gt;[&lt;message&gt;]: Ban someone from the room. You must be a room administrator to do this.
 command.kick=%S &lt;nick&gt;[&lt;message&gt;]: Remove someone from the room. You must be a room moderator to do this.
 command.invite=%S &lt;jid&gt;[&lt;message&gt;]: Invite a user to join the current room with an optional message.
+command.inviteto=%S &lt;room jid&gt;[&lt;password&gt;]: Invite your conversation partner to join a room, together with its password if required.
 command.me=%S &lt;action to perform&gt;: Perform an action.
 command.nick=%S &lt;new nickname&gt;: Change your nickname.
 command.msg=%S &lt;nick&gt; &lt;message&gt;: Send a private message to a participant in the room.
--- a/chat/protocols/xmpp/xmpp-commands.jsm
+++ b/chat/protocols/xmpp/xmpp-commands.jsm
@@ -79,16 +79,38 @@ function splitByNick(aString, aConv) {
   splitParams.push(nickName);
 
   let msg = params.substring(nickName.length);
   if (msg)
     splitParams.push(msg.trimLeft());
   return splitParams;
 }
 
+// Splits aMsg in two entries and checks the first entry is a valid jid, then
+// passes it to aConv.invite().
+// Returns false if aMsg is empty, otherwise returns true.
+function invite(aMsg, aConv) {
+  let params = splitInput(aMsg);
+  if (!params.length)
+    return false;
+
+  // Check user's jid is valid.
+  let account = getAccount(aConv);
+  let jid = account._parseJID(params[0]);
+  if (!jid) {
+    aConv.writeMessage(aConv.name,
+                      _("conversation.error.invalidJID", params[0]),
+                      {system: true});
+    return true;
+  }
+
+  aConv.invite(...params);
+  return true;
+}
+
 var commands = [
   {
     name: "join",
     get helpString() { return _("command.join3", "join"); },
     run: function(aMsg, aConv, aReturnedConv) {
       let account = getAccount(aConv);
       let params = aMsg.trim();
       let conv;
@@ -172,38 +194,30 @@ var commands = [
       return true;
     }
   },
   {
     name: "invite",
     get helpString() { return _("command.invite", "invite"); },
     usageContext: Ci.imICommand.CMD_CONTEXT_CHAT,
     run: function(aMsg, aConv) {
-      let params = splitInput(aMsg);
-      if (!params.length)
-        return false;
-
       let conv = getMUC(aConv);
       if (!conv)
         return true;
 
-      // Check user's jid is valid.
-      let account = getAccount(aConv);
-      let jid = account._parseJID(params[0]);
-      if (!jid) {
-        conv.writeMessage(conv.name,
-                          _("conversation.error.invalidJID", params[0]),
-                          {system: true});
-        return true;
-      }
-      conv.invite(...params);
-      return true;
+      return invite(aMsg, conv);
     }
   },
   {
+    name: "inviteto",
+    get helpString() { return _("command.inviteto", "inviteto"); },
+    usageContext: Ci.imICommand.CMD_CONTEXT_IM,
+    run: (aMsg, aConv) => invite(aMsg, getConv(aConv))
+  },
+  {
     name: "me",
     get helpString() { return _("command.me", "me"); },
     usageContext: Ci.imICommand.CMD_CONTEXT_CHAT,
     run: function(aMsg, aConv) {
       let params = aMsg.trim();
       if (!params)
         return false;
 
--- a/chat/protocols/xmpp/xmpp.jsm
+++ b/chat/protocols/xmpp/xmpp.jsm
@@ -657,16 +657,25 @@ var XMPPConversationPrototype = {
       who = this._account._connection._jid.jid;
     if (!who)
       who = this._account.name;
     let alias = this.account.alias || this.account.statusInfo.displayName;
     this.writeMessage(who, aMsg, {outgoing: true, _alias: alias});
     delete this._typingState;
   },
 
+  // Invites the contact to a MUC room.
+  invite: function(aRoomJid, aPassword = null) {
+    // XEP-0045 (7.8): Inviting Another User to a Room.
+    // XEP-0045 (7.8.1) and XEP-0249: Direct Invitation.
+    let x = Stanza.node("x", Stanza.NS.conference, {jid: aRoomJid,
+                                                    password: aPassword});
+    this._account.sendStanza(Stanza.node("message", null, {to: this.to}, x));
+  },
+
   /* Perform entity escaping before displaying the message. We assume incoming
      messages have already been escaped, and will otherwise be filtered. */
   prepareForDisplaying: function(aMsg) {
     if (aMsg.outgoing && !aMsg.system)
       aMsg.displayMessage = TXTToHTML(aMsg.displayMessage);
     GenericConversationPrototype.prepareForDisplaying.apply(this, arguments);
   },
 
@@ -1770,16 +1779,46 @@ var XMPPAccountPrototype = {
       let muc = this._mucs.get(norm);
       let nick = this._parseJID(from).resource;
       // TODO There can be multiple subject elements with different xml:lang
       // attributes.
       muc.setTopic(subject.innerText, nick);
       return;
     }
 
+    let invitation = this.parseInvitation(aStanza);
+    if (invitation) {
+      let messageID;
+      if (invitation.reason)
+        messageID = "conversation.muc.invitationWithReason";
+      else
+        messageID = "conversation.muc.invitationWithoutReason";
+      if (invitation.password)
+        messageID += ".password";
+      let params =
+        [invitation.from, invitation.mucJid, invitation.password,
+         invitation.reason].filter(s => s);
+      let message = _(messageID, ...params);
+
+      if (Services.prefs.getIntPref("messenger.conversations.autoAcceptChatInvitations") == 1) {
+        // Auto-accept the invitation.
+        let chatRoomFields = this.getChatRoomDefaultFieldValues(invitation.mucJid);
+        if (invitation.password)
+          chatRoomFields.setValue("password", invitation.password);
+        let muc = this.joinChat(chatRoomFields);
+        muc.writeMessage(muc.name, message, {system: true});
+      }
+      else {
+        // Otherwise, just notify the user.
+        let conv = this.createConversation(invitation.from);
+        if (conv)
+          conv.writeMessage(invitation.from, message, {system: true});
+      }
+    }
+
     if (body) {
       let date;
       let delay = aStanza.getElement(["delay"]);
       if (delay && delay.uri == Stanza.NS.delay) {
         if (delay.attributes["stamp"])
           date = new Date(delay.attributes["stamp"]);
       }
       if (date && isNaN(date))
@@ -1790,42 +1829,16 @@ var XMPPAccountPrototype = {
           this.WARN("Received a groupchat message for unknown MUC " + norm);
           return;
         }
         let muc = this._mucs.get(norm);
         muc.incomingMessage(body, aStanza, date);
         return;
       }
 
-      let invitation = this.parseInvitation(aStanza);
-      if (invitation) {
-        if (invitation.reason) {
-          body = _("conversation.muc.invitationWithReason",
-                   invitation.from, invitation.mucJid, invitation.reason);
-        }
-        else {
-          body = _("conversation.muc.invitationWithoutReason",
-                   invitation.from, invitation.mucJid);
-        }
-        if (Services.prefs.getIntPref("messenger.conversations.autoAcceptChatInvitations") == 1) {
-          // Auto-accept the invitation.
-          let chatRoomFields = this.getChatRoomDefaultFieldValues(invitation.mucJid);
-          if (invitation.password)
-            chatRoomFields.setValue("password", invitation.password);
-          let muc = this.joinChat(chatRoomFields);
-          muc.writeMessage(muc.name, body, {system: true});
-          return;
-        }
-        // Otherwise, just notify the user.
-        let conv = this.createConversation(invitation.from);
-        if (conv)
-          conv.writeMessage(invitation.from, body, {system: true});
-        return;
-      }
-
       let conv = this.createConversation(from);
       if (!conv)
         return;
       conv.incomingMessage(body, aStanza, date);
     }
     else if (type == "error") {
       let conv = this.createConversation(from);
       if (conv)