# @exoplay/exobot

> a chatbox

Latest version **5.0.0-rc.6** (published 2017-05-18) · LGPL license · 0 weekly downloads

## Install

```sh
npm install @exoplay/exobot
pnpm add @exoplay/exobot
yarn add @exoplay/exobot
bun add @exoplay/exobot
```

## Health

**Score 15/100 (F)** — status: abandoned.

Positive: no vulnerabilities.

Warnings: low downloads; no types; no esm support.

Negative: abandoned; low maintenance score.

## Facts

| | |
|---|---|
| Version | 5.0.0-rc.6 |
| Published | 2017-05-18 |
| First published | 2016-06-01 |
| Weekly downloads | 0 |
| License | LGPL |
| TypeScript types | none |
| Module format | CommonJS |
| Dependencies | 13 |
| Known vulnerabilities | 0 |
| Install scripts | no |
| GitHub stars | 8 |
| Author | Jack Lawson |
| Maintainers | ajacksified |
| Keywords | chatbot |

## Links

- npm: https://www.npmjs.com/package/@exoplay/exobot
- Repository: https://github.com/exoplay/exobot
- Homepage: https://github.com/exoplay/exobot#readme
- Issues: https://github.com/exoplay/exobot/issues
- npm.io page: https://npm.io/package/@exoplay/exobot

## Dependencies (13)

