@cse-public/webex-node-bot-framework
Version:
Webex Teams Bot Framework for Node JS
1,223 lines (1,015 loc) • 72.5 kB
Markdown
# webex-node-bot-framework
### Node JS Bot Framework for Cisco Webex
This project is inspired by, and provides an alternate implementation of, the awesome [node-flint](https://github.com/flint-bot/flint/) framework by [Nick Marus](https://github.com/nmarus). The framework makes it easy to quickly develop a Webex messaging bot, abstracting away some of the complexity of Webex For Developers interfaces, such as registering for events and calling REST APIs. A bot developer can use the framework to focus primarily on how the bot will interact with users in Webex, by writing "handlers" for various message or membership events in spaces where the bot has been added.
The primary change in this implementation is that it is based on the [webex-jssdk](https://webex.github.io/webex-js-sdk) which continues to be supported as new features and functionality are added to Webex.
For developers who are familiar with flint, or who wish to port existing bots built on node-flint to the webex-node-bot-framework, this implementation is NOT backwards compatible. Please see [Migrating from the original flint framework](./docs/migrate-from-node-flint.md)
Feel free to join the ["Webex Node Bot Framework" space on Webex](https://eurl.io/#BJ7gmlSeU) to ask questions and share tips on how to leverage this framework.
## News
* May, 2020 - Version 2 introduces a some new configuration options designed to help developers restrict access to their bot. This can be helpful during the development phase (`guideEmails` parameter) or for production bots that should be restricted for use to users that have certain email domains (`restrictedToEmailDomains` parameter). See [Membership-Rules README](./docs/membership-rules-readme.md)
* October 31, 2020 - Earlier this year, a series of blog posts were published to help developers get started building bots with the framework:
* [From zero to webex chatbot in 15 minutes](https://developer.webex.com/blog/from-zero-to-webex-teams-chatbot-in-15-minutes)
* [Introducing the Webex bot framework for node.js](https://developer.webex.com/blog/introducing-the-webex-teams-bot-framework-for-node-js)
* [A deeper dive into the framework](https://developer.webex.com/blog/a-deeper-dive-into-the-webex-bot-framework-for-node-js)
* [Five tips for well behaved bots](https://developer.webex.com/blog/five-tips-for-well-behaved-webex-bots)
For first timers, I strongly recommend following these, running the sample app, stepping through it in the debugger, and getting a sense of how the framework works. Once you have done the detailed documentation here will make a lot more sense!
## [Full Version History](./docs/version-history.md)
## Contents
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Installation](#installation)
- [Overview](#overview)
- [Authentication](#authentication)
- [Storage](#storage)
- [Bot Accounts](#bot-accounts)
- [Framework Reference](#framework-reference)
- [Classes](#classes)
- [Objects](#objects)
- [Events](#events)
- [Framework](#framework)
- [Bot](#bot)
- [Trigger : <code>object</code>](#trigger--codeobjectcode)
- ["log"](#log)
- ["stop"](#stop)
- ["start"](#start)
- ["initialized"](#initialized)
- ["roomLocked"](#roomlocked)
- ["roomUnocked"](#roomunocked)
- ["roomRenamed"](#roomrenamed)
- ["memberEnters"](#memberenters)
- ["botAddedAsModerator"](#botaddedasmoderator)
- ["botRemovedAsModerator"](#botremovedasmoderator)
- ["memberAddedAsModerator"](#memberaddedasmoderator)
- ["memberRemovedAsModerator"](#memberremovedasmoderator)
- ["memberExits"](#memberexits)
- ["mentioned"](#mentioned)
- ["message"](#message)
- ["files"](#files)
- ["spawn"](#spawn)
- ["despawn"](#despawn)
- [Storage Driver Reference](#storage-driver-reference)
- [MongoStore](#mongostore)
- [License](#license)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Installation
#### Via Git
```bash
mkdir myproj
cd myproj
git clone https://github.com/webex/webex-node-bot-framework
npm install ./webex-node-bot-framework
```
#### Via NPM
```bash
mkdir myproj
cd myproj
npm install webex-node-bot-framework
```
#### Example Template Using Express
```js
var Framework = require('webex-node-bot-framework');
var webhook = require('webex-node-bot-framework/webhook');
var express = require('express');
var bodyParser = require('body-parser');
var app = express();
app.use(bodyParser.json());
// framework options
var config = {
webhookUrl: 'http://myserver.com/framework',
token: 'Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u',
port: 80
};
// init framework
var framework = new Framework(config);
framework.start();
// An initialized event means your webhooks are all registered and the
// framework has created a bot object for all the spaces your bot is in
framework.on("initialized", function () {
framework.debug("Framework initialized successfully! [Press CTRL-C to quit]");
});
// A spawn event is generated when the framework finds a space with your bot in it
// You can use the bot object to send messages to that space
// The id field is the id of the framework
// If addedBy is set, it means that a user has added your bot to a new space
// Otherwise, this bot was in the space before this server instance started
framework.on('spawn', function (bot, id, addedBy) {
if (!addedBy) {
// don't say anything here or your bot's spaces will get
// spammed every time your server is restarted
framework.debug(`Framework created an object for an existing bot in a space called: ${bot.room.title}`);
} else {
// addedBy is the ID of the user who just added our bot to a new space,
// Say hello, and tell users what you do!
bot.say('Hi there, you can say hello to me. Don\'t forget you need to mention me in a group space!');
}
});
var responded = false;
// say hello
framework.hears('hello', function(bot, trigger) {
bot.say('Hello %s!', trigger.person.displayName);
responded = true;
});
// Its a good practice to handle unexpected input
framework.hears(/.*/gim, function(bot, trigger) {
if (!responded) {
bot.say('Sorry, I don\'t know how to respond to "%s"', trigger.message.text);
}
responded = false;
});
// define express path for incoming webhooks
app.post('/framework', webhook(framework));
// start express server
var server = app.listen(config.port, function () {
framework.debug('Framework listening on port %s', config.port);
});
// gracefully shutdown (ctrl-c)
process.on('SIGINT', function() {
framework.debug('stoppping...');
server.close();
framework.stop().then(function() {
process.exit();
});
});
```
[**Websocket Example**](./docs/example3.md)
[**Buttons and Cards Example**](./docs/buttons-and-cards-example.md)
[**Restify Example**](./docs/example2.md)
## Overview
The framework provides developers with some basic scaffolding to quickly get a bot up and running. Once a framework object is created with a configuration that includes a bot token, calling the framework.start() method kicks of the setup of this scaffolding. The framework registers for all Webex Teams events, and may discover existing Webex Teams spaces that the bot is already a member of.
A `bot` object is created for each space, and the framework generates a `spawn` event each time it finds a new one. When all existing bot objects are created the framework generates an `initialized` event signalling that it is ready to begin "listening" for user input.
```js
// init framework
var framework = new Framework(config);
framework.start();
// An initialized event means your webhooks are all registered and the
// framework has created bot objects for the spaces your bot was found in
framework.on("initialized", function () {
framework.debug("Framework initialized successfully! [Press CTRL-C to quit]");
});
// A spawn event is generated when the framework finds a space with your bot in it
// You can use the bot object to send messages to that space
// The id field is the id of the framework
// If addedBy is set, it means that a user has added your bot to a new space
// Otherwise, this bot was in the space before this server instance started
framework.on('spawn', function (bot, id, addedBy) {
if (!addedBy) {
// don't say anything here or your bot's spaces will get
// spammed every time your server is restarted
framework.debug(`Framework created an object for an existing bot in a space called: ${bot.room.title}`);
} else {
// addedBy is the ID of the user who just added our bot to a new space,
// Say hello, and tell users what you do!
bot.say('Hi there, you can say hello to me. Don\'t forget you need to mention me in a group space!');
}
});
```
Most of the framework's functionality is based around the `framework.hears()` function. This
defines the phrase or pattern the bot is listening for and what actions to take
when that phrase or pattern is matched. The `framework.hears()` function gets a callback
that includes three objects: the bot object, and the trigger object, and the id of the framework.
The bot object is a specific instance of the `bot` class associated with the Webex Teams space that triggered the `framework.hears()` call.
The `trigger` object provides details about the message that was sent, and the person who sent it, which caused the `framework.hears()` function to be triggered.
A simple example of a framework.hears() function setup:
```js
framework.hears(phrase, function(bot, trigger, id) {
bot.<command>
.then(function(returnedValue) {
// do something with returned value
})
.catch(function(err) {
// handle errors
});
});
```
* `phrase` : This can be either a string or a regex pattern.
If a string, the string is matched against the first word in the room message.
message.
If a regex pattern is used, it is matched against the entire message text.
* `bot` : The bot object that is used to execute commands when the `phrase` is
triggered.
* `bot.<command>` : The Bot method to execute.
* `then` : Node JS Promise keyword that invokes additional logic once the
previous command is executed.
* `catch` : handle errors that happen at either the original command or in any
of the chained 'then' functions.
* `trigger` : The object that describes the details around what triggered the
`phrase`.
* `commands` : The commands that are ran when the `phrase` is heard.
## Authentication
The token used to authenticate the Framework with the Webex API is passed as part of the
options used when instantiating the Framework class. To change or update the
token, use the Framework#setWebexToken() method.
**Example:**
```js
var newToken = 'Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u';
framework.setWebexToken(newToken)
.then(function(token) {
console.log('token updated to: ' + token);
});
```
## Storage
The storage system used in the framework is a simple key/value store and resolves around these 3 methods:
* `bot.store(key, value)` - Store a value to a bot instance where 'key' is a
string and 'value' is a boolean, number, string, array, or object. *This does
not not support functions or any non serializable data.* Returns the a promise
with the value.
* `bot.recall(key)` - Recall a value by 'key' from a bot instance. Returns a
resolved promise with the value or a rejected promise if not found.
* `bot.forget([key])` - Forget (remove) value(s) from a bot instance where 'key'
is an optional property that when defined, removes the specific key, and when
undefined, removes all keys. Returns a resolved promise if deleted or not found.
When a bot is first spawned, the framework calls the `bot.initStorage` method which attepts to load any previously existing bot storage elements (if using a persistent storage driver such as [MongoStore](#MongoStore)), or will create an optional initial set of key/value pairs that were specified in the framework's configuration options `initBotStorageData` element. If this is not set, new bots start off with no key/value pairs until `bot.store()` is called.
When a bot despawns (is removed from a space), the key/value store for that bot
instance will automatically be removed from the store. Framework currently has an
in-memory store and a mongo based store. By default, the in-memory store is
used. Other backend stores are possible by replicating any one of the built-in
storage modules and passing it to the `framework.storeageDriver()` method.
The [MongoStore](#MongoStore) (and potentially other stores that use a persistent storage mechanism), also support the following methods:
* `initialize()` -- this must be called before `framework.storageDriver()` and `framework.start()` are called, and will validate that the configuration is correct
* `writeMetrics()` -- is a new, optional, method for persistent storage adaptors that can be called to write breadcrumbs into the database that can be used to build reports on the bot's usage
See [MongoStore](#MongoStore), for details on how to configure this storage adaptor.
The redis adaptor is likely broken and needs to be updated to support the new functions. It would be great if a flint user of redis wanted to [contribute](./contributing.md)!
## Bot Accounts
**When using "Bot Accounts" the major differences are:**
* Webhooks for message:created only trigger when the Bot is mentioned by name
* Unable to read messages in rooms using the Webex API
**Differences with trigger.args using Framework with a "Bot Account":**
The trigger.args array is a shortcut in processing the trigger.text string. It
consists of an array of the words that are in the trigger.message string split
by one or more spaces. Punctation is included if there is no space between the
symbol and the word. With bot accounts, this behaves a bit differently.
* If defining a `framework.hears()` using a string (not regex), `trigger.args` is a
filtered array of words from the message that begins *after* the first match of
bot mention.
* If defining a framework.hears() using regex, the trigger.args array is the entire
message.
# Framework Reference
## Classes
<dl>
<dt><a href="#Framework">Framework</a></dt>
<dd></dd>
<dt><a href="#Bot">Bot</a></dt>
<dd></dd>
</dl>
## Objects
<dl>
<dt><a href="#Trigger">Trigger</a> : <code>object</code></dt>
<dd><p>Trigger Object</p>
</dd>
</dl>
## Events
<dl>
<dt><a href="#event_log">"log"</a></dt>
<dd><p>Framework log event.</p>
<p>Applications may implement a framework.on("log") handler to process
log messags from the framework, such as details about events that were
not sent due to mebership rules. See <a href="./doc/membership-rules-readme.md">Membership-Rules README</a></p>
</dd>
<dt><a href="#event_stop">"stop"</a></dt>
<dd><p>Framework stop event.</p>
</dd>
<dt><a href="#event_start">"start"</a></dt>
<dd><p>Framework start event.</p>
</dd>
<dt><a href="#event_initialized">"initialized"</a></dt>
<dd><p>Framework initialized event.</p>
</dd>
<dt><a href="#event_roomLocked">"roomLocked"</a></dt>
<dd><p>Room Locked event.</p>
</dd>
<dt><a href="#event_roomUnocked">"roomUnocked"</a></dt>
<dd><p>Room Unocked event.</p>
</dd>
<dt><a href="#event_roomRenamed">"roomRenamed"</a></dt>
<dd><p>Room Renamed event.</p>
</dd>
<dt><a href="#event_memberEnters">"memberEnters"</a></dt>
<dd><p>Member Enter Room event.</p>
</dd>
<dt><a href="#event_botAddedAsModerator">"botAddedAsModerator"</a></dt>
<dd><p>Bot Added as Room Moderator.</p>
</dd>
<dt><a href="#event_botRemovedAsModerator">"botRemovedAsModerator"</a></dt>
<dd><p>Bot Removed as Room Moderator.</p>
</dd>
<dt><a href="#event_memberAddedAsModerator">"memberAddedAsModerator"</a></dt>
<dd><p>Member Added as Moderator.</p>
</dd>
<dt><a href="#event_memberRemovedAsModerator">"memberRemovedAsModerator"</a></dt>
<dd><p>Member Removed as Moderator.</p>
</dd>
<dt><a href="#event_memberExits">"memberExits"</a></dt>
<dd><p>Member Exits Room.</p>
</dd>
<dt><a href="#event_mentioned">"mentioned"</a></dt>
<dd><p>Bot Mentioned.</p>
</dd>
<dt><a href="#event_message">"message"</a></dt>
<dd><p>Message Recieved.</p>
</dd>
<dt><a href="#event_files">"files"</a></dt>
<dd><p>File Recieved.</p>
</dd>
<dt><a href="#event_spawn">"spawn"</a></dt>
<dd><p>Bot Spawned.</p>
</dd>
<dt><a href="#event_despawn">"despawn"</a></dt>
<dd><p>Bot Despawned.</p>
</dd>
</dl>
<a name="Framework"></a>
## Framework
**Kind**: global class
**Properties**
| Name | Type | Description |
| --- | --- | --- |
| id | <code>string</code> | Framework UUID |
| active | <code>boolean</code> | Framework active state |
| initialized | <code>boolean</code> | Framework fully initialized |
| isBotAccount | <code>boolean</code> | Is Framework attached to Webex using a bot account? |
| isUserAccount | <code>boolean</code> | Is Framework attached to Webex using a user account? |
| person | <code>object</code> | Framework person object |
| email | <code>string</code> | Framework email |
| webex | <code>object</code> | The Webex JSSDK instance used by Framework |
* [Framework](#Framework)
* [new Framework(options)](#new_Framework_new)
* [.options](#Framework+options) : <code>object</code>
* [.setWebexToken(token)](#Framework+setWebexToken) ⇒ <code>Promise.<String></code>
* [.getWebexSDK()](#Framework+getWebexSDK) ⇒ <code>object</code>
* [.stop()](#Framework+stop) ⇒ <code>Promise.<Boolean></code>
* [.start()](#Framework+start) ⇒ <code>Promise.<Boolean></code>
* [.restart()](#Framework+restart) ⇒ <code>Promise.<Boolean></code>
* [.getBotByRoomId(roomId)](#Framework+getBotByRoomId) ⇒ <code>object</code>
* [.hears(phrase, action, [helpText], [preference])](#Framework+hears) ⇒ <code>String</code>
* [.clearHears(id)](#Framework+clearHears) ⇒ <code>null</code>
* [.showHelp([header], [footer])](#Framework+showHelp) ⇒ <code>String</code>
* [.setAuthorizer(Action)](#Framework+setAuthorizer) ⇒ <code>Boolean</code>
* [.clearAuthorizer()](#Framework+clearAuthorizer) ⇒ <code>null</code>
* [.storageDriver(Driver)](#Framework+storageDriver) ⇒ <code>Promise.<Boolean></code>
* [.use(path)](#Framework+use) ⇒ <code>Boolean</code>
* [.checkMembershipRules()](#Framework+checkMembershipRules)
* [.myEmit()](#Framework+myEmit)
<a name="new_Framework_new"></a>
### new Framework(options)
Creates an instance of the Framework.
| Param | Type | Description |
| --- | --- | --- |
| options | <code>Object</code> | Configuration object containing Framework settings. |
**Example**
```js
var options = {
webhookUrl: 'http://myserver.com/framework',
token: 'Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u'
};
var framework = new Framework(options);
```
<a name="Framework+options"></a>
### framework.options : <code>object</code>
Options Object
**Kind**: instance namespace of [<code>Framework</code>](#Framework)
**Properties**
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| token | <code>string</code> | | Webex Token. |
| [webhookUrl] | <code>string</code> | | URL that is used for Webex API to send callbacks. If not set events are received via websocket |
| [webhookSecret] | <code>string</code> | | If specified, inbound webhooks are authorized before being processed. Ignored if webhookUrl is not set. |
| [httpsProxy] | <code>string</code> | | If specified the https proxy to route request to webex through. Ie: "https://proxy.mycompany.com:8090" |
| [maxStartupSpaces] | <code>number</code> | | If specified, the maximum number of spaces with our bot that the framework will discover during startup. If not specified the framework will attempt to discover all the spaces the framework's identity is in and "spawn" a bot object for all of them before emitting an "initiatialized" event. For popular bots that belog to hundreds or thousands of spaces, this can result in long startup times. Setting this to a number (ie: 100) will limit the number of bots spawned before initialization. Bots that are driven by external events and rely on logic that checks if an appropriate bot object exists before sending a notification should not modify the default. Bots that are driven primarily by webex user commands to the bot may set this to 0 or any positive number to facilitate a faster startup. After initialization new bot objects are created ("spawned") when the bot is added to a new space or, if the framework receives events in existing spaces that it did not discover during initialization. In the case of these "late discoveries", bots objects are spawned "just in time". This behavior is similar to the way the webex teams clients work. See the [Spawn Event docs](#"spawn") to discover how to handle the different types of spawn events. |
| [messageFormat] | <code>string</code> | <code>"text"</code> | Default Webex message format to use with bot.say(). |
| [initBotStorageData] | <code>object</code> | <code>{}</code> | Initial data for new bots to put into storage. |
| [id] | <code>string</code> | <code>"random"</code> | The id this instance of Framework uses. |
| [webhookRequestJSONLocation] | <code>string</code> | <code>"body"</code> | The property under the Request to find the JSON contents. |
| [removeWebhooksOnStart] | <code>Boolean</code> | <code>true</code> | If you wish to have the bot remove all account webhooks when starting. Ignored if webhookUrl is not set. |
| [removeDeviceRegistrationsOnStart] | <code>Boolean</code> | <code>false</code> | If you use websockets and get "excessive device registrations" during iterative development, this will delete ALL device registrations. Use with caution! Ignored if webhookUrl is set. |
| [restrictedToEmailDomains] | <code>string</code> | | Set to a comma seperated list of email domains the bot may interact with, ie "myco.com,myco2.com". For more details see the [Membership-Rules README](./doc/membership-rules-readme.md) |
| [guideEmails] | <code>string</code> | | Set to a comma seperated list of Webex users emails who MUST be in a space in order for the bot to work, ie "user1@myco.com,user2@myco2.com". For more details see the [Membership-Rules README](./doc/membership-rules-readme.md) |
| [membershipRulesDisallowedResponse] | <code>string</code> | | Message from bot when it detects it is in a space that does not conform to the membership rules specified by the `restrictedToEmailDomains` and/or the `guideEmails` parameters. Default messages is "Sorry, my use is not allowed for all the members in this space. Will ignore any new messages to me.". No message will be sent if this is set to an empty string. |
| [membershipRulesStateMessageResponse] | <code>string</code> | | Message from bot when it is messaged in a space that does not conform to the membership rules specified by the `restrictedToEmailDomains` and/or the `guideEmails` parameters. Default messages is "Sorry, because my use is not allowed for all the members in this space I am ignoring any input.". No message will be sent if this is set to an empty string. |
| [membershipRulesAllowedResponse] | <code>string</code> | | Message from bot when it detects that an the memberships of a space it is in have changed in in order to conform with the membership rules specified by the The default messages is "I am now allowed to interact with all the members in this space and will no longer ignore any input.". No message will be sent if this is set to an empty string. |
<a name="Framework+setWebexToken"></a>
### framework.setWebexToken(token) ⇒ <code>Promise.<String></code>
Tests, and then sets a new Webex Token.
**Kind**: instance method of [<code>Framework</code>](#Framework)
| Param | Type | Description |
| --- | --- | --- |
| token | <code>String</code> | New Webex Token for Framework to use. |
**Example**
```js
framework.setWebexToken('Tm90aGluZyB0byBzZWUgaGVyZS4uLiBNb3ZlIGFsb25nLi4u')
.then(function(token) {
console.log('token updated to: ' + token);
});
```
<a name="Framework+getWebexSDK"></a>
### framework.getWebexSDK() ⇒ <code>object</code>
Accessor for Webex SDK instance
Access SDK functionality described in [SDK Reference](https://developer.webex.com/docs/sdks/browser#sdk-api-reference)
**Kind**: instance method of [<code>Framework</code>](#Framework)
**Returns**: <code>object</code> - - Framework's Webex SDK instance
**Example**
```js
let webex = framework.getWebexSDK();
webex.people.get(me)
.then(person => {
console.log('SDK instantiated by: ' + person.displayName);
}).catch(e => {
console.error('SDK failed to lookup framework user: ' + e.message);
});
```
<a name="Framework+stop"></a>
### framework.stop() ⇒ <code>Promise.<Boolean></code>
Stop Framework.
**Kind**: instance method of [<code>Framework</code>](#Framework)
**Example**
```js
framework.stop();
```
<a name="Framework+start"></a>
### framework.start() ⇒ <code>Promise.<Boolean></code>
Start Framework.
**Kind**: instance method of [<code>Framework</code>](#Framework)
**Example**
```js
framework.start();
```
<a name="Framework+restart"></a>
### framework.restart() ⇒ <code>Promise.<Boolean></code>
Restart Framework.
**Kind**: instance method of [<code>Framework</code>](#Framework)
**Example**
```js
framework.restart();
```
<a name="Framework+getBotByRoomId"></a>
### framework.getBotByRoomId(roomId) ⇒ <code>object</code>
Get bot object associated with roomId.
Returns null if no object exists
**Kind**: instance method of [<code>Framework</code>](#Framework)
**Returns**: <code>object</code> - - found bot object or null
| Param | Type | Description |
| --- | --- | --- |
| roomId | <code>string</code> | id of room to search for |
**Example**
```js
let bot = framework.getBotByRoomId(roomId);
if (bot) {
bot.say('Hi, I\'m the bot in this room!');
} else {
console.log('Could not find bot for room ID: ' + roomId);
}
```
<a name="Framework+hears"></a>
### framework.hears(phrase, action, [helpText], [preference]) ⇒ <code>String</code>
Add action to be performed when bot hears a phrase.
**Kind**: instance method of [<code>Framework</code>](#Framework)
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| phrase | <code>Regex</code> \| <code>String</code> | | The phrase as either a regex or string. If regex, matches on entire message.If string, matches on first word. |
| action | <code>function</code> | | The function to execute when phrase is matched. Function is executed with 2 variables. Trigger and Bot. The Trigger Object contains information about the person who entered a message that matched the phrase. The Bot Object is an instance of the Bot Class as it relates to the room the message was heard. |
| [helpText] | <code>String</code> | | The string of text that describes how this command operates. |
| [preference] | <code>Number</code> | <code>0</code> | Specifies preference of phrase action when overlapping phrases are matched. On multiple matches with same preference, all matched actions are excuted. On multiple matches with difference preference values, only the lower preferenced matched action(s) are executed. |
**Example**
```js
// using a string to match first word and defines help text
framework.hears('/say', function(bot, trigger, id) {
bot.say(trigger.args.slice(1, trigger.arges.length - 1));
}, '/say <greeting> - Responds with a greeting');
```
**Example**
```js
// using regex to match across entire message
framework.hears(/(^| )beer( |.|$)/i, function(bot, trigger, id) {
bot.say('Enjoy a beer, %s! 🍻', trigger.person.displayName);
});
```
<a name="Framework+clearHears"></a>
### framework.clearHears(id) ⇒ <code>null</code>
Remove a "framework.hears()" entry.
**Kind**: instance method of [<code>Framework</code>](#Framework)
| Param | Type | Description |
| --- | --- | --- |
| id | <code>String</code> | The "hears" ID. |
**Example**
```js
// using a string to match first word and defines help text
var hearsHello = framework.hears('/framework', function(bot, trigger, id) {
bot.say('Hello %s!', trigger.person.displayName);
});
framework.clearHears(hearsHello);
```
<a name="Framework+showHelp"></a>
### framework.showHelp([header], [footer]) ⇒ <code>String</code>
Display help for registered Framework Commands.
**Kind**: instance method of [<code>Framework</code>](#Framework)
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [header] | <code>String</code> | <code>Usage:</code> | String to use in header before displaying help message. |
| [footer] | <code>String</code> | <code>Powered by Webex Node Bot Framework - https://github.com/webex/webex-node-bot-framework</code> | String to use in footer before displaying help message. |
**Example**
```js
framework.hears('/help', function(bot, trigger, id) {
bot.say(framework.showHelp());
});
```
<a name="Framework+setAuthorizer"></a>
### framework.setAuthorizer(Action) ⇒ <code>Boolean</code>
Attaches authorizer function.
**Kind**: instance method of [<code>Framework</code>](#Framework)
| Param | Type | Description |
| --- | --- | --- |
| Action | <code>function</code> | The function to execute when phrase is matched to authenticate a user. The function is passed the bot, trigger, and id and expects a return value of true or false. |
**Example**
```js
function myAuthorizer(bot, trigger, id) {
if(trigger.personEmail === 'john@test.com') {
return true;
}
else if(trigger.personDomain === 'test.com') {
return true;
}
else {
return false;
}
}
framework.setAuthorizer(myAuthorizer);
```
<a name="Framework+clearAuthorizer"></a>
### framework.clearAuthorizer() ⇒ <code>null</code>
Removes authorizer function.
**Kind**: instance method of [<code>Framework</code>](#Framework)
**Example**
```js
framework.clearAuthorizer();
```
<a name="Framework+storageDriver"></a>
### framework.storageDriver(Driver) ⇒ <code>Promise.<Boolean></code>
Defines storage backend.
**Kind**: instance method of [<code>Framework</code>](#Framework)
**Returns**: <code>Promise.<Boolean></code> - - True if driver loaded succesfully
| Param | Type | Description |
| --- | --- | --- |
| Driver | <code>function</code> | The storage driver. |
**Example**
```js
// define memory store (default if not specified)
framework.storageDriver(new MemStore());
```
<a name="Framework+use"></a>
### framework.use(path) ⇒ <code>Boolean</code>
Load a Plugin from a external file.
**Kind**: instance method of [<code>Framework</code>](#Framework)
| Param | Type | Description |
| --- | --- | --- |
| path | <code>String</code> | Load a plugin at given path. |
**Example**
```js
framework.use('events.js');
```
**Example**
```js
// events.js
module.exports = function(framework) {
framework.on('spawn', function(bot) {
console.log('new bot spawned in room: %s', bot.myroom.title);
});
framework.on('despawn', function(bot) {
console.log('bot despawned in room: %s', bot.myroom.title);
});
framework.on('messageCreated', function(message, bot) {
console.log('"%s" said "%s" in room "%s"', message.personEmail, message.text, bot.myroom.title);
});
};
```
<a name="Framework+checkMembershipRules"></a>
### framework.checkMembershipRules()
Private function to check for memembership rules in config
**Kind**: instance method of [<code>Framework</code>](#Framework)
<a name="Framework+myEmit"></a>
### framework.myEmit()
Private emit functions that check the membership rules
before emitting and event
**Kind**: instance method of [<code>Framework</code>](#Framework)
<a name="Bot"></a>
## Bot
**Kind**: global class
**Properties**
| Name | Type | Description |
| --- | --- | --- |
| id | <code>string</code> | Bot UUID |
| active | <code>boolean</code> | Bot active state |
| person | <code>object</code> | Bot's Webex Person Object |
| email | <code>string</code> | Bot email |
| room | <code>object</code> | Bot's Webex Room object |
| membership | <code>object</code> | Bot's Webex Membership object |
| isLocked | <code>boolean</code> | If bot is locked |
| isModerator | <code>boolean</code> | If bot is a moderator |
| isGroup | <code>boolean</code> | If bot is in Group Room |
| isDirect | <code>boolean</code> | If bot is in 1:1/Direct Room |
| isTeam | <code>boolean</code> | If bot is in a Room associated to a Team |
| isDirectTo | <code>string</code> | Recipient Email if bot is in 1:1/Direct Room |
| lastActivity | <code>date</code> | Last bot activity |
* [Bot](#Bot)
* [new Bot(framework, options, webex)](#new_Bot_new)
* [.exit()](#Bot+exit) ⇒ <code>Promise.<Boolean></code>
* [.getWebexSDK()](#Bot+getWebexSDK) ⇒ <code>object</code>
* [.add(email(s), [moderator])](#Bot+add) ⇒ <code>Promise.<Array></code>
* [.remove(email(s))](#Bot+remove) ⇒ <code>Promise.<Array></code>
* [.getModerators()](#Bot+getModerators) ⇒ <code>Promise.<Array></code>
* [.newRoom(name, emails, isTeam)](#Bot+newRoom) ⇒ [<code>Promise.<Bot></code>](#Bot)
* [.newTeamRoom(name, emails)](#Bot+newTeamRoom) ⇒ [<code>Promise.<Bot></code>](#Bot)
* [.moderateRoom()](#Bot+moderateRoom) ⇒ [<code>Promise.<Bot></code>](#Bot)
* [.unmoderateRoom()](#Bot+unmoderateRoom) ⇒ [<code>Promise.<Bot></code>](#Bot)
* [.moderatorSet(email(s))](#Bot+moderatorSet) ⇒ [<code>Promise.<Bot></code>](#Bot)
* [.moderatorClear(email(s))](#Bot+moderatorClear) ⇒ [<code>Promise.<Bot></code>](#Bot)
* [.implode()](#Bot+implode) ⇒ <code>Promise.<Boolean></code>
* [.say([format], message)](#Bot+say) ⇒ <code>Promise.<Message></code>
* [.sayWithLocalFile(message, filename)](#Bot+sayWithLocalFile) ⇒ <code>Promise.<Message></code>
* [.reply(replyTo, message, [format])](#Bot+reply) ⇒ <code>Promise.<Message></code>
* [.dm(person, [format], message)](#Bot+dm) ⇒ <code>Promise.<Message></code>
* [.sendCard(cardJson, fallbackText)](#Bot+sendCard) ⇒ <code>Promise.<Message></code>
* [.dmCard(person, cardJson, fallbackText)](#Bot+dmCard) ⇒ <code>Promise.<Message></code>
* [.uploadStream(filename, stream)](#Bot+uploadStream) ⇒ <code>Promise.<Message></code>
* [.censor(messageId)](#Bot+censor) ⇒ <code>Promise.<Message></code>
* [.roomRename(title)](#Bot+roomRename) ⇒ <code>Promise.<Room></code>
* [.store(key, value)](#Bot+store) ⇒ <code>Promise.<String></code> \| <code>Promise.<Number></code> \| <code>Promise.<Boolean></code> \| <code>Promise.<Array></code> \| <code>Promise.<Object></code>
* [.recall([key])](#Bot+recall) ⇒ <code>Promise.<String></code> \| <code>Promise.<Number></code> \| <code>Promise.<Boolean></code> \| <code>Promise.<Array></code> \| <code>Promise.<Object></code>
* [.forget([key])](#Bot+forget) ⇒ <code>Promise.<String></code> \| <code>Promise.<Number></code> \| <code>Promise.<Boolean></code> \| <code>Promise.<Array></code> \| <code>Promise.<Object></code>
<a name="new_Bot_new"></a>
### new Bot(framework, options, webex)
Creates a Bot instance that is then attached to a Webex Team Room.
| Param | Type | Description |
| --- | --- | --- |
| framework | <code>Object</code> | The framework object this Bot spawns under. |
| options | <code>Object</code> | The options of the framework object this Bot spawns under. |
| webex | <code>Object</code> | The webex sdk of the framework object this Bot spawns under. |
<a name="Bot+exit"></a>
### bot.exit() ⇒ <code>Promise.<Boolean></code>
Instructs Bot to exit from room.
**Kind**: instance method of [<code>Bot</code>](#Bot)
**Example**
```js
bot.exit();
```
<a name="Bot+getWebexSDK"></a>
### bot.getWebexSDK() ⇒ <code>object</code>
Accessor for Webex SDK instance
This is a convenience and returns the same shared Webex SDK instance
that is returned by a call to framework.getWebexSDK()
Access SDK functionality described in [SDK Reference](https://developer.webex.com/docs/sdks/browser#sdk-api-reference)
**Kind**: instance method of [<code>Bot</code>](#Bot)
**Returns**: <code>object</code> - - Bot's Webex SDK instance
**Example**
```js
let webex = bot.getWebexSDK();
webex.people.get(me)
.then(person => {
console.log('SDK instantiated by: ' + person.displayName);
}).catch(e => {
console.error('SDK failed to lookup framework user: ' + e.message);
});
```
<a name="Bot+add"></a>
### bot.add(email(s), [moderator]) ⇒ <code>Promise.<Array></code>
Instructs Bot to add person(s) to room.
**Kind**: instance method of [<code>Bot</code>](#Bot)
**Returns**: <code>Promise.<Array></code> - Array of emails added
| Param | Type | Description |
| --- | --- | --- |
| email(s) | <code>String</code> \| <code>Array</code> | Email Address (or Array of Email Addresses) of Person(s) to add to room. |
| [moderator] | <code>Boolean</code> | Add as moderator. |
**Example**
```js
// add one person to room by email
bot.add('john@test.com');
```
**Example**
```js
// add one person as moderator to room by email
bot.add('john@test.com', true)
.catch(function(err) {
// log error if unsuccessful
console.log(err.message);
});
```
**Example**
```js
// add 3 people to room by email
bot.add(['john@test.com', 'jane@test.com', 'bill@test.com']);
```
<a name="Bot+remove"></a>
### bot.remove(email(s)) ⇒ <code>Promise.<Array></code>
Instructs Bot to remove person from room.
**Kind**: instance method of [<code>Bot</code>](#Bot)
**Returns**: <code>Promise.<Array></code> - Array of emails removed
| Param | Type | Description |
| --- | --- | --- |
| email(s) | <code>String</code> \| <code>Array</code> | Email Address (or Array of Email Addresses) of Person(s) to remove from room. |
**Example**
```js
// remove one person to room by email
bot.remove('john@test.com');
```
**Example**
```js
// remove 3 people from room by email
bot.remove(['john@test.com', 'jane@test.com', 'bill@test.com']);
```
<a name="Bot+getModerators"></a>
### bot.getModerators() ⇒ <code>Promise.<Array></code>
Get room moderators.
**Kind**: instance method of [<code>Bot</code>](#Bot)
**Example**
```js
bot.getModerators()
.then(function(moderators) {
console.log(moderators);
});
```
<a name="Bot+newRoom"></a>
### bot.newRoom(name, emails, isTeam) ⇒ [<code>Promise.<Bot></code>](#Bot)
Create new room with people by email
**Kind**: instance method of [<code>Bot</code>](#Bot)
| Param | Type | Description |
| --- | --- | --- |
| name | <code>String</code> | Name of room. |
| emails | <code>Array</code> | Emails of people to add to room. |
| isTeam | <code>Boolean</code> | - Create a team room (if bot is already in a team space) |
<a name="Bot+newTeamRoom"></a>
### bot.newTeamRoom(name, emails) ⇒ [<code>Promise.<Bot></code>](#Bot)
Create new Team Room
This can also be done by passing an optional boolean
isTeam param to the newRoom() function, but this function
is also kept for compatibility with node-flint
**Kind**: instance method of [<code>Bot</code>](#Bot)
| Param | Type | Description |
| --- | --- | --- |
| name | <code>String</code> | Name of room. |
| emails | <code>Array</code> | Emails of people to add to room. |
<a name="Bot+moderateRoom"></a>
### bot.moderateRoom() ⇒ [<code>Promise.<Bot></code>](#Bot)
Enable Room Moderation.
This function will not work when framework was created
using a bot token, it requires an authorized user token
**Kind**: instance method of [<code>Bot</code>](#Bot)
**Example**
```js
bot.moderateRoom()
.then(function(err) {
console.log(err.message)
});
```
<a name="Bot+unmoderateRoom"></a>
### bot.unmoderateRoom() ⇒ [<code>Promise.<Bot></code>](#Bot)
Disable Room Moderation.
This function will not work when framework was created
using a bot token, it requires an authorized user token
**Kind**: instance method of [<code>Bot</code>](#Bot)
**Example**
```js
bot.unmoderateRoom()
.then(function(err) {
console.log(err.message)
});
```
<a name="Bot+moderatorSet"></a>
### bot.moderatorSet(email(s)) ⇒ [<code>Promise.<Bot></code>](#Bot)
Assign Moderator in Room
This function will not work when framework was created
using a bot token, it requires an authorized user token
**Kind**: instance method of [<code>Bot</code>](#Bot)
| Param | Type | Description |
| --- | --- | --- |
| email(s) | <code>String</code> \| <code>Array</code> | Email Address (or Array of Email Addresses) of Person(s) to assign as moderator. |
**Example**
```js
bot.moderatorSet('john@test.com')
.then(function(err) {
console.log(err.message)
});
```
<a name="Bot+moderatorClear"></a>
### bot.moderatorClear(email(s)) ⇒ [<code>Promise.<Bot></code>](#Bot)
Unassign Moderator in Room
This function will not work when framework was created
using a bot token, it requires an authorized user token
**Kind**: instance method of [<code>Bot</code>](#Bot)
| Param | Type | Description |
| --- | --- | --- |
| email(s) | <code>String</code> \| <code>Array</code> | Email Address (or Array of Email Addresses) of Person(s) to unassign as moderator. |
**Example**
```js
bot.moderatorClear('john@test.com')
.then(function(err) {
console.log(err.message)
});
```
<a name="Bot+implode"></a>
### bot.implode() ⇒ <code>Promise.<Boolean></code>
Remove a room and all memberships.
**Kind**: instance method of [<code>Bot</code>](#Bot)
**Example**
```js
framework.hears('/implode', function(bot, trigger) {
bot.implode();
});
```
<a name="Bot+say"></a>
### bot.say([format], message) ⇒ <code>Promise.<Message></code>
Send text with optional file to room.
**Kind**: instance method of [<code>Bot</code>](#Bot)
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [format] | <code>String</code> | <code>text</code> | Set message format. Valid options are 'text' or 'markdown'. |
| message | <code>String</code> \| <code>Object</code> | | Message to send to room. This can be a simple string, or a object for advanced use. |
**Example**
```js
// Simple example
framework.hears('/hello', function(bot, trigger) {
bot.say('hello');
});
```
**Example**
```js
// Simple example to send message and file
framework.hears('/file', function(bot, trigger) {
bot.say({text: 'Here is your file!', file: 'http://myurl/file.doc'});
});
```
**Example**
```js
// Markdown Method 1 - Define markdown as default
framework.messageFormat = 'markdown';
framework.hears('/hello', function(bot, trigger) {
bot.say('**hello**, How are you today?');
});
```
**Example**
```js
// Markdown Method 2 - Define message format as part of argument string
framework.hears('/hello', function(bot, trigger) {
bot.say('markdown', '**hello**, How are you today?');
});
```
**Example**
```js
// Mardown Method 3 - Use an object (use this method of bot.say() when needing to send a file in the same message as markdown text.
framework.hears('/hello', function(bot, trigger) {
bot.say({markdown: '*Hello <@personEmail:' + trigger.personEmail + '|' + trigger.personDisplayName + '>*'});
});
```
**Example**
```js
// Send an Webex card by providing a fully formed message object.
framework.hears('/card please', function(bot, trigger) {
bot.say({
// Fallback text for clients that don't render cards is required
markdown: "If you see this message your client cannot render buttons and cards.",
attachments: [{
"contentType": "application/vnd.microsoft.card.adaptive",
"content": myCardsJson
}]
});
```
<a name="Bot+sayWithLocalFile"></a>
### bot.sayWithLocalFile(message, filename) ⇒ <code>Promise.<Message></code>
Send optional text message with a local file to room.
**Kind**: instance method of [<code>Bot</code>](#Bot)
| Param | Type | Description |
| --- | --- | --- |
| message | <code>String</code> \| <code>Object</code> | Message to send to room. If null or empty string is ignored. If set the default messageFormat is used |
| filename | <code>String</code> | name of local file to send to space |
**Example**
```js
// Simple example
framework.hears('/file', function(bot, trigger) {
bot.sayWithLocalFile('here is a file', './image.jpg);
});
```
<a name="Bot+reply"></a>
### bot.reply(replyTo, message, [format]) ⇒ <code>Promise.<Message></code>
Send a threaded message reply
**Kind**: instance method of [<code>Bot</code>](#Bot)
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| replyTo | <code>String</code> \| <code>Object</code> | | MessageId or message object or attachmentAction object to send to reply to. |
| message | <code>String</code> \| <code>Object</code> | | Message to send to room. This can be a simple string, or a message object for advanced use. |
| [format] | <code>String</code> | <code>text</code> | Set message format. Valid options are 'text' or 'markdown'. Ignored if message is an object |
**Example**
```js
// Simple example
framework.hears('/hello', function(bot, trigger) {
bot.reply(trigger.message, 'hello back at you');
});
```
**Example**
```js
// Markdown Method 1 - Define markdown as default
framework.messageFormat = 'markdown';
framework.hears('/hello', function(bot, trigger) {
bot.reply(trigger.message, '**hello**, How are you today?');
});
```
**Example**
```js
// Markdown Method 2 - Define message format as part of argument string
framework.hears('/hello', function(bot, trigger) {
bot.reply(trigger.message, '**hello**, How are you today?', 'markdown');
});
```
**Example**
```js
// Mardown Method 3 - Use an object (use this method of bot.reply() when needing to send a file in the same message as markdown text.
framework.hears('/hello', function(bot, trigger) {
bot.reply(trigger.message, {markdown: '*Hello <@personEmail:' + trigger.personEmail + '|' + trigger.personDisplayName + '>*'});
});
```
**Example**
```js
// Reply to a card when a user hits an action.submit button
framework.on('attachmentAction', function(bot, trigger) {
bot.reply(trigger.attachmentAction, 'Thanks for hitting the button');
});
```
<a name="Bot+dm"></a>
### bot.dm(person, [format], message) ⇒ <code>Promise.<Message></code>
Send text with optional file in a direct message.
This sends a message to a 1:1 room with the user (creates 1:1, if one does not already exist)
**Kind**: instance method of [<code>Bot</code>](#Bot)
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| person | <code>String</code> | | Email or personId of person to send Direct Message. |
| [format] | <code>String</code> | <code>text</code> | Set message format. Valid options are 'text' or 'markdown'. |
| message | <code>String</code> \| <code>Object</code> | | Message to send to room. This can be a simple string, or a object for advanced use. |
**Example**
```js
// Simple example
framework.hears('dm me', function(bot, trigger) {
bot.dm(trigger.person.id, 'hello');
});
```
**Example**
```js
// Simple example to send message and file
framework.hears('dm me a file', function(bot, trigger) {
bot.dm(trigger.person.id, {text: 'Here is your file!', file: 'http://myurl/file.doc'});
});
```
**Example**
```js
// Markdown Method 1 - Define markdown as default
framework.messageFormat = 'markdown';
framework.hears('dm me some rich text', function(bot, trigger) {
bot.dm(trigger.person.id, '**hello**, How are you today?');
});
```
**Example**
```js
// Markdown Method 2 - Define message format as part of argument string
framework.hears('dm someone', function(bot, trigger) {
bot.dm('john@doe.com', 'markdown', '**hello**, How are you today?');
});
```
**Example**
```js
// Mardown Method 3 - Use an object (use this method of bot.dm() when needing to send a file in the same message as markdown text.
framework.hears('dm someone', function(bot, trigger) {
bot.dm('someone@domain.com', {markdown: '*Hello <@personId:' + trigger.person.id + '|' + trigger.person.displayName + '>*'});
});
```
<a name="Bot+sendCard"></a>
### bot.sendCard(cardJson, fallbackText) ⇒ <code>Promise.<Message></code>
Send a Webex Teams Card to room.
**Kind**: instance method of [<code>Bot</code>](#Bot)
**See**
- [Buttons and Cards Guide](https://developer.webex.com/docs/api/guides/cards#working-with-cards) for further information.
- [Buttons and Cards Framework Example](./docs/buttons-and-cards-example.md)
| Param | Type | Description |
| --- | --- | --- |
| cardJson | <code>Object</code> | The card JSON to render. This can come from the Webex Buttons and Cards Designer. |
| fallbackText | <code>String</code> | Message to be displayed on client's that can't render cards. |
**Example**
```js
// Simple example
framework.hears('card please', function(bot, trigger) {
bot.SendCard(
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.0",
"body": [
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"width": 2,
"items": [
{
"type": "TextBlock",
"text": "Card Sample",
"weight": "Bolder",
"size": "Medium"
},
{
"type": "TextBlock",
"text": "What is your name?",
"wrap": true