読者です 読者をやめる 読者になる 読者になる

MANA-DOT

PIXEL ART, PROGRAMING, ETC.

HubotでIRCbot consoleと同等のことをする

hubot

会社のIRCircbot console を利用してbotを立てて、Jenkinsタスクの完了時にカスタマイザブルなメッセージを流したり、github enterpriseやfecruのRSSを加工して流したりしてたのですが、拡張性に限界を感じたので、今更ながらhubotとcoffeescriptを触ってみたよという話。

今回色々触った成果物はこちら github

IRCbot console でやっていたこととhubotでの実装法

  • botに呼びかけるとしゃべる
  • 周期スクリプトで通知
  • RSSを監視してつぶやく
  • curl経由で様々なメッセージを流す

↑すべてをとりあえずhubotでも実装出来ました。

hubotをIRCで使うための基本

hubotとともにhubot-ircを入れれば良いです。 こちら などが詳しいです。 検索すると結構noticeができなくて躓いてる人がいるようですが、hubot-ircのadapterでnoticeメソッドが定義されていたので、

robot.adapter.notice bot.envelope, "こんにちは!"

みたくすれば利用可能でした。ドキュメントをちゃんと読んでないので、これが開発者の意図した使い方かは不明ですが。

botに呼びかけるとしゃべる

これはhubotの基本機能、hearを使い、bot名で反応するようにすることでできます。

module.exports = (robot) ->
  robot.hear /mana_bot/, (bot) ->
    robot.send bot.envelope, "こんにちは!"

周期スクリプトで通知

nodeのモジュールにcronを追加し、enterでcron登録することで実現可能です。

cron = require('cron').CronJob
module.exports = (robot) ->
  robot.enter ->
   new cron
    cronTime: "0 0 17 * * *"
    start: true
    timeZone: "Asia/Tokyo"
    onTick: ->
      robot.send { room: "#mana_bot" }, "もう5時か・・・小腹空いたなあ"

部屋指定のためにオブジェクトでっち上げてますが、これでhubot的に正しいのかは不明。とりあえず動作します。

RSSを監視してつぶやく

nodeのモジュールにrequest、feedparserを追加して ↑と同じくRSSを取りに行きたいタイミングでcron実行してやればよいです。 ただし、何もしないと同じエントリを何回もつぶやいてしまうので、つぶやいたかどうかはrobot.brainに保存しておいてあげます。

cron = require('cron').CronJob
feedparser = require 'feedparser'
request = require 'request'

module.exports = (robot) ->
  watchFeed = (cronTime, url, callback) ->
    new cron
      cronTime: cronTime,
      start: true,
      timeZone: "Asia/Tokyo",
      onTick: ->
        entries = []
        request(url)
          .pipe(new feedparser [])
          .on('error', console.log.bind console)
          .on('data', entries.push.bind entries)
          .on('end', ->
            lastEntries = {}
            entries.forEach (entry) ->
              lastEntries[entry.link] = true
              if robot.brain.data[url]? and not robot.brain.data[url][entry.link]?
                callback entry

            robot.brain.data[url] = lastEntries
            robot.brain.save()
          )

  robot.enter ->
    watchFeed '0 * * * * *', "http://b.hatena.ne.jp/entrylist?sort=hot&threshold=3&mode=rss", (entry) ->
      console.log entry.title
      robot.send { room: "#mana_bot" }, "#{entry.title} #{entry.link}"

はてなの人気エントリを毎分取りに行って新しいものをつぶやかせる例です。

curl経由で様々なメッセージを流す

hubotは内部にhttpdも持っているので、それを利用してつぶやくようにしてあげればOKです。 環境によっては環境変数 PORT と HUBOT_HTTPDを設定してあげる必要があるかもしれません。

module.exports = (robot) ->
  robot.router.get "/send", (req, res) ->
    msg = req.query.msg
    ch = req.query.ch
    robot.send { room: ch }, msg
    res.end JSON.stringify {ch:ch,msg:msg}

/sendに対してチャンネル名とメッセージをGETで送ってあげると、それをしゃべります。

IRCbot consoleでできたことが大体できて、僕満足!

coffeescriptについて

こちらも今回はじめて触ったのですが(今更)、ブログ読んだりしてる限りはJavaScriptのシンプルさをそのままに記述を簡潔にし、よりスクリプティングに向いた言語というイメージでしたが、だいたいそんな感じでした。カッコ省略周りやクロージャの記述が柔軟なので、DSLっぽく利用するのに向いていると思います。

個人的にはオブジェクトリテラルの省略記法と関数適用のカッコ省略を合わせて、

new User
  name: "manaten"
  url:  ""

みたく、名前付き引数っぽく記述できるのと、

on "click" ->
  console.log "click!"

として、関数に無名関数を引数に渡すのを、そういう構文っぽく記述できるのが好みです。 シンプルな記述を組み合わせて柔軟に表現できるとかそういうのが好き。

ただJavaScriptと比べて気になった点が幾つか。

  • var文がないので、同名の変数をスコープの内外で定義できない
  • カッコを省略できるからと言って、省略し過ぎると 特に .演算子などがおかしく適用されてしまう

このあたり、coffeeはJavaScriptコンパイルされてから動くということもあり、ハマると結構はまってしまうので慣れるまで注意が必要だと思いました。