Unable to pass control to another method within a dialog in botbuilder for Node.js - node.js

I'm creating my first bot with Node.js and MS Bot Framework and I'm trying to figure out how to pass control from one method to another within a dialog.
In Bot Framework for C#, it's very easy:
context.Wait(NextMethodName);
where NextMethodName is the name of the method that runs after the bot receives the next user message.
I am trying to do a similar thing in Node.js. I have two functions. The first one prompts the user to enter something or click a button, and the second should process the user's input. I am struggling with passing control to the second function.
bot.dialog('subscribe', [
function (session) {
var card = new builder.HeroCard(session)
.title("Subscribe for reminders?")
.text("It seems you're not enrolled yet. Subscribe for reminders to submit your work hours?")
.buttons([
builder.CardAction.imBack(session, "subscribe", "Subscribe")
]);
var msg = new builder.Message(session).attachments([card]);
session.send(msg);
//next(); //compile error
},
function (session, results) {
if (results.response === 'subscribe') {
session.send('You are now subscribed to reminders through ' + session.message.user.channelId + '.');
}
else {
session.send('You must subscribe to reminders before using this bot.');
}
}
]);
How do I make the second function run after the user clicks the button or answers anything?

In node's botbuilder sdk, you can define waterfall dialogs that contains what are called as 'steps'. Each step leads to another (like a waterfall). According to docs:
'Waterfalls let you collect input from a user using a sequence of steps. A bot is
always in a state of providing a user with information or asking a
question and then waiting for input. In the Node version of Bot
Builder it's waterfalls that drive this back-n-forth flow'.
Some steps can start with a prompt to ask the user for information, and then the following step handles the response by saving it using dialogData. Then you can invoke the next function argument to pass control to the next step. In your case, calling next() gives you an error because that function isn't in scope, you must provide it as a parameter to your step function.
Check this sample here:
https://github.com/Microsoft/BotBuilder-Samples/tree/master/Node/core-MultiDialogs
In your first step code I would do:
function (session,args,next) {
var card = new builder.HeroCard(session)
.title("Subscribe for reminders?")
.text("It seems you're not enrolled yet. Subscribe for reminders to submit your work hours?")
.buttons([
builder.CardAction.imBack(session, "subscribe", "Subscribe")
]);
var msg = new builder.Message(session).attachments([card]);
session.send(msg);
next();
}
But that would just lead you to the next step, so if you want to wait for user input (with text prompt), or for example using HeroCard actions, like you defined in your sample:
Your card triggers an action called "subscribe" with the parameter "Subscribe" via a button. Think of this as a function that is called within your dialog by pressing the button on the card. Now to define that function, we do:
// An action is essentially a card calling a global dialog method
// with respective parameters. This dialog action will route the action
// command to a dialog.
bot.beginDialogAction('subscribe', '/subscribe');
// Create the dialog for the action...
bot.dialog('/subscribe', [
function (session, args) {
//do something!
}
]);

Related

Using the onUpdate function in firebase, how do I pass in information from the front end?

So basically, I have a function that sends a user an email when a room Status becomes "Available"
exports.sendAvailableRoomEmail = functions.firestore.document('rooms/{roomId}/Status').onUpdate(async (snap, context) => {
const newVal = snap.after.data();
const oldVal = snap.before.data();
if(newVal == "Available"){
// send email
}
})
what I need passed into the function is the specific room that the user wants a notification from: {roomId}
How can I pass that specific room only when the user clicks the notify me button right next to it?
The triggers for a Cloud Function needs to be known when you deploy that function. That means you can either trigger for all rooms, or you can trigger for a specific room, but you can't trigger for rooms that meet a dynamic condition such as yours.
The common way to implement the use-case is to query Firestore inside the Cloud Functions code to find if any user need to be notified for the specific room that was updated.

How to add Get Started button in the typing bar using bot builder sdk for node.js

