12
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

今更ながらSlackBotを作ってみた

Last updated at Posted at 2019-05-05

今回は、SlackBotを作ってみます。
いまさらながらな感はありますが、Slackアプリをスマホに常時入れているし、LINEと違って、いつでもメッセージを送信できるからです。(LINEの場合好きな時に送るには有料あるいはTrial版である必要があったります)

今回はまずはエコーバックするところまでです。

(補足)
ただし、1点だけ、残念なことがありまして、AWS Lambdaでも同じように動くようにしたかったのですが、現時点では不十分です。SlackからのWebhook呼び出しに対する応答時間の制限が短いためです。うまくやればできるのかもしれませんが、まだAWSの勉強不足のため、また今度にしたいと思います。
ですので、とりあえず、ローカルに立ち上げるSwagger環境を前提とします。

Swagger環境は、以下の投稿を参考にしてください。
 SwaggerでLambdaのデバッグ環境を作る(1)

以下、参考情報です。

slack api
 https://api.slack.com/

Node Slack SDK
 https://github.com/slackapi/node-slack-sdk

以下、投稿の続編です。
 今更ながらSlackBotを作ってみた:Slach Commands
 今更ながらSlackBotを作ってみた:Interactive Components
 今更ながらSlackBotを作ってみた:ダイアログ
 今更ながらSlackBotを作ってみた:Incoming Webhooks

#ワークスペースを作成する

まずは、まだワークスペースを作成していない場合は作成しておきます。

slack
 https://slack.com/intl/ja-jp/

image.png

右上のワークスペースボタンから、「ワークスペースを作成する」を選択

image.png

ご自身のメールアドレスを入力して、Next→ を押下

image.png

そうすると、メールアドレスに、3+3の確認コードの数字が送られてきますので、入力します。

image.png

たとえば、TestTream とでもしておきます。

image.png

TestProjectとでもしておきます。

image.png

あとでも招待できるので、skip for now します。

image.png

できあがりました。
「新規登録する」ボタンを押下します。

image.png

表示させたい自分の名前と、次にログインするときのパスワードを入力します。

image.png

SlackのURLは、自動的に割り振られていますが、以降使うので、(他人と重ならなければ)変えることができます。

image.png

招待は後回しに、「完了」ボタンを押下で完了です。

#Botを作成する

さっそくBotを作成していきます。
まずは、slack apiを開きます。
 https://api.slack.com/

image.png

次にページの右上にある「Your Apps」をクリックします。

image.png

そして、「Create New App」ボタンを押下します。
AppNameには適当に「TestBot」とでもしておきます。
Development Slack Workspaceには、先ほど作成したワークスペースを選択します。

image.png

作成が完了し、以下のページが表示されます。Setting-Basic Informationです。
以降このページを中心に設定を進めます。

image.png

作ったAppをBotにするため、「Bots」を選択します。

image.png

「Add a Bot User」ボタンを押下します。

image.png

DisplayNameに「TestBot」、Default usernameには「testbot」とでもしておきます。
Always Show My Bots as Onlineは、常時サーバを立ち上げるので、Onにしておきましょう。
最後に「Add Bot User」ボタンを押下します。

今のままだと何もできないので、メッセージ送信できるようにします。

Setting-Basic Information に戻ります。
Add features and functionality を選択します。
「Permmissioins」を選択します。

image.png

ScopesのSelect Permission Scopesのところで、以下を選択します。
「Send message as user」

最後に、「Save Changes」ボタンを押下します。
同じページの最初に戻って、「Install App to Workspace」を押下します。

image.png

そうすると、許可を確認する画面が表示されます。「許可する」を押下します。

image.png

これで、ワークスペース「TestTeam」に「TestBot」が追加されました。

#WebAPIの種類

Slackにはメッセージ通信するAPIとしていくつかの種類があります。
以下、私なりの理解です。

  • Incoming Webhook
     いつでも好きな時に、特定チャネルまたは特定ユーザにメッセージを送信します。(受信ではない)

  • Slash Command
     「/」(スラッシュ)に続けて入力するコマンドを作ります。

  • RTM(Real Time Messaging) API
     WebSocketを使って、リアルタイムにイベントを受信したりメッセージを送信します。

  • Event API
     メッセージ投稿やメンバー追加などのイベント発生時に通知を受け取り、メッセージで応答します。

  • Interactive Components
     上記のメッセージ送受信に加えて、ボタンや選択肢などのリッチなUIを提供します。(ダイアログもこれに該当?)

