Beautifully readable dialogue for facebook messenger bots
The goal of this library is to enable you to write bot dialogue in JavaScript or TypeScript. It utilizes template literals to enable you to write dialogue in a highly readable way, making it easier to review the dialogue at a glance, it currently has been designed to work with Facebook Messenger bots only. See dialogue-builder-example for a working example.
exports.default = dialogue('Onboarding ', (name) => [
say `Hi ${name}, welcome to nosy bot!`,
say `This inquisitive little bot will ask a bunch of questions for no reason`,
say `It will log your answers pointlessly to the console`,
say `You can always type back if you make mistake`,
ask `How old are you?`,
expect `My age is`, {
[onText]: (text) => console.log(`${name}'s age is ${text}`)
},
ask `What length is your hair?`,
expect `My hair length is`, {
'Long': (text) => console.log(`${name}'s hair is ${text}`),
'Short': (text) => console.log(`${name}'s hair is ${text}`),
'Shaved': (text) => {
console.log(`${name}'s hair is ${text}`);
return goto `after_hair_colour`;
},
},
ask `What colour is your hair?`,
expect `My hair colour is`, {
'Black': (text) => console.log(`${name}'s hair colour is ${text}`),
'Brown': (text) => console.log(`${name}'s hair colour is ${text}`),
'Blonde': (text) => console.log(`${name}'s hair colour is ${text}`),
},
'after_hair_colour',
ask `Where do you live?`,
expect `I live at`, {
[location]: (lat, long, title, url) => console.log(`User located at ${lat}, ${long}`)
},
say `Thanks ${name}, have a nice day`,
]);
npm install dialogue-builder
If the example dialogue above was defined in a file called onboarding.js
you would write the following to get your bot to follow it:
const botBuilder = require('claudia-bot-builder');
const { Dialogue } = require("dialogue-builder");
const onboarding = require('onboarding');
module.exports = botBuilder(function (message, apiRequest) {
const dialogue = new Dialogue(onboarding, {
store(state: Object) => console.log('Need to persist this somewhere')
retrieve() => return Promise.resolve(new Object())
}, 'Dave');
dialogue.setKeywordHandler('back', 'undo');
return dialogue.consume(message, apiRequest);
});
Dialogue builder is built on top of the excellent bot-builder by claudia.js and the code above is the entry point for a bot builder project.
Each invocation of the function above is caused by an incoming message from the user. The consume
method called above would continue the dialogue where it left off, calling any responses handlers for the incoming message and returning the next set of outgoing messages.
Except, in the example above, the bot would simply repeat the beginning of the dialogue each time the user sent a message because the storage handler (the store
and retrieve
methods) is not persisting the internal dialogue state (which is a JSON object). You would normally store this state under your user record in the persistence storage mechanism on your choosing. See dialogue-builder-example for an example on how to implement this using DynamoDB.
The dialogue-builder
module exports the following interface:
dialogue
function
say
, ask
, audio
, video
, image
, file
, expect
, goto
template literal tag functions
buttons
, list
functions
onText
, location
, onLocation
, onImage
, onAudio
, onVideo
, onFile
, defaultAction
symbols
UnexpectedInputError
class
Dialogue
class
mock
namespace
dialogue
functiondialogue(name: string, script: (...context: any) => Array<BaseTemplate | Label | Expect | Goto | ResponseHandler>): DialogueBuilder`
This function is used to define your script, the first arg is the name of your dialogue (not shown to the user) and the second is your script function which should return an array (the lines of your script). This function is passed any custom args you passed to the Dialogue constructor.
say
, ask
, audio
, video
, image
, file
, expect
, goto
template literal tag functionsThe array passed to the dialogue function form the lines of your script, an element in this array has to be one of:
say
string: Your bot will simply repeat the string passed inask
string: Identical to say
except only ask
statements are repeated on undo or an unhandled responseaudio
string: Send an audio file, the string passed in should be a urlvideo
string: Send a video file, the string passed in should be a urlimage
string: Send an image file, the string passed in should be a urlfile
string: Send a file, the string passed in should be a urlexpect
string: This statement marks a break in the dialogue to wait for a user response. The string you pass is the response you expect from the user, it's used as a key when persisting the state of the conversation and so must be a string unique amongst all expect statements. An expect statement must always be immediately followed by a ResponseHandler
goto
string: A goto statement will cause the dialogue to jump to another location in the script. The string you pass in specifies the label to jump to. goto
statements can also be returned from a ResponseHandler's
methodsfbTemplate.BaseTemplate
: You can embed any facebook message type supported by bot builder directly in your script, see Facebook Template Builder for more infobuttons
, list
functionstype ButtonHandler = { [title: string]: () => Goto | void }
type Bubble = [string, string, string, ButtonHandler]
function buttons(id: string, text: string, handler: ButtonHandler): Button
function list(id: string, type: 'compact' | 'large', bubbles: Bubble[], handler: ButtonHandler): List
The buttons
function allows you to send a Button Template in your script. The first arg must be a string unique amongst all templates defined in your script, the second is the title to display to the user,and the third is your button handler object which defines the buttons names and handler functions in the same way as quick replies in a ResponseHandler
, it returns a Button
The list
function allows you to send a List Template in your script. The first arg must be a string unique amongst all templates defined in your script, the second is the list type, the third is an array of bubbles in your list, and the forth is a button handler object which defines the button names and handler function in the same way as quick replies in a ResponseHandler
, it returns a List
location
, onText
, onLocation
, onImage
, onAudio
, onVideo
, onFile
, defaultAction
symbolsA ResponseHandler
is an object who's methods are called on receiving a message from the user to handle the response to the immediately preceding expect
statement. The supported methods are:
(text?: string)
: A string property causes a quick reply to be attached to the last outgoing message, the function is called on the user selecting the reply, the text passed in will always be the same as the function name[location](lat: number, long: number, title?: string, url?: string)
: The location
symbol property causes a location quick reply to be attached to the last outgoing message, the function is called on the user selecting the reply[onText](text: string)
: The onText
symbol property is called when the user types a text response that doesn't match any of the quick replies[onLocation](lat: number, long: number, title?: string, url?: string)
: The onLocation
symbol property is called when the user sends a location, you cannot define both location
and onLocation
properties on the same response handler[onImage](url: string)
: The onImage
symbol property is called when the user sends an image[onAudio](url: string)
: The onAudio
symbol property is called when the user sends an audio recording[onVideo](url: string)
: The onVideo
symbol property is called when the user sends a video[onFile](url: string)
: The onFile
symbol property is called when the user sends a file[defaultAction]()
: The defaultAction
symbol property is called if no other mathod matches the user's response so can be used as a catch all. It is also used to specify the default action on buttons and lists
All response handler methods support returning one of Goto | Expect | void
, you can also return a promise resolving to one of the same set of types: Promise<Goto | Expect | void>
Returning a goto statement from a ResponseHandler
method will cause the dialogue to jump to the specified label
Returning a expect statement from a ResponseHandler
method will delegate the handling of the response to the relevant handler function of the response handler defined for the expect statement specified
UnexpectedInputError
classclass UnexpectedInputError {
constructor(message: string, repeatQuestion?: boolean)
}
When a ResponseHandler
recieves a message from the user for which is does not contain a handler method for an instance of UnexpectedInputError
is thrown, this will cause the question to be repeated. You can invoke this behaviour in a handled response by throwing this error from the handler method.
The string you pass to the constructor will be sent to the user followed by repeating the question (the ask statements). If you don't want to repeat the question, pass true
as the second constructor arg
Dialogue
classclass Dialogue {
constructor(builder: DialogueBuilder, storage: Storage, ...context: any)
baseUrl: string
execute(directive: Goto | Expect)
consume(message: Message, apiRequest: Request): Promise<any[]>
setKeywordHandler(keywords: string | string[], handler: 'restart' | 'undo' | (() => void | Goto)): void
}
The Dialogue
class constructor has two required args, the first is the dialogue (the return value from the dialogue
function and the second is the storage handler, you need to pass an object conforming to the following interface to store the dialogue state, typically under your user record in a persistence storage mechanism on your choosing:
interface Storage {
store(state: Object): Promise<void>
retrieve(): Promise<Object>
}
Any additional args passed to the constructor are passed to the dialogue
function this would typically be used to pass through the user's details to customize the dialogue plus any object needed in the ResponseHandlers
to act on user responses.
Setting the baseUrl
property allows you to pass uris into functions that would normally expect a full url, such as audio
, video
, image
, file
template literal tag functions and the buttons
, list
functions
Call the execute
method to jump to another location in the script specified by a goto
or expect
statement. This is useful for writing unit tests in combination with the mock
namespace
Call the consume
method to process the input from the user, you need pass in the message and apiRequest from your bot builder handler method, you can return the result of this method directly from your bot builder handler method.
Call the setKeywordHandler
method to create a keyword which will trigger the callback passed in whenever the user sends any of the keywords passed as the first arg, at any point in the conversation. The callback can return a goto statement to cause the dialogue to jump to the specified label.
Two built-in keyword handlers exist, which you can assign keywords to by replacing the callback with either undo
or restart
undo
The undo keyword handler will repeat the last question asked in the dialogue, allowing the user to correct a mistake
restart
The restart keyword handler will reset the dialogue to the beginning and is useful to enable during development: TIP: Set your restart keyword to match your Get Started button call to action payload so when users delete the conversation and initiate a new one your dialogue will begin from the start
mock
namespaceexport namespace mock {
const apiRequest: Request
function message(text: string): Message
function postback(payload?: string): Message
function location(lat: number, long: number, title?: string, url?: string): Message
function multimedia(type: 'image' | 'audio' | 'video' | 'file' | 'location', url: string): Message
}
The constants and functions defined in the mock
namespace allow you to easily mock input when calling the consume
method of the Dialogue
class, for example:
dialogue.consume(mock.message('Hi'), mock.apiRequest)