SYWEB-152: Migrate IRC command logic to commands-service.

This commit is contained in:
Kegan Dougal 2014-11-13 11:55:02 +00:00
parent 0046df4b51
commit 8ce69e802d
4 changed files with 180 additions and 168 deletions

View file

@ -33,6 +33,7 @@ var matrixWebClient = angular.module('matrixWebClient', [
'notificationService',
'recentsService',
'modelService',
'commandsService',
'infinite-scroll',
'ui.bootstrap',
'monospaced.elastic'

View file

@ -0,0 +1,164 @@
/*
Copyright 2014 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';
/*
This service contains logic for parsing and performing IRC style commands.
*/
angular.module('commandsService', [])
.factory('commandsService', ['$q', '$location', 'matrixService', 'modelService', function($q, $location, matrixService, modelService) {
// create a rejected promise with the given message
var reject = function(msg) {
var deferred = $q.defer();
deferred.reject({
data: {
error: msg
}
});
return deferred.promise;
};
// Change your nickname
var doNick = function(room_id, args) {
if (args) {
return matrixService.setDisplayName(args);
}
return reject("Usage: /nick <display_name>");
};
// Join a room
var doJoin = function(room_id, args) {
if (args) {
var matches = args.match(/^(\S+)$/);
if (matches) {
var room_alias = matches[1];
$location.url("room/" + room_alias);
// NB: We don't need to actually do the join, since that happens
// automatically if we are not joined onto a room already when
// the page loads.
return reject("Joining "+room_alias);
}
}
return reject("Usage: /join <room_alias>");
};
// Kick a user from the room with an optional reason
var doKick = function(room_id, args) {
if (args) {
var matches = args.match(/^(\S+?)( +(.*))?$/);
if (matches) {
return matrixService.kick(room_id, matches[1], matches[3]);
}
}
return reject("Usage: /kick <userId> [<reason>]");
};
// Ban a user from the room with an optional reason
var doBan = function(room_id, args) {
if (args) {
var matches = args.match(/^(\S+?)( +(.*))?$/);
if (matches) {
return matrixService.ban(room_id, matches[1], matches[3]);
}
}
return reject("Usage: /ban <userId> [<reason>]");
};
// Unban a user from the room
var doUnban = function(room_id, args) {
if (args) {
var matches = args.match(/^(\S+)$/);
if (matches) {
// Reset the user membership to "leave" to unban him
return matrixService.unban(room_id, matches[1]);
}
}
return reject("Usage: /unban <userId>");
};
// Define the power level of a user
var doOp = 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 powerLevelEvent = modelService.getRoom(room_id).current_room_state.state("m.room.power_levels");
return matrixService.setUserPowerLevel(room_id, user_id, powerLevel, powerLevelEvent);
}
}
}
return reject("Usage: /op <userId> [<power level>]");
};
// Reset the power level of a user
var doDeop = function(room_id, args) {
if (args) {
var matches = args.match(/^(\S+)$/);
if (matches) {
var powerLevelEvent = modelService.getRoom(room_id).current_room_state.state("m.room.power_levels");
return matrixService.setUserPowerLevel(room_id, args, undefined, powerLevelEvent);
}
}
return reject("Usage: /deop <userId>");
};
var commands = {
"nick": doNick,
"join": doJoin,
"kick": doKick,
"ban": doBan,
"unban": doUnban,
"op": doOp,
"deop": doDeop
};
return {
/**
* Process the given text for commands and perform them.
* @param {String} roomId The room in which the input was performed.
* @param {String} input The raw text input by the user.
* @return {Promise} A promise of the pending command, or null if the
* input is not 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);
var args = bits[3];
if (commands[cmd]) {
return commands[cmd](roomId, args);
}
return reject("Unrecognised IRC-style command: " + cmd);
}
return null; // not a command
}
};
}]);

View file

@ -45,6 +45,7 @@
<script src="components/matrix/event-handler-service.js"></script>
<script src="components/matrix/notification-service.js"></script>
<script src="components/matrix/recents-service.js"></script>
<script src="components/matrix/commands-service.js"></script>
<script src="components/matrix/model-service.js"></script>
<script src="components/matrix/presence-service.js"></script>
<script src="components/fileInput/file-input-directive.js"></script>

View file

@ -15,8 +15,8 @@ limitations under the License.
*/
angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput', 'angular-peity'])
.controller('RoomController', ['$modal', '$filter', '$scope', '$timeout', '$routeParams', '$location', '$rootScope', 'matrixService', 'mPresence', 'eventHandlerService', 'mFileUpload', 'matrixPhoneService', 'MatrixCall', 'notificationService', 'modelService', 'recentsService',
function($modal, $filter, $scope, $timeout, $routeParams, $location, $rootScope, matrixService, mPresence, eventHandlerService, mFileUpload, matrixPhoneService, MatrixCall, notificationService, modelService, recentsService) {
.controller('RoomController', ['$modal', '$filter', '$scope', '$timeout', '$routeParams', '$location', '$rootScope', 'matrixService', 'mPresence', 'eventHandlerService', 'mFileUpload', 'matrixPhoneService', 'MatrixCall', 'notificationService', 'modelService', 'recentsService', 'commandsService',
function($modal, $filter, $scope, $timeout, $routeParams, $location, $rootScope, matrixService, mPresence, eventHandlerService, mFileUpload, matrixPhoneService, MatrixCall, notificationService, modelService, recentsService, commandsService) {
'use strict';
var MESSAGES_PER_PAGINATION = 30;
var THUMBNAIL_SIZE = 320;
@ -435,172 +435,18 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput', 'a
// Store the command in the history
history.push(input);
var promise;
var cmd;
var args;
var promise = commandsService.processInput($scope.room_id, input);
var echo = false;
var isEmote = input.indexOf("/me ") === 0;
// Check for IRC style commands first
// 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+?)( +(.*))?$/);
cmd = bits[1];
args = bits[3];
console.log("cmd: " + cmd + ", args: " + args);
switch (cmd) {
case "/me":
promise = matrixService.sendEmoteMessage($scope.room_id, args);
echo = true;
break;
case "/nick":
// Change user display name
if (args) {
promise = matrixService.setDisplayName(args);
}
else {
$scope.feedback = "Usage: /nick <display_name>";
}
break;
case "/join":
// Join a room
if (args) {
var matches = args.match(/^(\S+)$/);
if (matches) {
var room_alias = matches[1];
if (room_alias.indexOf(':') == -1) {
// FIXME: actually track the :domain style name of our homeserver
// with or without port as is appropriate and append it at this point
}
var room_id = modelService.getAliasToRoomIdMapping(room_alias);
console.log("joining " + room_alias + " id=" + room_id);
if ($scope.room) { // TODO actually check that you = join
// don't send a join event for a room you're already in.
$location.url("room/" + room_alias);
}
else {
promise = matrixService.joinAlias(room_alias).then(
function(response) {
// TODO: factor out the common housekeeping whenever we try to join a room or alias
matrixService.roomState(response.room_id).then(
function(response) {
eventHandlerService.handleEvents(response.data, false, true);
},
function(error) {
$scope.feedback = "Failed to get room state for: " + response.room_id;
}
);
$location.url("room/" + room_alias);
},
function(error) {
$scope.feedback = "Can't join room: " + JSON.stringify(error.data);
}
);
}
}
}
else {
$scope.feedback = "Usage: /join <room_alias>";
}
break;
case "/kick":
// Kick a user from the room with an optional reason
if (args) {
var matches = args.match(/^(\S+?)( +(.*))?$/);
if (matches) {
promise = matrixService.kick($scope.room_id, matches[1], matches[3]);
}
}
if (!promise) {
$scope.feedback = "Usage: /kick <userId> [<reason>]";
}
break;
case "/ban":
// Ban a user from the room with an optional reason
if (args) {
var matches = args.match(/^(\S+?)( +(.*))?$/);
if (matches) {
promise = matrixService.ban($scope.room_id, matches[1], matches[3]);
}
}
if (!promise) {
$scope.feedback = "Usage: /ban <userId> [<reason>]";
}
break;
case "/unban":
// Unban a user from the room
if (args) {
var matches = args.match(/^(\S+)$/);
if (matches) {
// Reset the user membership to "leave" to unban him
promise = matrixService.unban($scope.room_id, matches[1]);
}
}
if (!promise) {
$scope.feedback = "Usage: /unban <userId>";
}
break;
case "/op":
// Define the power level of a user
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 powerLevelEvent = $scope.room.current_room_state.state("m.room.power_levels");
promise = matrixService.setUserPowerLevel($scope.room_id, user_id, powerLevel, powerLevelEvent);
}
}
}
if (!promise) {
$scope.feedback = "Usage: /op <userId> [<power level>]";
}
break;
case "/deop":
// Reset the power level of a user
if (args) {
var matches = args.match(/^(\S+)$/);
if (matches) {
var powerLevelEvent = $scope.room.current_room_state.state("m.room.power_levels");
promise = matrixService.setUserPowerLevel($scope.room_id, args, undefined, powerLevelEvent);
}
}
if (!promise) {
$scope.feedback = "Usage: /deop <userId>";
}
break;
default:
$scope.feedback = ("Unrecognised IRC-style command: " + cmd);
break;
}
}
// By default send this as a message unless it's an IRC-style command
if (!promise && !cmd) {
// Make the request
promise = matrixService.sendTextMessage($scope.room_id, input);
if (!promise) { // not a non-echoable command
echo = true;
if (isEmote) {
promise = matrixService.sendEmoteMessage($scope.room_id, input.substring(4));
}
else {
promise = matrixService.sendTextMessage($scope.room_id, input);
}
}
if (echo) {
@ -608,8 +454,8 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput', 'a
// To do so, create a minimalist fake text message event and add it to the in-memory list of room messages
var echoMessage = {
content: {
body: (cmd === "/me" ? args : input),
msgtype: (cmd === "/me" ? "m.emote" : "m.text"),
body: (isEmote ? input.substring(4) : input),
msgtype: (isEmote ? "m.emote" : "m.text"),
},
origin_server_ts: new Date().getTime(), // fake a timestamp
room_id: $scope.room_id,
@ -642,7 +488,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput', 'a
}
},
function(error) {
$scope.feedback = "Request failed: " + error.data.error;
$scope.feedback = error.data.error;
if (echoMessage) {
// Mark the message as unsent for the rest of the page life