今回、次回以降の記事で、ひとつひとつ説明していきます。(RTM APIを除く)

#エコーバックするBotにする

まずは、一番単純なエコーバックするBotを作成しましょう。
先に、エコーバックするためのサーバを立ち上げます。

その前に、メモっておく必要がある文字列が2つあります。
まずは、Settings-Basic Informationの、App Credentialsにある「Verification Token」です。

image.png

次に、Features - OAuth&Permissions にある「Bot User OAuth Access Token」です。

image.png

それでは、サーバの立ち上げを始めます。

以下のnpmモジュールを使います。
・@slack/web-api
・node-fetch
・dotenv

以下の2つのエンドポイントを作成します。
この2つの違いは、入出力パラメータのフォーマットの違いです。メッセージを送受信する方法はいくつかあるのですが、それぞれによって微妙にフォーマットが違うのです。

swagger.yaml

  /slack-testbot:
    post:
      x-swagger-router-controller: routing
      operationId: slack-testbot
      produces:
        - text/plain
      parameters:
        - in: body
          name: body
          schema:
            type: object
      responses:
        200:
          description: Success
          schema:
            type: string

  /slack-testbot-cmd:
    post:
      x-swagger-router-controller: routing
      operationId: slack-testbot-cmd
      consumes:
        - application/x-www-form-urlencoded
      produces:
        - text/plain
      parameters:
        - in: body
          name: body
          schema:
            type: object
      responses:
        200:
          description: Success
          schema:
            type: string
・・・

エンドポイントから実装へのルーティングです。

controllers\routing.js
・・・
/* 関数を以下に追加する */
const func_table = {
//  "test-func" : require('./test_func').handler,
  "slack-testbot" : require('./slack_testbot').handler,
  "slack-testbot-cmd" : require('./slack_testbot').handler,
};
・・・

エンドポイントに対応する実装です。

controllers\slack_testbot\index.js
'use strict';

const SLACK_VERIFICATION_TOKEN = process.env.SLACK_VERIFICATION_TOKEN;
const SLACK_ACCESS_TOKEN = process.env.SLACK_ACCESS_TOKEN;

const HELPER_BASE = process.env.HELPER_BASE || '../../helpers/';
const SlackUtils = require(HELPER_BASE + 'slack-utils');
const app = new SlackUtils(SLACK_VERIFICATION_TOKEN, SLACK_ACCESS_TOKEN);

exports.handler = app.lambda();

Slackボットのためのユーティリティです。

controllers\helpers\slack-utils.js
'use strict';

const fetch = require('node-fetch');
const { WebClient } = require('@slack/web-api');

class SlackUtils{
    constructor(verification_token, access_token){
        this.web = new WebClient(access_token);
        this.verification_token = verification_token;
        this.map = new Map();
    }

    async initialize(){
        if( this.my_bot_id )
            return Promise.resolve();

        return this.web.auth.test()
        .then(result =>{
            this.my_user_id = result.user_id;
            console.log('userid=' + this.my_user_id);
            return this.web.users.info({user: result.user_id})
        })
        .then(result =>{
            this.my_app_id = result.user.profile.api_app_id;
            console.log('appid=' + this.my_app_id);
            return this.web.bots.info({bot: result.user.profile.bot_id})
        })
        .then(result =>{
            this.my_bot_id = result.bot.id;
            console.log('botid=' + this.my_bot_id);
        })
        .catch(error =>{
            console.log(error);
        });
    }

    postMessage(message){
        return this.web.chat.postMessage(message);
    }

    dialogOpen(options){
        return this.web.dialog.open(options);
    }

    incomingMessage(webhook_url, body){
        return this.responseMessage(webhook_url, body);        
    }

    responseMessage(response_url, body){
        return fetch(response_url, {
            method : 'POST',
            body : JSON.stringify(body),
            headers: { "Content-Type" : "application/json; charset=utf-8" } 
        })
        .then((response) => {
            if( response.status != 200 )
                throw 'status is not 200';
            return response.text();
        });
    }

    ackResponse(){
        var response = {
            statusCode: 200,
            headers: { "Content-Type": "text/plain" },
            body: ""
        };
        return response;
    }

    message(handler){
        this.map.set('message', handler);
    }

    action(handler){
        this.map.set('action', handler);
    }

    response(handler){
        this.map.set('response', handler);
    }

    command(handler){
        this.map.set('command', handler);
    }

    submission(handler){
        this.map.set('submission', handler);
    }

    cancellation(handler){
        this.map.set('cancellation', handler);
    }

