private string HandleWhois(SlashCommand message)
        {
            if (message.text.IsValidEmail())
            {
                _bus.Publish(new WhoisEmailRequest
                {
                    CorrelationId = Guid.NewGuid(),
                    EmailAddress = message.text,
                    RequestedByUser = message.user_name,
                    RespondToChannel =
                        message.channel_name == "directmessage" ?
                        "@" + message.user_name :
                        "#" + message.channel_name
                });
                return string.Format("Looking up e-mail address *{0}*, one moment please...", message.text);
            }

            if (message.text.CouldBeTwitterHandle())
            {
                _bus.Publish(new WhoisTwitterRequest
                {
                    CorrelationId = Guid.NewGuid(),
                    TwitterHandle = message.text,
                    RequestedByUser = message.user_name,
                    RespondToChannel =
                        message.channel_name == "directmessage"
                            ? "@" + message.user_name
                            : "#" + message.channel_name
                });
                return string.Format("Looking up Twitter handle *{0}*, one moment please...", message.text);
            }

            return "Sorry, I'm only able to work with e-mail addresses and Twitter handles.";
        }
Example #2
0
        private async Task SendCommandResponse(SlashCommand command, IList <Block> msgBlocks)
        {
            var message = new SlashCommandResponse()
            {
                Message = new Message
                {
                    Blocks  = msgBlocks,
                    Channel = command.ChannelId,
                    AsUser  = false,
                },
                ResponseType = ResponseType.InChannel
            };

            // Add information about command caller
            var callerInfo = new PlainText($"Command from: {command.UserName}.");

            if (message.Message.Blocks.Last() is ContextBlock ctx)
            {
                ctx.Elements.Add(callerInfo);
            }
            else
            {
                message.Message.Blocks.Add(new ContextBlock()
                {
                    Elements = new List <IContextElement>()
                    {
                        callerInfo
                    }
                });
            }

            await slack.Respond(command, message);
        }
        public string PostShiftRating([FromForm] SlashCommand slashCommand)
        {
            try
            {
                //rating can only be between 1-4
                var rating     = Int32.Parse(slashCommand.text);
                var ratingList = new List <int> {
                    1, 2, 3, 4
                };

                if (String.IsNullOrEmpty(slashCommand.text) || !ratingList.Contains(rating))
                {
                    throw new Exception();
                }

                var userName = slashCommand.user_name;
                var employee = _employeeRepo.GetByUserName(userName);
                return(_shiftData.SetShiftRating(employee, (Rating)rating));
            }

            catch (Exception e)
            {
                return("The command doesn't seem right :worried:. Use /ratemyshift <rating 1-4> to rate your latest shift.");
            }
        }
Example #4
0
        /// <summary>
        /// Forgive mentioned user's debt fully. Order-less way to alter balance.
        /// </summary>
        private async Task HandleForgiveAction(SlashCommand command)
        {
            try
            {
                // Example: ['forgive', '@user']
                var args = command.Text.Split(' ');
                if (args.Length != 2)
                {
                    throw new FormatException();
                }

                var debtor   = FoodUser.CreateFromString(args[1].Trim());
                var creditor = new FoodUser(command.UserId, command.UserName);

                if (debtor.UniqueId == creditor.UniqueId)
                {
                    await SendError(command.ResponseUrl, new BadRequestException("You can't do it to yourself."));

                    return;
                }

                await foodService.ResetCredit(debtor, creditor);
            }
            catch (FormatException)
            {
                await SendError(command.ResponseUrl,
                                new BadRequestException($"Incorrect format, expected: `/food {CommandTexts.Forgive} @<user>`"));
            }
        }
        public string PostClockOutTime([FromForm] SlashCommand slashCommand)
        {
            try
            {
                var response = "";
                var userName = slashCommand.user_name;
                var employee = _employeeRepo.GetByUserName(userName);
                //check if incoming command is correct and the employee exists using username
                if (!String.IsNullOrEmpty(slashCommand.text) || employee == null)
                {
                    throw new Exception();
                }

                var shiftToClockOut = _shiftData.GetClockedInShift(employee);
                if (shiftToClockOut != null)
                {
                    response = _shiftData.SetClockOutTime(shiftToClockOut, employee);
                    SendMessage(slashCommand.user_name, response);
                    return(response);
                }

                response = "You have not clocked in yet :timer_clock:! Please clock in before you clock out.";
                SendMessage(slashCommand.user_name, response);
                return(response);
            }

            catch (Exception e)
            {
                var response =
                    "Looks like the command you entered is not quite right :worried:. If you want to clock out make sure you use the command /clockout.";
                SendMessage(slashCommand.user_name, response);
                return(response);
            }
        }
