How do I best modularize dialogs with Microsoft Bot Framework (v3)? - node.js

I've just been given a project where I'll need to learn and deal with Microsoft Bot Framework, version 3.2 (it's an old project that I'm modifying).
I'm working through the examples and trying to understand how dialog flow works, and how I might best modularize it.
As I understand it, when you create your bot like this
// CODE SAMPLE 1
const bot = new builder.UniversalBot(connector, [
function (session) {
session.send("Welcome to the dinner reservation.");
session.beginDialog('askForDateTime');
},
/*
functions omitted for brevity
*/
]).set('storage', inMemoryStorage); // Register in-memory storage
the array of functions that comprise your "default dialog" must be given when the bot is created -- that is, you can't add the default dialog at a later point, or change it. Is that correct?
Then, later, if you want to modularize your dialog structure, you can have something like this (referring to the code above)
// CODE SAMPLE 2 (in same file as code above)
bot.dialog('askForDateTime', [
function (session) {
builder.Prompts.time(session, "Please provide a reservation date and time (e.g.: June 6th at 5pm)");
},
function (session, results) {
session.endDialogWithResult(results);
}
]);
So, is bot.dialog registering this dialog with the bot for later use? That is, there's a look-up of some sort at run-time -- during the conversation -- based on this string that connects session.beginDialog('askForDateTime'); in the first code sample with the functions registered with bot.dialog('askForDateTime') in the second code sample?
When I looked at the SDK reference, I see that beginDialog accepts an IAddress
function beginDialog(address: IAddress, dialogId: string, dialogArgs?: any, done?: (err: Error) => void)
where it says
Address routing information for an event. Addresses are bidirectional
meaning they can be used to address both incoming and outgoing events.
They're also connector specific meaning that connectors are free to
add their own fields to the address.
So this 'registration' via string is basically an event registration system, kind of like addEventListener, but in this case it's not registering an action per se, but a dialog?
Two last questions:
Can one call session.beginDialog from within a bot.dialog? That is, have a nested tree of dialogs? As it is, the only example is of nesting from the default dialog, but I didn't know if it could go deeper.
Finally, how can one modularize your dialogs into separate node modules, that is, move your sub-dialogs into separate files? I thought of something like this:
// askForDateTime.js
module.exports = bot =>
bot.dialog('askForDateTime', [
function (session) {
builder.Prompts.time(session, "Please provide a reservation date and time (e.g.: June 6th at 5pm)");
},
function (session, results) {
session.endDialogWithResult(results);
}
]);
but don't see how to use it in my main app
// app.js
const askDateTimeDialog = require('./askForDateTime')(bot) // how to use this? I need to pass in a bot that's not yet created. Do I even need to import it?
const bot = new builder.UniversalBot(connector, [
function (session) {
session.send("Welcome to the dinner reservation.");
session.beginDialog('askForDateTime'); // <--- how to beginDialog with imported dialog? Or is the 'registration' behind the scenes sufficient since I'm just using the same string?
},
/*
functions omitted for brevity
*/
]).set('storage', inMemoryStorage); // Register in-memory storage
Thanks for any help! I realize this probably all is easier with version 4, but I need to use an earlier version.

First and foremost, please be aware the Botbuilder V3 is in sunset with support ending at the end of 2019. You can read more about this topic here and options for migrating from v3 to v4 here.
Regarding default dialogs, the other method is to only pass the connector into the adapter and then start a dialog when the conversationUpdate occurs. There are some challenges that can bubble up when using conversationUpdate, so I would look over this blog post before continuing.
var bot = new builder.UniversalBot(connector)
bot.on('conversationUpdate', function (message) {
if (message.membersAdded) {
message.membersAdded.forEach(function (identity) {
if (identity.id === message.address.bot.id) {
bot.beginDialog(message.address, '/main');
}
});
}
});
// ----greetings.js -----
bot.dialog('/main', [
function (session, args, next) {
session.send("Glad you could join.");
session.beginDialog('/nextDialog');
}
]);
Regarding beginDialog() registration/addressing, there is no pre-registeration that occurs. When a dialog is called, it's essentially called like any other function is. The address, more or less, is used for managing dialog state (i.e. where a bot is in a conversation) - whether a dialog is being added to stack, is in use, or being popped off the stack.
Regarding calling one dialog within another, yes it is doable, as you can see in this sample:
lib.dialog('/', [
[...other functions...]
function (session, args) {
session.dialogData.recipientPhoneNumber = args.response;
session.beginDialog('validators:notes', {
prompt: session.gettext('ask_note'),
retryPrompt: session.gettext('invalid_note')
});
},
[...other functions...]
]);
Lastly, regarding modularizing, yes, this is also doable. Look over either of these two samples. core-MultiDialogs is the less complicated but demo-ContosoFlowers is also a good example to reference.
Hope of help!

Related

problem when making call interconnection using the plivo api