    lambda(){
        return async (event, context, callback) => {
            await this.initialize();
            
            var body = JSON.parse(event.body);
            if( body.payload )
                body = JSON.parse(body.payload);
            
            if( body.token != this.verification_token )
                return;
            
            if( body.type == 'url_verification' ){
                var response = {
                    statusCode: 200,
                    headers: { "Content-Type": "text/plain" },
                    body: body.challenge
                };
                return response;                
            }

            console.log('body.user_id', body.user_id);
            if( body.event )
                console.log('body.event.user', body.event.user);
            if( body.user )
                console.log('body.user.id', body.user.id);
            console.log(JSON.stringify(body));

            if( body.user_id == this.my_user_id || (body.event && body.event.user == this.my_user_id ) )
                return this.ackResponse();

            var type = 'message';
            if( body.event && body.event.message )
                type = 'response';
            if( body.command )
                type = 'command';
            if( body.type == 'block_actions' )
                type = "action";
            if( body.type == 'dialog_submission')
                type = "submission";
            if( body.type == 'dialog_cancellation')
                type = "cancellation";

            var handler = this.map.get(type);
            if( handler )
                handler(body, this.web);
            else
                console.log(type + ' is not defined.');

            callback(null, this.ackResponse());
        }
    }
};

module.exports = SlackUtils;

環境変数に、各人のVerification TokenとBot User OAuth Access Tokenを設定します。

.env
SLACK_VERIFICATION_TOKEN="【Verification Token】"
SLACK_ACCESS_TOKEN="【Bot User OAuth Access Token】"

さっそく立ち上げておきます。

エコーバックのトリガとしては、Event APIを使っていきます。
以下の手順で有効にします。

・Features-Event Subscriptions を選択し、Enable EventsをOnにします。
・そして、Request URLのところに、先ほど立ち上げたサーバのエンドポイントをフルパスで指定します。/slack-testbotの方です。

image.png

無事に、「Verified」となりましたでしょうか?slack-utils.jsがVerifyに必要な処理をしています。

次に、Subscribe to Bot Events に、「message.im」を選択し、最後に右下の「Save Changes」を押下します。
これにより、メッセージを受信したときに、Eventが発火されるようになります。
そこら辺のハンドリングは、slack-utils.jsがうまくやってくれるようにしておきました。

発火されたEventは、app.message()に指定したコールバックを呼び出します。
したがって、このコールバック関数内に、処理したい内容(今回はエコーバック)を記述します。
index.jsを少し追記します。

controllers\slack_testbot\index.js
'use strict';

const SLACK_VERIFICATION_TOKEN = process.env.SLACK_VERIFICATION_TOKEN;
const SLACK_ACCESS_TOKEN = process.env.SLACK_ACCESS_TOKEN;

const HELPER_BASE = process.env.HELPER_BASE || '../../helpers/';
const SlackUtils = require(HELPER_BASE + 'slack-utils');
const app = new SlackUtils(SLACK_VERIFICATION_TOKEN, SLACK_ACCESS_TOKEN);

// 追記ここから
app.message(async (body, web) =>{
    if(body.event.text)
        app.postMessage({ channel: body.event.channel, text: body.event.text + " です。", as_user: true });
});
// 追記ここまで

exports.handler = app.lambda();

ちょっと補足します。

受信しているメッセージは以下を参考にしてください。

slack api:Event API:Receiving Events
 https://api.slack.com/events-api#receiving_events
slack api:message.im
 https://api.slack.com/events/message.im

返信している内容は以下を参考にしてください。

slack api:Reference: Message payloads
 https://api.slack.com/reference/messaging/payload

#動作確認

スマホで動作確認してみましょう。
以降は、AndroidのSlackでの画面です。

左上隅をタッチします。

image.png

ワークスペースを追加 を選択します

image.png

ワークスペース「TestTeam」を作った人と同じであれば、そのワークスペースが表れていると思います。
サインインを押下します。

image.png

さっきのパスワードを入力します。

image.png

ログインできました。
左上隅をタッチし、TestBotを選択します。

image.png

image.png

TestBotが開きましたので、「あかさたな」と投稿してみましょう。

image.png

「あかさたな です」と返ってくれば成功です。

終わりに

今回はここまでとします。
次回から、ボタンや選択肢を表示してインタラクションを高めたり、ダイアログボックスを表示したり、スラッシュコマンドを追加したりしてみます。

SlackのAPIは多数あり、わかりにくいです。
ですので、もしかしたら使い方が間違っているかもしれませんので、ご指摘いただけると助かります。

以上

12
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?