Example #6
0
        public void RegisterSlashCommandHandler()
        {
            // Arrange
            var handler           = Substitute.For <ISlashCommandHandler>();
            var asyncHandler      = Substitute.For <IAsyncSlashCommandHandler>();
            var otherSlashCommand = new SlashCommand {
                Command = "/other"
            };
            var slashCommand = new SlashCommand {
                Command = "/command"
            };
            var asyncSlashCommand = new SlashCommand {
                Command = "/asyncCommand"
            };
            var responder = Substitute.For <Responder <SlashCommandResponse> >();

            var sut = Configure(c => c
                                .RegisterSlashCommandHandler("/command", handler)
                                .RegisterAsyncSlashCommandHandler("/asyncCommand", asyncHandler));

            // Act
            HandleSlashCommands(sut, responder, new[] { otherSlashCommand, slashCommand, asyncSlashCommand });

            // Assert
            handler.DidNotReceive().Handle(otherSlashCommand);
            handler.Received().Handle(slashCommand);
            handler.DidNotReceive().Handle(asyncSlashCommand);

            asyncHandler.DidNotReceive().Handle(otherSlashCommand, responder);
            asyncHandler.DidNotReceive().Handle(slashCommand, responder);
            asyncHandler.Received().Handle(asyncSlashCommand, responder);
        }
Example #7
0
        /// <summary>
        /// Add some debt by caller to mentioned user. Order-less way to alter balance.
        /// </summary>
        private async Task HandleOweAction(SlashCommand command)
        {
            try
            {
                // Example: ['owe', '@user' '4.99']
                var args = command.Text.Split(' ');
                if (args.Length != 3)
                {
                    throw new FormatException();
                }

                var creditor = FoodUser.CreateFromString(args[1].Trim());
                var debtor   = new FoodUser(command.UserId, command.UserName);

                if (debtor.UniqueId == creditor.UniqueId)
                {
                    await SendError(command.ResponseUrl, new BadRequestException("You can't do it to yourself."));

                    return;
                }

                var creditValue = decimal.Parse(args[2].Replace(',', '.'));
                await foodService.OweCredit(debtor, creditor, creditValue);
            }
            catch (FormatException)
            {
                await SendError(command.ResponseUrl,
                                new BadRequestException($"Incorrect format, expected: `/food {CommandTexts.Owe} @<user> <cost>`"));
            }
        }
Example #8
0
        public void OnSlashCommand(MessageEventArgs args, SlashCommand command)
        {
            // client sends "&gc info 1" on entering the world
            var split = command.Command.Split(' ');

            switch (split[0].ToLower(CultureInfo.InvariantCulture))
            {
            case "&quit":
                args.Session.Send(new Quit(false));
                break;

            case "&exit":
                args.Session.Send(new Quit(true));
                break;

            case "&speed":
                if (split.Length > 1 && ushort.TryParse(split[1], out ushort value))
                {
                    var speed = new CharacterSpeed(value, 100, false);
                    args.Session.Send(speed);
                }
                break;

            case "&up":
                if (split.Length > 1 && ushort.TryParse(split[1], out ushort height))
                {
                    var coords = _selected.Coordinates;
                    _selected.Coordinates = new Coordinates(coords.X, coords.Y, coords.Z + height, coords.Heading);
                    var position = new PositionAndObjectId(_selected);
                    args.Session.Send(position);
                }
                break;
            }
        }
