10
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

posted at

使えない Hubot アダプターをつくろう

これは Hubot Advent Calendar 2014 の 24 日目の記事です。

また、今回は @bouzuya の Hubot 連載の第 16 回です。目次は、第 1 回の記事にあるので、そちらをどうぞ。

前回まで、そして今回は

前回は Hubot 標準同梱の Shell アダプターを読んでみよう ということで、Hubot アダプターについて軽く紹介し、コードを読みました。

今回は Hubot アダプターを実際につくってみましょう。

Chadventar という仮想チャット

今回のために bouzuya/hubot-chadventar というアダプターをつくりました。動くかは分かりません。今回のためにつくった仮想チャットのための Hubot アダプターなので。

今回、想定しているのは Chadventar というチャットです。

POST /messages{ name: ..., text: ... }JSON を受け付けて、受信したメッセージをチャットに流します。

チャットに流れているメッセージは指定した URL (今回なら Hubot の /chadventar/messages ) に POST メソッドで { name: ..., text: ... }JSON で送られます。

では、見てみましょう。

サンプルコード

index.coffee
{Adapter, TextMessage} = require 'hubot'

config =
  baseUrl: process.env.HUBOT_CHADVENTAR_BASE_URL ? 'http://localhost:8888'

class Chadventar extends Adapter
  send: (envelope, strings...) ->
    data = JSON.stringify { name: envelope.name, text: strings.join('\n') }
    @robot.http(config.baseUrl + '/messages')
      .header('Content-Type', 'application/json')
      .post(data) (err) =>
        return @robot.logger.error(err) if err?

  run: ->
    @robot.router.post '/chadventar/messages', (req, res) =>
      {name, text} = req.body
      user = @robot.brain.userForId name, room: 'room'
      @receive new TextMessage(user, text, 'messageId')
      res.send 201

    @emit 'connected'

module.exports.use = (robot) ->
  new Chadventar(robot)

ソースコードは大したことないですね。

ほとんどは昨日紹介したとおりです。念のため解説を入れます。

module.exports.use

module.exports.use = (robot) ->
  new Chadventar(robot)

アダプターのインスタンスを返します。

Chadventar#run

  run: ->
    @robot.router.post '/chadventar/messages', (req, res) =>
      {name, text} = req.body
      user = @robot.brain.userForId name, room: 'room'
      @receive new TextMessage(user, text, 'messageId')
      res.send 201

    @emit 'connected'

アダプターの初期化処理をします。

今回は Web Hook を待ち受けるために robot.router を使って待ち受けています。メッセージを受信したら robot.brain.userForId でユーザーを get or new して brain に登録します。Adapter#receive で Hubot 内にメッセージを流します。

上記の待ち受け設定を完了したら 'connected'emit します。

Chadventar#send

  send: (envelope, strings...) ->
    data = JSON.stringify { name: envelope.name, text: strings.join('\n') }
    @robot.http(config.baseUrl + '/messages')
      .header('Content-Type', 'application/json')
      .post(data) (err) =>
        return @robot.logger.error(err) if err?

メッセージをチャットに送信する処理です。 Robot#messageRoom などの room には対応していません。

最後まで紹介できなかった、しなかった、標準のクソ HTTP クライアント technoweenie/node-scoped-http-client を使って、Chadventar サーバーにメッセージを送信しています。

テストコード

動かすのが面倒なので、テストコードを書いています。解説する余裕がないので、コードを貼り付けておきます。おそらく前々回、軽く紹介しているので、分かると思います。

Hubot はテストコードを書こうとすると自然に読みきってしまえる分量なので、 Hubot 本体の理解のためにテストコードを書くのはオススメです。

{expect} = require('chai').use(require('sinon-chai'))
{Robot, User} = require 'hubot'
bodyParser = require 'body-parser'
chadventar = require '../'
express = require 'express'
http = require 'http'
path = require 'path'
request = require 'supertest'
sinon = require 'sinon'

describe 'chadventer', ->
  beforeEach (done) ->
    @sinon = sinon.sandbox.create()
    # for warning: possible EventEmitter memory leak detected.
    # process.on 'uncaughtException'
    @sinon.stub process, 'on', -> null

    # start HTTP server (localhost:8888)
    @app = express()
    @app.use bodyParser.json()
    @server = http.createServer(@app)
    @server.listen 8888, =>
      @robot = new Robot(path.resolve(__dirname, '.'), 'shell', true, 'hubot')
      @robot.adapter.on 'connected', =>
        @robot.load path.resolve(__dirname, '../../src/scripts')
        done()
      @robot.run()

  afterEach (done) ->
    # stop server
    @server.close =>
      @robot.brain.on 'close', =>
        close = =>
          @sinon.restore()
          done()
        # NOTE: robot.shutdown() does not close the `robot.server`.
        if @robot.server._handle?
          @robot.server.close close
        else
          close()
      @robot.shutdown()

  describe '#receive', ->
    beforeEach ->
      @robot.adapter.receive = @receive = @sinon.spy()

    it 'works', (done) ->
      request(@robot.server)
        .post '/chadventar/messages'
        .send name: 'bouzuya', text: 'hello'
        .expect 201
        .end (err, res) =>
          expect(@receive).to.have.been.called
          message = @receive.firstCall.args[0]
          expect(message).to.have.property('id', 'messageId')
          expect(message).to.have.property('text', 'hello')
          expect(message).to.have.deep.property('user.id', 'bouzuya')
          expect(message).to.have.deep.property('user.name', 'bouzuya')
          expect(message).to.have.deep.property('user.room', 'room')
          expect(message).to.have.property('room', 'room')
          done(err)

  describe '#send', ->
    it 'works', (done) ->
      name = 'bouzuya'
      text = 'hoge'
      @app.post '/messages', (req, res) ->
        expect(req.body).to.have.deep.equal({ name, text })
        res.sendStatus 201
        done()
      @robot.send new User(name, room: 'room'), text

まとめ

いそぎ足で、ほとんど最小構成の Hubot アダプターをつくってみました。おそらく、お使いのチャットには既に Hubot アダプターはあると思いますが、余裕があれば、自作してみるといいかもしれません。

最後に

今日はベイマックス見てきました。だから時間がない、と (ひどい) 。

scoped-http-client は心底ゴミですね。使いづらすぎて笑えるレベル。さっさと投げ捨てて request でも載せてくれるとうれしいんですけど。

[hubot-adventar-2014-22]:

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
10
Help us understand the problem. What are the problem?