Skip to content

Commit

Permalink
Make the bot bi-directional.
Browse files Browse the repository at this point in the history
  • Loading branch information
Jaykul committed Feb 18, 2015
1 parent e4d3f33 commit e80ae72
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 28 deletions.
5 changes: 3 additions & 2 deletions config-example.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
var slackbot = require('./lib/bot');

var config = {
server: 'irc.freenode.com',
server: 'irc.freenode.net',
nick: 'slackbot',
username: 'slackbot-username',
token: 'XXXX-XXXXXXXXXX-XXXXXXXXXX-XXXXXXXXXX-XXXXXX',
// use all lowercase. use # on IRC channel, but not on slack channel
channels: {
'#irc-channel password(optional)': '#slack-channel'
'#irc-channel password(optional)': 'slack-channel'
},
users: {
'~irclogin': 'slackuser'
Expand Down
166 changes: 142 additions & 24 deletions lib/bot.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
var _ = require('underscore');
var IRC = require('irc');
var slack = require('./slacker');
var SlackClient = require('slack-client');
var Log = require('log');

/**
* IRC Bot for syncing messages from IRC to Slack
Expand All @@ -18,6 +19,8 @@ var Bot = function (config) {
nick: 'slckbt',
username: 'slckbt'
});

this.logger = new Log(process.env.SLACK_LOG_LEVEL || 'info');
// default node-irc options
// (@see https://github.com/martynsmith/node-irc/blob/0.3.x/lib/irc.js)
this.irc = {
Expand Down Expand Up @@ -45,9 +48,11 @@ var Bot = function (config) {
nicks: {}
};
this.client = new IRC.Client(this.config.server, this.config.nick, this.irc);
this.slacker = new slack.Slacker({ token: this.config.token });
this._trackUsers();
this._handleErrors();

this.slacker = new SlackClient(this.config.token);
this._slackOn();
return this;
};

Expand All @@ -59,8 +64,10 @@ Bot.prototype._handleErrors = function () {
this.client.addListener('error', function (message) {
var channel = message.args[1];
var error_message = mapPronouns(message.args[2]);
self.speak(channel, 'I don\'t feel so well because ' + error_message);
self.ircSpeak(channel, 'I don\'t feel so well because ' + error_message);
});

// TODO: deal with slack-side errors
};

/**
Expand All @@ -79,7 +86,6 @@ Bot.prototype._trackUsers = function () {
self._usermap.nicks[nick] = self._usermap.users[whois.user];
});
});
self.speak(channel, 'I\'m all over you slackers');
});
// New user has joined, match him up
this.client.addListener('join', function (channel, nick, whois) {
Expand All @@ -89,8 +95,6 @@ Bot.prototype._trackUsers = function () {
else {
self._usermap.nicks[nick] = self._usermap.users[whois.user];
self.giveOps(channel, nick);
self.speak(channel, 'i\'m watching you slacker @' +
self._usermap.nicks[nick]);
}
});
// Existing user has changed nickname
Expand All @@ -100,13 +104,22 @@ Bot.prototype._trackUsers = function () {
}
self._usermap.nicks[new_nick] = self._usermap.nicks[old_nick];
delete self._usermap.nicks[old_nick];
channels.forEach(function (channel) {
self.speak(channel, 'don\'t think you can hide slacker @' +
self._usermap.nicks[new_nick]);
});
});
};

Bot.prototype._slackOn = function () {
var self = this;
this.slacker.addListener('loggedIn', function (user, team){
self.logger.info('Slack client now logged in (' + user.id + ') as ' + user.name + ' to ' + team.name);
});

this.slacker.addListener('open', function (){
self.logger.info('Slack client now connected');
});

self.slacker.login();
};

/**
* Attempt to give a user op controls
* @param {string} channel IRC channel name
Expand All @@ -121,28 +134,133 @@ Bot.prototype.giveOps = function (channel, nick) {
*/
Bot.prototype.listen = function () {
var self = this;
// Handle public user post

// Handle slack messages
this.slacker.addListener('message', function (message) {
self.logger.info('Message: ' + message);
if (message.hidden) {
return;
}
if (!message.text && !message.attachments) {
return;
}
if (message.subtype === 'bot_message') {
return;
}
if (!message.user) {
return;
}
if (message.user === self.config.username) {
return;
}
if(message.getChannelType() === 'DM') {
return;
}
channel = self.slacker.getChannelGroupOrDMByID(message.channel);

if (message.subtype === 'channel_join' || message.subtype === 'group_join') {
} else if (message.subtype === 'channel_leave' || message.subtype === 'group_leave') {
} else if (message.subtype === 'channel_topic' || message.subtype === 'group_topic') {
} else {
var username = message.username || self.slacker.getUserByID(message.user).name
self.logger.info('SLACK_MSG> ' + channel.name + ": " + username + ': ' + message.getBody());

for(to in self.config.channels) {
if(self.config.channels[to] === channel.name) {
self.ircSpeak(to, message.getBody(), username);
}
}
}
});

// Handle irc user post
this.client.addListener('message', function (from, to, message) {
self.slacker.send('chat.postMessage', {
channel: self.config.channels[to],
text: self.prepareMessage(message, self._usermap.nicks),
username: self._usermap.nicks[from] || from,
parse: 'full',
link_names: 1,
unfurl_links: 1
});
self.logger.info('IRC_MSG> ' + to + ": " + from + ': ' + message);

from = self._usermap.nicks[from] || from
to = self.config.channels[to.toLowerCase()]
message = self.prepareMessage(message, self._usermap.nicks)

self.slackSpeak(to, message, from)
});

};

Bot.prototype.removeFormatting = function(txt) {
txt = txt.replace(/<([\@\#\!])(\w+)(?:\|([^>]+))?>/g, (function(_this) {
return function(m, type, id, label) {
var channel, user;
if (label) {
return label;
}
switch (type) {
case '@':
user = _this.client.getUserByID(id);
if (user) {
return "@" + user.name;
}
break;
case '#':
channel = _this.client.getChannelByID(id);
if (channel) {
return "\#" + channel.name;
}
break;
case '!':
if (id === 'channel' || id === 'group' || id === 'everyone') {
return "@" + id;
}
}
return "" + type + id;
};
})(this));
txt = txt.replace(/<([^>\|]+)(?:\|([^>]+))?>/g, (function(_this) {
return function(m, link, label) {
if (label) {
return label + " " + link;
} else {
return link;
}
};
})(this));
return txt;
};

/**
* Push a message to a channel
* @param {string} channel IRC channel name
* @param {string} message Message to push to channel
* @param {string} message Text to push to channel
* @param {string} username Who sent the message
*/
Bot.prototype.ircSpeak = function (channel, message, username) {
this.logger.info('IRC> ' + channel + ": " + username + ': ' + message);
this.client.say(channel, username + ": " + message);
};

/**
* Push a message to a channel
* @param {string} channel slack channel name
* @param {string} message Text to push to channel
* @param {string} username Who sent the message
*/
Bot.prototype.speak = function (channel, message) {
if (!this.config.silent) {
this.client.say(channel, message);
}
Bot.prototype.slackSpeak = function (channel, message, username) {
this.logger.info('SLACK> ' + channel + ": " + username + ': ' + message);

if(typeof message == 'string' || message instanceof String)
message = { text: self.removeFormatting(message) }
else
message = message || {};

if(username)
message.username = username

var channel = this.slacker.getChannelByName(channel);

message.parse = 'full'
message.link_names = 1
message.unfurl_links = 1

channel.postMessage(message)
};

/**
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@
"node": "*"
},
"dependencies": {
"request": "~2.33.0",
"underscore": "^1.7.0",
"irc": "~0.3.6",
"underscore": "^1.7.0"
"slack-client": "~1.3.1",
"log": "~1.4.0"
},
"devDependencies": {},
"bin": {
Expand Down

0 comments on commit e80ae72

Please sign in to comment.