Example #9
0
        public void ReadCategorizedButDontWriteResults()
        {
            var runDir        = AppDomain.CurrentDomain.BaseDirectory;
            var inputDataFile = Path.Combine(runDir, "Data/N1024_D128_K16.txt");

            Console.WriteLine($"Looking for datafile here: {inputDataFile}");
            // Largest value < 256
            var bitsPerDimension = 10;
            var config           = new SlashConfig()
            {
                AcceptableBCubed = 0.98
            };

            config.Index.BitsPerDimension = bitsPerDimension;
            config.Data = new SlashConfig.DataConfig()
            {
                InputDataFile = inputDataFile,
                IdField       = "id",
                CategoryField = "category",
                ReadHeader    = true
            };
            config.DensityClassifier.SkipDensityClassification = true;
            config.Output.OutputDataFile = null;
            var command = new SlashCommand(SlashCommand.CommandType.Cluster, config)
            {
                OutputFile = null
            };

            // Need to put this here, because the command initializes the logger differently.
            Logger.SetupForTests(null);
            command.Execute();

            Assert.IsTrue(command.IsClassificationAcceptable, $"The BCubed value of {command.MeasuredChange.BCubed} was not good enough.");
        }
        /// <summary>
        /// Method to get last leave status and details on slack
        /// </summary>
        /// <param name="slackText">list of string contain leave slash command parameter</param>
        /// <param name="leave">leave slash command</param>
        /// <param name="accessToken">User's access token</param>
        /// <returns>Reply text to be send</returns>
        private async Task <string> SlackLeaveStatusAsync(List <string> slackText, SlashCommand leave, string accessToken)
        {
            // if slackText count is more then 1 then its means that user want to get leave list of someone else
            if (slackText.Count > 1)
            {
                // other user slack user name
                SlackUserDetailAc slackUser = await _slackUserRepository.GetBySlackNameAsync(slackText[1]);

                if (slackUser != null)
                {
                    var user = await _userManagerRepository.FirstOrDefaultAsync(x => x.SlackUserId == slackUser.UserId);

                    _logger.Debug("SlackLeaveStatusAsync user name other : " + user.UserName);
                    // last leave details of other user
                    replyText = await LeaveStatusBySlackUserIdAsync(user.Id, accessToken);
                }
                else
                {
                    replyText = string.Format(_stringConstant.UserDetailsNotFound, slackText[1]);
                }
            }
            else
            {
                var user = await _userManagerRepository.FirstOrDefaultAsync(x => x.SlackUserId == leave.UserId);

                _logger.Debug("SlackLeaveStatusAsync user name own : " + user.UserName);
                // last leave details of own
                replyText = await LeaveStatusBySlackUserIdAsync(user.Id, accessToken);
            }
            return(replyText);
        }
        /// <summary>
        /// Method to check leave Balance from slack
        /// </summary>
        /// <param name="leave">leave slash command</param>
        /// <param name="accessToken">User's access token</param>
        /// <returns>Reply text to be send</returns>
        private async Task <string> SlackLeaveBalanceAsync(SlashCommand leave, string accessToken)
        {
            var userDetails = await _userManagerRepository.FirstOrDefaultAsync(x => x.SlackUserId == leave.UserId);

            // get user details from oAuth server
            User user = await _oauthCallsRepository.GetUserByUserIdAsync(userDetails.Id, accessToken);

            if (user.Id != null)
            {
                _logger.Debug("SlackLeaveBalanceAsync user name : " + user.UserName);
                // get user leave allowed details from oAuth server
                LeaveAllowed allowedLeave = await _oauthCallsRepository.AllowedLeave(user.Id, accessToken);

                // method to get user's number of leave taken
                LeaveAllowed leaveTaken       = _leaveRepository.NumberOfLeaveTaken(user.Id);
                double       casualLeaveTaken = leaveTaken.CasualLeave;
                double       sickLeaveTaken   = leaveTaken.SickLeave;
                double       casualLeaveLeft  = allowedLeave.CasualLeave - casualLeaveTaken;
                double       sickLeaveLeft    = allowedLeave.SickLeave - sickLeaveTaken;
                replyText = string.Format(_stringConstant.ReplyTextForCasualLeaveBalance, casualLeaveTaken,
                                          allowedLeave.CasualLeave, Environment.NewLine, casualLeaveLeft);
                replyText += string.Format(_stringConstant.ReplyTextForSickLeaveBalance, sickLeaveTaken,
                                           allowedLeave.SickLeave, Environment.NewLine, sickLeaveLeft);
            }
            else
            {
                // if user doesn't exist in Oauth server
                replyText = _stringConstant.LeaveNoUserErrorMessage;
            }
            return(replyText);
        }
