From 9aa66c1d8b06f148859ed76fc29e906f4202ceeb Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Thu, 16 Jul 2015 17:41:12 +0100 Subject: [PATCH 1/5] Add SlashCommands to functionally process /commands. --- src/SlashCommands.js | 192 +++++++++++++++++++ src/controllers/molecules/MessageComposer.js | 20 ++ 2 files changed, 212 insertions(+) create mode 100644 src/SlashCommands.js diff --git a/src/SlashCommands.js b/src/SlashCommands.js new file mode 100644 index 00000000..e24de861 --- /dev/null +++ b/src/SlashCommands.js @@ -0,0 +1,192 @@ +/* +Copyright 2015 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +'use strict'; + +var MatrixClientPeg = require("./MatrixClientPeg"); +var dis = require("./dispatcher"); + +var reject = function(msg) { + return { + error: msg + }; +}; + +var success = function(promise) { + return { + promise: promise + }; +}; + +var commands = { + // Change your nickname + nick: function(room_id, args) { + if (args) { + return MatrixClientPeg.get().setDisplayName(args); + } + return reject("Usage: /nick "); + }, + + // Invite a user + invite: function(room_id, args) { + if (args) { + var matches = args.match(/^(\S+)$/); + if (matches) { + return success( + MatrixClientPeg.get().invite(room_id, matches[1]) + ); + } + } + return reject("Usage: /invite "); + }, + + // Join a room + join: function(room_id, args) { + if (args) { + var matches = args.match(/^(\S+)$/); + if (matches) { + var room_alias = matches[1]; + // TODO: + // Map alias to room ID + // Find the room (if joined, then just view the room) + // else join the room alias + /* + dis.dispatch({ + action: 'view_room', + room_id: roomId + }); */ + } + } + return reject("Usage: /join [NOT IMPLEMENTED]"); + }, + + // Kick a user from the room with an optional reason + kick: function(room_id, args) { + if (args) { + var matches = args.match(/^(\S+?)( +(.*))?$/); + if (matches) { + return success( + MatrixClientPeg.get().kick(room_id, matches[1], matches[3]) + ); + } + } + return reject("Usage: /kick []"); + }, + + // Ban a user from the room with an optional reason + ban: function(room_id, args) { + if (args) { + var matches = args.match(/^(\S+?)( +(.*))?$/); + if (matches) { + return success( + MatrixClientPeg.get().ban(room_id, matches[1], matches[3]) + ); + } + } + return reject("Usage: /ban []"); + }, + + // Unban a user from the room + unban: function(room_id, args) { + if (args) { + var matches = args.match(/^(\S+)$/); + if (matches) { + // Reset the user membership to "leave" to unban him + return success( + MatrixClientPeg.get().unban(room_id, matches[1]) + ); + } + } + return reject("Usage: /unban "); + }, + + // Define the power level of a user + op: function(room_id, args) { + if (args) { + var matches = args.match(/^(\S+?)( +(\d+))?$/); + var powerLevel = 50; // default power level for op + if (matches) { + var user_id = matches[1]; + if (matches.length === 4 && undefined !== matches[3]) { + powerLevel = parseInt(matches[3]); + } + if (powerLevel !== NaN) { + var room = MatrixClientPeg.get().getRoom(room_id); + if (!room) { + return reject("Bad room ID: " + room_id); + } + var powerLevelEvent = room.currentState.getStateEvents( + "m.room.power_levels", "" + ); + return success( + MatrixClientPeg.get().setPowerLevel( + room_id, user_id, powerLevel, powerLevelEvent + ) + ); + } + } + } + return reject("Usage: /op []"); + }, + + // Reset the power level of a user + deop: function(room_id, args) { + if (args) { + var matches = args.match(/^(\S+)$/); + if (matches) { + var room = MatrixClientPeg.get().getRoom(room_id); + if (!room) { + return reject("Bad room ID: " + room_id); + } + + var powerLevelEvent = room.currentState.getStateEvents( + "m.room.power_levels", "" + ); + return success( + MatrixClientPeg.get().setPowerLevel( + room_id, args, undefined, powerLevelEvent + ) + ); + } + } + return reject("Usage: /deop "); + } +}; + +module.exports = { + /** + * Process the given text for /commands and perform them. + * @param {string} roomId The room in which the command was performed. + * @param {string} input The raw text input by the user. + * @return {Object|null} An object with the property 'error' if there was an error + * processing the command, or 'promise' if a request was sent out. + * Returns null if the input didn't match a command. + */ + processInput: function(roomId, input) { + // trim any trailing whitespace, as it can confuse the parser for + // IRC-style commands + input = input.replace(/\s+$/, ""); + if (input[0] === "/" && input[1] !== "/") { + var bits = input.match(/^(\S+?)( +(.*))?$/); + var cmd = bits[1].substring(1).toLowerCase(); + var args = bits[3]; + if (commands[cmd]) { + return commands[cmd](roomId, args); + } + } + return null; // not a command + } +}; \ No newline at end of file diff --git a/src/controllers/molecules/MessageComposer.js b/src/controllers/molecules/MessageComposer.js index 38d4a4f4..db1f4550 100644 --- a/src/controllers/molecules/MessageComposer.js +++ b/src/controllers/molecules/MessageComposer.js @@ -17,6 +17,7 @@ limitations under the License. 'use strict'; var MatrixClientPeg = require("../../MatrixClientPeg"); +var SlashCommands = require("../../SlashCommands"); var dis = require("../../dispatcher"); var KeyCode = { @@ -171,6 +172,25 @@ module.exports = { onEnter: function(ev) { var contentText = this.refs.textarea.getDOMNode().value; + + var cmd = SlashCommands.processInput(this.props.room.roomId, contentText); + if (cmd) { + ev.preventDefault(); + if (cmd.promise) { + cmd.promise.done(function() { + console.log("Command success."); + }, function(err) { + console.error("Command failure: %s", err); + }); + this.refs.textarea.getDOMNode().value = ''; + } + else if (cmd.error) { + console.error(cmd.error); + } + + return; + } + var content = null; if (/^\/me /i.test(contentText)) { content = { From d4a98b38500b04286f274f552a2d9b8517da5be3 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Thu, 16 Jul 2015 17:57:57 +0100 Subject: [PATCH 2/5] Show correct membership messages for ban/unban. --- skins/base/views/molecules/MRoomMemberTile.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/skins/base/views/molecules/MRoomMemberTile.js b/skins/base/views/molecules/MRoomMemberTile.js index 92b6b119..51a22b08 100644 --- a/skins/base/views/molecules/MRoomMemberTile.js +++ b/skins/base/views/molecules/MRoomMemberTile.js @@ -36,10 +36,17 @@ module.exports = React.createClass({ switch (ev.getContent().membership) { case 'invite': return senderName + " invited " + targetName + "."; + case 'ban': + return senderName + " banned " + targetName + "."; case 'join': return targetName + " joined the room."; case 'leave': - return targetName + " left the room."; + if (ev.getSender() === ev.getStateKey()) { + return targetName + " left the room."; + } + else { + return senderName + " unbanned " + targetName + "."; + } } }, From 006907e52f62627c66014f5b65debab8adc004d4 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Fri, 17 Jul 2015 09:47:30 +0100 Subject: [PATCH 3/5] Add kick message. Add reasons if given. --- skins/base/views/molecules/MRoomMemberTile.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/skins/base/views/molecules/MRoomMemberTile.js b/skins/base/views/molecules/MRoomMemberTile.js index b13b44a8..e79130c3 100644 --- a/skins/base/views/molecules/MRoomMemberTile.js +++ b/skins/base/views/molecules/MRoomMemberTile.js @@ -33,20 +33,29 @@ module.exports = React.createClass({ // XXX: SYJS-16 var senderName = ev.sender ? ev.sender.name : "Someone"; var targetName = ev.target ? ev.target.name : "Someone"; + var reason = ev.getContent().reason ? ( + " Reason: " + ev.getContent().reason + ) : ""; switch (ev.getContent().membership) { case 'invite': return senderName + " invited " + targetName + "."; case 'ban': - return senderName + " banned " + targetName + "."; + return senderName + " banned " + targetName + "." + reason; case 'join': return targetName + " joined the room."; case 'leave': if (ev.getSender() === ev.getStateKey()) { return targetName + " left the room."; } - else { + else if (ev.getPrevContent().membership === "ban") { return senderName + " unbanned " + targetName + "."; } + else if (ev.getPrevContent().membership === "join") { + return senderName + " kicked " + targetName + "." + reason; + } + else { + return targetName + " left the room."; + } } }, From 001372ec3929b34534196c85a6ff8d351214c4c2 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Fri, 17 Jul 2015 11:13:05 +0100 Subject: [PATCH 4/5] Fix nick changes --- src/SlashCommands.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/SlashCommands.js b/src/SlashCommands.js index e24de861..de9f5c5b 100644 --- a/src/SlashCommands.js +++ b/src/SlashCommands.js @@ -35,7 +35,9 @@ var commands = { // Change your nickname nick: function(room_id, args) { if (args) { - return MatrixClientPeg.get().setDisplayName(args); + return success( + MatrixClientPeg.get().setDisplayName(args) + ); } return reject("Usage: /nick "); }, From 2bb22954995535d483261f2db24a51cd56de72f0 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Fri, 17 Jul 2015 11:48:40 +0100 Subject: [PATCH 5/5] Implement /join --- src/SlashCommands.js | 51 ++++++++++++++++---- src/controllers/molecules/MessageComposer.js | 5 +- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/src/SlashCommands.js b/src/SlashCommands.js index de9f5c5b..a3426347 100644 --- a/src/SlashCommands.js +++ b/src/SlashCommands.js @@ -61,15 +61,48 @@ var commands = { var matches = args.match(/^(\S+)$/); if (matches) { var room_alias = matches[1]; - // TODO: - // Map alias to room ID - // Find the room (if joined, then just view the room) - // else join the room alias - /* - dis.dispatch({ - action: 'view_room', - room_id: roomId - }); */ + // Try to find a room with this alias + var rooms = MatrixClientPeg.get().getRooms(); + var roomId; + for (var i = 0; i < rooms.length; i++) { + var aliasEvents = rooms[i].currentState.getStateEvents( + "m.room.aliases" + ); + for (var j = 0; j < aliasEvents.length; j++) { + var aliases = aliasEvents[j].getContent().aliases || []; + for (var k = 0; k < aliases.length; k++) { + if (aliases[k] === room_alias) { + roomId = rooms[i].roomId; + break; + } + } + if (roomId) { break; } + } + if (roomId) { break; } + } + if (roomId) { // we've already joined this room, view it. + dis.dispatch({ + action: 'view_room', + room_id: roomId + }); + return success(); + } + else { + // attempt to join this alias. + return success( + MatrixClientPeg.get().joinRoom(room_alias).done( + function(room) { + dis.dispatch({ + action: 'view_room', + room_id: room.roomId + }); + }, function(err) { + console.error( + "Failed to join room: %s", JSON.stringify(err) + ); + }) + ); + } } } return reject("Usage: /join [NOT IMPLEMENTED]"); diff --git a/src/controllers/molecules/MessageComposer.js b/src/controllers/molecules/MessageComposer.js index db1f4550..34183083 100644 --- a/src/controllers/molecules/MessageComposer.js +++ b/src/controllers/molecules/MessageComposer.js @@ -176,18 +176,19 @@ module.exports = { var cmd = SlashCommands.processInput(this.props.room.roomId, contentText); if (cmd) { ev.preventDefault(); + if (!cmd.error) { + this.refs.textarea.getDOMNode().value = ''; + } if (cmd.promise) { cmd.promise.done(function() { console.log("Command success."); }, function(err) { console.error("Command failure: %s", err); }); - this.refs.textarea.getDOMNode().value = ''; } else if (cmd.error) { console.error(cmd.error); } - return; }