
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
##Table of Contents
HotBot is the perfect starting point if you have many different tasks you'd like to control through Slack and don't want to try and configure a million different apps and integrations.
Anything. The HotBot I run in production at the moment does:
Hotbot is made of 3 primary components, a config file, Modules and Commands that interact with each other in various ways.
Latest release:
$ npm install hotbot
A basic HotBot project is structured as follows
|--app.js
|--config
| |--config.js
|--modules
| |--myModule
| | |--index.js
| |--myOtherModule
| |--index.js
|--commands
|--myCommand
| |--index.js
|--myOtherCommand
|--index.js
The mimimal configuration (/config/config.json) is as follows :
const config = {
modulesPath: 'path/to/modules/directory',
modules: [], // module config goes here, see further down for details
slack: {
name: 'slack',
connection: {
token: 'xoxo-mySlackBotToken',
autoReconnect: true,
autoMark: true
},
commandsPath: 'path/to/commands/directory'
}
};
module.exports = config;
And the most basic app possible (does absolutely nothing, but will light up your bot's dot in the slack panel) :
const config = require('./config/config.js');
const HotBot = require('hotbot');
const app = new HotBot(config);
app.plugModules();
app.on('init', () => console.log('All modules init'))
.on('log', m => console.log(m));
Modules are essentially daemons. Example of modules would be :
Modules live in a directory that you can specify, and will be auto-loaded/init on startup, in an order that you can also configure. They can also pass messages to each other, and to the Slack Bot (which is also a Module), and request components from each other. For example, your Github module needs to add a route to your Express server module at runtime? Not a problem!
|--app.js
|--config
| |--config.js
|--commands
|--modules
|--web
|--index.js
const config = {
modulesPath: path.resolve(__dirname, '../modules'),
modules: [
{
name: 'web',
priority: 1,
port: 1987
}
],
slack: {
name: 'slack',
connection: {
token: 'xoxb-tokentokentoken',
autoReconnect: true,
autoMark: true
},
commandsPath: path.resolve(__dirname, '../commands')
}
};
module.exports = config;
Priority isn't an issue, so we'll leave it at 1, we want the web server to start as soon as possible.
const express = require('express');
const Module = require('hotbot').Module;
const bodyParser = require('body-parser');
class Web extends Module {
constructor(config) {
super();
this.config = config;
this.app = express()
.use(bodyParser.json());
}
init(cb) {
this.app.get('/', (req,res)=>{
this.sendToSlack('Someone is on our server!', ['me','otheruser'], ['general','otherchannel']);
res.send('Hello, world!')
});
this.app.listen(this.config.port, () => {
console.log('web start');
cb();
});
}
}
module.exports = Web;
The sendToSlack method takes 2 arrays, the first one, for DM names (eg:user names), the second for channel names. Any time someone will ping our server on port 1987, we'll get notified on Slack.
In this follow-up example, let's add a module that will notify us when someone commits to one of our organization projects on Github. we have setup an organization-wide webhook to send us notifications to htxp://example.com:1987/commit-hook.
Now let's get the other side of the hook ready!
Building on the previous example, we already have a web server. Our new module should load after it, so we can inject the route for our hook.
modules: [
{
name: 'web',
priority: 1,
port: 1987
}, {
name: 'git',
priority: 60,
notifyChannel: 'bot_broadcast',
notifyDM: 'xshirase'
}
],
We've also set up a default user and channel to notify. Now let's get to the real thing.
const Module = require('hotbot').Module;
class Git extends Module {
constructor(config) {
super(config);
}
init(cb) {
this.requestRoute('addRoute');
cb();
}
addRoute(app) {
app.post('/hook_push', (req, res) => {
this.processCommit(req.body);
res.status(200).json({});
});
}
processCommit(git) {
const o = {
pusher: git.pusher.name,
repo: git.repository.name,
branch: git.ref.replace(/.*heads\//, ''),
num: git.after,
mod: git.head_commit.modified,
add: git.head_commit.added,
del: git.head_commit.deleted,
url: git.head_commit.url,
commits: git.commits
};
this.log(`Git commit on ${o.repo}/${o.branch}`);
const chan = this.config.notifyChannel;
const dm = this.config.notifyDM;
const msg = this.buildMsg(o);
this.sendToSlack(msg, dm, chan);
}
buildMsg(o) {
return `*Git Report* : \n - Push by ${o.pusher} on *${o.repo} / _${o.branch}_* \n- Head Commit URL: ${o.url}\n- *Commits (${o.commits.length})* : \n${o.commits.map(el => `_${el.message}_`).join('\n')}`;
}
}
module.exports = Git;
The result, on a random commit :

Commands are what you type into the Slack app, and how the bot reacts to it. Commands take priority over regular messages.
The Command class includes a slack.dataStore, for easy access to all the helpers it provides. It also abstracts a lot of the things you want to do multiple times, like send a message, wait for a specific user input, etc.
To add a new command, create a new folder in the commands directory (configurable), and add an index.js containing the command config and class.
|--app.js
|--config
| |--config.js
|--modules
|--commands
|--fw
|--index.js
const config = {
name: 'fw',
args: [{type: 'string'}, {type: 'string'}],
help: 'fw <channel> "<message>"',
logLevel: 0
};
const Command = require('hotbot').Command;
class Fw extends Command {
constructor(dataStore) {
super(dataStore, config);
}
execute(msg) {
const args = msg.text;
if (this.store.getChannelByName(args[0])) {
this.send(args[1], this.store.getChannelByName(args[0]).id);
}else if (this.store.getDMByName(args[0])) {
this.send(args[1], this.store.getDMByName(args[0]).id);
}else {
this.send(`Can't find recipient *${args[0]}*`, msg.channel);
}
}
}
module.exports = Fw;
The above example simply forwards a message to a channel or DM of your choice.
Notice how the argument number and types are defined, by default, a command string will be parsed first by delimiting quotes, THEN by spaces, and msg.text will reach your command as an Array of arguments.
fw general "HotBot Rocks!"
Type it in any channel your bot lives in, and it'll write "HotBot rocks!" in the general channel.
|--app.js
|--config
| |--config.js
|--modules
|--commands
|--rps
|--index.js
const Command = require('hotbot').Command;
const config = {
name: 'rps',
args: [{type: 'string'}, {type: 'string'}],
help: 'rps <opponent> <move>',
logLevel: 0
};
class RPS extends Command {
constructor(dataStore) {
super(dataStore, config);
this.moves = ['rock', 'paper', 'scissors'];
this.map = {};
this.moves.forEach((choice, i) => {
this.map[choice] = {};
this.map[choice][choice] = 1;
this.map[choice][this.moves[(i + 1) % 3]] = this.moves[(i + 1) % 3];
this.map[choice][this.moves[(i + 2) % 3]] = choice;
});
}
execute(msg) {
// Receiving the command message
const args = msg.text;
const op = this.store.getDMByName(args[0]).id;
const move = args[1];
const name = this.store.getUserById(msg.user).name;
// We expect a reply, and store the game parameters for that channel
this.expect(op, {op, opname: args[0], ch: msg.channel, name, move});
// it's on, baby, it's ON!
this.send(`${name} has challenged you to Rock,Paper,Scissors!! Your move? (rock,paper,scissors)`, op);
this.send('Challenge sent, waiting for opponent', msg.channel);
}
score(a, b) { //uses send() and sendMulti() to print out the results
let win;
let lose;
if ((this.map[b.text] || {})[a.move] === b.text) {
win = [a.op, a.opname, b.text];
lose = [a.ch, a.name, a.move];
this.send(`${win[2]} beats ${lose[2]}, YOU WIN`, win[0]);
this.send(`${win[2]} beats ${lose[2]}, YOU LOSE`, lose[0]);
}else if ((this.map[b.text] || {})[a.move] === 1) {
this.sendMulti('It\'s a tie!', [a.ch, a.op]);
}else {
win = [a.ch, a.name, a.move];
lose = [a.op, a.opname, b.text];
this.send(`${win[2]} beats ${lose[2]}, YOU WIN`, win[0]);
this.send(`${win[2]} beats ${lose[2]}, YOU LOSE`, lose[0]);
}
}
reply(msg) { //this is where our expected reply arrives
if (this.moves.includes(msg.text)) {
const game = this.getExpected(msg.channel);
if(game){
console.log('game');
this.rmExpect(msg.channel);
this.score(game, msg);
}else{
this.log('something went wrong');
}
}else {
this.send('Invalid move, try again!', msg.channel);
}
}
}
module.exports = RPS;
rps colleague rockThis will send a challenge to @colleague. he will obviously not see your move until he's played. you can type other commands in between your challenge and the reply. You can also configure on which method to reply, to allow for more intricate commands and interactions.
See : DB example
Well, thank you, kind stranger!
v1.1
YES! You're more than welcome to, through the issues, or by forking and creating pull requests.
FAQs
Fully modular Slack Bot framework with pluggable modules and commands.
We found that hotbot demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.