3

I am trying to transition the script from one state to another based on Smooch postback payloads; but getting error code H12.

Consider the example https://github.com/smooch/smooch-bot-example

Say I modify the script https://github.com/smooch/smooch-bot-example/blob/master/script.js as follows

start: {
    receive: (bot) => {
        return bot.say('Hi! I\'m Smooch Bot! Continue? %[Yes](postback:askName) %[No](postback:bye) );
    }
},
bye: {
    prompt: (bot) => bot.say('Pleasure meeting you'),
    receive: () => 'processing'
},

The intention is that the's bot's state would transition depending on the postback payload.

Question is, how do I make that happen?

My approach was add

stateMachine.setState(postback.action.payload)

to the handlePostback method of github.com/smooch/smooch-bot-example/blob/master/heroku/index.js

However, that threw an error code H12. I also experimented with

stateMachine.transition(postback.action,postback.action.payload)

to no avail.

sera
  • 111
  • 2
  • 10

3 Answers3

4

I got the same issue with the [object Object] instead of a string. This is because the state you get or set with a function is contained in an object, not a string... I fixed it with this code inside index.js, replacing the existing handlePostback function in the smooch-bot-example GitHub repo:

function handlePostback(req, res) {

const stateMachine = new StateMachine({
    script,
    bot: createBot(req.body.appUser)
});

const postback = req.body.postbacks[0];
if (!postback || !postback.action) {
    res.end();
};

const smoochPayload = postback.action.payload;

// Change conversation state according to postback clicked
switch (smoochPayload) {
    case "POSTBACK-PAYLOAD":
        Promise.all([
            stateMachine.bot.releaseLock(),
            stateMachine.setState(smoochPayload), // set new state
            stateMachine.prompt(smoochPayload) // call state prompt() if any
        ]);
        res.end();
    break;

    default:
        stateMachine.bot.say("POSTBACK ISN'T RECOGNIZED") // for testing purposes
            .then(() => res.end());
};
}

Then inside script.js all you need to do is define states corresponding to the exact postback payloads. If you have multiple postbacks that should take the user to other states, just add them to the case list like so :

case "POSTBACK-PAYLOAD-1":
case "POSTBACK-PAYLOAD-2":
case "POSTBACK-PAYLOAD-3":
case "POSTBACK-PAYLOAD-4":
Promise.all([
        stateMachine.bot.releaseLock(),
        stateMachine.setState(smoochPayload), // set new state
        stateMachine.prompt(smoochPayload) // call state prompt() if any
    ]);
    res.end();
break;

Note that you should not write break; at the end of each case if the outcome you want is the same (here : setting the state and prompting the corresponding message).

If you want to handle other postbacks differently, you can add cases after the break; statement and do other stuff instead.

Hope this helps!

dstoiko
  • 61
  • 1
  • 5
  • This is super useful. Thanks. This is much better than my hack. – sera Jul 27 '16 at 17:01
  • No problem! It is a bit unnerving at first but I just took inspiration from the `handleMessage` function to solve this object vs string issue... Please upvote if you think it's useful ;-) – dstoiko Jul 27 '16 at 17:24
  • I have credited the correct answer to you on my repo that implements this [Working github repo](https://github.com/nbikar/smooch-bot-example) – sera Jul 27 '16 at 17:25
  • Nice touch, appreciate it. – dstoiko Jul 27 '16 at 17:45
  • @sera Have you tested the answer I gave you? Mine doesn't seem to be working on FB Messenger and is still buggy on the webchat version at times, still throwing the `[object Object]` error on the logs on some conversations... – dstoiko Jul 28 '16 at 14:28
  • Yes. My bot is live at http://m.me/waylobot. It does still throw error at times though. I am migrating to native FB messenger bot so didn't spend a lot of time on debugging smooch. – sera Jul 28 '16 at 20:54
  • I did also try a few things out with native FB bots. Would love to chat about that. What's the best way to get in touch? – dstoiko Jul 29 '16 at 09:23
2

Postbacks won't automatically transition your conversation from one state to the next, you have to write that logic yourself. Luckily the smooch-bot-example you're using already has a postback handler defined here:

https://github.com/smooch/smooch-bot-example/blob/30d2fc6/heroku/index.js#L115

So whatever transition logic you want should go in there. You can do this by creating a stateMachine and calling receiveMessage() on it the same way handleMessages() already works. For example:

const stateMachine = new StateMachine({
    script,
    bot: createBot(req.body.appUser)
});

stateMachine.receiveMessage({
    text: 'whatever your script expects'
})

Alternatively, you could have your handlePostback implementation call stateMachine.setState(state) and stateMachine.prompt(state) independently, if you wanted to have your postbacks behave differently from regular text responses.

Andrew Lavers
  • 8,023
  • 1
  • 33
  • 50
  • Thanks for the reply. As I mentioned in my OP, I would have expected this to work. Not sure why it doesn't. I forked the smooch-bot-example https://github.com/nbikar/smooch-bot-example and deployed it https://postback-example1.herokuapp.com/ . The bot does not make state transitions. I have made two changes https://github.com/nbikar/smooch-bot-example/blob/master/script.js#L18 , https://github.com/nbikar/smooch-bot-example/blob/master/heroku/index.js#L118 . Thanks a lot in advance. – sera Jul 25 '16 at 17:04
  • It looks like you're treating postback responses as if they were user messages saying "yes" or "no", but there's nothing in your script.js that handles those responses. – Andrew Lavers Jul 25 '16 at 17:27
  • Note that the name of a state in your script.js ("start", "bye", "askName"..) has nothing to do with the user's response. You have to handle message or postback events yourself and define how a state should transition. For example the askName handler can transition to finish by resolving with the string "finish" here: https://github.com/nbikar/smooch-bot-example/blob/master/script.js#L35 You could also use stateMachine.setState as I suggested in my answer. – Andrew Lavers Jul 25 '16 at 17:31
  • Thanks a lot. [stateMachine.setState(postback.action.payload)](https://github.com/nbikar/smooch-bot-example/blob/master/heroku/index.js#L126) led to Undefined state [object Object] error. [stateMachine.prompt(postback.action.payload)](https://github.com/nbikar/smooch-bot-example/blob/master/heroku/index.js#L127) leads to state transitions. However, if you try https://postback-example1.herokuapp.com/, you'll see that the askName state keeps on repeating prompt without waiting for message. – sera Jul 25 '16 at 18:29
  • Of course, because you didn't define any state named "yes" or "no". Stack Overflow comments are the wrong place to debug this kind of thing, I'm going stop here. I'm sure you can figure this out on your own. – Andrew Lavers Jul 25 '16 at 18:32
  • Thank you for the help. Let me figure this out. I have added additional state; but the absence of states named "yes" or "no" have nothing to do with prompt repeating and not waiting for a message. Case in point, even if you select "No" from the first postback, there is a similar error. – sera Jul 25 '16 at 19:10
0

If you want to advance the conversation based on a postback you'll have to first output the buttons from the bot's prompt (so you can handle the button click in the receive), modify the handlePostback function in index.js, then handle the user's "reply" in your receive method - try this - modify script.js like so:

start: {
    prompt: (bot) => bot.say(`Hi! I'm Smooch Bot! Continue? %[Yes](postback:askName) %[No](postback:bye)`),
    receive: (bot, message) => {

      switch(message.text) {
        case 'Yes':
          return bot.say(`Ok, great!`)
            .then(() => 'hi')
          break;
        case 'No':
          return bot.say(`Ok, no prob!`)
            .then(() => 'bye')
          break;
        default:
          return bot.say(`hmm...`)
            .then(() => 'processing')
          break;          
      }
    }
},

hi: {
    prompt: (bot) => bot.say('Pleasure meeting you'),
    receive: () => 'processing'
},

bye: {
    prompt: (bot) => bot.say('Pleasure meeting you'),
    receive: () => 'processing'
},

Then modify the handlePostback function in index.js so that it treats a postback like a regular message:

function handlePostback(req, res) {

    const postback = req.body.postbacks[0];

    if (!postback || !postback.action)
        res.end();

    const stateMachine = new StateMachine({
        script,
        bot: createBot(req.body.appUser)
    });

    const msg = postback;

    // if you want the payload instead just do msg.action.paylod
    msg.text = msg.action.text;

    stateMachine.receiveMessage(msg)
      .then(() => res.end())
      .catch((err) => {
        console.error('SmoochBot error:', err);
        res.end();
      });
}

Now when a user clicks your button it will be pushed to the stateMachine and handled like a reply.

Dave Roma
  • 2,449
  • 3
  • 18
  • 13