Example #12
0
        public void AssessCommand()
        {
            var runDir        = AppDomain.CurrentDomain.BaseDirectory;
            var inputDataFile = Path.Combine(runDir, "Data/N1024_D128_K16.txt");

            Console.WriteLine($"Looking for datafile here: {inputDataFile}");
            // Largest value < 256
            var bitsPerDimension = 10;
            var config           = new SlashConfig()
            {
                AcceptableBCubed = 0.98
            };

            config.Index.BitsPerDimension = bitsPerDimension;
            config.Data = new SlashConfig.DataConfig()
            {
                InputDataFile = inputDataFile,
                IdField       = "id",
                CategoryField = "category",
                ReadHeader    = true
            };
            config.DensityClassifier.SkipDensityClassification = true;
            config.Output.OutputDataFile = null;
            var command = new SlashCommand(SlashCommand.CommandType.Assess, config)
            {
                OutputFile = null
            };

            // Need to put this here, because the command initializes the logger differently.
            Logger.SetupForTests(null);
            command.Execute();
            var assessment = command.Assessor.HowClustered;

            Assert.AreEqual(ClusteringTendency.ClusteringQuality.HighlyClustered, assessment, $"Expected HighlyClustered, got {command.Assessor}");
        }
Example #13
0
        /// <summary>
        /// Method to check leave Balance from slack
        /// </summary>
        /// <param name="leave"></param>
        /// <param name="accessToken"></param>
        /// <returns>replyText</returns>
        private async Task <string> SlackLeaveBalance(SlashCommand leave, string accessToken)
        {
            // get user details from oAuth server
            var user = await _projectUser.GetUserByUsername(leave.Username, accessToken);

            if (user.Id != null)
            {
                // get user leave allowed details from oAuth server
                var allowedLeave = await _projectUser.CasualLeave(leave.Username, accessToken);

                // method to get user's number of leave taken
                var leaveTaken       = _leaveRepository.NumberOfLeaveTaken(user.Id);
                var casualLeaveTaken = leaveTaken.CasualLeave;
                var sickLeaveTaken   = leaveTaken.SickLeave;
                var casualLeaveLeft  = allowedLeave.CasualLeave - casualLeaveTaken;
                var sickLeaveLeft    = allowedLeave.SickLeave - sickLeaveTaken;
                replyText  = string.Format("You have taken {0} casual leave out of {1}{2}You have casual leave left {3}", casualLeaveTaken, allowedLeave.CasualLeave, Environment.NewLine, casualLeaveLeft);
                replyText += string.Format("{2}You have taken {0} sick leave out of {1}{2}You have sick leave left {3}", casualLeaveTaken, allowedLeave.SickLeave, Environment.NewLine, sickLeaveLeft);
            }
            else
            {
                // if user doesn't exist in Oauth server
                replyText = _stringConstant.LeaveNoUserErrorMessage;
            }
            return(replyText);
        }
Example #14
0
        /// <summary>
        /// Method to send error message to user od slack
        /// </summary>
        /// <param name="leave"></param>
        public void Error(SlashCommand leave)
        {
            // if something error will happen user will get this message
            var replyText = string.Format("{0}{1}{2}{1}{3}", _stringConstant.LeaveNoUserErrorMessage, Environment.NewLine, _stringConstant.OrElseString, _stringConstant.SlackErrorMessage);

            _client.SendMessage(leave, replyText);
        }
        private string HandleWhois(SlashCommand message)
        {
            if (message.text.IsValidEmail())
            {
                _bus.Publish(new WhoisEmailRequest
                {
                    CorrelationId    = Guid.NewGuid(),
                    EmailAddress     = message.text,
                    RequestedByUser  = message.user_name,
                    RespondToChannel =
                        message.channel_name == "directmessage" ?
                        "@" + message.user_name :
                        "#" + message.channel_name
                });
                return(string.Format("Looking up e-mail address *{0}*, one moment please...", message.text));
            }

            if (message.text.CouldBeTwitterHandle())
            {
                _bus.Publish(new WhoisTwitterRequest
                {
                    CorrelationId    = Guid.NewGuid(),
                    TwitterHandle    = message.text,
                    RequestedByUser  = message.user_name,
                    RespondToChannel =
                        message.channel_name == "directmessage"
                            ? "@" + message.user_name
                            : "#" + message.channel_name
                });
                return(string.Format("Looking up Twitter handle *{0}*, one moment please...", message.text));
            }

            return("Sorry, I'm only able to work with e-mail addresses and Twitter handles.");
        }
