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

Slack で簡単 Interactive な Bot 作成

More than 1 year has passed since last update.

概要

Slackで対話的なBotをさくっと作る。
こんな感じのやつ↓
ezgif.com-video-to-gif (2).gif

大まかな流れ

  1. Slack App 作成(Botトークン取得)
  2. Bot から Slack へメッセージ投稿
  3. Slack からアクションの受信、メッセージ書き換え

0. 準備

Botはローカルマシンで動かす想定。
サンプルは Node.js で書く。(仕組みがわかれば何でもいい)

$ node -v
v7.8.0

3. Slack からアクションの受信 では Slack から POST が飛んでくる為、外部からアクセス出来るようにしておく。
ngrok を使えば簡単。

$ ngrok http 3000
...
Forwarding                    http://3f62ee8a.ngrok.io -> localhost:3000
Forwarding                    https://3f62ee8a.ngrok.io -> localhost:3000

これで https://なんちゃら.ngrok.io をローカルの3000番にフォワーディングしてくれる。

1. Slack App 作成(Botトークン取得)

https://api.slack.com/apps
スクリーンショット 2018-02-18 1.44.46.png

Create an App から適当な名前でアプリ作成。

スクリーンショット 2018-02-18 1.48.42.png

Bots と Interactive Components を有効にする。

スクリーンショット 2018-02-18 2.28.22.png

Interactive Components の Request URL には、外部公開したURLを設定
(公式ストアにアプリ登録するならHTTPS必須らしいけど、自分で使う分には関係なさそう)

スクリーンショット 2018-02-18 2.29.28.png

Permissions からワークスペースにアプリインストールする。

スクリーンショット 2018-02-18 2.30.02.png

これで Bot ユーザ用のトークンが貰える。

2. Bot から Slack へメッセージ投稿

Slack Web API の chat.postMessage を使う。
https://slack.com/api/chat.postMessage に適当な JSON 投げれば良い。
Attachment を含めると、ボタンやメニュー等の Interactive messages も含むリッチなメッセージを表現できる。
npm にシンプルでいい感じのラッパーがあるので使わせて頂く。

post_sample.js
const Slack = require('slack');
const slack = new Slack();

slack.chat.postMessage({
    token: 'xoxb-316871944213-oq9BcdxFWcaU2vIaxUrGXF6R', // ※Bot token 
    channel: 'C6N4G0BHS', // ※チャンネルID 詳細後述
    text: 'クソ動物園',
    attachments: [{
        callback_id: 'animals_button',
        text: '',
        actions: ['パンダ', 'コアラ', 'タスマニアタイガー', 'ディンゴ', 'タスマニアデビル'].map(v => ({
            type: 'button',
            text: v,
            name: v
        }))
    }]
}).then(console.log).catch(console.error);

トークンと Channel ID を設定して実行。

npm install slack
node post_sample.js

ボタンが投稿できた。

スクリーンショット 2018-02-18 3.02.41.png

補足: Channel ID

Slack のチャンネル名は変更される事があるので、内部的に常に一意なIDを持っている。
channels.list の API を叩くか、ブラウザのURLから知ることが出来る。

スクリーンショット 2018-02-18 3.10.18.png

3. Slack からアクションの受信、メッセージ書き換え

ユーザがボタンを押すと、Slack から Interactive Components で登録した URL にリクエストが来る。
適当に受け取ってパースしよう。

receive_sample.js
const express = require('express');
const bodyParser = require('body-parser');
const app = express();

app.post('/', bodyParser.urlencoded({ extended: false }), (req, res) => {
    const payload = JSON.parse(req.body.payload);
    console.log(payload);
    res.json({
        text: '押されたよ',
        attachments: [{
            text: payload.actions[0].name
        }]
    });
});
app.listen(3000);

ここで JSON を返すとメッセージを書き換え可能。

さっきのボタン投稿のやつと合わせて実行。

npm install express body-parser
node post_sample.js
node receive_sample.js

ボタンを押すとメッセージが書き換えられる!

ezgif.com-video-to-gif (1).gif

補足:メッセージを変更する他の方法

