1

I'm creating a simple chatbot through the Messenger Platform API and I'm pretty much stuck on how to effectively recognise a set of commands that the bot can react to. I'm currently using a switch statement to detect commands that begin with an exclamation mark (e.g. !showlist; !additem <single/set of parameter(s)>).

Here is what I currently have:

switch(true){
            case stristr($receivedMsg,'!additem'):
....
}

At any matching stage the code, either execute a set of statements or extrapolate the eventual parameters first and then executes some statements with them.

The issues I'm having with the above setup are the following:

  • in case of no parameters commands it is possible to get the related code to execute even if the command is misspelled. E.g. !additem#$%% will still invoke the actual command's code in the switch statement.
  • in case of commands that take parameters, when retrieving those parameters with say this statement:

    $item=str_replace('!additem', '', $receivedMsg);
    

    it is very easy to include unwanted text in the parameters; you may deal with spaces with trim() or imply there will always be a space and edit the above statement to include it in the function. E.g.

    $item=str_replace('!additem ', '', $receivedMsg);
    

    but this makes other problem arise when trying to separate the command from the params.

I'm aware that a solution could be hardcoding with systematic string manipulation functions but that doesn't seem correct to me. What do people do in this situation? Isn't there a specific way to exactly match commands and securely spot eventual users' typos?

n1nsa1d00
  • 856
  • 9
  • 23
  • Are parameters enclosed in quotes for example? or space character is delimiter? – revo Feb 10 '18 at 17:47
  • @revo no. I'd like the single space character to be the delimiter (e.g. right after the command so !additem{space}param). Then in case of multiple parameters I'd like the comma to be the delimiter. – n1nsa1d00 Feb 10 '18 at 17:52
  • Why bother with misplaced commands? This is going to cause problems. – Havenard Feb 10 '18 at 19:02
  • @Havenard what do you mean by 'misplaced commands'? – n1nsa1d00 Feb 10 '18 at 19:27
  • @MarcoDufal Nevermind, I think I misread something there, anyway you shouldn't bother with misspelled commands, if we are going to start trying to guess what the hell the user is trying to do with his inability to provide commands with proper syntax, we are going to need to enter the realm of artificial intelligence. This is definitively not how a bot should be operated. – Havenard Feb 10 '18 at 19:39
  • @Havenard I wanted to have that in order to spot typos and specifically address those with the right prompt message, but as you're pointing out maybe is better to not bother with it. As you can see I'm quite misinformed concerning bots. Can you direct me to resources to grasp these standard aspect that a bot should have? – n1nsa1d00 Feb 10 '18 at 19:42

3 Answers3

1

You didn't work with Regular Expressions in your own solution but tagged it correctly. By stristr() function I found you are not looking for more coming commands so I applied the same logic onto RegEx:

$msg = 'Gonna add it !additem param1,param2';
preg_match('~(!\S+)\s*(.*)~', $msg, $match);
$command = $match[1];
$parameters = preg_split('~\s*,\s*~', $match[2]);

I tried to do it a one-liner but later thought this would be much more cleaner. BTW I wonder about the switch statement.

RegEx Breakdown:

~   # regex delimiter
    (   # Start of Capturing Group (1)
        !\S+    # Match non-space characters that start with !
    )   # End of CG1
    \s* # Any number of white-sapce characters
    (   # Start of CG2
        .*  # Match up to end
    )   # End of CG2
~   # regex delimiter

preg_split too receives a regex as its first argument and tries to split on it, almost a explode with regex. \s*,\s* means a comma that may be enclosed in any number of spaces.

revo
  • 47,783
  • 14
  • 74
  • 117
  • For some reason I've been avoiding regular expression for too long. I think it's time for me to learn more about them... Ok I get you solution but of course I don't really understand the regex, but don't worry about this for the moment. Am I correct in saying that removing the `~` will only match text beginning with `!`? Also what if I want to allow a space after commas? Should I be strict in defining commands or allow certain variants like possible extra spaces etc.? – n1nsa1d00 Feb 10 '18 at 18:35
  • Thanks for the breakdown! Are you using capturing groups because you've passed `$match` as a parameter? Also why is `~` needed? – n1nsa1d00 Feb 11 '18 at 17:07
1
if ($receivedMsg[0] == '!')
    switch (strtolower(substr(reset(explode(' ', $receivedMsg)), 1)))
    {
        case 'additem':
            // do this
            break;
        case 'delitem':
            // do that
            break;
        default:
            echo 'Command not recognized.';
    }

Well that's one way to do it. You can also declare an array with the functions that handle each command, example:

$handles = [
    'additem' = function ($args) { /* add something */ },
    'delitem' = function ($args) { /* del something */ },
    // ...
];

if ($receivedMsg[0] == '!')
{
    $args = explode(' ', $receivedMsg);
    $cmd  = strtolower(substr($args[0], 1));
    if (isset($handles[$cmd]))
        $handles[$cmd]($args);
    else
        echo 'Command not recognized.';
}
Havenard
  • 27,022
  • 5
  • 36
  • 62
  • First code snippet is like *Welcome to all `Notice`s!* – revo Feb 10 '18 at 19:12
  • I like the second solution more. Seems way more tidier than my switch approach and it uses functional programming aspect as well! – n1nsa1d00 Feb 10 '18 at 19:19
  • @revo I see what you mean there... Thanks to you both. You've given me two more than valid solutions! I think that what would work for me would be a mixture of the two to a certain extent.. – n1nsa1d00 Feb 10 '18 at 19:26
  • @revo Well I guess you have to make sure message length isn't zero before testing if the first character is '!', aside from that I'm not sure what you mean. – Havenard Feb 10 '18 at 19:28
0

Basing my solution on the answers that @Havenhard and @revo provided, I've written the following solution that perfectly works for me:

$this->senderId = $messaging['sender']['id'];
$command_handlers = [
        'additem' => "addItemCommand",
        'showlist' => "showListCommand",
        'rngroup' => "renameGroupCommand"
    ];

    $actionCompletedOrh = new OutRequestHandler($this->senderId);

    if(!empty($messaging['message']) && empty($messaging['message']['quick_reply'])){
        $receivedMsg = $messaging['message']['text'];
        $replyMsg = "";
        $this->performSenderAction(0);
        //isCommand uses this regex to perform the evaluation 
        //(^!\w+\s+([\w,\s]*$)|^!\w+$)"
        if($this->isCommand($receivedMsg)){
            //regex matching to get params in raw form
            preg_match("~(^!\w+\s+([\w,\s]*$)|^!\w+$)~",$receivedMsg,$match);
            //regex matching to get the command
            preg_match("~^!\w+~",$match[0],$_match); 
            $command = strtolower($_match[0]);
            $params = null; 
            if(count($match)>2){
                //the function below uses preg_split as in @revo's example
                $params = $this->getCommandParams($match[2]);
            }
            if(array_key_exists(substr($command,1), $command_handlers)){
                $func = $command_handlers[substr($command,1)];
                $replyMsg=$this->$func($params,$connection);
            }
            else{
                $replyMsg=$this->getPromptMessage("cmer1");
            }
        }
        else{
            //All other messages - possibly processed with NPL
        }
        $this->performSenderAction(2);
        $replyMsg = json_encode($replyMsg);
        $actionCompletedOrh->sendJustTextMessage($replyMsg,$access_token);
    }

Do you see anything I could improve? Please let me know what and why in the comments!

n1nsa1d00
  • 856
  • 9
  • 23