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 = {