I am currently working with the plivo api to build an ivr, however, I have used all the recommendations given by the documentation and so far I can not establish a successful connection within the conference calls in the application, below I attach the code that is involved in the conference function.
getDialConnecting(numberFrom, numberTo, route){
let ivr = new Ivr();
let client = ivr.getClient();
client.calls.create(
`${numberFrom}`,
`${numberTo}`,
`${process.env.HOST}${route}`,
{
answerMethod: "POST"
},
).then(function(response){
console.log(response);
}, function(err){
console.log(err);
});
this function is called each time I make a conference call and enter the following parameters
I am currently working with the plivo api to build an ivr, however, I have used all the recommendations given by the documentation and so far I can not establish a successful connection within the conference calls in the application, below I attach the code that is involved in the conference function.
call.getDialConnecting(`${incomingNumber}`, `${incomingNumberTransmitter}`, 'conference');
in addition this is the path that is performing the handling of the function that accepts the call
const ivrGetConference = route.post('/voice/conference', call.callRequestConfirmed);
My name is Mohammed Huzaif, and I work at Plivo as a Product Evangelist.
From the information shared, I'm unable to determine the error you may have received on your end or the documents utilised.
However, you can follow the below steps to build an IVR.
First, we'll create our IVR, To do so, follow the directions in this documentation.
Once the IVR system is developed, we will make a call to the destination number by using the URL generated in above step.
To make a call, use the below code.
Note: Replace the placeholders "from": with the caller_id, "to": Destination number, and "answer_url": the url generated in above step.
var plivo = require('plivo');
(function main() {
'use strict';
var client = new plivo.Client("<auth_id>","<auth_token>"); // https://console.plivo.com/dashboard/
client.calls.create(
"+14151234567", // from
"+15671234567", // to
"https://s3.amazonaws.com/static.plivo.com/answer.xml", // answer url
{
answerMethod: "POST",
},
).then(function (response) {
console.log(response);
}, function (err) {
console.error(err);
});})();
In case, if you still need any assistance, feel free to reach out to our support-team.

Handling asynchronous calls in bot framework

We are using a Bot configured via Microsoft Bot Framework written in NodeJS. During the execution flow of a dialog, we present the user with certain information and then some server processing is done via SOAP and the result of this SOAP response would be needed before the next waterfall method starts.
In short, we have the below piece of code:
bot.dialog('changedefaultlogingroupDialog', [
async function (session, args, next) {
wargs[0] = 'change default login group';
var sourceFile = require('./fetchSharePointUserDetail.js');
session.privateConversationData.userSharepointEmail = global.DEVSharepointBotRequestorEmailID;
console.log('\nsession.privateConversationData.userSharepointEmail:'+session.privateConversationData.userSharepointEmail);
var get_SharepointUserId_args = ['get Sharepoint user id', session.privateConversationData.userSharepointEmail];
sourceFile.login(get_SharepointUserId_args);
setTimeout(() => {
global.DEVSharepointTeamcenterUserID = require('./fetchSharePointUserDetail.js').DEVTeamcenterUserId;
console.log('\nglobal.DEVSharepointTeamcenterUserID:'+global.DEVSharepointTeamcenterUserID+'\n');
console.log("Request has been made from directline channel by user id <"+global.DEVSharepointTeamcenterUserID+">");
session.privateConversationData.requestor_id = global.DEVSharepointTeamcenterUserID;
session.privateConversationData.create_ques = session.message.text;
next();
}, 3000);
},
async function (session, result, next) {
Do processing here that is dependent on session.privateConversationData.requestor_id
}
As you can see from the above example, the setTimeout method is waiting for 3 seconds to have the SOAP response retrieved. While this worked in DEV landscape, it failed in our PRD landscape. So I wanted to know what is the more appropriate way of doing this. Is 'await' a correct case for using in this context?. I am asking this as this is in BOT Framework Context and not sure if that has any side affects.
Please suggest.
Thanks,
Pavan.
Await is the correct way to look at this.
I'm not familiar with the bot framework, but I'm guessing that they asynchronous part of your code happens during the login.
await sourceFile.login(get_SharepointUserId_args);
Would be where the asynchronous call is. It could also be in the fetchSharePointUserDetail.js
There is likely a better way to load that file as a module so that you are calling functions on a returned object, rather than returning variables from some code that is obviously executing something.

Return Stripe Response within Firebase Function to Swift Application

I’m trying to make an iOS that connects to Stripe and can show user information and data and things.
I’m doing this by using Firebase Functions, so I don’t have to maintain a server, and also because I’m a newb to the extreme.
But when I try to say, get a customer by using the Firebase callable functions, ex.
exports.getCustomer = functions.callableFunctions((data, context) => {
stripe.customers.retrieve(
data.customerID, function (err, customer) {
console.log(‘customer’)
});
});
I’m not sure where to place the “return” in order to actually use that ‘customer’ object in my app. I tried to stick a “return customer” under the console.log, but it’s never....... returning. I’ve also tried creating an empty string variable that I set after the console log and return, but that is always coming up as an empty string on the app.
Sorry for the typesetting issues, and this question is very theoretical - I’m typing on my phone because I don’t want to forget it and I’ll be away from my computer for a while.
Can anyone provide any guidance on how I’d return the ‘customer’ object to my iOS app?
As detailed here, since the release of the Stripe node.js API version 2, they added support for promises: "Every resource method now returns a Promises/A+ compliant promise in addition to supporting the conventional callback".
So you could do as follows:
exports.getCustomer = functions.callableFunctions((data, context) => {
const stripeCustomerID = data.customerID;
return stripe.customers.retrieve(stripeCustomerID)
.then(customer => {
return { customer: customer };
})
.catch(err => {
throw new functions.https.HttpsError('<status error code>', 'xxxxxxx');
});
});
As indicated in the doc, have a look here for the possible values of

Bot Framework Node.js ad hoc message TO A SPECIFIC USER

I have been staring at this for hours and can't find a solution and that is even though by all suggestions it SHOULD be quite easy - https://learn.microsoft.com/en-us/bot-framework/nodejs/bot-builder-nodejs-proactive-messages.
I have created a simple code which will "register" the user and save their data in my cosmosDatabse on Azure. That works perfectly.
//ON "register" SAVE USER DATA AND SAY REGISTERED MESSAGE
bot.dialog('adhocDialog', function(session, args) {
var savedAddress = session.message.address;
session.userData.savedAddress = savedAddress;
//REGISTERED MESSAGE
session.endDialog("*Congratulations! You are now registered in our network! (goldmedal)*");
})
.triggerAction({
matches: /^register$/i
})
But how can I then access that specific user and send him a message if, say, a condition is met? (in fact on HTTP request)
I am fairly certain we have to write the conversation ID or user ID somewhere. The question is where?
function startProactiveDialog(address) {
bot.beginDialog(address, "A notification!");
}
This is how simple I think it should be. But where do you specify the user then?
You've saved the address of the user inside of your database by saving it to session.userData.savedAddress. When the event triggers, perform a query to your database that checks for the users that meet two criteria.
They're registered to listen for the event
Their address has been saved inside of the database.
In your case, you can save a property to the session.userData object, a property that lists which events they're listening for. If you just need to send a message to the user, then you can simply use bot.loadSession(savedAddress) to ping the user.
Edit:
So instead of looking specifically by user ID, you should send a query to your CosmosDB that looks for entries that have a "listen-to" Boolean-type flag corresponding to the event.
You're not worrying about the user ID at first, you're just retrieving all entries with a query that would (broadly speaking) look like this:
SELECT * FROM BotState WHERE data LIKE 'listenForEvent=1.
So to setup your session.userData so that the above theoretical query would work, you would need to modify that snippet of code in your question to something like the following:
bot.dialog('adhocDialog', function(session, args) {
var savedAddress = session.message.address;
session.userData.savedAddress = savedAddress;
session.userData.listenForEvent = 1 // Our property we're going to look for.
session.endDialog("*Congratulations! You are now registered in our network! (goldmedal)*");
})
.triggerAction({
matches: /^register$/i
})
Actually, the savedAddress should be an instance of IAddress, and also, the function loadSession(address: IAddress, callback: (err: Error, session: Session) => void): void; and address(adr: IAddress): Message; under Message class all require IAddress as the parameter.
So first of all, you should save the entire address json object in cosmosDB for later using.
As botbuilder for Node.js is built on Restify or Express, you can build an addition route for your user to trigger and send proactive messages. The work flow could be following:
Guide user to register & Save the user's address object with the account mapping in your DB
Create a Route in Restify or Expressjs for trigger the proactive message:
server.get('/api/CustomWebApi', (req, res, next) => {
//find the user's address in your DB as `savedAddress`
var msg = new builder.Message().address(savedAddress);
msg.text('Hello, this is a notification');
bot.send(msg);
res.send('triggered');
next();
}
);
or if you want to leverage loadSession
server.get('/api/CustomWebApi', function (req, res, next) {
bot.loadSession(savedAddress, (err, session) => {
if (!err) {
session.send('Hello, this is a notification')
session.endConversation();
}
})
res.send('triggered');
next();
});
I created a users.json file, to which I save all the users. It works the way I need it to. I guess database would be better, but I don't really have a clue where to begin with that. Database is a whole new chapter I have not encountered yet, so it doesn't make sense to work on it when the project needs are resolved.

Bot Framework: Handle incorrect user inputs

I've built a bot that asks user to upload an attachment. But I also want to give the ability to the user to just type any text instead of uploading an attachment, but whenever I do that it says
I didn't receive a file. Please try again.
In the command line, I can see it says no intent handler found for null. How do I handle these nulls/incorrect inputs?
Sample code:
intents.matchesAny([/lost and found/i], [
function (session) {
builder.Prompts.attachment(session,"Please upload a picture of the item.");
},
function (session) {
session.endConversation('Thank you');
}
]);
Per your issue message, no intent handler found for null, which seems that you are using builder.IntentDialog, and the issue means that your bot didn't match any intents provided in your bot.
Also I notice that your are using intents.matchesAny, according to the comment:
Invokes a handler when any of the given intents are detected in the users utterance. So I think you forget to set such intent lost and found in your LUIS server.
If you want to trigger any miss catched user utterance, you can try to use:
intents.onDefault([
function (session) {
builder.Prompts.attachment(session,"Please upload a picture of the item.");
},
function (session) {
session.endConversation('Thank you');
}
]);

Resources