LoginSignup
34
23

More than 3 years have passed since last update.

Slack Appに挑戦(3) - Event APIとBotUser

Last updated at Posted at 2019-10-05

「SlackBotを真面目に学ぶ」の第3回
今回は、EventAPIを使ってSlackからのイベントを受け取る方法です。

1回目 https://qiita.com/kanaxx/items/a12a523ca3143b5822b8
2回目 https://qiita.com/kanaxx/items/f2025cbc961de2c6bc5b

はじめに

前回までで、SlackBotから、Incoming Webhookを使って一方的にメッセージを送りつけたり、スラッシュコマンドに反応して仕事をすることはできるようになりました。まだ、@で呼びかけてもらうことはできません。今回はEvent APIを受け取る仕組みを実装して、@で呼びかけてもらえるようになります。

環境

  • Heroku
    • PHP 7.3
    • Laravel 6.0.3
    • Clear DB
  • Windows
    • PHP 7.2.11
    • Laravel 6.0.3
    • Maria DB Ver 15.1

いつもどおり、基本的にはWindowsでの作業です。

前回作ったSigning SecretによるRequest Verificationが適用済みの環境でのサンプルです。今回公開するソースコードにはVerificationに関するものは含まれておりません。インターネット上のサービスを公開するときには、各自の判断のもと自己責任でお願いします。

EventAPIとは何か?

EventAPIは、Slack内で起きたイベントをトリガーに、発生した内容をApp指定のURLに送る仕組みです。インターネット経由でコールバックを受ける仕組みです。Slackが送信元になり、HTTPSで待ち受ける形式です。

公式ドキュメントに書いてあるとおり、こちら側(your server)には以下の動きを期待されています。

  1. A user creates a circumstance that triggers an event subscription to your application
  2. Your server receives a payload of JSON describing that event
  3. Your server acknowledges receipt of the event
  4. Your business logic decides what to do about that event
  5. Your server carries out that decision

お作法

SlackからEventを受け取ったら、3秒以内に2xx系のレスポンスを返却する必要があります。これはSlash Commandと同じです。時間のかかる仕事はイベントの処理と分離してレスポンスだけ先に返すような設計が必要です。なお、3秒以内にレスポンスがない場合には、エラーとして扱われ、3回までイベントが再送されてきます。
あと、Slashコマンドと違い、EventAPIではメッセージを送り返すことができません(response_urlが含まれていないので)。EventAPIで何か仕事をしたあとに返却したい場合には、第1回でやったIncoming Webhook(Channel固定のもの)に返すか、WEBAPIを使ってメッセージの送信をします。

やってみよう

Event APIのエンドポイントを作成

Event APIのエンドポイントと認められるには、最低限、以下の仕様を満たす必要があります。

SlackからのHTTP(POST)を受け取り、パラメータに含まれているchallengeの値をそのまま返す。

まずは、最低限の要件を満たすLaravelのコントローラを作ります。

Controller

artisanコマンドで作って、eventメソッドを実装します。

php artisan make:controller SlackEventController
Controller created successfully.
app/Http/Controller/SlackEventController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Log;

class SlackEventController extends Controller
{
    public function event(Request $request){
        //challengeを返す実装
        if( $request->input('type') == 'url_verification'){
            return $request->input('challenge');
        }

        return '';
    }

}

routeの設定

routes/api.php
Route::any('/event', 'SlackEventController@event');

ここまでの成果をHerokuにデプロイします。以下のURLにアクセスして有効であることを確認しておきます。

{"ok":false,"error":"signature error"}

Event APIの設定

次は、このURLに対してSlackからEventを飛ばす設定をします。

Event APIのエンドポイントの設定

AppのBasic Informationから、Event Subscriptionをクリックします。

image.png

Enable EventsのスイッチをONに切り替えて、Request URLにエンドポイントのURLを入れます。URLを入力するとすぐにSlackからアクセスが発生し、URLが有効かどうか判定されます。Slackが送ったchallengeの値を返していない場合には、下のようになり有効になりません。
image.png

有効な場合には、verifiedという緑チェックがつきます。
image.png

これで、Slack内で発生したイベントをHerokuで受け取ることができます。

受信するイベントの設定

Appで受け取るイベントを決めます(Subscribeと呼びます)。ここで設定したイベントが発生した場合だけ、SlackからRequest URLにリクエストが送られます。イベントの送信は1時間あたり30000回という制限値がありますので、必要なものだけを設定しましょう。

Rate limiting
We don't want to flood your servers with events it can't handle.
Event deliveries currently max out at 30,000 per workspace per 60 minutes. If your app would receive more than one workspace's 30,000 events in a 60 minute window, you'll receive app_rate_limited events describing the conditions every minute.
引用
https://api.slack.com/events-api#rate_limiting

実際にサブスクライブするときは、Add Wordspace Eventボタンをクリックしてイベント情報をセットするだけです。
image.png

今回は、reaction_addedだけを設定しました。BotがいるSlackワークスペース内のどこかでリアクションが発生するとEventが飛んできます。

いざ実行

Slack内でreaction_addedイベントが発生するように、リアクションを付けます。どこでも大丈夫です。今回は#randomチャネルでリアクションを付けます。
image.png

Event APIのエンドポイントに設定した、SlackEventControllerのeventメソッドが呼ばれます。現在の実装では、パラメータのログがSlackに飛んでくるだけです。
image.png

届きましたね。これで、Eventのデータ構造がわかります。
requestの:one:event.typeが発生したイベントの種類で、:two: event.reactionがリアクションしたアイコンの種類ですね。

イベントからメッセージを返す

今のままだと、イベントをひたすら受け止めるだけなので、イベントに反応してみます。Slash Commandは呼びかけに返すことができるのですが、EventAPIではできないので、普通にIncoming Webhookで受け取ったイベントに反応してみます。