実は上の方法だと、レスポンスを3秒以内に返す必要がある。(Timeoutになる)
その為、時間がかかる処理をする場合は一旦空のレスポンスを返しておき、後から改めて書き換える必要がある。

  • payload.response_url で渡される URL に JSON を POST
  • chat.update の Web API で書き換え

の2通りがあるが、 response_url の方は「30分以内に5回まで」という制限がある。
トークンを持っているならばchat.updateで、無ければpayload.response_urlを使うのが良さそう。

response_url_sample.js
var request = require('request');
const express = require('express');
const bodyParser = require('body-parser');
const app = express();

app.post('/', bodyParser.urlencoded({ extended: false }), (req, res) => {
    const payload = JSON.parse(req.body.payload);
    res.end();

    setTimeout(() => {
        request.post({
            uri: payload.response_url,
            headers: { 'Content-type': 'application/json' },
            json: {
                text: '後から変更(response_url)',
                attachments: [{
                    text: payload.actions[0].name
                }]
            }
        });
    }, 5000);
});
app.listen(3000);
chat_update_sample.js
const Slack = require('slack');
const slack = new Slack();
const express = require('express');
const bodyParser = require('body-parser');
const app = express();

app.post('/', bodyParser.urlencoded({ extended: false }), (req, res) => {
    const payload = JSON.parse(req.body.payload); console.log(payload);
    res.end();

    setTimeout(() => {
        slack.chat.update({
            token: 'xoxb-316871944213-oq9BcdxFWcaU2vIaxUrGXF6R',
            channel: payload.channel.id,
            text: '後から変更(chat.update)',
            attachments: [{
                text: payload.actions[0].name
            }],
            ts: payload.message_ts
        });
    }, 5000);
});
app.listen(3000);

クソ動物園Bot

クソロジックを追加して完成。

kuso_zoo.js
const Slack = require('slack');
const slack = new Slack();
const BOT_TOKEN = 'xoxb-316871944213-oq9BcdxFWcaU2vIaxUrGXF6R';
const CHANNEL_ID = 'C6N4G0BHS';
let animalLog = [];

slack.chat.postMessage({
    token: BOT_TOKEN,
    channel: CHANNEL_ID,
    text: 'パンダ楽しみー!',
    attachments: getAttachments()
}).then(console.log).catch(console.error);

// Interactive Message 待ち受け
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.post('/', bodyParser.urlencoded({ extended: false }), (req, res) => {
    const payload = JSON.parse(req.body.payload);
    animalLog.push(payload.actions[0].name);
    res.json({
        text: 'パンダ楽しみー!',
        attachments: getAttachments()
    });
});
app.listen(3000);

function getAttachments() {
    let attachments = [{
        callback_id: 'animals_button',
        color: '#36a64f',
        text: '',
        actions: ['パンダ', 'コアラ', 'タスマニアタイガー', 'ウォンバット', 'ディンゴ'].map(v => ({
            type: 'button',
            text: v,
            name: v
        }))
    }];
    // animalLog の数だけクソレスポンスを追加
    let memo = {};
    animalLog.forEach(animal => {
        attachments.push({
            text: `あっ ${animal} だ!  ` + (() => {
                if (memo[animal]) return 'もう見た';
                memo[animal] = true;
                if (animal === 'ディンゴ') return '知らなーい';
                return 'カワイイーッ';
            })() + ' >'
        });
    });
    return attachments;
}

ezgif.com-video-to-gif (2).gif

なお、Interactive messages のガイドライン によると

Though messages may contain up to 20 attachments, messages containing buttons or menus shouldn't have more than one or two attachments.

との事なので、attachments どんどん増やすこの使い方は良くない。
アクション用のメッセージとは別に、レスポンス用のメッセージを用意しましょう。

補足:よりBotらしく

ユーザのアクションを捕捉する方法は他にも色々提供されており、組み合わせれば大体何でも出来る。

外部からのアクセスを許可出来るなら Events API を、そうでないなら RTM API が使いやすい。

参考

Slack Web API https://api.slack.com/web
Interactive messages https://api.slack.com/interactive-messages

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
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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