この記事を読んだらできるようになること
- SlackのReactionが押されたときに、何かの処理をしたりできるようになります
この記事で伝えたいこと
- Real Time Messaging APIとEvents APIの存在
- Slackをハックする楽しさ
今回作ったもの
- リアクションがたくさんある投稿(質問内容)が上にくる質問システム
(弊社では年に2回ぜんいん社長合宿というものがあるのですが、質問タイムのときに特定のチャンネルで呟いてもらい、共感した人はReactionを押すと注目度の高い質問が上の方へ来るという仕組みです。司会がWeb上のコンテンツをプロジェクターで表示しながら、質問に答えていくというスタイルでやりたいと、今年の司会であるはまーさんから製作依頼を受けました。)
このエントリーのターゲット
- SlackのReactionを使って何かしてみたい人
* すでにSlackのReal Time Messaging APIやEvents APIを扱ったことがある人は退屈だと思います。
はじめに
こんにちは。 Tech KAYAC Advent Calendar 2017の16日目の記事をお届けします。受託チーム(CL)の新卒サーバサイドエンジニア、 serinuntiusこと芹川です。
皆様、Slackをハックしてますか?
趣味でSlackのBotを製作して社内で公開してから、Slack関係のソフトウェアの製作依頼や質問が来るようになりました。 嬉しいことですね!
早速ですが、Botを作成していきましょう!
SlackのBotsを作成する
1. Apps
を押す
2. View App Directory
を押す
3. Bots
と打って選択
4. Botを追加する権限があれば
Add configuration
が選べる。
もし、権限がないとこんな表示になると思います。
Request to install
を押すとAdminの人とかに(たぶん)通知がいくので承認してもらいましょう!
5. Bot名を適当に入れて鍵を入手しましょう
後で使うので、コピーしておいてください。
言うまでもないがこの鍵は慎重に扱いましょう! (漏れるとBotがいるチャンネルでの会話が筒抜けになる可能性がある)
その他、アイコン等も設定しておくと良いと思います。 まったくつぶやかないBotなら設定しなくてもいいと思います。
6. Botを部屋に招待しておく
これが終わればSlack側の設定は終わったはず!!!
BotがいるチャンネルのReactionだけが取れます。
コードの部分
今回はサクッと作りたかったので、Node.jsで実装しました。
本当はServerless & Lambdaで作ろうと思ってたから・・・
1. 依存ライブラリのインストールとか
mkdir slack-reaction cd slack-reaction yarn add @slack/client dotenv echo SLACK_BOT_TOKEN=xoxb-xxxxxxx > .env
2. ミニマムな構成のコード
// main.js const RtmClient = require('@slack/client').RtmClient; const RTM_EVENTS = require('@slack/client').RTM_EVENTS; const dotenv = require('dotenv'); dotenv.load(); const botToken = process.env.SLACK_BOT_TOKEN || ''; const rtm = new RtmClient(botToken); rtm.on(RTM_EVENTS.REACTION_ADDED, event =>{ console.log('Reaction added:', event); // ここに好きな処理を書きましょう!!! }); rtm.start();
特に説明するほどのこともないかと思いますが、こんな感じに書いて
node main.js
で起動させて、
SlackでBotを招待したチャンネルで呟いてReactionを付けると次のようなログが吐き出されると思います。
Reaction added: { type: 'reaction_added', user: 'U6HJ69ECD', item: { type: 'message', channel: 'C7JHG5GQG', ts: '1513301808.000130' }, reaction: '100', item_user: 'U6HJ69ECD', event_ts: '1513301811.000176', ts: '1513301811.000176' }
各項目について一応説明しておくとこんな感じ。 event_tsとtsの違うがよくわからん・・・。
key | 説明 |
---|---|
user | Slack内部で使われているuserのID |
item.type | messageとかfileとかfile_commentとか |
item.channel | Slack内部で使われているチャンネルのID |
item.ts | messageのuniqなtimestamp(これがメッセージのIDみたいなもの) |
reaction | reactionの名前 |
item_user | そのmessageを書いたuser |
event_ts | eventが発生したtimestamp(↓違いがわからん) |
ts | timestamp(↑と違いがわからん) |
これを見ていてびっくりするのが、messageの内容が書かれていないよう(激寒)ってこと。
なんとかして、reactionが付いたmessageが何か知りたい。
そう思ってググってたら、下記のStackoverflowが見つかりました。
slack api - How to search for a message by a specific ts - Stack Overflow
どうやら、conversation.history APIとmessageのtsを使えば取れそうだ。
3. 改良したメッセージの内容がわかるやつ
const RtmClient = require('@slack/client').RtmClient; const RTM_EVENTS = require('@slack/client').RTM_EVENTS; const WebClient = require('@slack/client').WebClient; const dotenv = require('dotenv'); dotenv.load(); const botToken = process.env.SLACK_BOT_TOKEN || ''; const rtm = new RtmClient(botToken); const web = new WebClient(botToken); rtm.on(RTM_EVENTS.REACTION_ADDED, reaction =>{ console.log('Reaction added:', reaction); web.conversations.history(reaction.item.channel, { latest: reaction.item.ts, limit: 1, inclusive: true, }, (err, res) => { if (err) { console.log(err); return } const messageText = res.messages[0].text; console.log(messageText) }); }); rtm.start();
よしよし!イイ感じに取れた。
けど、普通に考えてユーザ名も知りたいですよね・・・。
4. ユーザの名前もわかる版(完成版)
そろそろ、コールバックが辛くなるのでPromise化もしましょう!
users.info APIでuserIDからuserの名前を調べる。
const RtmClient = require('@slack/client').RtmClient; const RTM_EVENTS = require('@slack/client').RTM_EVENTS; const WebClient = require('@slack/client').WebClient; const dotenv = require('dotenv'); dotenv.load(); const botToken = process.env.SLACK_BOT_TOKEN || ''; const rtm = new RtmClient(botToken); const web = new WebClient(botToken); const getMessageText = (event) => { return new Promise((resolve, reject) => { web.conversations.history(event.item.channel, { latest: event.item.ts, limit: 1, inclusive: true, }, (err, res) => { if (err) { reject(err); } const _messageText = res.messages[0].text; const _userID = res.messages[0].user; const _reaction = event.reaction; resolve({_messageText, _reaction, _userID}) }); }) }; const getName = (userID) => { return new Promise((resolve, reject) => { web.users.info(userID, (err, res) => { if (err) { reject(err) } resolve(res.user.name) }) }) }; rtm.on(RTM_EVENTS.REACTION_ADDED, event => { let messageText, reaction, userName; if (event.item.type !== 'message') { console.log('this is not message'); } getMessageText(event) .then(({_messageText, _reaction, _userID}) => { messageText = _messageText; reaction = _reaction; return getName(_userID) }) .then(_userName => { userName = _userName; console.log(messageText, reaction, userName); }) .catch((err) => { console.error(err); }) }); rtm.start();
これで、何を作るにしてもだいたい必要になる3種の神器
がそろいました。
動作イメージ
実際のアプリケーションでは、DyanmoDBに保存したりフロントから参照するためにExpressでAPI作ったりしたのですが、今回の大筋とはかけ離れるのでやめておきます。
一応サンプルをGitHubにおいておきます。
おもったこと
今回あまり下調べをせずに勢いに任せてReal Time Messaging APIを使って実装してしまったのですが、Events APIの方を使えばいつも使っているお気に入り構成のServerless Framework & AWS Lambda & DynamoDB が出来たのにな〜って強く思いました。
Lambdaでも出来なくはないと思うのですが、タイムアウトが300秒なので5分毎に実行させて・・・
みたいなことになるので、あまりキレイな設計ではないですね。
Events APIの方を使って、API GatewayとLambdaでAPIを作ってReactionごとにAPIコールしてもらうのが一番いい構成です。(たぶん) 今回のアプリケーションはec2のt2.nanoとかで動かしました。
おわりに
いかがだったでしょうか。 今回はSlackのReactionごとに何かするアプリケーションを作りました。
Slackをハックする楽しみが少しでも伝われば幸いです。
カヤックではSlackをハックするのがエンジニアも、そうでないエンジニアも募集しております!
明日は tamiflu
さんの GoogleTestかなあ
です。乞うご期待!