Example #16
0
        public void ClusterWithoutFiles()
        {
            var bitsPerDimension = 10;
            var data             = new GaussianClustering
            {
                ClusterCount   = 20,
                Dimensions     = 50,
                MaxCoordinate  = (1 << bitsPerDimension) - 1,
                MinClusterSize = 200,
                MaxClusterSize = 600
            };
            var expectedClassification = data.MakeClusters();

            var config = new SlashConfig()
            {
                AcceptableBCubed = 0.98
            };

            config.Index.BitsPerDimension = bitsPerDimension;
            config.UseNoFiles();
            var command = new SlashCommand(SlashCommand.CommandType.Cluster, config)
            {
                InputFile  = null,
                OutputFile = null
            };

            command.Configuration.DensityClassifier.SkipDensityClassification = true;
            // Need to put this here, because the command initializes the logger differently.
            Logger.SetupForTests(null);
            command.LoadData(expectedClassification);

            command.Execute();

            Assert.IsTrue(command.IsClassificationAcceptable, $"The BCubed value of {command.MeasuredChange.BCubed} was not good enough.");
        }
Example #17
0
        private SlashCommand CreateCommand(Type commandClass)
        {
            if (ActivatorUtilities.CreateInstance(_provider, commandClass) is not ICommand instance)
            {
                throw new Exception("Could not create ICommand instance");
            }

            var parameters = CreateCommandParameters(commandClass).ToList();
            var checks     = GetChecks(commandClass).ToList();
            var parent     = GetCommandParent(commandClass);
            var command    = new SlashCommand(instance.Name, instance.Description, commandClass, parameters, checks, parent);

            if (parent is not null)
            {
                return(command);
            }

            // This is a root command, so we need to check for name collisions and add it to the hashset.
            if (_commands.Contains(instance.Name) || _groups.ContainsKey(instance.Name))
            {
                throw new Exception($"A command or a command group already exists with this name: '{instance.Name}'");
            }
            _commands.Add(command.Name);

            return(command);
        }
Example #18
0
        public async Task JoinRunAsync(SlashCommand slashCommand, string optionName)
        {
            var user = await GetOrCreateUser(slashCommand.UserId, slashCommand.UserName).ConfigureAwait(false);

            var room = await GetOrCreateRoom(slashCommand.ChannelId, slashCommand.ChannelName, user.Id).ConfigureAwait(false);

            await JoinRunAsync(user.Id, room.Id, optionName, slashCommand.ToCallbackData()).ConfigureAwait(false);
        }
Example #19
0
 internal static CallbackData ToCallbackData(this SlashCommand command)
 {
     return(new CallbackData
     {
         UserId = command.UserId,
         ResponseUrl = command.ResponseUrl
     });
 }
Example #20
0
 private async Task RunSlashCommand(SlashCommand slashCommand, bool closeWindow = true)
 {
     var(commandText, _) = ParseCommandText(SearchText);
     if (closeWindow)
     {
         ClearSearchAndHide();
     }
     await _mediator.Send(slashCommand.CreateRequest(slashCommand.SubstituteCommandText ?? commandText, _settings));
 }
        public ActionResult Post()
        {
            SlashCommand slashCommand = Request.Form.ToSlashCommand();

            return(slashCommand.Command.Substring(1).ToLower() switch
            {
                "approve" => new OkObjectResult("Approved!"),
                _ => new OkObjectResult("Unsupported command - sorry!")
            });
Example #22
0
 public static DiscordApplicationCommandOption FromSlashSubCommand(SlashCommand command)
 {
     return(new DiscordApplicationCommandOption(
                command.Name,
                command.Description,
                ApplicationCommandOptionType.SubCommand,
                options: command.Parameters?.Values.Select(FromSlashCommandParameter)
                ));
 }
Example #23
0
        public HttpResponseMessage Start([FromUri] SlashCommand command)
        {
            HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);

            response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
            response.Content    = new StringContent(_commandHandler.Process(command));
            response.StatusCode = HttpStatusCode.OK;

            return(response);
        }
 private async Task<ActionResult> SendApprovalMessage(SlashCommand command)
 {
     var request = new PostMessageRequest
     {
         Channel = "C01C9M0DAMB",
         Blocks = GenerateApproval(command)
     };
     await _webapi.Chat.Post(request);
     return new OkObjectResult($"Your approval request for {command.Text} has been sent");
 }
