Edited at

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

今回は、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を設定します。

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は多数あり、わかりにくいです。

ですので、もしかしたら使い方が間違っているかもしれませんので、ご指摘いただけると助かります。

以上