2017/11/13

Node.js+Botkit+localtunnelでSlack Interactive Messageを試す

ずいぶん前にSlackにInteractive Messageという機能が追加された。この機能はBotやAppからのリプライにリストやボタンを含めて返すもので、Bot/Appの表現の幅が広がった。

ということで、実際にNode.js+Botkitで試してみようと思う!

ただ、実装しようとググっても、BotkitのExampleを使ってもどうにも動かなくてかなりハマったので、なるべく丁寧に解説しようと思う。


今回使う環境は以下のとおり。
  • Node.js@8.x
  • Botkit@0.6.6
    • Botアプリケーションをつくるためのフレームワーク
  • localtunnel@1.8.3
    • httpsの外部連携(OAuthやリクエストを受け取る)するためにローカル環境を外部に公開するツール
    • HerokuやngrokなどでもOK



Slack Appを作成する


Interactive Messageを使うためにはSlack Appとして作成する必要がある。
api.slack.comからSlack Appを登録する。

右上の[Create New App]ボタンをクリックし、Slack Appを作成する。


[App Name]にはSlack Appの名前(後で[Display Infomation]から変更可能)と、開発用のワークスペースを入力し、[Create App]ボタンをクリックする。


これでSlack Appの枠は作れた。
次はSlack Appの設定について。



Slack Appの設定をする


Interactive Componentの有効化やBot Userの作成、Permissionの設定を行う。


まずは、Interactive Messageを使えるようにするために、Interactive Componentsを有効化する。
Slack App作成後に表示される[Basic Infomation]ページから、[Add features and functionality]のアコーディオンメニューを展開し、[Interactive Components]をクリックする。(またはサイドメニューの[Features]>[Interactive Components]から飛ぶ)

[Interactive Components]ページで、[Enable Interactive Components]ボタンをクリックして有効化する。


すると以下のように[Request URL]フィールドが表示されるので、https://{YOUR DOMAIN}/slack/receiveと入力する。
Slackでボタンを押したり、リストから選択したときにこのエンドポイントに対してリクエストが飛ぶ。



次にBot Userを作成する。
ここで作成したBotを自分のワークスペースにジョインさせることでメッセージを送ることができる。

また[Basic Infomation]のページに戻って[Bots]をクリックするか、サイドメニューの[Features]>[Bot Users]をクリックする。


[Bot User]のページで、[Add a Bot User]ボタンをクリックしてBot Userを追加する。


すると以下のように[Display name]、[Default username]などのフィールドが表示される。
ここでSlack上で見えるBot Userの名前を設定する。(変更可能)

また[Always Show My Bot as Online]トグルボタンは、[On]にすることで常時Onlineで見えるようになる。今回はRTM APIを使って、アプリケーション起動時だけOnlineにしたいので[Off]のままにしておく。

OfflineでもBotアプリケーションが起動していればやり取りは可能なので、自分のわかりやすい設定で良いと思う。


次にPermissionの設定を行う。
また[Basic Infomation]のページに戻って[Permissions]をクリックするか、サイドメニューの[Features]>[OAuth & Permissions]をクリックする。

OAuth認証をするために[Redirect URLs]を設定するため、[Add a new Redirect URL]ボタンをクリックする。

すると以下のように[Redirect URLs]フィールドが表示されるので、https://{YOUR DOMAIN}/oauthと入力する。OAuth認証するときにこのエンドポイントに対してアクセスすることになる。

最後に、今回作成したSlack Appを自分のワークスペースにインストールする。
また[Basic Infomation]のページに戻って[Install your app to your workspace]のアコーディオンメニューを展開し、[Install App to Workspace]ボタンをクリックする。

するとBot Userを追加する旨の説明が表示されるので、問題なければ[Authorize]ボタンをクリックして、インストールを完了させる。

これでSlack Appの設定は完了。
あとはClient IDやClient Secret、Access Tokenをメモって置いて、Slack Appを実行するときに設定すればOK。



Slack Appを実装する


まず必要なライブラリをインストールする。
$ npm i -D botkit localtunnel

# yarnの場合
$ yarn add botkit localtunnel

localtunnelは前述のとおり、あってもなくても良い。今回は「無料」でかつ「サブドメイン指定」ができるサービスということで、localtunnelを利用する。


次に、Node.jsでSlack Appを実装していく。
実装するのにClient ID、Client Secretが必要なのであらかじめ取得しておく。(Bot Access Tokenは任意だけど、RTM API使うなら必要)

Client IDとClient Secretは、[Basic Infomation]ページの[App Credentials]から取得できる。

Bot Access Tokenは、サイドメニューの[OAuth & Permissions]をクリックし表示されたページの[Token for Your Workspace]から取得できる。
ちなみにBotで使う場合は[Bot User OAuth Access Token]フィールドの「xoxb-」から始まるTokenを使用する。

const LocalTunnel = require('localtunnel');
const Botkit = require('botkit');

