たまには実用的なものをつくろうと思って、Go+Luaで置くだけで動くチャットボットを作ってみました。Slack, IRC, Hipchatをサポートしています。

チャットボットといえばHubotだと思いますが、もっとさくっと動かしたいという方におすすめです。置けばうごきます。

特徴は以下です。

  • Goなので置けば動く
  • それでいてLuaでスクリプトを書ける
  • 最初からマルチスレッド(複数goroutine)を考慮している
  • HTTP(S)サーバ機能があるのでWEBHOOKも一緒に作れる
  • 定期ジョブも流せる
function main()
  local bot = golbot.newbot("Slack", { token = "xxxxx" })

  bot:respond([[\s*(\d+)\s*\+\s*(\d+)\s*]], function(m, e) -- 3
    bot:say(e.target, tostring(tonumber(m[2]) + tonumber(m[3])))
  end)

  bot:serve(function(msg)
    if msg.type == "say" then
      bot:say(msg.channel, msg.message)
      respond(msg, true)
    end
  end)
end

こんな感じのよくあるAPIです。特徴的なのがworkerの仕組みで

function main()
  bot:respond([[deploy]], function(m, e)
    bot:say(e.target, "accepted")
    goworker({target=e.target, type="deploy"})
  end)

  bot:serve(function(msg)
    if msg.type == "say" then
      bot:say(msg.target, msg.message)
    end
  end)
end

function worker(msg)
  if msg.type == "deploy" then
    do_deploy()
    notifymain({type="say", target=msg.target, message="your deployment has been completed"})
  end
end

このように goworker でLuaからGoroutineをつくって重い処理などをWorkerで実行することができます。Workerからは notifymain でメインGroutineにメッセージをおくることができます。

HTTPサーバ機能では以下のような関数を定義するだけで簡単にWEBHOOKが作れます。

 function http(r)
   if r.method == "POST" and r.URL.path == "/webhook" then
     local data = assert(json.decode(r:readbody()))
     local message = data.item.message.message
     local user = data.item.message.from.name
     local room = data.item.room.name

     local ret = {
       message = "hello! from webhook",
       message_format = "html"
     }

     return 200, headers, json.encode(ret)
   end
   return 400, headers, json.encode({result="not found"})
 end

定期ジョブは以下のような感じ。

 function main()
   golbot.newbot("Null", {
     http = "0.0.0.0:6669" ,
     crons = {
       { "0 * * * * * ", "job1"}
     }
   }):serve(function() end)
 end

 function job1()
   print "hello!"
 end

チャットボットのためだけにNode.jsとnpmはちょっと・・・という場合にぜひ。


日本人である以上、いくらUTF-8が主権を得てきたといっても文字コード変換というカルマからは逃れられません。Pure Goでの文字コード変換はLLに比べるといろいろめんどくさい。

それに、そのままだと任意の文字コードを指定させるのが難しい。特定の文字コード決め打ちならいいんですけど、全世界の利用者に向けて任意の文字コード設定できるようにする場合とかはさらにめんどくさいし、かといって iconv に依存するのも嫌なのライブラリにしました。

以下のような感じで簡単に文字コードを変換できます。Pythonと同じで encode がUTF-8からUTF-8以外へ、 decode がUTF-8以外からUTF-8へ、です。内部では golang.org/x/net/html/charset を使っているので文字コードの指定もそれに習います。(WHATWGで定義されている名前になります) 。なので cp932 などではなく Windows-31J です。

 b, err = EncodeString("こんにちわ", "Windows-31J")
 b, err = Encode("こんにちわ", "Windows-31J")
 b, err = EncodeBytes([]byte("こんにちわ"), "Windows-31J")
 b, err = EncodeReader(strings.NewReader("こんにちわ"), "Windows-31J")
 b = MustEncodeString("こんにちわ", "Windows-31J")
 b = MustEncode("こんにちわ", "Windows-31J")
 b = MustEncodeBytes([]byte("こんにちわ"), "Windows-31J")
 b = MustEncodeReader(strings.NewReader("こんにちわ"), "Windows-31J")

 s, err = DecodeString(string(source), "Windows-31J")
 s, err = Decode(source, "Windows-31J")
 s, err = DecodeBytes(source, "Windows-31J")
 s, err = DecodeReader(bytes.NewReader(source), "Windows-31J")
 s = MustDecodeString(string(source), "Windows-31J")
 s = MustDecode(source, "Windows-31J")
 s = MustDecodeBytes(source, "Windows-31J")
 s = MustDecodeReader(bytes.NewReader(source), "Windows-31J")

もう2015年も終わろうとしています。今31歳ですが私は誕生日が後半なのでこの1年はほぼ30歳として過ごしたことになります。

GopherLua

さて、今年は2月に公開した GopherLua がGithubで1000スター以上もいただき、バンバンIssueやPRが来て新しい体験ができた年でした。

自分としてはGoは現状速い言語を書くには向いていないし、これは設定ファイルの拡張用、くらいに思っていたのですが意外とサーバサイドに使おうという人がいて驚きましたね。それでもやはり圧倒的にコマンドラインツールの拡張用が多くてシングルバイナリ+拡張言語がマッチする分野だと思います。

私はあくまで趣味プログラマなのでこういった機会はOSS活動をしていなければできない経験だったのでとても有益でしたね。英語にとても苦手意識があるのですが、案外適当な英語で通じるものです。

なんで趣味でOSSを書いているか

なんで趣味でOSSを書いているか…まぁ好きだからですよね。とにかくプログラミングが好きです。

あとは…どこかに「自分はまだ最低これくらい書ける」というのを確認したいという思いがあるのだと思います。そしてその裏には「やっぱり仕事でコード書きたいなあ」という思いがあるのでしょう。

この業界は実務経験がすべてですから、もはや実務から離れて久しい私がWEBやモバイル業界に行くことはないでしょうけど、万が一の時に自分の技術力を示す手段としてGithubという場を借りているのかな、と。

意外と?成果物経由で仕事しませんかというメールをもらえたりとGithubは見られてるんだな、と思いましたね。

今後は…

転職は35が限界、などという話もありますし今後をどうするか考える時期ですね。正直なところ今の勤め先が定年まで現状維持できる気がまったくしないので

  1. もっと安定している職業へ転職する
  2. いっそWEB、モバイル業界にチャレンジする
  3. (深く考えず今の勤め先で頑張る)

くらいでしょうか。安定している…というと公務員系ですかね。

いざという時に支えてくれる人がいればリスクを取りやすいのでしょうが、あいにく実家は離婚の上極貧、親戚づきあいなし、兄弟なし、独身(そして結婚も子供も予定なし)とある意味頼れるのは自分と会社だけ、という人間です。

さらに健康上の理由で海外は絶対行きたくない、となると1しかない気がしてきています。

その場合は、もうOSSでコード書いてもしかたないかなあ、とかこのブログも消そうかなあ、とか思うところはあります。

実際には惰性で3.でしょうが(まぁ20年くらいはつぶれないし待遇もそこまで落ちないでしょ…)。

2016年

とはいえ、2016年もなにかまとまったものが書ければな…とは思っているんです。やっぱり独自言語書きたいですね、普通にCで。 Goで実用的なものもなにか書きたいんですけど、ほんと趣味レベルではなかなかいいモノが思いつきません。いやー、実践でGo書いてみたいので副業できるなら喜んでGoで何か書ける仕事探すんですがねえ…。