- [log](https://npm.io/package/log.md) ^1.4.0
- [lowdb](https://npm.io/package/lowdb.md) ^0.16.2
- [cryptr](https://npm.io/package/cryptr.md) ~2.0.0
- [lodash](https://npm.io/package/lodash.md) ^4.17.3
- [lodash-id](https://npm.io/package/lodash-id.md) ^0.14.0
- [node-uuid](https://npm.io/package/node-uuid.md) ~1.4.7
- [proptypes](https://npm.io/package/proptypes.md) ^0.14.3
- [superagent](https://npm.io/package/superagent.md) ^3.3.1
- [change-case](https://npm.io/package/change-case.md) ^3.0.1
- [eventemitter3](https://npm.io/package/eventemitter3.md) ^2.0.2
- [babel-polyfill](https://npm.io/package/babel-polyfill.md) ^6.20.0
- [@exoplay/exobot-build](https://npm.io/package/@exoplay/exobot-build.md) ^1.0.1
- [superagent-promise-plugin](https://npm.io/package/superagent-promise-plugin.md) ~3.2.0

## Recent versions

- 5.0.0-rc.6 (latest) — 2017-05-18
- 5.0.0-rc.5 — 2017-05-18
- 5.0.0-rc.4 — 2017-05-18
- 5.0.0-rc.3 — 2017-05-18
- 4.3.1 — 2017-05-18
- 5.0.0-rc.2 — 2017-05-18
- 5.0.0-rc.1 — 2017-05-18
- 4.3.0 — 2017-05-18
- 4.2.3 — 2017-05-18
- 4.2.2 — 2017-02-24
- 4.2.1 — 2017-02-22
- 4.2.0 — 2017-02-22
- 4.1.3 — 2017-02-03
- 4.1.2 — 2017-01-25
- 4.1.1 — 2017-01-22
- … 53 more at https://npm.io/package/@exoplay/exobot/versions

## README

# Exobot

An ES6+ chatbot. Requires Node ^6.2.



## Installation

* `npm install --save @exoplay/exobot`


## A Brief Example

To start an exobot instance, you need to import the bot itself and initialize it
with plugins and chat service adapters. Here's an example:

```javascript
import { Exobot, adapters, plugins, LogLevels } from '@exoplay/exobot';
import { Points } from '@exoplay/exobot-plugin-points';

const { Help, Greetings } = plugins;

const BOT_ALIAS = '!e';
const BOT_NAME = 'exobot';
const LOG_LEVEL = LogLevels.INFO;

const shell = adapters.Shell;

const bot = new Exobot(BOT_NAME, {
  alias: BOT_ALIAS,
  adapters: [
    new shell(),
  ],
  plugins: [
    new Help(),
    new Greetings(),
    new Points(),
  ],
  logLevel: LOG_LEVEL,
});

module.exports = bot;
```

```
$ npm run build
$ node exobot.js
> Chat: hi
> exobot: hi, shell!
> Chat: exobot++ for being awesome
> exobot: exobot has 1337 points, 1336 of which are for being awesome
```


What did we do there?

* Created a file named `./src/exobot.js`
* Imported the `Exobot` class, service adapters, and plugins
* Initialized a new bot, passing in its name, configured service adapters and
  plugins
* Built the bot to turn ES7+ into node-compatible ES6
* Started the bot
* Ran `node index.js` and interacted with the bot


## Getting started

The easiest way to start is copy the example above - this will get you started
with a chatbot with a shell adapter. The shell adapter will start an
interactive console with which you can chat in a single "room"; Exobot will
respond to messages that trigger plugins.

1. Make a directory somewhere that you want to keep your bot's configuration
  code.
2. Run `git init` (or source control initialization method of choice), then
  `npm init` to start up an NPM package. (You probably won't publish your bot
  as its own package - but this will create a `package.json` file that contains
  your dependencies.)
3. Run `npm install --save @exoplay/exobot` to install the chatbot.
4. Add [npm scripts](https://docs.npmjs.com/misc/scripts)
  such as `"build": "exobot-build"` and `"watch": "exobot-build --watch"` to
  your package.json to get access to the bot building commands
4. Copy the example above to `./src/exobot.js`.
5. Build the bot with `npm run build`
5. Run `node exobot.js`. Chat with yourself for a while, then read on to learn
  how to configure your chatbot, or even build your own plugins and adapters.


## Configuration

Exobot is configured in its constructor, which takes two arguments - a bot
name (a required string), and an options object.

The bot name is used for commands - if your bot's name is `'exobot'`, it will
`respond` to commands beginning with `'exobot'`. You'll want this to match the
name used in your chat service (so if its name is actually `'DEATHBOT_9000'` in
Slack, you should call it that here too, or people may be confused.) 

The options object contains all other configuration - such as a list of
plugins and chat service adapters, log levels, and data encryption keys.

* `alias` - an additional way to trigger exobot commands. `'/'`, `';'`, or
  `'hey bot'`, for example.
* `adapters` - an array of initialized chat adapters, such as
  [slack](https://github.com/exoplay/exobot-adapter-slack),
  [discord](https://github.com/exoplay/exobot-adapter-discord),
  or [twitch](https://github.com/exoplay/exobot-adapter-twitch). exobot
  also comes with a `shell` adapter for playing around in your terminal.
* `plugins` - an array of initialized plugins, such as 
  [giphy](https://github.com/exoplay/exobot-plugin-giphy) or
  [points](https://github.com/exoplay/exobot-plugin-points). exobot also
  comes with `help` and `greetings` plugins as examples.
* `readFile` and `writeFile` - functions called when the in-memory json db
  is saved. By default, this writes a json file to `cwd/data/botname.json`,
  but you could also override the default local file storage to use s3 with
  [exobot-db-s3](https://github.com/exoplay/exobot-db-s3).
* `dbPath` - if you're using local file storage, you can set where to save.
  Defaults to `cwd/data/botname.json`.



## Building plugins

Most plugins respond to chat messages - either by `listen`ing to _all_ chat
messages, or `respond`ing to specific commands. exobot comes with `greetings`
and `help` plugins, but building your own is easy. Some examples:

* [giphy, for gif search](https://github.com/exoplay/exobot-plugin-giphy)
* [points, for fun](https://github.com/exoplay/exobot-plugin-points)

The [ES2017 decorators proposal](https://github.com/wycats/javascript-decorators)
is used to hook commands to validation functions or regexes, to assign permission
groups, and to provide help text.

### An Example Plugin

```javascript
import { ChatPlugin, respond, help, permissionGroup } from '@exoplay/exobot';

export default class Ping extends ChatPlugin {
  static name = 'ping';

  @help('Says "pong" when you send it "ping"');
  @permissionGroup('ping');
  @respond(/^ping$/);
  pong (match, message) {
    return 'pong';
  }
}
```

In this plugin, we have extended exobot's ChatPlugin class - this gives it
functionality to respond to chat messages. We've then told it to `respond` to
the regex `/ping/` by firing a function, called `pong`. The `return` value of
the function is then sent back to the chat channel.

### A Detailed Anatomy of a Chat Plugin

Chat plugins follow the following lifecycle:

First, The `constructor` is called with options sent in. As the bot is
initialized with _instances_ of plugins, this is where you would pass in
configuration options, such as:

```javascript
import { ChatPlugin, respond, help, permissionGroup } from '@exoplay/exobot';

class StatusPlugin extends ChatPlugin {
  constructor (options) {
    super(options);
    this.endpoint = options.endpoint;
  }

  //...

  @help('Gets the status of the configured endpoint.');
  @permissionGroup('get');
  @respond(m => m.text === 'status');
  async getStatus () {
    const res = await this.http.get(this.endpoint);
    return res.statusCode;
  }
}
```

In the above example, we'd initialize the exobot instance with
`plugins: [ new StatsPlugin({ endpoint: 'https://github.com' }) ]` to pass
in the options we need later on.

Next, when the bot instance begins listening, the plugin's `register` method is
called, with the `bot` instace passed in. Note that the constructor doesn't have
the bot yet - it doesn't exist until `register`, fired next.

You'll want to give the plugin a `static` `name` property - thi is used if you
use the permissions plugin to restrict access to commands.

`listen` and `respond` are decorators that take a function, and fire the method
when a match is found. `listen`
and `respond` are the most important parts of your chat plugin - these allow the
bot to interact with chat. Each can take either a regex _or_ a function, and if
a match is found (or, if a function, if it is truthy), it will fire the
function passed in. Functions for responding can be promises (or
ES7 `async` functions) and will resolve when the promises do. This makes it
easy to write asynchronous code, such as firing http requests.

The responding function gets two arguments: a `match` object, which is either
the regex's `exec` response or the function return value, and a `Message`
object, which contains the original message, user, and whether the message is a
whisper.

You can optionally add a `help` decorator, which exobot's `help` plugin uses to
explain to useres how the plugin works.

You should also add a `permissionsGroup`, which you can then use with
exobot's `Permissions` plugin to restrict access to certain commands. In the
following case, you can give access to `status.get` to groups, and if you deny
access by default in configuration, only users in the group with access to
`status.get` can use the command. (The bot will ignore the command from
everyone else.)

Finally, the bot also exposes `bot.http`, which is a promise-ified
[superagent](https://visionmedia.github.io/superagent/) wrapper, to make http
calls easy to make.

```javascript
import { ChatPlugin, respond, listen, permissionsGroup, help } from '@exoplay/exobot';

class StatusPlugin extends ChatPlugin {
  static name = 'Status';

  help = [
    'Get the status of an http endpoint. Responds to `status` or listens to',
    'status <http://whatever.com>.'
  ].join('\n');

  constructor (options) {
    super(options);
    this.endpoint = options.endpoint;
  }

  register (bot) {
    super.register(bot);

    if (!this.endpoint) {
      bot.log.warn('No endpoint passed in to StatusPlugin.');
    }

    this.respond(/status/, this.getStatus);
    this.listen(/^status (http:\/\/\S+)/, this.getStatus);
    this.listen(m => m.text === 'status', this.getStatus);
  }


  @help('use status or status <http> to get http status codes.');
  @permissionsGroup('get');
  @respond(/^status$/);
  @listen(/^status (http:\/\/\S+)/);
  @listen(m => m.text === 'status');
  async getStatus (match, message) {
    let endpoint = this.endpoint;

    // if the regex succeeded, match[1] should be an http endpoint
    if (match && match.length) {
      endpoint = match[1];
    }

    const res = await this.http.get(this.endpoint);
    return res.statusCode;
  }
}
```

You can also build other types of plugins: `EventPlugin`, `HTTPPlugin`, or build
your own class of plugin with the `Plugin` class. Documentation to come someday.

Exobot exports a handy scripts for testing your plugins: `exobot-try`. By either
installing exobot globally (`npm install -g @exoplay/exobot`) or adding a script
to your package.json (`"try": "exobot-try"`), exobot will fire up a simple bot
with a shell adapter so you can test your plugin.


## Building Adapters

Adapters allow your bot to connect to a chat service, such as Slack or Discord.
exobot comes with a shell adapter by default, but you could also build your own
for your chat service of choice. Some examples:

* [slack](https://github.com/exoplay/exobot-adapter-slack)
* [discord](https://github.com/exoplay/exobot-adapter-discord)
* [twitch](https://github.com/exoplay/exobot-adapter-twitch)

### An Example Adapter

```javascript
// An example: import an API lib for your chat service, or do it with raw
// sockets or http, or whatever.
import ChatServiceLibrary from '@chatservice/lib';
import { Adapter, User } from '@exoplay/exobot';

export default class ChatServiceAdapter extends Adapter {
  constructor ({ token, username }) {
    super(...arguments);
    this.token = token;
    this.username = username;
  }

  register (bot) {
    super.register(bot);
    // Initialize the chat service lib we pulled in earlier
    this.service = new ChatServiceLibrary(this.username, this.token);

    // listen to some events. bind the functions to `this` to make sure we can
    // access our class instance, bot, and `super`.
    this.service.on('ready', this.serviceReady.bind(this));
    this.service.on('message', this.serviceMessage.bind(this));
  }

  // the `send` funciton is defined by Adapter and called by plugins when they
  // resolve (if they resolve.)
  send (message) {
    this.bot.log.debug(`Sending ${message.text} to ${message.channel}`);

    // Send the message data to the chat service client.
    this.service.sendMessage({
      to: message.channel,
      message: message.text,
    });
  }

  serviceReady () {
    this.status = Adapter.STATUS.CONNECTED;
    this.bot.emitter.emit('connected', this.id);
    this.bot.log.notice('Connected to ChatService.');
  }

  // We'll pretend our fake chat service lib takes a function which is called
  // with message, user, and channel. We'll take these arguments and "receive"
  // them, which will fire off all of the plugins so they can respond where
  // necessary.
  serviceMessage (user, text, channel) {
    // We don't want to listen to messages from ourself.
    if (user.name === this.username) { return; }

    // Create a new User instance to pass along in the Message.
    const user = new User(user.name, user.id);

    // Check if our fake chat service lib says the channel is "private". If it
    // is a private message between the user and bot, we'll make it act like a
    // "respond" command instead of just a "listen".
    if (channel.private) {
      return super.receiveWhisper({ user, text, channel });
    }

    return this.receive({ user, text, channel });
  }

  // This is useful for chat services where names aren't unique, for use by
  // plugins where uniqueness matters (like Permissions.)
  getUserIdByUserName (name) {
    return this.service.getUserByName(name).id;
  }
}

```

### A Detailed Anatomy of a Chat Service Adapter

Chat service adapters have a similar lifecycle to plugins:

1. Constructor, before the bot is initialized;
2. Register, when the bot is initialized, where you first get acccess to the
  exobot instance, and where you listen to your chat service;
3. Functions called by events fired by the chat service;
4. Finally, `send`, called by the bot instance when plugins resolve.

You can listen and fire any arbitrary functions - for example, some chat
services may include presence information, and fire `enter` and `leave` events.
You can then `receive` your own `PresenceMessage` similar to how we `receive` a
`TextMessage` in the `serviceMessage` in the example. (Right now, the only
`Message` classes are `TextMessage` and `PresenceMessage`). Many adapters may
also want to make use of the `Status` enum, which could be:

* UNINITIALIZED
* CONNECTING
* CONNECTED
* DISCONNECTED
* RECONNECTING
* ERROR

You may also want to use `bot.log` to log important events to stdout, such as
connection or configuration errors. `bot.log` can fire:

* debug
* info
* notice
* warning
* error
* critical
* alert
* emergency

In order of ascending severity.


## Acknowledgements

Exobot is loosely based on [hubot](https://github.com/github/hubot), for which
the author has a great deal of admiration. Hubot is more user-friendly in many
ways (autoloading scripts, for example, instead of requiring the user to write
their own imports and configuration). In other ways, this flexibility can be
limiting; it's easier to make a pure-js bot more efficient and testable (and
the author thinks that ES6, rather than Coffeescript, is a more viable choice
of language; plugin-writers can always choose to opt-in to Coffeescript and
export a built file if they want.)



## License

LGPL licensed. Copyright 2016 Exoplay, LLC. See LICENSE file for more details.

---
_Source: https://npm.io/package/@exoplay/exobot · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
