diff --git a/Dockerfile b/Dockerfile index ce9fad5..1af6e22 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,6 @@ -FROM node:0.10 +FROM fntsrlike/node-irc:latest MAINTAINER Ruoshi Ling -# Requirment libs -RUN apt-get update && apt-get install -y libicu-dev - # Node_modules Cache COPY ./package.json /app/package.json COPY ./README.md /app/README.md diff --git a/README.md b/README.md index b8342d9..68b98a9 100644 --- a/README.md +++ b/README.md @@ -1,99 +1,136 @@ # Slack IRC Syncbot Synchronize messages between [Slack](http://slack.com) and [IRC](https://en.wikipedia.org/wiki/Internet_Relay_Chat). -## Usage +## 1. Usage for node.js +### 1-1. Requirments +- `git` +- `node.js` & `npm` + +### 1-2. Steps ```bash -git clone https://github.com/fntsrlike/slack-irc-syncbot.git -cd slack-irc-syncbot -npm install +$ git clone https://github.com/fntsrlike/slack-irc-syncbot.git +$ cd slack-irc-syncbot +$ npm install ``` -Write your own configuration file (Refer `config.sample.js`) is a good starting point for building your own. The details can refer Configure part in below. +Refer `config.js.sample` to write your own configuration. Meaning of setting detail could refer the section "Configuration". After create your configuration to project root as `config.js`, execute the command: + +```bash +$ node <設定檔名> +``` -Save this to a file in the root of the project then run your bot with: +It will execute program. To join IRC channel may need for a while. When finished setup progress, it will display messages as following: - node your-config +```bash +$ node config.js +.............done! +Server running at http://localhost:8080/ +``` -This will launch the bot in your terminal based on provided configuration. +## 2. Usage for Docker-Compose -## Usage for Docker +This project also support docker. If you have install docker and docker-compose, you can excute project without installing node.js in your local. -This project also support docker. you can clone the project and configure it. BTW, your config file should be named `config.js`. -```shell -$ git clone https://github.com/fntsrlike/slack-irc-syncbot.git -$ cd slack-irc-syncbot -$ cp config.sample.js config.js -$ vim config.js -... -``` +### 2-1. Requirements +- `git` +- `docker` +- `docker-compose` -Then build image and run. -```shell -$ docker build --tag="slack-irc-syncbot" ./ -... -$ docker run -d -P slack-irc-syncbot -``` +### 2-2. Steps -If you don't want to rebuild images after editing config file, just use volume to sync config and restart container. -```shell -$ docker run -d -P -v $PWD/config.js:/app/config.js --name="slackbot" slack-irc-syncbot -$ vim config.js -... -$ docker restart slackbot +After you create your own configuration as `config.js` in project root, edit `docker-compose.yml` to setting arguments of docker, usually we will set volumes and ports. At last, execute command: + +```bash +$ docker-compose up -d ``` -In addition, if your want to specify expose port, remember to use `-p` argument replace `-P` +`docker` will create image and container to execute program in background. + +## 3. Configuration + +In thie section, we will explain how to set configuration with `config.js.sample`. -## Configuration -Explain configuration in config.sample.js with comment. ```javascript +'use strict'; + +var App = require('./lib/app'); + +// -- Setting form this line + +// Basic configuration of IRCBot +// var config = { +// nick: '', +// username: '', +// password: '', +// token: '', +// incomeUrl: ''], +// serverPort: 80 +// }; + var config = { - // Require item nick: 'slackbot', username: 'slackbot-username', token: 'XXXX-XXXXXXXXXX-XXXXXXXXXX-XXXXXXXXXX-XXXXXX', incomeUrl: 'https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX', - outgoingToken: 'XXXXXXXXXXXXXXXXXXXXXXXX', + outgoingToken: ['XXXXXXXXXXXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXXXXXXXXXXX'], + serverPort: 80 }; -// Channels map +// The channels wanna be sync messages. config.channels = { - '#irc-channel password(optional)': '#slack-channel' + '# [password, option]': '#', + '#sample' : '#general' }; -// Users map -// Index is not nick, but username at IRC. It could be get by `/whois`. -// Value is slack's username. +// To sync nicks and avatar of user config.users = { - 'ircLogin (not nick)': 'slackUser' + '': '', + 'ircuser': 'slackuser' }; + +// Baned list of IRC Nicks. Usually are bots. +config.bannedIRCNicks = [ + '', + 'ircNick' +]; + +// To filter messages has bad word or dirty word +config.bannedKeywords = [ + 'keyword' +]; + +// -- Setting end on this line + +App( config ).start(); ``` -### Requirements +### 3-1. Requirements - `nick`: IRCbot's nick, it should be unique on IRC Server - `username`: IRC bot's username - `token`: Slack's API token - `incomeUrl`: From Incoming WebHooks of Integrations - `outgoingToken`: From Outgoing WebHooks of Integrations -- `channels`: Channels map. Decide to sync which IRC chanel to Slack channel -- `users`: Users map. Let messages sync from IRC could dislpay usesname of Slack -### Options +### 3-2. Options - `server`: IRC server, default is `irc.freenode.com` - `iconUrl`: Default ICON for messages from IRC to Slack by url. 48*48 size is better - `iconEmoji`: As iconUrl but by emoji code. It will override by iconUrl - `serverPort`: The port of web application to get post request from slack. default is 80 +- `httpsServerPort`: As `serverPort` but in https connection. default is 443. - `initializeTimeoutLimit`: Set seconds to be limit time of initialization. - `listUpdatedPeriod`: Set seconds to update user list -### Flags +### 3-3. Flags - `isSilent`: Set true to stop IRC bot from speaking into the channel - `isSystemSilent`: Set true to stop speak any bot's system messages - `isUsersTracking`: Set true to tracking IRC users' nick. Otherwise, user mapping will be turn off. - `isAutoTildeAdded`: Set true to add tilde prefix on IRC nicks - `isShowSlackChannel`: Set true to display channel name of slack on IRC channel -- `isMapName`: -- `isMapAvatar`: +- `isMapName`: Set true to sync nicks. +- `isMapAvatar`: Set true to sync avatar +- `isHttpsConnecttion' Set `true` to turn on https connection. +- `isDisplayInfo' Set true to let index display some public information. Other config options about IRCBot, can refer [node-irc](https://github.com/martynsmith/node-irc/blob/0.3.x/lib/irc.js) diff --git a/README_zh-tw.md b/README_zh-tw.md new file mode 100644 index 0000000..bec33ef --- /dev/null +++ b/README_zh-tw.md @@ -0,0 +1,138 @@ +# Slack IRC Syncbot + +同步 [Slack](http://slack.com)、[IRC](https://en.wikipedia.org/wiki/Internet_Relay_Chat) 兩者之間的訊息。 + +## 1. 直接使用 node.js 執行 + +### 1-1. 前置需求 +- `git` +- `node.js` & `npm` + +### 1-2. 步驟 + +```bash +$ git clone https://github.com/fntsrlike/slack-irc-syncbot.git +$ cd slack-irc-syncbot +$ npm install +``` + +參考 `config.js.sample` 編寫您所需要的設定檔,設定值詳細的意義請參考下面的「設定」章節。然後將設定值儲存至專案根目錄(檔名通常是 `config.js`)。然後輸入下面的指令: + + $ node <設定檔名> + +之後就會運行機器人,掛載 IRC 頻道可能會需要一段時間,當完成時,即會顯示如下的訊息: + +``` +$ node config.js +.............done! +Server running at http://localhost:8080/ +``` + +## 2. 透過 Docker-Compose 執行 + +這個專案是支援 `Docker` 的,只要您的電腦有 `Docker` 和 `docker-compose`,您就不用刻意去安裝 `node.js`。 + +### 2-1. 前置需求 +- `git` +- `docker` +- `docker-compose` + +### 2-2. 步驟 + +在你修改玩設定檔後,可以在修改 `docker-compose.yml` 的 `volumes` 和 `ports` 去決定有使用的設定檔或輸出的 port。然後就可以執行下列指令 + +```shell +$ docker-compose up -d +``` + +`docker-compose` 就會去建立該 `image` 然後自動在背景執行程式。 + + +## 3. 設定檔詳解 + +在這個章節我們會透過講解 `config.js.sample` 裡面項目,幫助您要如何撰寫您專屬的 `config.js`。 + +```javascript +'use strict'; + +var App = require('./lib/app'); + +// -- 從這邊開始進行設定 + +// 關於機器人的基本設定值 +// var config = { +// nick: '', +// username: '', +// password: '', +// token: '', +// incomeUrl: ''], +// serverPort: 80 +// }; + +var config = { + nick: 'slackbot', + username: 'slackbot-username', + token: 'XXXX-XXXXXXXXXX-XXXXXXXXXX-XXXXXXXXXX-XXXXXX', + incomeUrl: 'https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX', + outgoingToken: ['XXXXXXXXXXXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXXXXXXXXXXX'], + serverPort: 80 +}; + +// 機器人要同步的頻道,前後分別為 IRC Server 和 Slack 的 Channel 名稱。 +config.channels = { + '# [頻道密碼、選填]': '#', + '#sample' : '#general' +}; + +// 使用者暱稱、頭像同步,前後分別為 IRC Server 和 Slack 的 username。 +config.users = { + '': '', + 'ircuser': 'slackuser' +}; + +// IRC 暱稱黑名單 +config.bannedIRCNicks = [ + '', + 'ircNick' +]; + +// 關鍵字黑名單,擁有以下關鍵字的訊息將會被篩選掉。 +config.bannedKeywords = [ + 'keyword' +]; + +// -- 到此行結束設定。 + +App( config ).start(); +``` + +### 3-1. 必填的屬性 +- `nick`: IRC 帳號的暱稱。每個暱稱在 IRC Server 都是不可重複的。若是重複,程式會自動加上數字遞疊。 +- `username`: IRC 帳號的使用者名稱。 +- `token`: Slack 的 API token。 +- `incomeUrl`: Slack Incoming WebHooks Integrations 提供的網址。 +- `outgoingToken`: Slack Outgoing WebHooks Integrations 提供的 `token`。若同步多個頻道,則會有多個 token,以 Array 的形式儲存。 + +### 3-2. 選填的屬性 +#### 3-2-1. 屬性 +- `server`: IRC server 位址,預設是 `irc.freenode.com`。 +- `iconUrl`: IRC 同步到 Slack 訊息顯示預設頭像的網址。48 * 48 的長寬是比較推薦的。 +- `iconEmoji`: 同上,但是是輸入 emoji code。效果會被 `iconUrl` 覆蓋。 +- `serverPort`: 接收 Slack 訊息用的網頁所使用的 port。預設是 `80` port。 +- `httpsServerPort`: 同上,但是在 https 的連線狀況。預設是 `443` port。 +- `initializeTimeoutLimit`: 設定初始化時,判定超時、失敗的秒數。 +- `listUpdatedPeriod`: 設定定期更新使用者名單的秒數。 + +#### 3-2-2. 開關 +- `isSilent`: 設成 `true` 去對機器人在 IRC 上靜音。預設是 `false`。 +- `isSystemSilent`: 同上。不過特別針對系統訊息。 +- `isUsersTracking`: 設成 `true` 去追蹤 IRC 上使用者的暱稱。預設是 `true`。 +- `isAutoTildeAdded`: 設成 `true` 去讓雙平台使用者名稱綁定的 IRC 名稱前,加上 `~` 作為前綴。預設是 `false`。 +- `isShowSlackChannel`: 設成 `true` 讓同步到 Slack 訊息的使用者名稱前加上 IRC Channel 的名稱。預設是 `false`, +- `isMapName`: 設成 `true` 決定是否開啟暱稱同步的功能。預設為 `true`。 +- `isMapAvatar`:設成 `true` 決定是否開啟頭像同步的功能。預設為 `true`。 +- `isHttpsConnecttion' 設成 `true` 去開啟 https 連線服務。 +- `isDisplayInfo' 設成 `true` 讓首頁顯示可公開資訊。預設為 `true`。 + +其他關於程式的設定,可以參考 [node-irc](https://github.com/martynsmith/node-irc/blob/0.3.x/lib/irc.js)。 diff --git a/config.sample.js b/config.js.sample similarity index 71% rename from config.sample.js rename to config.js.sample index 7d63838..312e135 100644 --- a/config.sample.js +++ b/config.js.sample @@ -7,7 +7,7 @@ var config = { username: 'slackbot-username', token: 'XXXX-XXXXXXXXXX-XXXXXXXXXX-XXXXXXXXXX-XXXXXX', incomeUrl: 'https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX', - outgoingToken: 'XXXXXXXXXXXXXXXXXXXXXXXX', + outgoingToken: ['XXXXXXXXXXXXXXXXXXXXXXXX'], serverPort: 80 }; @@ -16,7 +16,15 @@ config.channels = { }; config.users = { - 'ircLogin (not nick)': 'slackUser' + 'ircAccount': 'slackUser' }; +config.bannedIRCNicks = [ + 'ircNick' +]; + +config.bannedKeywords = [ + // 'keyword' +]; + App( config ).start(); diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c512a54 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,8 @@ +app: + build: ./ + environment: + - TZ=Asia/Taipei + volumes: + - ./config.js:/app/config.js + ports: + - "8080:80" diff --git a/lib/app.js b/lib/app.js index cfb5383..1a2a8cd 100644 --- a/lib/app.js +++ b/lib/app.js @@ -1,12 +1,13 @@ 'use strict'; -var _ = require('underscore'), - IrcBot = require('./ircbot'), - IrcToSlack = require('./irc-to-slack'), - SlackBot = require('./slackbot'), - SlackRes = require('./slack-res'), - SlackToIrc = require('./slack-to-irc'), - NameMap = require('./name-map'); +var _ = require('underscore'); + +var IrcBot = require('./ircbot'); +var IrcToSlack = require('./irc-to-slack'); +var SlackBot = require('./slackbot'); +var SlackRes = require('./slack-res'); +var SlackToIrc = require('./slack-to-irc'); +var NameMap = require('./name-map'); var App = function(config) { if (!(this instanceof App)) { @@ -24,7 +25,7 @@ var App = function(config) { this._precheck(); this.nameMap = NameMap(this.config); this.ircBot = IrcBot(this.config); - this.ircToSlack = IrcToSlack(this.config) + this.ircToSlack = IrcToSlack(this.config); this.slackBot = SlackBot(this.config); this.slackRes = SlackRes(this.config); this.slackToIrc = SlackToIrc(this.config); @@ -64,10 +65,9 @@ App.prototype._precheck = function() { }; App.prototype._confirmReady = function(callback) { - var - checkingCycle = 0.1, - isReady = !_.isEmpty(this.slackRes.getUserMap()) && this.nameMap.isChecked, - isTimeout = this.config.initializeTimeoutLimit < this.confirmCounter; + var checkingCycle = 0.1; + var isReady = !_.isEmpty(this.slackRes.getUserMap()) && this.nameMap.isChecked; + var isTimeout = this.config.initializeTimeoutLimit < this.confirmCounter; this.confirmCounter += checkingCycle; diff --git a/lib/irc-msg-encoder.js b/lib/irc-msg-encoder.js index 87ebf76..148afd6 100644 --- a/lib/irc-msg-encoder.js +++ b/lib/irc-msg-encoder.js @@ -12,7 +12,6 @@ var IrcMsgEncoder = function(message, type) { this.message = message; this.channelMap = {}; - this.userMap = {}; this.nickMap = {}; }; @@ -25,11 +24,6 @@ IrcMsgEncoder.prototype.setChanelMap = function(map) { return this; }; -IrcMsgEncoder.prototype.setUserMap = function(map) { - this.userMap = map; - return this; -}; - IrcMsgEncoder.prototype.setNickMap = function(map) { this.nickMap = map; return this; diff --git a/lib/irc-to-slack.js b/lib/irc-to-slack.js index e69759b..b981262 100644 --- a/lib/irc-to-slack.js +++ b/lib/irc-to-slack.js @@ -1,7 +1,8 @@ 'use strict'; -var _ = require('underscore'), - IrcMsgEncoder = require('./irc-msg-encoder'); +var _ = require('underscore'); + +var IrcMsgEncoder = require('./irc-msg-encoder'); var IrcToSlack = function(config) { if (!(this instanceof IrcToSlack)) { @@ -9,26 +10,40 @@ var IrcToSlack = function(config) { } this.config = _.defaults(config, { + 'bannedIRCNicks': [], + 'bannedKeywords': [] }); + this.config.bannedIRCNicks = this.config.bannedIRCNicks.join('|').toLowerCase().split('|'); + this.config.bannedKeywords = this.config.bannedKeywords.join('|').toLowerCase().split('|'); + return this; }; IrcToSlack.prototype._prepareMessage = function(message, type) { return IrcMsgEncoder(message, type) .setChanelMap(this.ircBot.config.channels) - .setUserMap(this.nameMap.getIrcWhoIsMap()) .setNickMap(this.nameMap.getIrcNickMap()) .autoMention().encodeMention().encodeAmpersand().toString(); }; IrcToSlack.prototype._sentToSlack = function(type, from, to, message) { - var - username = from, - avatar = this.config.iconUrl, - avatars = this.config.avatarMap, - hasAvatar = false, - isNickExist = !!this.nameMap.getSlackNameByIrcNick(from); + var username = from; + var avatar = this.config.iconUrl; + var avatars = this.config.avatarMap; + var hasAvatar = false; + var isNickExist = !!this.nameMap.getSlackNameByIrcNick(from); + var bannedKeywords = this.config.bannedKeywords; + + if (_.contains(this.config.bannedIRCNicks, username.toLowerCase())) { + return; + } + + for (var i=0, len=bannedKeywords.length; i < len; i++) { + if (message.toLowerCase().indexOf(bannedKeywords[i]) > -1) { + return; + } + } if (this.config.isMapName && isNickExist) { username = this.nameMap.getSlackNameByIrcNick(from); diff --git a/lib/ircbot.js b/lib/ircbot.js index 8e1a7a1..d4f1019 100755 --- a/lib/ircbot.js +++ b/lib/ircbot.js @@ -1,7 +1,7 @@ 'use strict'; -var _ = require('underscore'), - Irc = require('irc'); +var _ = require('underscore'); +var Irc = require('irc'); var IrcBot = function(config) { if (!(this instanceof IrcBot)) { @@ -25,6 +25,7 @@ var IrcBot = function(config) { this._configureClient(); this.client = new Irc.Client(this.config.server, this.config.nick, this.irc); + this.client.setMaxListeners(0); // To avoid warning about memory leak detected this._handleErrors(); return this; @@ -44,18 +45,17 @@ IrcBot.prototype._configureClient = function() { floodProtection: true }; - nodeIrcOptions.forEach(function(opt) { - if (this.config[opt]) { - this.irc[opt] = this.config[opt]; + nodeIrcOptions.forEach(function (opt) { + if (this.config[opt] !== undefined) { + this.irc[opt] = this.config.opt; } }.bind(this)); }; IrcBot.prototype._handleErrors = function() { this.client.addListener('error', function(message) { - var - channel = message.args[1], - errorMessage = this.mapPronouns(message.args[2]); + var channel = message.args[1]; + var errorMessage = this.mapPronouns(message.args[2]); this.systemSpeak(channel, 'I don\'t feel so well because ' + errorMessage); }.bind(this)); @@ -75,8 +75,10 @@ IrcBot.prototype._trackUserExisting = function() { if (whois.user === botUsername) { return; } - var slackName = this.nameMap.getSlackNameByIrcWhoIs(whois.user); - this.nameMap.setSlackNameByIrcNick(slackName, nick); + if (whois.account !== undefined) { + var slackName = this.nameMap.getSlackNameByIrcAccount(whois.account); + this.nameMap.setSlackNameByIrcNick(slackName, nick); + } }.bind(this)); }.bind(this)); this.systemSpeak(channel, 'I\'m all over you slackers'); @@ -89,8 +91,8 @@ IrcBot.prototype._trackUserEntering = function() { if (whois.user === botUsername) { return; } - else { - var slackName = this.nameMap.getSlackNameByIrcWhoIs(whois.user); + if (whois.account !== undefined) { + var slackName = this.nameMap.getSlackNameByIrcAccount(whois.account); this.nameMap.setSlackNameByIrcNick(slackName, nick); this.systemSpeak(channel, 'i\'m watching you slacker @' + slackName); } @@ -103,7 +105,9 @@ IrcBot.prototype._trackUserNicking = function() { return; } var slackName = this.nameMap.getSlackNameByIrcNick(oldNick); - this.nameMap.replaceIrcNick(oldNick, newNick); + if (slackName !== undefined) { + this.nameMap.replaceIrcNick(oldNick, newNick); + } channels.forEach(function(channel) { this.systemSpeak(channel, 'don\'t think you can hide slacker @' + slackName); }.bind(this)); @@ -124,7 +128,14 @@ IrcBot.prototype.addListener = function(type, listener) { IrcBot.prototype.speak = function(channel, message) { if (!this.config.isSilent) { - this.client.say(channel, message); + try { + this.client.say(channel, message); + } catch (e) { + console.log('[DEBUG] Something wrong when IRCBot say'); + console.log(e); + console.log('[DEBUG] End'); + } + } }; @@ -139,8 +150,8 @@ IrcBot.prototype.mapPronouns = function(message) { 'you': 'i', 'you\'re': 'i\'m' }; - return message.split(' ').map(function(word) { - return map[word.toLowerCase()] ? map[word.toLowerCase()] : word; + return message.split(' ').map(function (word) { + return map[word.toLowerCase()] || word; }).join(' '); }; diff --git a/lib/name-map.js b/lib/name-map.js index d0e8c2f..621e777 100644 --- a/lib/name-map.js +++ b/lib/name-map.js @@ -1,7 +1,7 @@ 'use strict'; -var _ = require('underscore'), - request = require('request'); +var _ = require('underscore'); +var request = require('request'); var NameMap = function(config) { if (!(this instanceof NameMap)) { @@ -14,26 +14,17 @@ var NameMap = function(config) { users: {} }); - this.whoIsMap = {}; // ircWhoIs : slackName + this.accountMap = {}; // freenode account : slack username this.nickMap = {}; // ircNick : slackName this.isChecked = false; this._checkMapType(); }; -NameMap.prototype._addTiledePrefix = function() { - Object.keys(this.whoIsMap).forEach(function(username) { - if (username[0] !== '~') { - this.whoIsMap['~' + username] = this.whoIsMap[username]; - delete this.whoIsMap[username]; - } - }.bind(this)); -}; - NameMap.prototype._checkMapType = function() { if (_.isString(this.config.users)) { this._updateMap(); } else { - this.whoIsMap = this.config.users; + this.accountMap = this.config.users; this.isChecked = true; } }; @@ -49,7 +40,7 @@ NameMap.prototype._getUserMapByUrl = function(url) { url: url }, function(error, response, body) { this._check(body); - this.whoIsMap = JSON.parse(body); + this.accountMap = JSON.parse(body); this.isChecked = true; if (this.config.isAutoTildeAdded) { @@ -66,20 +57,20 @@ NameMap.prototype._updateMap = function() { }.bind(this), this.config.listUpdatedPeriod * 1000); }; -NameMap.prototype.getIrcWhoIsMap = function() { - return this.whoIsMap; +NameMap.prototype.getIrcAccountMap = function() { + return this.accountMap; }; NameMap.prototype.getIrcNickMap = function() { - return this.whoIsMap; + return this.nickMap; }; NameMap.prototype.getSlackNameByIrcNick = function(ircNick) { return this.nickMap[ircNick]; }; -NameMap.prototype.getSlackNameByIrcWhoIs = function(ircWhoIs) { - return this.whoIsMap[ircWhoIs]; +NameMap.prototype.getSlackNameByIrcAccount = function(ircAccount) { + return this.accountMap[ircAccount]; }; NameMap.prototype.setSlackNameByIrcNick = function(slackName, ircNick) { diff --git a/lib/slack-msg-decoder.js b/lib/slack-msg-decoder.js index a0d5ae1..e05b83e 100644 --- a/lib/slack-msg-decoder.js +++ b/lib/slack-msg-decoder.js @@ -5,12 +5,12 @@ var SlackMsgDecoder = function(message) { return new SlackMsgDecoder(message); } - if (typeof message == "string") { + if (typeof message === 'string') { this.message = message; } else { - console.log("[DEBUG] Got type error message:" + message) - this.message = "" + console.log('[DEBUG] Got type error message:' + message); + this.message = ''; } this.channelMap = {}; @@ -44,9 +44,8 @@ SlackMsgDecoder.prototype.decodeChannel = function() { SlackMsgDecoder.prototype.decodeUser = function() { this.message = this.message.replace(/<@U\w{8}>/g, function(matched) { - var - memberId = matched.match(/@(U\w{8})/)[1], - map = this.userMap; + var memberId = matched.match(/@(U\w{8})/)[1]; + var map = this.userMap; return '@' + map[memberId]; }.bind(this)); diff --git a/lib/slack-res.js b/lib/slack-res.js index 135d281..13765d8 100644 --- a/lib/slack-res.js +++ b/lib/slack-res.js @@ -1,7 +1,7 @@ 'use strict'; -var _ = require('underscore'), - request = require('request'); +var _ = require('underscore'); +var request = require('request'); var SlackRes = function(config) { if (!(this instanceof SlackRes)) { @@ -32,25 +32,24 @@ SlackRes.prototype._updateUsers = function() { request.get({ url: 'https://slack.com/api/users.list?token=' + this.config.token }, function(error, response, body) { - if (!error && response.statusCode == 200) { - this._check(body); - - var - res = JSON.parse(body), - userMap = this.getUserMap(), - avatarMap = this.getAvatarMap(); - - res.members.map(function(member) { - userMap[member.id] = member.name; - avatarMap[member.name] = member.profile.image_48; - }); - - this.setUsers(res.members); - this.setUserMap(userMap); - this.setAvatarMap(avatarMap); - } else { - console.error('Error! Can\'t get resource! Please check the Internet'); + if (error || response.statusCode !== 200) { + return console.error('Error! Can\'t get resource! Please check the Internet'); } + + this._check(body); + + var res = JSON.parse(body); + var userMap = this.getUserMap(); + var avatarMap = this.getAvatarMap(); + + res.members.map(function(member) { + userMap[member.id] = member.name; + avatarMap[member.name] = member.profile.image_48; + }); + + this.setUsers(res.members); + this.setUserMap(userMap); + this.setAvatarMap(avatarMap); }.bind(this)); }; @@ -58,22 +57,20 @@ SlackRes.prototype._updateChannels = function() { request.get({ url: 'https://slack.com/api/channels.list?token=' + this.config.token }, function(error, response, body) { - if (!error && response.statusCode == 200) { - this._check(body); + if (error || response.statusCode !== 200) { + return console.error('Error! Can\'t get resource! Please check the Internet'); + } + this._check(body); - var - channelMap = this.getChannelMap(), - res = JSON.parse(body); + var channelMap = this.getChannelMap(); + var res = JSON.parse(body); - res.channels.map(function(channel) { - channelMap[channel.id] = channel.name; - }); + res.channels.map(function(channel) { + channelMap[channel.id] = channel.name; + }); - this.setChannels(res.channels); - this.setChannelMap(channelMap); - } else { - console.error('Error! Can\'t get resource! Please check the Internet'); - } + this.setChannels(res.channels); + this.setChannelMap(channelMap); }.bind(this)); }; diff --git a/lib/slack-to-irc.js b/lib/slack-to-irc.js index 12ee762..d28ea07 100644 --- a/lib/slack-to-irc.js +++ b/lib/slack-to-irc.js @@ -1,9 +1,12 @@ 'use strict'; -var _ = require('underscore'), - http = require('http'), - querystring = require('querystring'), - SlackMsgDecoder = require('./slack-msg-decoder'); +var _ = require('underscore'); +var fs = require('fs'); +var http = require('http'); +var https = require('https'); +var querystring = require('querystring'); + +var SlackMsgDecoder = require('./slack-msg-decoder'); var SlackToIRC = function(config) { if (!(this instanceof SlackToIRC)) { @@ -12,8 +15,14 @@ var SlackToIRC = function(config) { this.config = _.defaults(config, { showSlackChannel: false, - serverPort: 80 + serverPort: 80, + httpsServerPort: 443, + isDisplayInfo: true, + isHttpsConnecttion: false }); + + // TODO: Check Ports are free or not + // TODO: Check SSL Keys are exist or not }; SlackToIRC.prototype.setIrcBot = function(bot) { @@ -24,18 +33,46 @@ SlackToIRC.prototype.setIrcBot = function(bot) { SlackToIRC.prototype.setSlackRes = function(resource) { this.slackRes = resource; return this; -} +}; SlackToIRC.prototype.listen = function() { this.config.avatarMap = this.slackRes.getAvatarMap(); + this._server().listen(this.config.serverPort); - console.log('Server running at ' + - 'http://localhost:' + this.config.serverPort + '/'); + console.log('HTTP Web Server running at ' + + 'http://localhost:' + this.config.serverPort + '/'); + + if (this.config.isHttpsConnecttion) { + console.log('this.config.httpsServerPort: ' + this.config.httpsServerPort); + this._httpsServer().listen(this.config.httpsServerPort); + console.log('HTTPS Web Server running at ' + + 'https://localhost:' + this.config.httpsServerPort + '/'); + } return this; }; SlackToIRC.prototype._server = function() { var server = http.createServer(function(req, res) { + if (req.method === 'POST') { + this._requestHandler(req, res); + } else { + res.end(this.config.isDisplayInfo ? this._getIndexHtml() : 'Recieved request (not post).'); + } + }.bind(this)); + + return server; +}; + +SlackToIRC.prototype._httpsServer = function() { + var options; + var server; + + options = { + key: fs.readFileSync('ssl/key.pem'), + cert: fs.readFileSync('ssl/cert.pem') + }; + + server = https.createServer(options, function (req, res) { if (req.method === 'POST') { this._requestHandler(req, res); } else { @@ -49,7 +86,9 @@ SlackToIRC.prototype._server = function() { SlackToIRC.prototype._requestHandler = function(req, res) { req.on('data', function(data) { var payload = querystring.parse(data.toString()); - if (payload.token === this.config.outgoingToken && payload.user_name !== 'slackbot') { + var isTokenValid = _.contains(this.config.outgoingToken, payload.token); + + if (isTokenValid && payload.user_name !== 'slackbot') { this._sentMessage(payload); res.end('Done.'); } @@ -58,10 +97,9 @@ SlackToIRC.prototype._requestHandler = function(req, res) { }; SlackToIRC.prototype._sentMessage = function(payload) { - var - message = this._decodeMessage(payload.text), - channel = Object.keys(this.config.channels)[0], - name = payload.user_name; + var message = this._decodeMessage(payload.text); + var channel = _.invert(this.config.channels)['#' + payload.channel_name]; + var name = payload.user_name; if (this.config.showSlackChannel) { name = name + '@' + payload.channel_name; @@ -80,4 +118,39 @@ SlackToIRC.prototype._decodeMessage = function(text) { .decodeAngel().encodeAmpersand().toString(); }; +SlackToIRC.prototype._getIndexHtml = function() { + var html = ''; + + html += '' + + 'Slack IRC Messages Syncbot' + + '' + + '' + + '

