Bot - Full Example: Libraries #3
//See: https://github.com/Microsoft/BotBuilder-Samples/tree/master/Node/capability-SimpleTaskAutomation
Bot Libraries for Creating Reusable Dialogs
Libraries of reusable parts can be developed by creating a new Library instance and adding dialogs just as you would to a bot. Your library should have a unique name that corresponds to either your libraries website or NPM module name. Bots can then reuse your library by simply adding your parts Library instance to their bot using UniversalBot.library().
To invoke dialogs within the bot, we use session.beginDialog() with a fully qualified dialog id in the form of ':'.
E.g.: To start the reset password's experience root dialog we use session.beginDialog('resetPassword:/).
// /dialogs/reset-password.js
const library = new builder.Library('resetPassword');
library.dialog('/', [
function (session) {
...
},
...
]);
// /app.js
var bot = new builder.UniversalBot(connector, [
function (session) {
builder.Prompts.choice(session,
'What do yo want to do today?',
[ChangePasswordOption, ResetPasswordOption],
{ listStyle: builder.ListStyle.button });
},
function (session, result) {
if (result.response) {
switch (result.response.entity) {
case ChangePasswordOption:
session.send('This functionality is not yet implemented! Try resetting your password.');
session.reset();
break;
case ResetPasswordOption:
session.beginDialog('resetPassword:/');
break;
}
} else {
session.send('I am sorry but I didn\'t understand that. I need you to select one of the options below');
}
},
...
]);
Another more common approach for this feature is encapsulating a re-usable dialog. A good example of these are prompt validators. In this sample, common validations are packaged in the validators library.
This is how you could package a phone validation:
var builder = require('botbuilder');
const PhoneRegex = new RegExp(/^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$/);
const library = new builder.Library('validators');
library.dialog('phonenumber',
builder.DialogAction.validatedPrompt(builder.PromptType.text, (response) =>
PhoneRegex.test(response)));
module.exports = library;
module.exports.PhoneRegex = PhoneRegex;
And this is how you can call the validator from your existing code:
//
library.dialog('/', [
function (session) {
session.beginDialog('validators:phonenumber', {
prompt: 'Please enter your phone number:',
retryPrompt: 'The value entered is not phone number. Please try again using the following format (xyz) xyz-wxyz:',
maxRetries: 2
});
},
function (session, args) {
if (args.resumed) {
session.send('You have tried to enter your phone number many times. Please try again later.');
session.endDialogWithResult({ resumed: builder.ResumeReason.notCompleted });
return;
}
session.dialogData.phoneNumber = args.response;
session.send('The phone you provided is: ' + args.response);
builder.Prompts.time(session, 'Please enter your date of birth (MM/dd/yyyy):', {
retryPrompt: 'The value you entered is not a valid date. Please try again:',
maxRetries: 2
});
},
It is worth noting that calling other dialogs within your library don't need to be prefixed with the library's id. It is only when crossing from one library context to another that you need to include the library name prefix on your session.beginDialog() calls.
To limit the times the user will reprompt when the response is not valid, the maxRetries can be specified
If the maximum number of retries are reached the next dialog is called with the args.resumed set in notCompleted. The message the bot will send when an no valid user input was received is customizable with the retryPrompt.
// == FULL CODE FOLLOWS ==========
// In APP.JS
var builder = require('botbuilder');
var restify = require('restify');
// Setup Restify Server
var server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3978, function () {
console.log('%s listening to %s', server.name, server.url);
});
// Create chat bot
var connector = new builder.ChatConnector({
appId: process.env.MICROSOFT_APP_ID,
appPassword: process.env.MICROSOFT_APP_PASSWORD
});
const ChangePasswordOption = 'Change Password';
const ResetPasswordOption = 'Reset Password';
var bot = new builder.UniversalBot(connector, [
(session) => {
builder.Prompts.choice(session,
'What do yo want to do today?',
[ChangePasswordOption, ResetPasswordOption],
{ listStyle: builder.ListStyle.button });
},
(session, result) => {
if (result.response) {
switch (result.response.entity) {
case ChangePasswordOption:
session.send('This functionality is not yet implemented! Try resetting your password.');
session.reset();
break;
case ResetPasswordOption:
session.beginDialog('resetPassword:/');
break;
}
} else {
session.send(`I am sorry but I didn't understand that. I need you to select one of the options below`);
}
},
(session, result) => {
if (result.resume) {
session.send('You identity was not verified and your password cannot be reset');
session.reset();
}
}
]);
//Sub-Dialogs
bot.library(require('./dialogs/reset-password'));
//Validators
bot.library(require('./validators'));
server.post('/api/messages', connector.listen());
//In RESET-PASSWORD.JS
var builder = require('botbuilder');
const uuid = require('uuid');
const library = new builder.Library('resetPassword');
library.dialog('/', [
(session) => {
session.beginDialog('validators:phonenumber', {
prompt: 'Please enter your phone number:',
retryPrompt: 'The value entered is not phone number. Please try again using the following format (xyz) xyz-wxyz:',
maxRetries: 2
});
},
(session, args) => {
if (args.resumed) {
session.send('You have tried to enter your phone number many times. Please try again later.');
session.endDialogWithResult({ resumed: builder.ResumeReason.notCompleted });
return;
}
session.dialogData.phoneNumber = args.response;
session.send('The phone you provided is: ' + args.response);
builder.Prompts.time(session, 'Please enter your date of birth (MM/dd/yyyy):', {
retryPrompt: 'The value you entered is not a valid date. Please try again:',
maxRetries: 2
});
},
(session, args) => {
if (args.resumed) {
session.send('You have tried to enter your date of birth many times. Please try again later.');
session.endDialogWithResult({ resumed: builder.ResumeReason.notCompleted });
return;
}
session.send('The date of birth you provided is: ' + args.response.entity);
var newPassword = uuid.v1();
session.send('Thanks! Your new password is _' + newPassword + '_');
session.endDialogWithResult({ resumed: builder.ResumeReason.completed });
}
]).cancelAction('cancel', null, { matches: /^cancel/i });
module.exports = library;
//In VALIDATORS.JS
var builder = require('botbuilder');
const PhoneRegex = new RegExp(/^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$/);
const library = new builder.Library('validators');
library.dialog('phonenumber',
builder.DialogAction.validatedPrompt(builder.PromptType.text, (response) =>
PhoneRegex.test(response)));
module.exports = library;
module.exports.PhoneRegex = PhoneRegex;