DATUM STUDIO の ChatWork Bot の紹介


本記事は DATUM STUDIO Advent Calendar 2016 に参加しています。

DATUM STUDIO では ChatWork を社内コミュニケーションツールとして利用しています。 DATUM STUDIO には ChatWork 上で動作している bot が 2 つあるので、それを紹介します。

DATUM STUDIO の ChatWork bot

DATUM STUDIO には 2 つの ChatWork bot アカウントがあります。「システム管理者」と「タムタム」です。

システム管理者 タムタム

システム管理者はもともとシステム管理者が ChatWork の管理操作を行うために作ったインフラチームの共有アカウントで、請求書を発行するときだけログインするくらいの役割だったのですが、余らせておくのももったいないので、 bot 化して自動通知に流用しました。アイコンは DATUM STUDIO のクリエイターが PowerPoint 用素材として作ってくれたものを流用しています。

タムタムは DATUM STUDIO のイメージキャラクターのひとりです。システム管理者だと管理者権限があり、社内のみんなに使ってもらうには微妙なので、新たに bot アカウントとして追加しました。システム管理者と同じクリエイターが作成しています。ちなみにタムタムの T シャツもあります。

ChatWork bot としての用途は上記のように、システム管理者が通知用、タムタムは社員用と分かれています。以下で各アカウントについて説明します。

システム管理者

システム管理者は、通知 bot です。 DATUM STUDIO では、以下の通知を行っています。

  • (インフラチーム) Trello のカード変更
  • (インフラチーム) Jenkins のジョブ結果
  • (全体) esa.io の変更
  • (全体) 日報のリマインダー

Trello、 Jenkins、 esa.io についてはそれぞれの webhook の仕組みを利用しています。 Trello と esa.io についてはそれぞれが webhook の仕組みを提供しています。 esa.io は通知先に ChatWork があるのでほとんど設定することはないのですが、 Trello の通知先に ChatWork は存在しないので、独自の webhook サーバーを構築しています。 Jenkins については ChatWork Plugin を利用して、ビルド後イベントとしてビルドの結果に応じた通知を行うようにしています。

日報のリマインダーについては、定期起動で平日の 19 時(定時)前くらいにリマインダーを送ります。日報は WordPress なので、データベースから直接投稿日とタイトルに含まれる日付を参照して、最近日報を書いていないユーザーにはメンションを送るようにしています。さらにサボり続けると、顔文字をつけてくれます。

タムタム

タムタムは、 ChatOps 促進のために(という名目のもとに Advent calendar のネタ作りのために)、社員に好きなよう利用してもらおうと最近作成したばかりの bot です。前述の通り、もともとイメージキャラクターとして作られたキャラクターなので、キャラ設定もきちんとあります。語尾は「〜タム」です。

タムタムは ChatWork API を利用して個人チャットや追加された部屋の情報を取得します。つまり、タムタムはどの部屋に追加されているか知っているし、その部屋で送信されたメッセージも取得することができます。

社員に ChatWork API トークンを渡すと、流出時(退職やセキュリティ事故)に非常に困ります。それを防ぐために、独自 API(DS API)を用意して ChatWork API を代理で呼び出す仕組みを提供しています。つまり、 DS API を呼び出すと、タムタムにメッセージを送信させることができるようになっています。 DS API の呼び出しを CRON や webhook サーバーに設定すれば、上記のシステム管理者と同じようにメッセージを部屋で共有することができます。

また、タムタムには部屋ごとに webhook サーバー[A]が設定されており(API でユーザーが送信先を変更できるようになっています)、新規メッセージを取得すると、そのメッセージを webhook サーバーに送信します。したがって、 webhook サーバーにメッセージに対する応答処理を記述すれば、 bot との会話も行えるようになっています。現在の仕様では 1 分間隔のポーリングなので、スムーズな会話のやり取りはできませんが。

要するに、タムタムは、 ChatWork のメッセージ投稿 API のプロキシーと、ポーリングを利用した擬似的な webhook の仕組みを提供しているということになります。

