Prg-chatbot - Facebook Messenger platform framework
Framework for building reusable chatbot components. Routing, Keyword recognition is built-in.
Requirements and installation
- requires
mongoose
> 4.0 - requires
nodejs
> 6.0 - requires
express
> 4.0 - requires
body-parser
> 1.10
$ npm i -S prg-chatbot
Basic setup with Express
It's easy. This basic example can handle everything. Note, that the handler is STRICTLY SYNCHRONOUS.
const express = require('express');
const bodyParser = require('body-parser');
const { createRouter, createProcessor } = require('prg-chatbot/express');
const handler = (req, res, postBack) => {
res.typingOn()
.wait();
switch (req.action()) {
case 'hello':
res.text('Hello world');
return;
default:
res.text('What you want?', {
hello: 'Say hello world'
})
}
};
const processor = createProcessor(handler, {
pageToken: 'stringhere',
appSecret: 'botappsecret'
});
app.use('/bot', createRouter(processor));
Processing asynchronous actions
How to deal with asynchronous action, when handlers are synchronous? Use postBack(action[, data])).
const handler = (req, res, postBack) => {
switch (req.action()) {
case 'downloaded':
const { data, err } = req.action(true);
if (err && err.code === 400) {
res.text('Image size exceeded');
} else if (err) {
res.text('Upload failed');
} else {
res.text('Hello world');
}
return;
default:
if (req.isImage()) {
const resolve = postBack.wait();
bufferloader(req.attachmentUrl(), 1000000)
.then(buffer => resolve('downloaded', { data: buffer }))
.catch(err => resolve('donwloaded', { err }))
}
}
};
Experimental: Router
Router is the way to handle strucured complex bots
const { Router } = require('prg-chatbot');
const app = new Router();
app.use('*', (req, res, postBack, next) => {
res.typingOn()
.wait();
next();
});
app.use('actionName', (req, res) => {
if (req.state.secondAttempt) {
res.text('hello again');
} else {
res.text('hello')
.setState({ secondAttempt: true });
}
});
app.use('textAction', /keyword|two-words?/, (req, res) => {
res.text('Welcome', {
actionName: 'Say hello'
})
});
API
Classes
- Request
- Responder
- ButtonTemplate ⇐
BaseTemplate
- ReceiptTemplate ⇐
BaseTemplate
- GenericTemplate ⇐
ButtonTemplate
- Router ⇐
ReducerWrapper
- ReducerWrapper ⇐
EventEmitter
- Settings
- Tester
- ResponseAssert
- AnyResponseAssert
Functions
- bufferloader(url, [limit], [limitJustByBody], [redirCount]) ⇒
Downloads a file from url into a buffer. Supports size limits and redirects.
- attachmentType(response, type, [message]) ⇒
boolean
Checks attachment type
- isText(response, [message]) ⇒
boolean
Checks, that response is a text
- contains(response, search, [message]) ⇒
boolean
Checks, that text contain a message
- quickReplyAction(response, action, [message]) ⇒
boolean
Checks quick response action
- templateType(response, expectedType, [message]) ⇒
boolean
Checks template type
- waiting(response, [message]) ⇒
boolean
Looks for waiting message
Request
Kind: global class
new Request()
Instance of {Request} class is passed as first parameter of handler (req)
request.state
Kind: instance property of Request
Properties
Name | Type | Description |
---|
state | object | current state of the conversation |
request.isAttachment() ⇒ boolean
Checks, when message contains an attachment (file, image or location)
Kind: instance method of Request
request.isImage([attachmentIndex]) ⇒ boolean
Checks, when the attachment is an image
Kind: instance method of Request
Param | Type | Default | Description |
---|
[attachmentIndex] | number | 0 | use, when user sends more then one attachment |
request.isFile([attachmentIndex]) ⇒ boolean
Checks, when the attachment is a file
Kind: instance method of Request
Param | Type | Default | Description |
---|
[attachmentIndex] | number | 0 | use, when user sends more then one attachment |
request.attachment([attachmentIndex]) ⇒ object
| null
Returns whole attachment or null
Kind: instance method of Request
Param | Type | Default | Description |
---|
[attachmentIndex] | number | 0 | use, when user sends more then one attachment |
request.attachmentUrl([attachmentIndex]) ⇒ string
| null
Returns attachment URL
Kind: instance method of Request
Param | Type | Default | Description |
---|
[attachmentIndex] | number | 0 | use, when user sends more then one attachment |
request.isMessage() ⇒ boolean
Returns true, when the request is text message, quick reply or attachment
Kind: instance method of Request
request.text([tokenized]) ⇒ string
Returns text of the message
Kind: instance method of Request
Param | Type | Default | Description |
---|
[tokenized] | boolean | false | when true, message is normalized to lowercase with - |
Example
console.log(req.text(true))
request.quickReply([getData]) ⇒ null
| string
| object
Returns action or data of quick reply
When getData
is true
, object will be returned. Otherwise string or null.
Kind: instance method of Request
Param | Type | Default |
---|
[getData] | boolean | false |
Example
typeof res.quickReply() === 'string' || res.quickReply() === null;
typeof res.quickReply(true) === 'object';
request.isPostBack() ⇒ boolean
Returns true, if request is the postback
Kind: instance method of Request
request.action([getData]) ⇒ null
| string
| object
Returns action of the postback or quickreply
When getData
is true
, object will be returned. Otherwise string or null.
- the postback is checked
- the quick reply is checked
- expected keywords are checked
- expected state is checked
Kind: instance method of Request
Param | Type | Default |
---|
[getData] | boolean | false |
Example
typeof res.action() === 'string' || res.action() === null;
typeof res.action(true) === 'object';
request.postBack([getData]) ⇒ null
| string
| object
Returns action or data of postback
When getData
is true
, object will be returned. Otherwise string or null.
Kind: instance method of Request
Param | Type | Default |
---|
[getData] | boolean | false |
Example
typeof res.postBack() === 'string' || res.postBack() === null;
typeof res.postBack(true) === 'object';
Responder
Kind: global class
new Responder()
Instance of responder is passed as second parameter of handler (res)
responder.text(text, [quickReplys]) ⇒ this
Send text as a response
Kind: instance method of Responder
Param | Type | Description |
---|
text | string | text to send to user, can contain placeholders (%s) |
[quickReplys] | object.<string, string> | |
Example
res.text('Hello %s', name, {
action: 'Quick reply',
complexAction: {
title: 'Another quick reply',
match: 'string' || /regexp/,
someData: 'Will be included in payload data'
}
})
responder.setState(object) ⇒ this
Sets new attributes to state (with Object.assign())
Kind: instance method of Responder
Example
res.setState({ visited: true });
responder.expected(action) ⇒ this
When user writes some text as reply, it will be processed as action
Kind: instance method of Responder
Param | Type | Description |
---|
action | string | desired action |
responder.image(imageUrl) ⇒ this
Sends image as response. Requires appUrl option to send images from server
Kind: instance method of Responder
Param | Type | Description |
---|
imageUrl | string | relative or absolute url |
Example
res.image('/img/foo.png');
res.image('https://google.com/img/foo.png');
responder.wait([ms]) ⇒ this
Sets delay between two responses
Kind: instance method of Responder
Param | Type | Default |
---|
[ms] | number | 600 |
responder.typingOn() ⇒ this
Sends "typing..." information
Kind: instance method of Responder
responder.typingOff() ⇒ this
Stops "typing..." information
Kind: instance method of Responder
responder.seen() ⇒ this
Reports last message from user as seen
Kind: instance method of Responder
responder.receipt(recipientName, [paymentMethod], [currency], [uniqueCode]) ⇒ ReceiptTemplate
Sends Receipt template
Kind: instance method of Responder
Param | Type | Default | Description |
---|
recipientName | string | | |
[paymentMethod] | string | "'Cash'" | should not contain more then 4 numbers |
[currency] | string | "'USD'" | sets right currency |
[uniqueCode] | string | null | when omitted, will be generated randomly |
Example
res.receipt('Name', 'Cash', 'CZK', '1')
.addElement('Element name', 1, 2, '/inside.png', 'text')
.send();
Sends nice button template. It can redirect user to server with token in url
Kind: instance method of Responder
Example
res.button('Hello')
.postBackButton('Text', 'action')
.urlButton('Url button', '/internal', true)
.urlButton('Other button', 'https://goo.gl')
.send();
Creates a generic template
Kind: instance method of Responder
Example
res.genericTemplate()
.addElement('title', 'subtitle')
.setElementImage('/local.png')
.setElementUrl('https://www.seznam.cz')
.postBackButton('Button title', 'action', { actionData: 1 })
.addElement('another', 'subtitle')
.setElementImage('https://goo.gl/image.png')
.setElementAction('action', { actionData: 1 })
.urlButton('Local link with extension', '/local/path', true, 'compact')
.send();
ButtonTemplate ⇐ BaseTemplate
Kind: global class
Extends: BaseTemplate
new ButtonTemplate()
Helps with creating of button template
Instance of button template is returned by {Responder}
buttonTemplate.urlButton(title, url, hasExtension, [webviewHeight]) ⇒ this
Adds button. When hasExtension
is set to true
, url will contain hash like:
#token=foo&senderId=23344
Kind: instance method of ButtonTemplate
Param | Type | Default | Description |
---|
title | string | | button text |
url | string | | button url |
hasExtension | boolean | | includes token in url |
[webviewHeight] | string | null | compact |
buttonTemplate.postBackButton(title, action, [data]) ⇒ this
Adds button, which makes another action
Kind: instance method of ButtonTemplate
Param | Type | Default | Description |
---|
title | string | | Button title |
action | string | | Button action (can be absolute or relative) |
[data] | object | {} | Action data |
ReceiptTemplate ⇐ BaseTemplate
Kind: global class
Extends: BaseTemplate
new ReceiptTemplate()
Provides fluent interface to make nice Receipts
Instance of button template is returned by {Responder}
receiptTemplate.addElement(title, [price], [quantity], [image], [subtitle]) ⇒ this
Adds item to receipt
Kind: instance method of ReceiptTemplate
Param | Type | Default | Description |
---|
title | string | | |
[price] | number | 0 | a item price |
[quantity] | number |
| amount of items |
[image] | string | null | image of item |
[subtitle] | string | null | optional subtitle |
Kind: global class
Extends: ButtonTemplate
- GenericTemplate ⇐
ButtonTemplate
- new GenericTemplate()
- .addElement(title, [subtitle], [dontTranslate]) ⇒
this
- .setElementUrl(url, [hasExtension]) ⇒
this
- .setElementImage(image) ⇒
this
- .setElementAction(url, hasExtension, [webviewHeight])
- .urlButton(title, url, hasExtension, [webviewHeight]) ⇒
this
- .postBackButton(title, action, [data]) ⇒
this
new GenericTemplate()
Generic template utility
genericTemplate.addElement(title, [subtitle], [dontTranslate]) ⇒ this
Adds element to generic template
Kind: instance method of GenericTemplate
Param | Type | Default |
---|
title | string | |
[subtitle] | string | null |
[dontTranslate] | boolean | false |
genericTemplate.setElementUrl(url, [hasExtension]) ⇒ this
Sets url of recently added element
Kind: instance method of GenericTemplate
Param | Type | Default |
---|
url | any | |
[hasExtension] | boolean | false |
genericTemplate.setElementImage(image) ⇒ this
Sets image of recently added element
Kind: instance method of GenericTemplate
genericTemplate.setElementAction(url, hasExtension, [webviewHeight])
Sets default action of recently added element
Kind: instance method of GenericTemplate
Param | Type | Default | Description |
---|
url | string | | button url |
hasExtension | boolean | false | includes token in url |
[webviewHeight] | string | null | compact |
genericTemplate.urlButton(title, url, hasExtension, [webviewHeight]) ⇒ this
Adds button. When hasExtension
is set to true
, url will contain hash like:
#token=foo&senderId=23344
Kind: instance method of GenericTemplate
Param | Type | Default | Description |
---|
title | string | | button text |
url | string | | button url |
hasExtension | boolean | | includes token in url |
[webviewHeight] | string | null | compact |
genericTemplate.postBackButton(title, action, [data]) ⇒ this
Adds button, which makes another action
Kind: instance method of GenericTemplate
Param | Type | Default | Description |
---|
title | string | | Button title |
action | string | | Button action (can be absolute or relative) |
[data] | object | {} | Action data |
Kind: global class
Extends: ReducerWrapper
new Router()
Cascading router
router.use([action], [pattern], reducer) ⇒ Object
Appends middleware, action handler or another router
Kind: instance method of Router
Param | Type | Description |
---|
[action] | string | name of the action |
[pattern] | RegExp | string | function | |
reducer | function | Router | |
Example
router.use((req, res, postBack, next) => {
next();
});
router.use('action', /help/, (req, res) => {
res.text('Hello!');
});
router.use('action', req => req.text() === 'a', (req, res) => {
res.text('Hello!');
});
router.use('/path', subRouter)
.next('exitAction', (data, req, res, postBack, next) => {
postBack('anotherAction', { someData: true })
});
router.reduce(req, res, postBack)
Reducer function
Kind: instance method of Router
Overrides: reduce
ReducerWrapper ⇐ EventEmitter
Kind: global class
Extends: EventEmitter
Emits: ReducerWrapper#event:action
new ReducerWrapper()
Solution for catching events. This is useful for analytics.
Example
const reducer = new ReducerWrapper((req, res) => {
res.text('Hello');
});
reducer.on('action', (senderId, processedAction, text, req) => {
});
reducerWrapper.reduce(req, res, postBack)
Reducer function
Kind: instance method of ReducerWrapper
ReducerWrapper.ReducerWrapper
Kind: static class of ReducerWrapper
new ReducerWrapper([reduce])
Creates an instance of ReducerWrapper.
Param | Type | Default | Description |
---|
[reduce] | function | o => o | the handler function |
Settings
Kind: global class
new Settings()
Utility, which helps us to set up chatbot behavior
settings.greeting([text]) ⇒ this
Sets or clears bot's greeting
Kind: instance method of Settings
Param | Type | Default | Description |
---|
[text] | string | false | leave empty to clear |
settings.getStartedButton([payload]) ⇒ this
Sets up the Get Started Button
Kind: instance method of Settings
Param | Type | Default | Description |
---|
[payload] | string | object | false | leave blank to remove button, or provide the action |
Example
const settings = new Settings(config.facebook.pageToken);
settings.getStartedButton('/start');
settings.whitelistDomain(domain) ⇒ this
Useful for using facebook extension in webviews
Kind: instance method of Settings
Param | Type |
---|
domain | string | Array.<string> |
Settings.Settings
Kind: static class of Settings
new Settings(token, [log])
Creates an instance of Settings.
Param | Type |
---|
token | string |
[log] | Object |
Tester
Kind: global class
new Tester()
Utility for testing requests
Returns single response asserter
Kind: instance method of Tester
Param | Type | Default | Description |
---|
[index] | number | 0 | response index |
Returns any response asserter
Kind: instance method of Tester
Returns last response asserter
Kind: instance method of Tester
tester.passedAction(path) ⇒ this
Checks, that app past the action
Kind: instance method of Tester
tester.getState() ⇒ object
Returns state
Kind: instance method of Tester
tester.setState([state])
Sets state with Object.assign()
Kind: instance method of Tester
Param | Type | Default |
---|
[state] | object | {} |
tester.text(text) ⇒ Promise
Makes text request
Kind: instance method of Tester
tester.quickReply(action, [data]) ⇒ Promise
Send quick reply
Kind: instance method of Tester
Param | Type | Default |
---|
action | string | |
[data] | object | {} |
tester.postBack(action, [data]) ⇒ Promise
Sends postback
Kind: instance method of Tester
Param | Type | Default |
---|
action | string | |
[data] | object | {} |
Tester.Tester
Kind: static class of Tester
new Tester(reducer, [senderId], [processorOptions])
Creates an instance of Tester.
Param | Type | Default | Description |
---|
reducer | Router | ReducerWrapper | function | | |
[senderId] | string | null | |
[processorOptions] | object | {} | options for Processor |
ResponseAssert
Kind: global class
new ResponseAssert()
Utility for asserting single response
responseAssert.contains(search) ⇒ this
Checks, that response contains text
Kind: instance method of ResponseAssert
responseAssert.quickReplyAction(action) ⇒ this
Checks quick response action
Kind: instance method of ResponseAssert
responseAssert.templateType(type) ⇒ this
Checks template type
Kind: instance method of ResponseAssert
responseAssert.attachmentType(type) ⇒ this
Checks attachment type
Kind: instance method of ResponseAssert
ResponseAssert.AnyResponseAssert#contains(search) ⇒ this
Checks, that response contains text
Kind: static method of ResponseAssert
ResponseAssert.AnyResponseAssert#quickReplyAction(action) ⇒ this
Checks quick response action
Kind: static method of ResponseAssert
ResponseAssert.AnyResponseAssert#templateType(type) ⇒ this
Checks template type
Kind: static method of ResponseAssert
ResponseAssert.AnyResponseAssert#attachmentType(type) ⇒ this
Checks attachment type
Kind: static method of ResponseAssert
AnyResponseAssert
Kind: global class
new AnyResponseAssert()
Utility for searching among responses
bufferloader(url, [limit], [limitJustByBody], [redirCount]) ⇒
Downloads a file from url into a buffer. Supports size limits and redirects.
Kind: global function
Returns: Promise.
Param | Type | Default | Description |
---|
url | string | | |
[limit] | number | 0 | limit in bytes |
[limitJustByBody] | boolean | false | when true, content size in header is ignored |
[redirCount] | number | 3 | maximmum amount of redirects |
Example
router.use('*', (req, res, postBack) => {
if (req.isFile()) {
bufferloader(req.attachmentUrl())
.then(buffer => postBack('downloaded', { data: buffer }))
.catch(err => postBack('donwloaded', { err }))
}
});
attachmentType(response, type, [message]) ⇒ boolean
Checks attachment type
Kind: global function
Param | Type | Default | Description |
---|
response | object | | |
type | string | | |
[message] | string | false | "'Attachment type does not match'" | use false for no asserts |
isText(response, [message]) ⇒ boolean
Checks, that response is a text
Kind: global function
Param | Type | Default | Description |
---|
response | object | | |
[message] | string | false | "'Should be a text'" | use false for no asserts |
contains(response, search, [message]) ⇒ boolean
Checks, that text contain a message
Kind: global function
Param | Type | Default | Description |
---|
response | object | | |
search | string | | |
[message] | string | false | "'Should contain a text'" | use false for no asserts |
quickReplyAction(response, action, [message]) ⇒ boolean
Checks quick response action
Kind: global function
Param | Type | Default | Description |
---|
response | object | | |
action | string | | |
[message] | string | false | "'Should contain the action'" | use false for no asserts |
templateType(response, expectedType, [message]) ⇒ boolean
Checks template type
Kind: global function
Param | Type | Default | Description |
---|
response | object | | |
expectedType | string | | |
[message] | string | false | "'Template type does not match'" | use false for no asserts |
waiting(response, [message]) ⇒ boolean
Looks for waiting message
Kind: global function
Param | Type | Default | Description |
---|
response | object | | |
[message] | string | false | "'Should be waiting placeholder'" | use false for no asserts |