/**
 * ローカル環境(10443ポート)を公開する
 * https://{YOUR SUBDOMAIN}.localtunnel.me
 */
const tunnel = LocalTunnel(10443, {
    subdomain: '{YOUR SUBDOMAIN}'
}, (err, tunnel) => {
    if (err) {
        throw new Error('ERROR: ' + err);
    }

    console.info('URL is ' + tunnel.url);
});

/**
 * コントロールを作成する
 * Slack API > App Credentialsから取得
 */
const controller = Botkit.slackbot({
    debug: true,
    json_file_store: './db_bot'
}).configureSlackApp({
    clientId: '{YOUR CLIENT ID}',
    clientSecret: '{YOUR CLIENT SECRET}}',
    scopes: ['bot']
});

/**
 * Endpointsを作成する
 */
controller.setupWebserver(10443, (err, webserver) => {
    // https://example.com/oauth のエンドポイントを作成する
    controller.createOauthEndpoints(webserver, (err, req, res) => {
        if (err) {
            res.status(500).send('ERROR: ' + err);
        } else {
            res.send('SUCCESS');
        }
    });

    // https://example.com/slack/receive のエンドポイントを作成する
    // Interacticve Messageのリプライがこのエンドポイントにくる
    controller.createWebhookEndpoints(webserver);
});


/**
 * BotをOnlineにする
 * ※ 1回でもエラーになるとOfflineになるので堕ちるたびにspawnするか、`Always Show My Bot as Online`をONにする
 */
const bot = controller.spawn({
    token: '{YOUR BOT ACCESS TOKEN(ex. xoxb-1234...)}'
});

bot.startRTM((err, bot, payload) => {
    if (err) {
        throw new Error('Could not connect to Slack:' + err);
    }
});


/**
 * RTM APIのイベント
 * ないと「Error: Stale RTM connection, closing RTM」というエラーになる
 */
controller.on('rtm_open', (bot, message) => {
    console.info('** The RTM api just connected!');
});
controller.on('rtm_close', (bot, message) => {
    console.info('** The RTM api just closed');
});


/**
 * Interactive Messageのコールバックイベント
 */
controller.on('interactive_message_callback', (bot, message) => {
    // callback_idで振り分ける
    if (message.callback_id === 'how_do_you_feeling') {
        const value = message.actions[0].value;
        const text = value === 'good' ? `It's good!!` : `It's bad.`;
    
        // メッセージを書き換えてリプライを送る
        bot.replyInteractive(message, {
            text: text
        }, err => {
            if (err) {
                console.error('ERROR: ' + err);
            }
        });

    } else {
        console.error('Could not find callback_id: ' + message.callback_id);
    }
});


/**
 * @botname heyで呼び出されるイベント
 */
controller.hears('hey', ['direct_message', 'direct_mention', 'mention'], (bot, message) => {
    // チームIDを保持するために登録する
    // チームIDを保存しておかないと「error: Could not load team while processing webhook:  Error: could not find team {チーム名}」というエラーになる
    controller.storage.teams.save({
        id: message.team,
        // [TypeError: Cannot read property 'user_id' of undefined]というエラーになるためbot情報も登録する
        bot: {
            user_id: bot.identity.id,
            name: bot.identity.name
        }
    }, (err, id) => {
        if (err) {
            throw new Error('ERROR: ' + err);
        }
    });

    // 'hey'に対する反応(リプライ)
    bot.reply(message, {
        attachments: [
            {
                title: 'How do you feeling?',
                callback_id: 'how_do_you_feeling',
                attachment_type: 'default',
                actions: [
                    {
                        name: 'good',
                        text: 'Good',
                        value: 'good',
                        type: 'button'
                    },
                    {
                        name: 'bad',
                        text: 'Bad',
                        value: 'bad',
                        type: 'button'
                    }
                ]
            }
        ]
    });
});


ポートはテキトーに「10443」にしているが、好きなもので大丈夫。
ただし、Macで3桁のポート番号を使おうとすると、パーミッションうんぬんでsudoコマンドを使わないと実装できない(記憶が曖昧だけど…。)


Interactive Messageを使うには、Slack Appをどこかにホスティングしなければならない(https://example.com/slack/receiveのリクエストを受け取るため)
Herokuがよく使われるがいちいちデプロイしなくちゃいけないので、今回はlocaltunnelを使用している。

ローカル環境を外部に公開することになるのでセキュリティ的にちょっと怖い。そのため、開発が終わったらlocaltunnel部分は削除してHerokuなどにホスティングするのが良いだろう。


あとはコード内に何に使うものかなど詳しいコメントを書いているので、そちらを参照してほしい。
ちなみに今回はButtonだけを使ったが、Listを使いたい場合はtype: 'select'にすることで実現できる。



このコードを実際に実行すると……

数のように今回作成したSlack AppがOnlineになり……

どこかのチャンネルにinviteしてからメンションを送ると、以下のようなやり取りができる。



参考サイト






以上

written by @bc_rikko

0 件のコメント :

コメントを投稿