タムタムは AWS 上に構築されており、メッセージ送信に関わるところだけを抜粋すると、以下のような感じの仕組みになっています。

入り口の API Gateway では独自 API トークンを利用した API 認証を行います。 API Gateway にも API トークン発行の仕組みがあるのですが、トークンの有効状態を社員アカウントと紐づけて自動判定したかったのでカスタム認証としています。

部屋・メッセージ取得は、上記の通り、 1 分間隔のポーリングです。 Lambda が最低 1 分間隔でしかスケジュール起動しないのと、社員全員でトークンを使いまわすのでポーリング頻度が上がるとメッセージリクエストと合わせて API リクエスト制限(5 分間で 100 回)にすぐ引っかかりそうなので、抑えめにしてあります[B]。ついうっかり自分のメッセージに反応して無限ループしてしまってもメッセージ送りすぎになりません。現状では作られたばかりの仕組みということもあり、ほとんど利用者がいない状態ですが、今後利用者が増えて API 制限にかかりそうになった場合は、もう一人の DATUM STUDIO のイメージキャラクターに出てきてもらうかも?

実装時に困ったのは、タムタムは追加された全部屋を対象として動作する bot であるというところです。社員が 50 名以上いる DATUM STUDIO で 1 分毎に全部屋にメッセージを取得に行くと、すぐに API リクエスト制限に引っかかってしてしまいます。未読メッセージがある部屋のみメッセージ取得リクエストを発行するようにすればリクエスト回数が減らせます。部屋の一覧を取得する ChatWork API は、部屋ごとの未読件数を返してくれるため、未読メッセージがある部屋のみ取得できるはずなのですが、これが曲者でした。ブラウザーでメッセージを読むと、未読状態がクリアされて 0 件になるのですが、 API で取得しても未読件数はクリアされないため、 1 度でもメッセージが送られると、管理者がブラウザーで既読にしないかぎり、ずっと未読のままです。そのため、未読件数を自分で管理して、前回取得時の未読件数との差分を計算して、本当に未読メッセージがあるかどうかを確認しています。

その他の技術情報

システム管理者の webhook サーバーや、タムタムの Lambda は、すべて Go 言語で実装しています。 Lambda は Go 言語をサポートしていないのですが、 Apex を使うことで、 Go 言語で仕組みを実現しています[C] apex の Go 言語 SDK であるところの go-apex は、 Kinesis Streams などのイベントに対応して型を定義してくれているのですが、 DynamoDB Streams に対してなぜかエラーが出ていたので、イベントに対する型を自分で定義したり、カスタム認証用のモジュールがなかったりしたので、若干自分で書かなければならないところがありました。

Webhook サーバー側は各自自由に定義してもらう都合上、こちらでセキュリティについて強い指示は出せません。 HTTP レスポンスヘッダーに、シークレットキー(API トークンとは別に用意)により生成した HMAC の Base64 エンコード文字列を付与しているので、クライアントはそれを用いて改ざん判定するようにドキュメントしているくらいです。不正な webhook サーバーが設定されないように時々の HEAD リクエストでシークレットキーを使った何らかの検証機能は追加したいなと考えていますが、その辺りはまだ検討段階です。

まとめ

DATUM STUDIO で運用されている ChatWork の bot アカウントについて紹介と仕組みの解説しました。

ChatWork API を利用することで、 ChatWork にメッセージを送ったり、メッセージを取得することができるため、これを利用して bot を作成することができます。 DATUM STUDIO ではこれを管理者あるいは社員全員が利用できるような仕組みを作成して提供しています。

おわりに

DATUM STUDIO では、タムタムのお友達を募集しています

脚注

  1. 定期的にポーリングしているので厳密には webhook とは呼ばないけど便宜上そう呼びます []
  2. これは強化学習の良い題材になりそうなので、今後 Lambda から常時起動マシンに置き換えて実装しようかと考えています。 []
  3. 11 月最終週に作り始めたので、あと数日早く Lambda の C# サポートがリリースされていれば F# で作ったに違いない(参考)。 []