Edited at

Cloud Functionsで作る選択肢つき Slack BOT

More than 1 year has passed since last update.

次のGIF画像のような、メンションに反応して選択肢を表示、そのあと選択に応じた処理をしてくれる BOT を作ります。

いわゆる ChatOps 的な使い方ができそうなやつです。

choices-bot.gif


全体の流れ

Slack の3つの機能を使っているため、ここをちゃんと分割して考えることができれば、理解がしやすくなります。


  • ユーザーからのメンションを受け取る部分 (Event Subscriptions)

  • チャンネルへメッセージを投稿する部分 (Slack API)

  • 選んだ選択肢を受け取る部分 (Interactive Components)

image.png


Cloud Functions の準備

Slack App から来るイベントを受け取るための HTTP サーバーとして Google Cloud Platform の Cloud Functions を使います。

Event Subscriptions の受け取り先になるには、そのサーバーを所有していることを証明する必要があり、 url_verification と呼ばれてます。

https://api.slack.com/events/url_verification

簡単にまとめると、challenge という値がリクエストに含まれて来るので、その challenge をそのまま返せばOKです。

さっそく app.js というファイルを作って、満たす関数をつくってみましょう。


app.js

const onRequest = (req, res) => {

const payload = req.body;

if (payload.type === 'url_verification') {
return res.status(200).json({ 'challenge': payload.challenge });
}

res.status(200).send('OK');
}

exports.slackChoicesBot = onRequest;


書き終わったら、gcloud コマンドですぐにデプロイできます。 (最近東京リージョンや Node.js 8 に対応したので嬉しいですね!)

npm init

gcloud beta functions deploy slackChoicesBot --runtime nodejs8 --trigger-http --region asia-northeast1

デプロイ完了したら、https://asia-northeast1... という関数の URL が出るので、これをメモしておきましょう。

Deploying function (may take a while - up to 2 minutes)...done.

availableMemoryMb: 256
entryPoint: slackChoicesBot
httpsTrigger:
url: https://asia-northeast1-xxxxxxx.cloudfunctions.net/slackChoicesBot
...

デプロイした関数は、Google Cloud Platform のコンソール からも確認できます。

image.png


Slack App の作成

Cloud Functions が準備できたので、次は Slack App を作って BOT ユーザーを作ります。


(1) Slack App と BOT User をつくる

https://api.slack.com/apps にアクセスして、Create New App ボタンから Slack App をつくり、Bot Users というメニューから BOT 用のユーザーを作ります。

image.png


(2) メンションを受け取るサーバーを指定

App 設定の Event Subscriptions をONにして、Request URL に Cloud Functions にデプロイした関数の URL を入れます。

次に、Subscribe to BOT Events のところに app_mention を追加します。

image.png


(3) 選択結果を送るサーバーを指定

App の設定で次のように、Interactivity: ON にして、Request URL に Cloud Functions にデプロイした関数の URL を入れます。

image.png


(4) Slack ワークスペースに App をインストール

App の設定の OAuth & PermissionsScopes で、bot という名前の scope を追加します。

image.png

そこまで完了したら、OAuth & Permissions ページにある Install App to Workspace で Slack ワークスペースにインストールしましょう。

image.png


メンションに反応して選択肢を返す

BOT に対してメンションを飛ばすと、Cloud Functions の関数に app_mention という種類のリクエストが来ます。

{

"type": "app_mention",
"user": "U061F7AUR",
"text": "<@U0LAN0Z89> is it everything a river should be?",
"ts": "1515449522.000016",
"channel": "C0LAN2Q65",
"event_ts": "1515449522000016"
}

この内容に対して、選択肢つきの返信をチャンネルに投稿する処理を書きます。チャンネルへの投稿は chat.postMessage API を普通に使います。

選択肢は Slack Attachments として記述できる ので、ドキュメントを読みながら組み立てていきました。


app.js

const request = require('request-promise-native');

async function postMessage(payload) {
await request.post('https://slack.com/api/chat.postMessage', {
headers: { 'Authorization': `Bearer ${process.env.BOT_USER_TOKEN}` },
json: payload,
});
}

const onRequest = async (req, res) => {
let payload = req.body;

if (payload.type === 'url_verification') {
return res.status(200).json({ 'challenge': payload.challenge });
}

if (payload.event && payload.event.type === 'app_mention') {
if (payload.event.text.includes('hi')) {
const slackRes = await postMessage({
text: `<@${payload.event.user}> hi!`,
channel: payload.event.channel,
attachments: [createSlackAttachment('BOT response')],
});
return res.status(200).send('OK');
}
}

...


また、チャンネルに投稿するために BOT User OAuth Access Token が必要ですが、このトークンはソースコードには含めたくないものなので、環境変数ファイルに分離 して関数をデプロイしました。


.env.yml

BOT_USER_TOKEN: xoxp-xxxxx...


デプロイコマンドはこんな感じです。

gcloud beta functions deploy slackChoicesBot --runtime nodejs8 --trigger-http --env-vars-file .env.yml --region asia-northeast1


選択肢を選んだ結果を受け取る

BOT が出した選択肢を選んだとき、Cloud Functions の関数に interactive_message という種類のリクエストが来ます。

https://api.slack.com/docs/interactive-message-field-guide#action_payload

リクエストの中に「どの選択肢を選んだか」などの情報がはいっているので、それに応じて関数でいろいろなことができます!

※app_mention のときと payload の形式が異なっているので注意が必要です。 app_mention はそのまま構造化された JSON で来ましたが、interactive_message のときは payload という名前のフィールドに 文字列化されたJSONが入っている 構造なので、payload を一度 JSON.parse する必要がありました。


app.js

...

const onRequest = async (req, res) => {
let payload = req.body;

...

if (typeof payload.payload === 'string') {
payload = JSON.parse(payload.payload)
}

if (payload.type === 'interactive_message') {
const action = payload.actions[0];
if (action.name === 'choices') {
const selectedOption = action.selected_options[0];
return res.status(200).send(`<@${payload.user.id}> Your select value: "${selectedOption.value}"`);
}
}
...



まとめ

全体のソースコードは GitHub にアップロードしておきました。

今回は選んだ値を Slack に出すだけの単純な BOT でしたが、これをベースに様々な BOT が開発できそうです。

最近は Cloud Functions のような関数だけをさくっと公開できるサービスが充実してて、非常に開発がやりやすいですね!

その影響もあって、プログラミングの部分よりは、Slack API の仕様の理解のほうが苦労します :sweat_smile: