diff --git a/config-example.js b/config-example.js index 677ec25..fdf2b58 100644 --- a/config-example.js +++ b/config-example.js @@ -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' diff --git a/lib/bot.js b/lib/bot.js index 5703244..60b2ef1 100755 --- a/lib/bot.js +++ b/lib/bot.js @@ -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 @@ -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 = { @@ -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; }; @@ -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 }; /** @@ -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) { @@ -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 @@ -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 @@ -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) }; /** diff --git a/package.json b/package.json index 9af7442..bb5f689 100644 --- a/package.json +++ b/package.json @@ -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": {