Help us understand the problem. What is going on with this article?

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:

castaneai
ネットゲームのサーバー等をつくってます すきなもの:socket/stream/security/database
https://castaneai.dev
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away