Example #25
0
        /// <summary>
        /// returns all the commands registered as a slash response
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        private SlashResponse GetAllTheCommands(SlashCommand arg)
        {
            var response = new SlashResponse()
            {
                response_type = SlashResponseType.in_channel,
                text          = $"Hello Everyone! The app Allthings supports these commands: {string.Concat(_commandImplementers.Keys.Select(k => k + "\n"))}"
            };

            return(response);
        }
Example #26
0
        /// <summary>
        /// Previousy implemented command, but intentionally omitted to test failures
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        private SlashResponse ProcessBasicCommand(SlashCommand arg)
        {
            var retVal = new SlashResponse()
            {
                response_type = SlashResponseType.in_channel,
                text          = "Beginners All Purpose Symbolic Instruction Code"
            };

            return(retVal);
        }
Example #27
0
 public async Task <SlashCommandResponse> Handle(SlashCommand command)
 {
     return(new()
     {
         Message = new Message
         {
             Text = command.Text
         }
     });
 }
        /// <summary>
        /// The below method used for sending resposne back to slack for a slash command in ephemeral mood. Required field response_url.
        /// </summary>
        /// <param name="leave">Slash Command object</param>
        /// <param name="replyText">Text to be send to slack</param>
        public void SendMessage(SlashCommand leave, string replyText)
        {
            var text = new SlashResponse()
            {
                ResponseType = _stringConstant.ResponseTypeEphemeral, Text = replyText
            };
            var textJson = JsonConvert.SerializeObject(text);

            WebRequestMethod(textJson, leave.ResponseUrl);
        }
Example #29
0
        /// <summary>
        /// Finish the order, update balance and publish the updated balance to slack along with closed order message.
        /// </summary>

        private async Task HandleFinalizeOrderAction(SlashCommand command)
        {
            var(finishedOrder, updatedBalance) = await foodService.FinishOrder();

            var responseBlocks = new List <Block>();

            responseBlocks.AddRange(SlackFormatter.BuildOrderMessage(finishedOrder));
            responseBlocks.AddRange(SlackFormatter.BuildBalanceMessage(updatedBalance));

            await SendCommandResponse(command, responseBlocks);
        }
Example #30
0
        /// <summary>
        /// Print current balance to slack publicly.
        /// </summary>

        private async Task HandleShowBalanceAction(SlashCommand command)
        {
            var balanceBook = await foodService.GetBalanceBook();

            if (balanceBook == null)
            {
                throw new BadRequestException("No balance is tracked.");
            }

            await SendCommandResponse(command, SlackFormatter.BuildBalanceMessage(balanceBook));
        }
Example #31
0
        /// <summary>
        /// Reopen last finished order, revert balance and notify slack.
        /// </summary>
        private async Task HandleReopenOrderAction(SlashCommand command)
        {
            var(reopenedOrder, updatedBalance) = await foodService.ReopenOrder();
            await SendPublicResponse(command, "Order reopened, reverting last balance changes.");

            var responseBlocks = new List <Block>();

            responseBlocks.AddRange(SlackFormatter.BuildBalanceMessage(updatedBalance));
            responseBlocks.AddRange(SlackFormatter.BuildOrderMessage(reopenedOrder));

            await SendCommandResponse(command, responseBlocks);
        }
