@aiteq/messenger-bot
A TypeScript coded Node.js package for effective building and managing Facebook Messenger Bots.
Features | Technologies | Getting started | Usage | Chat extensions | CLI | API Doc |
new BotServer({
verifyToken: "hasta-la-vista-baby",
accessToken: "open-sesame",
appSecret: "too-secret-to-say"
})
.hear("order", async (chat: Chat) => {
await chat.say("Well, let's order some Botcoins. I'll just ask you a few details.");
botcoinMachine.placeOrder({
amount: await chat.ask("How many Botcoins you want to buy?"),
wallet: await chat.ask("What's the address of your Botcoin wallet?"),
email: await chat.ask("And finally, tell me your email where I should send instructions.")
});
chat.say("Thank you for your order!");
})
.start();
Major features
- Express.js based, event-driven bot server handling both Webhook and Chat Extension requests.
- Subscribing to incoming text messages using regular expresions or to events emitted by the webhook middleware.
- Support for synchronous conversations between the bot and users.
- Utilities for calling Messenger Platform API functions from code outside chatbot flow.
- Standalone CLI for instant access to backing functions of the Messenger API.
- Complete type definitions so the package is ready to be used in both JavaScript and TypeScript projects.
Technologies used
Node.js
Node is an open-source, cross-platform JavaScript run-time environment for executing JavaScript code server-side. Fot its event-driven architecture capable non-blocking I/O it is perfectly fitting platform for building chatbots.
TypeScript
A syntactical superset of JavaScript adding static typing, implementing object-oriented principles and adopting latest ES features like generics, decorators or reflection. For Node projects the TypeScript especially brings a higher level of maintainability.
Express
Express is a helpful framework built around Node.js for performing actions as a web server. The package uses the Express for handling webhook requests (incoming chat messages) as well as providing access to Chat Extensions.
Axios
Axios provides fully Promise based HTTP client functionality, so it was a clear choice for implementation of calling Facebook Graph APIs.
Embedded JavaScript Templates
EJS is a very simple templating language that helps to create HTML for the pages to be shown in a programmatic way with injecting values. The package uses the EJS for rendering Chat Extensions.
Jest
Jest is yet another unit testing framework by Facebook. It covers all needed unit testing areas: tests definition, assertion and code coverage measuring and reporting. In addition the Jest supports TypeScript and Promises. It's interface is just right balanced between descriptiveness and verbosity, so its using is very intuitive..
Grunt
As a task runner the Grunt helps to organize building, releasing and maintaining the package.
Getting started
Prerequisites
- Node installed
- TypeScript installed
- Facebook developers account
- «optional» ngrok (or any other tunnel tool) for developing and debuging locally
Install
npm install @aiteq/messenger-bot --save
Facebook application
Create and setup a Facebook application using Quick Start Guide and find out access token and app secret.
Bot code
Create index.ts
and let's go to start coding:
import { BotServer, Chat } from "@aiteq/messenger-bot";
Create an instance of BotServer:
let bot: BotServer = new BotServer({
name: "MyBot",
port: process.env.PORT || 8080,
verifyToken: "hasta-la-vista-baby",
accessToken: "open-sesame",
appSecret: "too-secret-to-say"
});
Subscribe for some text:
bot.hear("hello", (chat: Chat) => {
chat.say("Hello! I'm Emil, the Bot. How are you?");
});
Start the server:
bot.start();
Build and start
Add some scripts to package.json
:
"scripts": {
"compile": "tsc -p .",
"start": "node ./dist/index.js"
},
Create tsconfig.json
:
{
"compilerOptions":
{
"module": "commonjs",
"target": "es6",
"rootDir": "src",
"outDir": "bin"
},
"include": [ "src/**/*" ]
}
Transpile the source:
npm run compile
Now the bot is ready and you can bring it to live:
npm run start
Start ngrok:
ngrok http 8080
and copy the provided https:// URL for the following step.
Setup the webhook
Follow the guide, paste the previously copied URL to Callback URL and add /webhook
. So, if the URL provided by ngrok is e.g. https://54d4f722.ngrok.io
, the Callback URL will be https://54d4f722.ngrok.io/webhook
.
Set hasta-la-vista-baby
(see creating the bot above) as Verify Token and click Verify and Save.
It's alive!
Now the bot is listening for messages sent to your page. Try to send message "hello".
Use cases
Hooking text
You can subscribe to specific content of incoming text messages in two ways: exact commands and regular expressions. The subscribing is done using the BotServer.hear() method.
Commands
Hooking exact words or phrases can be useful when your bot is supposed to listen for commands like a CLI. Commands are specified as strings or arrays of strings and are considered to be case-insensitive.
bot.hear("wait", (chat: Chat) => {
chat.say("I'm waiting ...");
})
.hear(["sleep", "go sleep", "go to sleep"], (chat: Chat) => {
chat.say("Well, good night");
});
Regular expressions
Subscribing to specific content using regular expressions is much more flexible than listing exact words. Like commands, regular expressions can be specified as an array or single. If the regular expression contains capturing groups they are passed to the callback as third argument.
bot.hear(/^good (night|morning)$/i, (chat: Chat, text: string, captured: string[]) => {
chat.say(`Hi, good ${captured[0]}!`);
});
In addition, you can mix commands and regular expressions in the same array.
Note: The regular expressions are used exactly as given, so if you want to test the text in case-insensitive manner you must explictily indicate it (the i
flag).
Hooking events
Besides searching for specific text you can also subscribe to a number of events emitted while the bot is receiving messages through webhook. The subscribing is done using the BotServer.on() method.
Check the Webhook.Event enum for complete set of available events.
Identified Postback events
When subscribing to Postback based events
you have two options:
- subscribe to the type of the event and receive all events of this type (e.g. Persistent Menu item selected):
bot.on(Webhook.Event.PERSISTENT_MENU, (chat: Chat) => {
chat.say("You've selected some menu item, but I really don't know what you want to do...");
});
- subscribe to the specific ID in addition to the type, what is much more useful way:
bot.on(Webhook.Event.PERSISTENT_MENU, "menu-item-about", (chat: Chat) => {
chat.say("What can I say about myself... I'm a bot.");
});
Conversation
Conversation is synchronous message exchange between users and the bot by setting a flow of questions and answers. It's a useful way to get conventional UI tasks, like form filling, closer to interpersonal communication.
In order to ensure execution of steps of the conversation synchronously, all methods of the Chat class return Promises. So you can call next step after resolving the current. And when you add a little bit of syntactic sugar using the async
/await
concept, the conversation flow will look much more readable, almost like a real dialog.
There are two methods for interaction with the user:
- ask() - asking with plain text question
- askWithMessage() - asking with structured message
Note: No events are emitted and no hear handlers called when the bot receives an answer to the question asked.
bot.on(Webhook.Event.PERSISTENT_MENU, "menu-item-song", async (chat: Chat) => {
profile.favSong = await chat.ask("What's your favourite song?");
});
Or more complex flow:
bot.on(Webhook.Event.PERSISTENT_MENU, "menu-item-order", async (chat: Chat) => {
await chat.say("Well, let's order some Botcoins. I'll just ask you a few details.");
order.amount = await chat.ask("How many Botcoins you want to buy?");
order.wallet = await chat.ask("What's the address of your Botcoin wallet?");
order.email = await chat.ask("And finally, tell me your email where I should send instructions for payment.");
chat.say("Thank you for your order!");
});
Input validation
As with classic forms, even in the case of a conversation, we need to validate user inputs. Therefore, the interface offers the ability to call a validation function wich you can pass when calling the ask() method. As a validator you can conveniently use functions from validator.js package:
import * as validator from "validator";
bot.on(Webhook.Event.PERSISTENT_MENU, "menu-item-form", async (chat: Chat) => {
//...
let email: string = await chat.ask("Give me your email address, please", validator.isEmail)
//...
});
The bot will automatically repeat the question until the user enters a valid email address.
Unanswered questions
The questions asked using the ask() or askWithMessage() method may remain unanswered by the user. In order to avoid pending Promises these questions are set to automatically expire. The expiration period is set to 5 minutes by default but you can override it using BotConfig.askTimeout parameter. The unanswered question is rejected after its expiration. If you want to react to this situation you may catch the rejection:
bot.on(Webhook.Event.PERSISTENT_MENU, "menu-item-name", async (chat: Chat) => {
try {
let name = await chat.ask("What's your name?");
chat.say(`Hello, ${name}. My name is Emil.`);
} catch (error) {
chat.say("I'm so sorry you have no name.");
}
});
If you won't catch the expiration the bot will swallow it without consequences. Don't worry about it.
Media reusing
When you're about to send a message with a media attached you can indicate wheather the media should be reused. The bot stores all reusable attachment ID's. When you try to send the same attachment (with the same URL and reusable
set to true
) twice or more times the bot replace media's URL with stored attachment ID.
BotUtils
The Facebook Messenger Platform API contains not only interactive functions for message exchange between the bot and users. There are a lot of services in the API backing the communication like activating Get Started button, installing Persistent Menu or generating Messenger Code.
Sometimes, we also want to send a push message - a message sent to the user proactively, not just as a response to some incoming message.
The above cases are not quite bot-aware functions. Thus, in order to keep BotServer's interface clean, these services are made available through the BotUtils class.
An instance of the BotUtils is initialized passing the the accessToken
.
let utils: BotUtils = new BotUtils("open, sesame");
Example: send push message
utils.sendText("123450987643", "RATE ALERT: Botcoin price has reached $1,000");
Example: activate Get Started button
utils.setGetStartedButton();
See BotUtils.setGetStartedButton()
Example: generate Messenger Code
utils.generateMessengerCode("my-m-code.png");
See BotUtils.generateMessengerCode()
Server monitoring
The bot server supports responding for ping requests. By default, the ping service is attached to /ping
path and may be overrided by BotConfig.pingPath configuration parameter. The ping request must use the GET method. If all goes well the "OK"
string is returned with 200 HTTP code.
The ping feature is useful with conjunction with up-time monitoring services like Uptime Robot.
Chat extensions
The package supports Embedded JavaScript Templates (EJS) for rendering Chat Extension views. For creating a new extension follow these steps.
ChatExtension interface
1. Implementimport { ChatExtension } from "@aiteq/messenger-bot";
export class MyExtension implements ChatExtension {
constructor() {
// name your extension - the name will be a part of extension's URL
super("my");
}
// implement abstract method getModel()
public getModel(): any {
return {
name: "Captain Nemo"
};
}
}
The chat extension class must implement abstract method getModel()
that provides data to be used in the view.
The getModel
is called every time an extension is requested.
2. Create view
In your project root create views
folder (default for placing views in Express application) and my.ejs
file within it.
<!DOCTYPE html>
<html>
<head>
<title>My Extension</title>
</head>
<body>
<div>Greetings, <%= name %>!</div>
</body>
</html>
3. Add the extension
At last you have to register the extension to the bot server using the addChatExtension method.
bot.addChatExtension(new MyExtension());
Now the extension is ready to use and you can test it pointing to <your-bot-url>/ext/my
.
Note that the default path for extensions is /ext
and you can chanage it by setting the extensionPath
property of BotConfig.
CLI
The BotUtils class is useful if you need non-interactive functions of the Messenger API to be called within your application. More often, however, you will need to use these features one-time, operatively, or as a part of such automated workflow like shell script. There is a Command Line Interface ready for these cases.
General usage
mbutil <group> [command] [options]
A group represents a specific part of the Messenger API. Available groups are:
Group | Functions |
---|---|
send |
Send text or attachment message |
getstarted |
Manage Get Started button |
greeting |
Manage page's localized greetings |
menu |
Manage Persistent Menu |
domains |
Manage Domain Whitelist |
audience |
Manage Target Audience settings |
accountlinking |
Manage Account Linking settings |
chatext |
Manage Chat Extensions settings |
code |
Generate Messenger Code |
For each group, you can view help by:
mbutil <group> --help
Global options:
Option | Function |
---|---|
--config <path> |
path to the config JSON file; must contain the accessToken property |
--accessToken <token> |
access token (one of --config or --accessToken must be specified) |
--help |
display help for the group |
send
Group: Send plain text or attachment push message.
Usage:
mbutil send "<text>" --recipient <id> [options]
mbutil send image|audio|video|file --url <url> --recipient <id> [options]
Options:
Option | Function |
---|---|
--recipient <id> |
ID of the recipient |
--url <url> |
URL of the file to be attached |
getstarted
Group: Manage Get Started button.
Display current setting:
mbutil getstarted get [options]
Activate the button with optional data:
mbutil getstarted set [--data "<data>"] [options]
Remove the button:
mbutil getstarted delete [options]
Options:
Option | Function |
---|---|
--data "<data>" |
text or JSON to be send when the user tapped the button |
greeting
Group: Manage page's localized Greeting.
Display current setting:
mbutil greeting get [options]
Add localized greeting text:
mbutil greeting add "<text>" [--locale <locale>] [options]
Remove greeting text:
mbutil greeting delete [options]
Options:
Option | Function |
---|---|
--locale <locale> |
greeting's locale (supported locales); if omitted the text will be set as default |
menu
Group: Manage Persistent Menu.
Display current setting:
mbutil menu get [options]
Set Persistent Menu according to definition in a JSON file:
mbutil menu set --file <path> [--locale <locale>] [options]
Remove Persistent Menu:
mbutil menu delete [options]
Options:
Option | Function |
---|---|
--file <path> |
path to menu definition JSON file |
Required structure of the JSON menu definition file is clear from the following example (object contains two variants of the menu for "default"
and "cs_CZ"
locales):
{
"default": {
"composerInputDisabled": false,
"items": [
{
"title": "Show exchange rate",
"id": "menu-rate"
},
{
"title": "Buy Botcoins",
"id": "menu-buy"
},
{
"title": "Aiteq International, Ltd.",
"url": "http://www.aiteq.international"
}
]
},
"cs_CZ": {
"composerInputDisabled": false,
"items": [
{
"title": "Aktuální kurz",
"id": "menu-rate"
},
{
"title": "Koupit Botcoiny",
"id": "menu-buy"
},
{
"title": "Aiteq Reloaded, s.r.o.",
"url": "http://www.aiteq.com"
}
]
}
}
domains
Group: Manage Domain Whitelist.
Display current whitelisted domains:
mbutil domains get [options]
Add one or more domains (space separated list) to the whitelist:
mbutil domains add <domain> [domains] [options]
Delete the domain whitelist:
mbutil domains delete [options]
audience
Group: Manage Target Audience settings. Countries are identified by ISO 3166 Alpha-2 codes.
Display current setting:
mbutil audience get [options]
Open Target Audience for all countries:
mbutil audience open [options]
Close Target Audience for all countries:
mbutil audience close [options]
Add one or more countries (space separated list) to the whitelist:
mbutil audience whitelist <country> [countries] [options]
Add one or more countries (space separated list) to the blacklist:
mbutil audience blacklist <country> [countries] [options]
Remove all Target Audience settings:
mbutil audience delete [options]
accountlinking
Group: Manage Account Linking URL.
Display currently set Account Linking URL:
mbutil accountlinking get [options]
Set Account Linking URL:
mbutil accountlinking set <url> [options]
Delete currently set Account Linking URL:
mbutil accountlinking delete [options]
chatext
Group: Manage Chat Extension URL.
Display currently set Chat Extension URL and settings:
mbutil chatext get [options]
Set Chat Extension URL:
mbutil chatext set <url> [options]
Delete currently set Chat Extension URL:
mbutil chatext delete [options]
Options:
Option | Value | Function |
---|---|---|
--inTest |
controls whether public users can see the Chat Extension | |
--shareButton |
controls whether the share button in the webview is enabled |
code
Group: Generate Messenger Code.
Set Chat Extension URL:
mbutil code generate [options]
Options:
Option | Value | Function |
---|---|---|
--out |
path | output file's path and name (default: ./code.png ) |
--size |
number between 100 - 2000
|
size of generated image, in pixels (default: 1000 ) |
--ref |
text | data to be received when user scans the code (optional) |
API documentation
Package's reference API documentation is located in doc folder.
Credits
License
MIT