npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@aiteq/messenger-bot

v1.1.0-RC.2

Published

A TypeScript coded Node.js package for effective building and managing Facebook Messenger Bots.

Downloads

192

Readme

@aiteq/messenger-bot

npm version  Dependency Status  Build Status  Coverage Status  Codacy Badge

A TypeScript coded Node.js package for effective building and managing Facebook Messenger Bots.

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

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:

  1. 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...");
});
  1. 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:

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");

See BotUtils.sendText()

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.

1. Implement ChatExtension interface

import { 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

Group: send

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

Group: getstarted

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

Group: greeting

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

Group: menu

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"
            }
        ]
    }
}

Group: domains

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]

Group: audience

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]

Group: accountlinking

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]

Group: chatext

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

Group: code

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

Tomáš Klíma, Aiteq & Aiteq

License

MIT