Example #32
0
 private string HandleTodo(SlashCommand message)
 {
     var listId = message.channel_id;
     var list = _todoService.GetItems(message.user_id, listId).ToArray();
     var @operator = message.text.SubstringByWords(0, 1);
     if ([email protected]())
     {
         @operator = @operator.ToLowerInvariant();
     }
     switch (@operator)
     {
         case "":
             {
                 // Just echo the list
                 break;
             }
         case "show":
             {
                 _bus.Publish(new MessageToSlack
                 {
                     channel = listId,
                     text = list.ToSlackString()
                 });
                 return null;
             }
         case "add":
             {
                 var todoText = message.text.SubstringByWords(1);
                 if (todoText.Missing())
                 {
                     return null;
                 }
                 _todoService.AddItem(message.user_id, listId, todoText);
                 break;
             }
         case "tick":
             {
                 var todoItemId = message.text.SubstringByWords(1, 1);
                 if (todoItemId.Missing())
                 {
                     return null;
                 }
                 var force = message.text.SubstringByWords(2, 1).ToLowerInvariant() == "force";
                 try
                 {
                     _todoService.TickItem(message.user_id, listId, todoItemId, force);
                 }
                 catch (TodoItemClaimedBySomeoneElseException ex)
                 {
                     return string.Format(
                         "This task is claimed by <@{0}>. Use `/todo {1} {2} force` to override.", 
                         ex.UserId,
                         @operator,
                         todoItemId);
                 }
                 break;
             }
         case "untick":
             {
                 var todoItemId = message.text.SubstringByWords(1, 1);
                 if (todoItemId.Missing())
                 {
                     return null;
                 }
                 _todoService.UntickItem(message.user_id, listId, todoItemId);
                 break;
             }
         case "remove":
             {
                 var todoItemId = message.text.SubstringByWords(1, 1);
                 if (todoItemId.Missing())
                 {
                     return null;
                 }
                 var force = message.text.SubstringByWords(2, 1).ToLowerInvariant() == "force";
                 try
                 {
                     _todoService.RemoveItem(message.user_id, listId, todoItemId, force);
                 }
                 catch (TodoItemClaimedBySomeoneElseException ex)
                 {
                     return string.Format(
                         "This task is claimed by <@{0}>. Use `/todo {1} {2} force` to override.",
                         ex.UserId,
                         @operator,
                         todoItemId);
                 }
                 break;
             }
         case "trim":
             {
                 _todoService.ClearItems(message.user_id, listId, includeUnticked: false, force: false);
                 break;
             }
         case "clear":
             {
                 var force = message.text.SubstringByWords(1, 1).ToLowerInvariant() == "force";
                 try
                 {
                     _todoService.ClearItems(message.user_id, listId, includeUnticked: true, force: force);
                 }
                 catch (TodoItemClaimedBySomeoneElseException ex)
                 {
                     return string.Format(
                         "There are tasks claimed by other people. Use `/todo {0} force` to override.",
                         @operator);
                 }
                 break;
             }
         case "claim":
         {
             var todoItemId = message.text.SubstringByWords(1, 1);
             if (todoItemId.Missing())
             {
                 return null;
             }
             var force = message.text.SubstringByWords(2, 1).ToLowerInvariant() == "force";
             try
             {
                 _todoService.ClaimItem(message.user_id, listId, todoItemId, force);
             }
             catch (TodoItemClaimedBySomeoneElseException ex)
             {
                 return string.Format(
                     "This task is claimed by <@{0}>. Use `/todo {1} {2} force` to override.",
                     ex.UserId,
                     @operator,
                     todoItemId);
             }
             break;
         }
         case "free":
         {
             var todoItemId = message.text.SubstringByWords(1, 1);
             if (todoItemId.Missing())
             {
                 return null;
             }
             var force = message.text.SubstringByWords(2, 1).ToLowerInvariant() == "force";
             try
             {
                 _todoService.FreeItem(message.user_id, listId, todoItemId, force);
             }
             catch (TodoItemClaimedBySomeoneElseException ex)
             {
                 return string.Format(
                     "This task is claimed by <@{0}>. Use `/todo {1} {2} force` to override.",
                     ex.UserId,
                     @operator,
                     todoItemId);
             }
             break;
         }
         case "help":
             {
                 return "TODO"; // TODO Return usage info
             }
         default:
             {
                 return "Sorry, that is not a valid syntax for the `/todo` command. Use `/todo help` to see available operations.";
             }
     }
     list = _todoService.GetItems(message.user_id, listId).ToArray();
     return list.ToSlackString();
 }