Slack RTM Bot Engine: eure/bobo
この記事は 「eureka Advent Calendar 2019」 12日目の記事です。
11日目はBIチームの栗村による「BigQueryとAirflowを活用したDataPlatform運用の10のケース・スタディ」でした。
彼の趣味はバイクです。アメリカンです。実家のバイクで走り出します。
彼は元々Androidエンジニアですが、今年からデータサイエンス的なムーブメントをしています。
でもこの2週間はドロイダーとしてまたKotlinコードを書いているみたいです。
そのくらいAndroidエンジニアが足りません。本当に足りないのでぜひ今年中に入社してください。
さて、みなさまいかがお過ごしでしょうか。
エウレカの森川と申します。
気がつけばもう12月、暑い夏、謎に暑すぎた秋も終わり、寒さが全身を襲い尽くす季節がやってきました。
そう、冬です。これはポケモンGoが辛い季節でもあります。軒並みカーブボールが外れてしまうほどの寒さが遍いてます。
そんなわけで、この記事ではGo言語で作るSlack RTM Botについて記載いたします。
対象者(前提条件)
- Go言語である程度何か書いたことがある人
- 既にSlack BotのTokenを取得している人
できるようになること/できるようにならないこと
- RTM APIを利用したSlack Botに対してGo言語でかんたんに機能追加できるようになります。
- ❌ RTM以外の方式のSlack Botはつくれません
- ❌ Go言語に対する深いナレッジやインサイトは得られません
eure/bobo について
というわけで、Slack botつくってますか?
Goだと nlopes/slack 使ったり、GoではないですがBoltを使ったりするのかなと思います。
Goで一からシコシコ作るのでもいいんですが、色んなbot作ってると、毎回似たような処理ばかりでめんどくさいですよね。
そんな甘ちゃんのあなた(わたし)に eure/bobo を紹介させてください。(といってもコード内部ではnlopes/slackを利用させてもらってます)
以下の手順で簡単にオリジナルSlackBotが作れちゃいます。
まずは適当なディレクトリにcloneするか、go get
しちゃってください。
# $ go get -u github.com/eure/bobo
$ git clone https://github.com/eure/bobo.git$ cd bobo
そして、SlackTokenを与えて起動すればサンプルのbotが動きます。
$ make build
$ SLACK_RTM_TOKEN=xoxb-0000... ./bin/bobo2019/12/10 21:24:06 [INFO] [bobo] pid=[11055]
そして話しかけると…
きちんと反応してくれますネ。
まあこんなゴミのような機能しかないゴミBotはゴミかSelf Pleasure以外のナニモノでもないので、もっとCOOLな機能を入れたいですよね。
安心してください、普段からGoを書いているあなたならきっと簡単にできます。
まずはエントリポイントのコードを見てみましょう。
不要なコードを取り除いてシンプルにすると↓みたいになります
コマンドの作成と追加は↓のようにします
(以下、READMEからほぼコピペ)
作ったコマンドを追加してみます.
これを起動すると…
うまく動きました👾
まあこれもいわゆるTHE・ゴミ機能ですね。
G・O・M・I
ゴミばっか生産している人間というレッテルを貼られる前にそろそろ現実的な機能を入れたいところですが、一旦コマンド周りのコード構成を確認したいと思います。
CommandData と Task
まず、コマンド作成時に引数となっている CommandData
ですが、以下のようなフィールドが存在しています。
コマンド内ではこれらのデータを利用しつつ処理を行い、複数のタスクを生成します。
タスク自体はinterfaceで以下の2つのメソッドが実装されていれば何でも構いません。
例としてSlackの指定チャンネルにメッセージを投稿する replyEngineTask
を見てみましょう.
NewReplyEngineTask
で必要なデータが入り、Run
内で engine.Reply
だけが実行されています。
なんとなく分かってきたでしょうか。
最低限必要な内容を説明したので、ここからは実際に何かを作って理解を深めていきます。
例1. 指定画像の顔を合成するコマンド
まずはみんなが好きそうな顔を合成するコマンドを作成します。
自分で処理を書くのは非常にしんどいので外部のAPIを利用します。
ここでは中国Megvii社のFace++というサービスを使います。現在のところ秒間1クエリまでは無料なので気軽に試すことができます。
ただし中国政府の監視カメラにも利用されている企業サービスなので、データの扱いには注意して利用してください。
ここでは解説しませんが、事前にサインアップして、 API Key
と API Secret
を取得しておいてください。発行はシンプルなので迷うことは少ないと思います。
Face++のGo用クライアントとしては evalphobia/go-face-plusplus を使います。
さて、これを実際に利用可能なコマンドとして追加していきます。
まずは顔画像を合成する関数を作成します。(↑の例とほぼ同じなので解説は省きます)
次にこの関数を利用するコマンドを作成していきます。
ちょっと長いですが、コードを見ればなんとなく何をしているかわかるかと思います。
// 一旦喋っておく
の箇所では作成したタスクをコマンドへ c.Add()
せずに、すぐに Run()
しています。
これは GenerateFn
の内容が先に実行されて、その他のタスクは最後にまとめて実行されるため、すぐに発言しておきたいものがタスクとしてコマンド内に追加されてしまうと以下の順番で実行されてしまいます。
- [GenerateFn] 画像マージAPIの実行
- [Task] Slackへの発言 「合成中…」
- [Task] 合成済み顔画像ファイルのアップロード
本来は 1 と 2 の順番が逆なので、以下のいずれかの形に書き換える必要があります。
- a) 2をその場でRunする
- b) 1をタスクとして追加して、結果をどうにかして3へ渡す
- c) 1と3を同じタスク内に定義・実行する
(このコードではaにしています)
どうせSlackBotなので、数百人でいじることもないだろうし各々書きやすい方法で書いてください。
さて、最後に作成したコマンドをCommandSetへ追加し、Botを起動させてみます。
(注: SlackBotには files.upload
の権限が必要です)
# 起動コマンド例
$ FACEPP_API_KEY=xxx \
FACEPP_API_SECRET=yyy \
SLACK_RTM_TOKEN=xoxb-... \
go run ./cmd/main.go
うーん、テクノロジーの勢いを感じますね。
もう一つやってみます。
いい感じに合成されましたっ☆ミ
ここまでは出来レースです。
しかし、サングラスはどうでしょうか…
…
ギリギリのラインです。初見なら失笑してくれそうだから良しとしましょう。
これでようやくゴミBotから脱却できましたんじゃないでしょうか。
例2. AWSの1日の料金を取得するコマンド
例1では(無料で)現代テクノロジーの風を感じることができましたが、まだまだ給料泥棒感は否めません。
得られたものはちょっとした社内の笑いだけで、それ以外はゴミです。
なんら生産性がありません。
年末なので評価面談も近いし、ちゃんと働いているところを見せるためにも、何か人の役に立つ機能を入れたいところです。
そこで次は、みんな大好きAWSの料金を取得するコマンドを作成します。
まずは完成形のイメージです。
上記画像のように(全て$0.00ですが..)、1日のAWS合計コストと各サービスのコストを列挙してくれるようにしていきます。
ちょっとコードが長い上にAWSの知識がちょこっと必要なので、本当はコードのURLだけ貼って終わりにしたいところではありますが、
会社のアドベントなカレンダーなのである程度説明を加えつつ泣く泣く進めていきます(/_;)
今回は例1とは違い、 command.BasicCommandTemplate
を使わずにオリジナルなコマンドテンプレートを作成してみます。
command.BasicCommandTemplate
という構造体は CommandTemplate
というinterfaceを満たしています。CommandTemplate
を満たすためには以下の5つのメソッドが必要です。
この5つのメソッドの内、Exec
以外の4つは固定値を返却するだけで大丈夫です。
今回は @bot awscost
でAWSのコストを出力してくれるコマンドを作成することにします。
まだ処理は空ですが、これで command.CommandTemplate
を満たすことができました。AWSCostCommand
にはServicesというフィールドがありますが、これを使ってコスト表示するAWSサービス名を指定できるようにしてみます。
また一番上の行は interfaceを満たすかどうかコンパイル時に判定できるので、利用するinterfaceが決まっている場合は極力記述するようにしましょう。
var _ command.CommandTemplate = AWSCostCommand{}
(テストコード中に書いてしまうとテストを実行するまで分かりませんが、これなら起動前にエラーが分かります)
ガワができたので、まずは大雑把に必要なロジックを書きます。
コード内に説明を入れながら、処理を順次足していきます。
と大きく4つの処理に分けて実行するようにしました。
順番に処理を作っていきます。
(1) AWS CloudWatchクライアント
まずはAWSのCloudWatch用のクライアント周りの処理を作ります。
「Cost Explorer APIじゃないんかい!」みたいなツッコミもあるかと思いますが、ここは古き良きのCloudWatch経由で取得していきます。
定形処理とRequest/Responseを楽に扱うために evalphobia/aws-sdk-go-wrapper を使用しています。
これで(1)の処理は完成です。
(2) 日時のパース
次にコストを計算したい日時を返却する処理を作成します。
(簡単なのでコードだけ載せて終わりでもいいでしょうか…)
(3–1) コスト取得: 対象となるサービスリストの取得
一番の肝となるコスト取得の前に、対象となるAWSサービスを返却する処理を追加します。
サービスが指定されていない場合はデフォルトのリストを返却してあげるメソッドを追加してみましょう。
(3–2) コスト取得
では、メインのコスト取得処理になります。
まずはコスト取得・計算周りの全体のロジックから書いていきます。
最初に全コスト合計値を取得し、その後のループ内で各AWSサービスごとのコストを取得します。
次に実際にAWS CloudWatch APIから各コストを取得する処理を書きます。
ちょっと長いですが、上記の処理でコストデータが取得できるはずです。おそらく…
fetchCosts
では指定日時(+指定サービス)のコストデータを取得しています
(ClowdWatchのAPIの話になるので詳細な解説は省略します)
fetchCosts
ではCloudWatchの時系列データが返却されるため、 getFirstMaximum
を使ってデータの先頭のMaximumを取得しています。
利用していないサービスでは、 そもそも何も返却されない = 時系列データが空になる と思うので、0が返却されます。
(4) コストの整形
最後はテキスト整形処理です。
何も考えずに fmt.Sprintf("%+v", cost)
とかしてもいいんですが、なるべく実用的にしたいところです。
ここでは、コストが高い順にソートして出力するようにしてみます。
最後にソートの処理を書きます。
(通常のソート処理なので、詳細な解説は省略します。。。)
こんな感じで完了です。
コマンド追加と実行
作成したコマンドを追加してボットを起動してみましょう。
# 起動コマンド例
$ AWS_ACCESS_KEY_ID=xxx \
AWS_SECRET_ACCESS_KEY=yyy \
SLACK_RTM_TOKEN=xoxb-... \
go run ./cmd/main.go
そして awscost
コマンドを実行すると...
無事に表示されたでしょうか?
同じ画像貼るだけなんですが、画像再掲を省略してみました。
例3. GoogleCalendarの予定を取得するコマンド
これはおまけです。
Google Calendar APIを利用するには credentials と oauth token が必要になります。
以下のチュートリアル等を参考にしてなんとか取得してください。。。
https://developers.google.com/calendar/quickstart/go
ここでは当日の予定を教えてくれるコマンドを作ります。
まずは完成形のイメージです。
こんな感じで、単に @bot calendar
とした場合は自分のカレンダーから予定を表示し、 @bot calendar @user
とした場合は他の人のカレンダーから予定を表示するようにしていきます。
まずは大雑把に必要なロジックを書きます。
今回も大きく4つの処理に分けて実行するようにしました。
順番に処理を作っていきます。
(1) Google Calendarクライアント
まずはGoogle Calendar用のクライアント周りの処理を作ります。
(こちらも楽に扱うために evalphobia/google-api-go-wrapper を使用しています)
これで(1)の処理は完成です。
(credential関連は環境変数で起動時に指定します)
(2) メールアドレスの取得
次に対象者のメールアドレスを取得するようにします。
SlackではユーザーIDからユーザーのメアドを取得することができるのでそうします。
(3) Google Calendarから予定を取得
ここは追加の実装はいりません。APIをそのまま使えばOKです。
calendarCli.EventList(email, maxEvent)
のようにメアドと取得したい予定数だけ入れれば取得できます。
日時を指定したい場合や、その他の細かいオプションを指定したい場合は calendarCli.EventListWithOption(email, option)
を使っても良いです。
(4) 予定の整形
見やすくなるようにテキストを整形してあげます。
こんな感じで完了です。(3回目)
コマンド追加と実行
作成したコマンドを追加してボットを起動してみましょう。(3回目)
# 起動コマンド例
$ GOOGLE_APPLICATION_CREDENTIALS=/path/to/credentials.json \
GOOGLE_API_OAUTH_TOKEN_FILE=/path/to/oauth_token.json \
SLACK_RTM_TOKEN=xoxb-... \
go run ./cmd/main.go
これで気になるあのコの予定も確認し放題になりました!
ここまでできればコマンド追加手順はバッチリですね。
(色々と変なものを追加しすぎてクビにならないように気をつけてください)
なお、少し手直ししたものは以下のレポジトリに掲載しており、謎コマンドを随時追加していく予定です。
おわり
さて明日13日は弊社の高橋 aka たこちゅー による「APIチーム 2019年のあゆみ」です。
一応、彼の(3年前の)LGTM画像を貼っておきますね。
現在エウレカではBotは募集していませんが、やっぱりエンジニアを募集しています。
サーバーサイドエンジニアは決済とか暗号化とか検索アルゴリズムとか辛いことはたくさんありますが、得られるものも大きいので興味がある方はぜひ応募してください :)
お待ちしております♥❤❥٩(♡ε♡ )۶❥❤♥