diff --git a/src/SlashCommands.js b/src/SlashCommands.js new file mode 100644 index 0000000000..08d68331f8 --- /dev/null +++ b/src/SlashCommands.js @@ -0,0 +1,312 @@ +/* +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. +*/ + +var MatrixClientPeg = require("./MatrixClientPeg"); +var dis = require("./dispatcher"); +var encryption = require("./encryption"); + +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 success( + MatrixClientPeg.get().setDisplayName(args) + ); + } + return reject("Usage: /nick "); + }, + + encrypt: function(room_id, args) { + if (args == "on") { + var client = MatrixClientPeg.get(); + var members = client.getRoom(room_id).currentState.members; + var user_ids = Object.keys(members); + return success( + encryption.enableEncryption(client, room_id, user_ids) + ); + } + if (args == "off") { + var client = MatrixClientPeg.get(); + return success( + encryption.disableEncryption(client, room_id) + ); + + } + return reject("Usage: encrypt "); + }, + + // Change the room topic + topic: function(room_id, args) { + if (args) { + return success( + MatrixClientPeg.get().setRoomTopic(room_id, args) + ); + } + return reject("Usage: /topic "); + }, + + // 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]; + if (room_alias[0] !== '#') { + return reject("Usage: /join #alias:domain"); + } + if (!room_alias.match(/:/)) { + var domain = MatrixClientPeg.get().credentials.userId.replace(/^.*:/, ''); + room_alias += ':' + domain; + } + + // 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).then( + function(room) { + dis.dispatch({ + action: 'view_room', + room_id: room.roomId + }); + }) + ); + } + } + } + return reject("Usage: /join "); + }, + + part: function(room_id, args) { + var targetRoomId; + if (args) { + var matches = args.match(/^(\S+)$/); + if (matches) { + var room_alias = matches[1]; + if (room_alias[0] !== '#') { + return reject("Usage: /part [#alias:domain]"); + } + if (!room_alias.match(/:/)) { + var domain = MatrixClientPeg.get().credentials.userId.replace(/^.*:/, ''); + room_alias += ':' + domain; + } + + // Try to find a room with this alias + var rooms = MatrixClientPeg.get().getRooms(); + 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) { + targetRoomId = rooms[i].roomId; + break; + } + } + if (targetRoomId) { break; } + } + if (targetRoomId) { break; } + } + } + if (!targetRoomId) { + return reject("Unrecognised room alias: " + room_alias); + } + } + if (!targetRoomId) targetRoomId = room_id; + return success( + MatrixClientPeg.get().leave(targetRoomId).then( + function() { + dis.dispatch({action: 'view_next_room'}); + }) + ); + }, + + // 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 "); + } +}; + +// helpful aliases +commands.j = commands.join; + +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 (cmd === "me") return null; + if (commands[cmd]) { + return commands[cmd](roomId, args); + } + else { + return reject("Unrecognised command: " + input); + } + } + return null; // not a command + } +}; diff --git a/src/encryption.js b/src/encryption.js new file mode 100644 index 0000000000..954bc030db --- /dev/null +++ b/src/encryption.js @@ -0,0 +1,38 @@ +/* +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. +*/ + +function enableEncyption(client, roomId, members) { + members = members.slice(0); + members.push(client.credentials.userId); + // TODO: Check the keys actually match what keys the user has. + // TODO: Don't redownload keys each time. + return client.downloadKeys(members, "forceDownload").then(function(res) { + return client.setRoomEncryption(roomId, { + algorithm: "m.olm.v1.curve25519-aes-sha2", + members: members, + }); + }) +} + +function disableEncryption(client, roomId) { + return client.disableRoomEncryption(roomId); +} + + +module.exports = { + enableEncryption: enableEncyption, + disableEncryption: disableEncryption, +}