Slack IRC Messages Syncbot

' + + '

Sync Channels

' + + '' + + '' + + '' + this._objectToHtmlTr(this.config.channels) + '' + + '
IRC ChannelSlack Channel
' + + '

Binding Users

' + + '' + + '' + + '' + this._objectToHtmlTr(this.config.users) + '' + + '
IRC AcoountSlack Account
' + + '

Banned IRC Nicks

' + this.config.bannedIRCNicks.join(', ') + '

' + + '

[Github]

' + + ''; + + return html; +} + +SlackToIRC.prototype._objectToHtmlTr = function(object) { + var html = ''; + + for (var key in object) { + html += '' + key + '' + object[key] + ''; + } + + return html; +} + module.exports = SlackToIRC; diff --git a/lib/slackbot.js b/lib/slackbot.js index f1b022a..db3e126 100644 --- a/lib/slackbot.js +++ b/lib/slackbot.js @@ -1,7 +1,7 @@ 'use strict'; -var _ = require('underscore'), - request = require('request'); +var _ = require('underscore'); +var request = require('request'); var SlackBot = function(config) { if (!(this instanceof SlackBot)) { @@ -23,7 +23,7 @@ SlackBot.prototype.send = function(method, args) { form: { payload: JSON.stringify(args) } }, function(error, response, body) { if (error || body.error) { - throw 'Error:', error || body.error; + throw 'Error:' + (error || body.error); } }); }; diff --git a/package.json b/package.json index f069b7b..0b4d60e 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,12 @@ { "name": "slack-irc-syncbot", + "version": "0.1.4", "description": "Synchronize messages between Slack and IRC.", - "version": "0.1.3", + "keywords": [ + "slack", + "irc" + ], "homepage": "https://github.com/fntsrlike/slack-irc-syncbot", - "author": { - "name": "Roushi Ling", - "email": "fntsrlike@gmail.com", - "url": "http://fntsr.tw/" - }, - "repository": { - "type": "git", - "url": "git://github.com/fntsrlike/slack-irc-syncbot.git" - }, "bugs": { "url": "https://github.com/fntsrlike/slack-irc-syncbot/issues" }, @@ -21,21 +16,27 @@ "url": "https://github.com/fntsrlike/slack-irc-syncbot/blob/master/LICENSE" } ], - "engines": { - "node": "*" + "author": { + "name": "Roushi Ling", + "email": "fntsrlike@gmail.com", + "url": "http://fntsr.tw/" + }, + "contributors": { + "name": "Shunyi", + "email": "M157q.tw@gmail.com", + "url": "http://m157q.github.io" + }, + "repository": { + "type": "git", + "url": "git://github.com/fntsrlike/slack-irc-syncbot.git" }, "dependencies": { - "irc": "~0.3.6", - "is-online": "^3.0.0", + "irc": ">=0.3.12", + "is-online": "~3.2.0", "request": "~2.33.0", - "underscore": "^1.7.0" + "underscore": "~1.8.3" }, - "devDependencies": {}, - "bin": { - "bot": "bin/bot.js" - }, - "keywords": [ - "slack", - "irc" - ] + "engines": { + "node": ">= 0.10.10" + } } diff --git a/ssl/.gitkeep b/ssl/.gitkeep new file mode 100644 index 0000000..e69de29