Webhookの設定

第一回でやった内容と同じく、Incoming Webhookを設定します。

image.png

発行されたURLを覚えておいてください。

Controllerを修正

SlackEventControllerのeventメソッドが、typeがurl_verification にしか対応していなかったので、event_callbackの分岐点を作り、event.typeの内容によってやることを変えました。
その中で、WebhookのHTTPSリクエストを発行するためGuzzileクライアントを作り、メッセージを送っています。

app/Http/Controller/SlackEventController.php
    public function event(Request $request){
        //challengeを返す実装
        if( $request->input('type') == 'url_verification'){
            return $request->input('challenge');
        }

        \Log::channel('slack')->info($request->all());

        if( $request->input('type') == 'event_callback'){
            if( $request->input('event.type') == 'reaction_added'){

                $emoji = $request->input('event.reaction');
                $user = $request->input('event.user', 'ななし');
                $channel = $request->input('event.item.channel');

                try {
                    //変更必要
                    $webhookToRandom = 'https://hooks.slack.com/services/xxx/yyy/zzz';
                    $guzzile = new \GuzzleHttp\Client();
                    $option = [];
                    $option['json']=[
                        'text'=> "$user さんが $channel にリアクションしました!" . ":$emoji:",
                    ];
                    $response = $guzzile->post($webhookToRandom, $option);
                    $body = $response->getBody();
                    $data = (String)$body;
                    $code = $response->getStatusCode();

                    \Log::channel('slack')->info('webhook:' . $code . " " . $data);
                } catch ( Exception $ex ) {
                    \Log::channel('slack')->info($ex);
                }

            }
        }
        //返したメッセージは使われない
        return 'ok';
    }

そんなに難しくないですね。
では、Herokuにデプロイします。

さて、実行

#randomチャネルのメッセージに対して、複数のアカウントからリアクションしてみます。
すると、Botのアカウントからメッセージが送られてきます。
image.png

見てのとおり、UserChannelの丈夫は、Slackで扱う内部的なIDで受け渡されます。そのままメッセージにしても伝わりにくいです。この課題は次回に持ち越しです。

@の呼びかけに反応する

次は@でボットに呼びかけたときに反応するやつです

Bot Userを定義

Slack Appには1つまでBot Userを追加できるようになっています。Bot Userは普通のユーザのように振る舞います。@をタイプすると、ヒントが出てきたりします。

左側のBot Userから
2019-10-05_00h58_29.png

Botの表示名とユーザ名を設定します。
2019-10-05_00h59_06.png

Appの横の:heavy_plus_sign:マークから、Bot Userを
image.png

サブスクライブするイベントの変更

Add Bot User Eventからapp_mentionを追加します。
image.png

先ほどのWorkspaceイベントと違うので注意

Controller修正

event_callbackの分岐の下に、app_mentionの分岐を作ります。実装の内容は受けた内容をログに出力するだけです。

app/Http/Controller/SlackEventController.php

    public function event(Request $request){
        //challengeを返す実装
        if( $request->input('type') == 'url_verification'){
            return $request->input('challenge');
        }

        \Log::channel('slack')->info($request->all());

        if( $request->input('type') == 'event_callback'){
            if( $request->input('event.type') == 'reaction_added'){

                //省略

            }else if( $request->input('event.type') == 'app_mention'){
                $user = $request->input('event.user', 'ななし');
                $channel = $request->input('event.channel');
                $text = $request->input('event.text');

                $log = 'メンション来ましたー' . PHP_EOL .
                 'ユーザ:'.$user . PHP_EOL .
                 'チェンネル:'.$channel . PHP_EOL .
                 'メッセージ:'.$text . PHP_EOL ;


                 \Log::info($log);
                 \Log::channel('slack')->info($log);
            }
        }
        return 'ok';
    }

よびかけ実行

ワークスペースにBotUserが追加されていると、@でBotUser候補が出てくるようになります。
image.png

呼びかけてみましょう
image.png

eventメソッドが呼ばれて、Log::channel('slack')->info($request->all());が実行され、ログが届きます。
image.png

Eventのデータ構造を確認します。
:one:のtypeがapp_mentionになっていて、 :two:のtext変数から実際の呼びかけの内容が受け取れます。

ログの確認

ログも確認しておきます。

まずは、\Log::info($log);で送ったログ

2019-10-05T07:36:26.422867+00:00 app[web.1]: [05-Oct-2019 16:36:26 Asia/Tokyo] [2019-10-05 16:36:26] production.INFO: メンション来ましたー
2019-10-05T07:36:26.422959+00:00 app[web.1]: ユーザ:UDX2143CM
2019-10-05T07:36:26.423053+00:00 app[web.1]: チェンネル:CDWNT347M
2019-10-05T07:36:26.423264+00:00 app[web.1]: メッセージ:<@UN9R2L9PV> おーい、おーい。
2019-10-05T07:36:26.423354+00:00 app[web.1]: 聞こえてますかー:robot_face:

textの中にあるユーザ名(今回はBotUserの名前)が<@slackのユーザID>の形で送られてきています。

次に、\Log::channel('slack')->info($log);で送ったログ

image.png

絵文字:robot_face:や@メンション部分<@UN9R2L9PV>が、人間の目で読める形に変換されて送られてきます。

まとめ

やっとBotらしくなってきました。ただ、この3回分の成果では過去ログを探して何かやることはできないんですね。なので、次回はWebAPIとUserToken, BotTokenあたりをやってみます。

参考URL

Slack Event API
https://api.slack.com/events-api

Slack APIでbotを作る7: Events API編
https://www.dkrk-blog.net/slack/slack_api07

34
23
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
34
23