hubotを使ってRocket.ChatにIRCの内容を転送する

# やったこと
slack を社内でも使いたいけどセキュリティなどの都合で使えないので、slack クローンの Rocket.Chat を最近導入しました。

irc をメインで使っていた(使っている)ので、現状でも irc 側に発言するメンバーとかがいたりします。発言が散らばるし、その時ネットワークに繋がってたら見落とすし…ってことで、irc の内容を Rocket.Chat に転送できるようにしました。

# 本題に入る前に…前置き
Qiita初投稿です。これまで読む専でしたが、

- アウトプットする事でスキルアップしたい！
- Rocket.Chat に関するノウハウ少ないしみんなに貢献したい！

という思いから記事を書いてます。Docker や coffeescript(javascript) に関しても初心者です。

なので、『記事の構成が ~~クソ~~ 良くないね』、『`Dockerfile`はこう書くべき』、『`coffeescript`はこう書くと良い』、『そもそも別の方法で簡単に実現できるんだけど』などなど ~~厳しい~~ 温かいコメントをお待ちしています！

# 動作イメージ
こんな感じで動作します。

![動作イメージ.png](https://qiita-image-store.s3.amazonaws.com/0/72108/5a9d0e2d-f802-d096-f817-4304dcc3424d.png)


# 全体構成イメージ

![全体構成.png](https://qiita-image-store.s3.amazonaws.com/0/72108/ed2cbd6b-280b-d648-31b4-23998554447f.png)

- IRC Server は外部に存在してます。
- Rocket.Chat は[公式の docker イメージ](https://hub.docker.com/r/library/rocket.chat/)を使用。
インストール手順は[公式ドキュメント](https://rocket.chat/docs/installation/docker-containers/)に従って実施してます。
- hubotは[@jpshadowapps](http://qiita.com/jpshadowapps)さんの公開されている[docker イメージ](https://hub.docker.com/r/goldeneggg/hubot-irc/)を使わせて頂きました。勝手ながらありがとうございました！

# ざっくりシーケンス
![ざっくりフロー.png](https://qiita-image-store.s3.amazonaws.com/0/72108/404b643a-e4a0-b79a-ce9c-ed481df7d184.png)


# 事前準備
- Rocket.Chat 管理者で bot ユーザーを作成しておいてください。


# ソースコードの前に注意点
- [REST API](https://rocket.chat/docs/developer-guides/rest-api/#rocket-chat-rest-api) は β版 なので、ある日仕様が変更になるかもしれません。
- 例外処理とかセキュリティ的なあれとか考えられていません(初心者過ぎて)。この記事を参考にする場合は自己責任でお願いします。あるいはゴリゴリと改造してください。~~(そして僕に教えてくだｓ~~

# 今度こそソースコード

## github リポジトリ
[github](https://github.com/17number/irc-forward-rocketchat) に Dockerfile と CoffeeScript をアップしました。
`README.md` も後ほど更新します！

### Dockerfile

以下の変数を自分の環境に合わせて設定して下さい。

|  環境変数名   | 説明 |
|----|---|
|HUBOT_IRC_NICK| IRC 側に表示される bot のニックネーム |
|HUBOT_IRC_SERVER| 接続先 IRC サーバ |
|HUBOT_IRC_ROOMS| 接続チャンネル |
|http_proxy, no_proxy| プロキシ環境下で必要に応じて使用 |


```bash:Dockerfile
FROM goldeneggg/hubot-irc

MAINTAINER 17number

# set hubot irc environment
# HUBOT_IRC_SERVER and HUBOT_IRC_ROOMS must be indicated by `docker run -e ...`
ENV HUBOT_IRC_NICK relaybot
ENV HUBOT_IRC_UNFLOOD true
ENV HUBOT_IRC_SERVER=<IRC SERVER IP ADDR>
ENV HUBOT_IRC_ROOMS="#ircroom1,#ircroom2"
# ENV http_proxy=http://proxyuser:proxypw@your.proxy.com:<proxyport>/
# ENV no_proxy="localhost,127.0.0.1,172.17.0.1"


# install pkgs
WORKDIR /root/mybot
RUN set -x && \
    npm install request --save && \
    npm install iconv --save && \
    npm install

# HTTP Listener listen port 9980
ENV PORT 9980
EXPOSE 9980

ADD ircforward.coffee /root/mybot/scripts/ircforward.coffee

# run redis-server and hubot("-a irc")
EXPOSE 6379
RUN /etc/init.d/redis-server start
ENTRYPOINT ["bin/hubot", "-a", "irc"]
CMD ["--name", "ircbot"]
```

### coffeescript

こちらも同様に自分の環境に合わせて設定して下さい。

|  変数名   | 説明 |
|----|---|
|dstip| Rocket.Chat が動作しているサーバ(コンテナ) |
|botusername| Rocket.Chat に追加した bot ユーザー名 |
|botuserpw| Rocket.Chat に追加した bot ユーザーのパスワード |
|loginrooms| ircroom と rcroom をセットで設定。<br>　　ircroom : IRC 側の転送元チャンネル名<br>　　rcroom : Rocket.Chat 側の転送先チャンネル名|

```coffeescript:ircforward.coffee
# Description:
#   Forwards irc messages to Rocket.Chat.
#

# Parameters
dstip = "172.17.0.1:3000"
baseurl = "http://#{dstip}/api"
botusername = "yourbotname"
botuserpw = "yourbotpw"
loginrooms = [
  {"ircroom": "#ircroom1"
  "rcroom": "rocketchatroom1"},
  {"ircroom": "#ircroom2"
  "rcroom": "rocketchatroom2"},
  "ircroom": "endofroom"
  "rcroom": "endofroom"
             ]

request = require 'request'

module.exports = (robot) ->
  robot.hear /(.*)/i, (msg) ->
    # Get Token
    request.post
      url: "#{baseurl}/login"
      form:
        user: "#{botusername}"
        password: "#{botuserpw}"
    , (err, response, body) ->
      throw err if err
      if response.statusCode is not 200
        msg.send "err(get token)"
        exit 1

      logindata = JSON.parse(body)
      token = logindata.data.authToken
      userid = logindata.data.userId

      # Get Rooms
      request
        url: "#{baseurl}/publicRooms"
        headers:
          'X-Auth-Token': token
          'X-User-Id': userid
      , (err, response, body) ->
        throw err if err
        if response.statusCode is not 200
          msg.send "err(get rooms)"
          exit 1

        userroom = msg.message.user.room
        for roominfo, roomindex in loginrooms
          if roominfo['ircroom'] is userroom
            break

        roomdata = JSON.parse(body)
        for room, index in roomdata.rooms
          if room.name is loginrooms[roomindex]["rcroom"]
            roomid = room._id
            break

        # Join Room
        request.post
          url: "#{baseurl}/rooms/#{roomid}/join"
          headers:
            'X-Auth-Token': token
            'X-User-Id': userid
        , (err, response, body) ->
          throw err if err
          if response.statusCode is not 200
            msg.send "err(join rooms)"
            exit 1

          # Convert Msg
          Iconv = require('iconv').Iconv
          jis2utf = new Iconv('ISO-2022-JP', 'UTF-8')
          utf_msg = jis2utf.convert(msg.message.text).toString()
          username = msg.message.user.name
          userroom = msg.message.user.room

          # Forward Msg
          request.post
            url: "#{baseurl}/rooms/#{roomid}/send"
            headers:
              'X-Auth-Token': token
              'X-User-Id': userid
            json:
              msg: "#{userroom}: (#{username}) #{utf_msg}"
          , (err, response, body) ->
            throw err if err
            if response.statusCode is not 200
              msg.send "err(forward msg)"
              exit 1

            # Logout
            request
              url: "#{baseurl}/logout"
              headers:
                'X-Auth-Token': token
                'X-User-Id': userid
            , (err, response, body) ->
              throw err if err
              if response.statusCode is 200
              else
                msg.send "err(logout)"
```

# コマンド実行例
```bash
$ git clone https://github.com/17number/irc-forward-rocketchat.git
$ cd irc-forward-rocketchat
$ vi Dockerfile
$ vi ircforward.coffee
$ docker build --tag=irc-forward-hubot . 
$ docker run -t -d irc-forward-hubot
```

# イケてない点
- エラー処理、例外処理が考慮できてない
Docker, CoffeScript, Hubot 全てにおいてにわか過ぎて…
- 関数分割できてない
同上
- 設定値を外部ファイルなどにできてない
おかげさまで同じような設定を `Dockerfile` と `CoffeeScript` の両方に入れないと…
- IRC 側のチャンネル設定を変更するのに `Docker build` からしないといけない


# 苦労した点
- docker 使ったことない
Qiita で ROM ってた(死語？)だけで触るのは初めて。
- coffescript/javascript 知らない
- hubot もふわっとしか分かってない
- 素敵なデバッグ方法が分からない
CoffeeScript が間違ってて、コンテナ再起動後に即死にした時とか、どうやってデバッグするのですか…？後でググッてみます。
- Rocket.Chat に関する参考ページが少ない
日本語ページばっかり探してましたが、英語をまじめに勉強しろってことですかね。
- 絶対他にもあるので、思い出したら追記します！！

# 参考にしたページ
以下のページには大変お世話になりました！理解しきれていない点も多々ありますが、今回のものを作る際に参考にさせてもらいました。掲載順には特に意図はありません。~~あと分類がうまくできているかも怪しい~~

## CoffeeScript
- [CoffeeScript](http://coffeescript.org/)
- [CoffeeScript 言語リファレンス - sappari wiki](http://memo.sappari.org/coffeescript/coffeescript-langref)
- [CoffeeScript基礎文法最速マスター | 株式会社インフィニットループ技術ブログ](https://www.infiniteloop.co.jp/blog/2011/03/coffeescript-master/)
- [CoffeeScript入門（前編） ― CoffeeScriptの基本構文 - Build Insider](http://www.buildinsider.net/web/rubyonrails4/0901)
- [CoffeeScript入門（後編） ― 関数／オブジェクト指向構文／即時関数 - Build Insider](http://www.buildinsider.net/web/rubyonrails4/0902)
- [CoffeeScriptベストプラクティス集　Node.jsアプリケーション編（2） (1/4)：CodeZine（コードジン）](https://codezine.jp/article/detail/6528)
- [CoffeeScriptによるモダンなWebアプリケーション開発連載一覧：CodeZine（コードジン）](https://codezine.jp/article/corner/442)
- [CoffeeScript入門メモ - Qiita](http://qiita.com/questbeat/items/031a0106749b8ba15aca)
- [Node.jsで、取得したWebページをUTF-8へ自動変換する - Qiita](http://qiita.com/ujiro99@github/items/648a9400e8b25d717b1e)

## Hubot(と CoffeeScript)
- [hubotスクリプトの書き方とサンプル集 | mitc](http://blog.fumiz.me/2012/08/05/hubot-irc-bot-script/)
- [Hubot + CoffeeScript ではじめるやわらかプログラミング入門 · GitHub](https://gist.github.com/udzura/0cb2447c305c51670414)
- [#15 「人体錬成に近いものの仕方、もしくはhubotの使い方」tech.kayac.com Advent Calendar 2012 | tech.kayac.com - KAYAC engineers' blog](http://tech.kayac.com/archive/15_hubottechkayaccom_advent_calendar_2012.html)
- [hubotで快適BOT生活](http://www.slideshare.net/kazufumiotani/hubotbot)
- [hubot-ircではmsg.replyのリプライ先が変わるので注意 | おおたの物置](http://ota42y.com/blog/2014/11/01/hubot-reply/)
- [ircにhubotを登場させる - 今からお前んちこいよ](http://www.hakopako.net/entry/2014/06/25/004308)
- [Hubot+IRCの文字化けを解決する - GeekFactory](http://int128.hatenablog.com/entry/2014/12/03/214747)
- [第4回　Hubotのスクリプトを書いてみる：GitHub社謹製！ bot開発・実行フレームワーク「Hubot」｜gihyo.jp … 技術評論社](http://gihyo.jp/dev/serial/01/hubot/0004)
- [第5回　実用的なHubotのスクリプトを書いてみる：GitHub社謹製！ bot開発・実行フレームワーク「Hubot」｜gihyo.jp … 技術評論社](http://gihyo.jp/dev/serial/01/hubot/0005)

## Docker
- [docker コマンド チートシート - Qiita](http://qiita.com/voluntas/items/68c1fd04dd3d507d4083)
- [IRC+HubotをDockerでサクッと試す - Qiita](http://qiita.com/jpshadowapps/items/056dbef8be84be09ee0c)
- [コンテナに入りたい？それ docker exec でできるよ - Qiita](http://qiita.com/yosisa/items/a5670e4da3ff22e9411a)
- [docker-composeを使うと複数コンテナの管理が便利に - Qiita](http://qiita.com/y_hokkey/items/d51e69c6ff4015e85fce)
- [fig/docker-compose事始め - Qiita](http://qiita.com/taka4sato/items/f03004e449538b325b5e)
- [Dockerfile の書き方「私的」なベストプラクティス - ようへいの日々精進XP](http://inokara.hateblo.jp/entry/2013/12/28/121828)
- [https://hub.docker.com/r/rocketchat/rocket.chat/](https://hub.docker.com/r/rocketchat/rocket.chat/)
- [プログラマのためのDocker超入門 01.仮想化 - Qiita](http://qiita.com/pottava/items/7086f9d63487aa3d7d08)

## Rocket.Chat
- [Rocket.Chat Docs - REST API](https://rocket.chat/docs/developer-guides/rest-api/#rocket-chat-rest-api)
- [Rocket.Chat 専用タグっぽいものを付けてまとめるための Hubot スクリプト · GitHub](https://gist.github.com/nishimaki10/ccc4c2af6ed7a15d0a89c1ee0b6356e1)

## その他
- [SlackとIRCを相互リレーする話 - Qiita](http://qiita.com/sifue/items/4fd6efee608da0cc849d)