I am using bot builder sdk for node.js to create a chatbot. Also connected it to facebook channel. I am using the following code to greet the user:
var bot = new builder.UniversalBot(connector, [
(session, result, next) => {
let text = '';
switch(session.message.address.channelId) {
case 'facebook':
text = 'Hi ' + session.message.user.name + ' !';
break;
default:
text = 'Hi !';
}
session.sendTyping();
session.say(text);
next();
},
(session, say) => {
}
]);
The above code works fine, but I want to add "Get Started" button in the typing bar to invoke the above code. Note that this button appears only once. Please find image of the typing bar below:
Is there a way to achieve this using bot builder sdk for node.js ?
Thanks
Although one can certainly add a button to start any activity with the bot, but that will limit the bots potential to only one customizable channel, i.e. WebChat.
I think there are better 2 alternative ways to get the desired functionality which will work across many channels.
First
I would suggest to add a conversation update event. Code goes in the botbuilder's middleware. Here is a sample code from the docs.
bot.on('conversationUpdate', function (message) {
if (message.membersAdded && message.membersAdded.length > 0) {
// Say hello
var txt = "Send me a Hi";
var reply = new builder.Message()
.address(message.address)
.text(txt);
bot.send(reply);
});
What this will do is make the bot send a message Send me a Hi to the user, if it determines this is a first time visitor. This will give the visitor enough cue to send the bot Hi by typing it. Although he can enter whatever he wants, but this will result in the invocation of the 1st dialog configured which in this case is the will be the dialog which you have posted in question.
Second
You can mark some dialog to be invoked automatically if your bot has never encountered this visitor. Here is the sample code...
var bot = new builder.UniversalBot(connector);
bot.dialog('firstRun', function (session) {
session.userData.firstRun = true;
session.send("Hello...").endDialog();
}).triggerAction({
onFindAction: function (context, callback) {
// Only trigger if we've never seen user before
if (!context.userData.firstRun) {
// Return a score of 1.1 to ensure the first run dialog wins
callback(null, 1.1);
} else {
callback(null, 0.0);
}
}
});
Here we have split the bot creation and dialog registration in 2 steps. And while registering the firstRun dialog, we have provided it the triggerAction that if the visitor is new, then trigger this dialog.
Both of these approaches do not use adding some extra buttons and it is up to the bot either to educate him on sending some message which in turn will start the 1st dialog or directly start some dialog.
For more info on conversationEvent you can refer to this page
I tried the above options, but they didn't seem to be working for facebook messenger. But I found a solution to add the Get Started button into the typing bar of the messenger. For that we need to use the Facebook Graph API and not the bot builder sdk.
https://graph.facebook.com/v2.6/me/messenger_profile?access_token=<PAGE_ACCESS_TOKEN>
{
"get_started":{
"payload":"Get Started"
}
}
The above API call will add the button for you to get the conversation started.
Thanks all for the help!!

args not returning expected LUIS result after implementing BotAuth

I have been creating a chat bot with MS Bot Framework in Nodejs and LUIS. I am recently trying to get certain information from the MS Graph API, and have (sort of) successfully implemented BotAuth and am able to get the information I want.
The issue I am facing now is that for the dialog that implements BotAuth, I am not able to get the usual args that comes with LUIS-intents triggered dialogs. Thus, I am not able to get any entities that the user might have entered. Other dialogs that do not implement BotAuth have no issues with this.
What I am getting now from args is:
{ response: undefined, resumed: 4 }
I am guessing that the issue lies with the [].concat part in this section:
bot.dialog('refreshSchDialog-oauth', [].concat(
ba.authenticate("aadv2"),
(session, args, skip) => {
let user = ba.profile(session, "aadv2");
session.endDialog(user.displayName);
session.userData.accessToken = user.accessToken;
session.userData.refreshToken = user.refreshToken;
console.log('args');
console.log(args);
if (user.accessToken) {
session.send('got leh');
// valid access token, check if luis has any entities (MV name)
// if there is, store conversationData and move to next dialog
if (args.entities) {
for (i = 0; i < args.entities.length; i++) {
if (args.entities[i].type == 'dbName') {
session.conversationData.mvName = args.entities[i].entity;
session.send(args.entities[i].entity);
}
}
}
session.beginDialog('refreshSchDialog');
} else {
// no valid access token
// TODO error message
}
}))
.triggerAction({
matches: 'refreshSchema',
intentThreshold: 0.3
});
May I know why the args is not returning the information from LUIS?
Looking at the BotAuth code it appears that the Auth dialog returns the user if properly authenticated or false if the dialog failed. It doesn’t copy over the args from LUIS. I would change your code so that the first function in your waterfall stores the LUIS data into session.dialogData, then call ba.authenticate and then use both results in your last waterfall step.

display a herocard using luis action binding

How to create and display a "HeroCard" within the fulfill() function of LUIS action binding using node.js ? I am following the samples provided by the microsoft(https://github.com/Microsoft/BotBuilder-Samples/tree/master/Node/blog-LUISActionBinding)
Here is that how I tried to do this...
fulfill: function (parameters, callback) {
utilities.FilterFunction(parameters.x, parameters.y).then(function (matches){
utilities.CreateCard(session, matches).then(function(cards){
var reply = new builder.Message(session)
.attachmentLayout(builder.AttachmentLayout.carousel)
.attachments(cards);
callback(util.format(reply));
});
});
}
How can I use session value in the fulfill method?...without session "utilities.CreateCard" won't work...
Since session is not available in the action's fulfill method, we can make call to only to the utilities.FilterFunction and return the result via the callback. Now in our main js file, in the fulfillReplyHandler we get the actionModel which contains the result from utilities.FilterFunction.
Now we can create the "HeroCard" using the "session" that is accessible in the fulfillReplyHandler.

Facebook Messenger Bot, can someone tell me how i catch the answer of a something i asked

So i working on my Facebook Messenger Bot.
I want to know ho can i catch a answer for a question like
Bot: Enter your E-mail
User: enters e-mail
Bot: adress was added
My code looks like the sample app from Facebook
app.post('/webhook', function (req, res) {
var data = req.body;
// Make sure this is a page subscription
if (data.object == 'page') {
// Iterate over each entry
// There may be multiple if batched
data.entry.forEach(function(pageEntry) {
var pageID = pageEntry.id;
var timeOfEvent = pageEntry.time;
// Iterate over each messaging event
pageEntry.messaging.forEach(function(messagingEvent) {
if (messagingEvent.optin) {
receivedAuthentication(messagingEvent);
} else if (messagingEvent.message) {
receivedMessage(messagingEvent);
} else if (messagingEvent.delivery) {
receivedDeliveryConfirmation(messagingEvent);
} else if (messagingEvent.postback) {
receivedPostback(messagingEvent);
} else {
console.log("Webhook received unknown messagingEvent: ", messagingEvent);
}
});
});
// Assume all went well.
//
// You must send back a 200, within 20 seconds, to let us know you've
// successfully received the callback. Otherwise, the request will time out.
res.sendStatus(200);
}
});
You can set a flag for their ID that the E-Mail prompt was sent, and then after they respond check to see if it's an E-mail, and if so, then save it and echo it back to them.
If the bot is based on question/answer, what I normally do to handle response tracking is treat the bot like a finite state automata. Assign every "state" your bot can be in to some unique state identifier, and use said state identifier to determine what the user is replying to. You could also store callbacks instead of state ids, but high level this will behave the same way.
For Example:
First define a finite automata. In this case, lets assume it's:
0 --> 1 --> 2
Where 0 means new user, 1 means waiting for email response, 2 means user successfully completed registration.
User messages bot
We check our database and see it's a new user. We assume
state==0.
Because state is 0, we ignore what was sent and prompt for email
Change state to 1 to denote the email was prompted.
User replies with email.
We check database and see state==1. We use the "1" routine to do fancy stuff to verify the email and store it.
Change state to 2 to denote the email was received and the program has ended.
Note:
If the conversation id for the platform you're targeting is reset
after a certain amount of inactivity (or if you just want the bot to
mimic real conversations), store the time of each user's last
interaction and purge all inactive conversations well after the
conversation has been terminated.

Resources