Slashコマンドでダイアログを表示する【Laravel】

Slack
two 3d humans give their hand for handshake
この記事は約18分で読めます。

今回はSlackのSlashコマンドを使って、ダイアログを表示する方法をまとめていきます!

今回の方法は少し古い方法になるので、新しい方法はまた今度書きます😅

新しい方法の公式記事はこちら↓

https://api.slack.com/block-kit/dialogs-to-modals

今回作るもの

  • Slashコマンドを実行して、休暇申請をするダイアログを表示する
  • ダイアログの入力を受け取ってパラメータを返す

という機能を組み合わせて休暇申請を行ってみたいと思います。

実装方法

ダイアログを表示して、結果を受け取る機能を作るには以下の流れで進めていきます

Step1. Slashコマンドからダイアログを表示する

  • Slashコマンドの登録
  • Slashコマンドの受け口の作成
  • ダイアログの生成

Step2. ダイアログからのパラメータをサーバーで受け取り処理を行う

  • InteractiveComponentsの登録
  • ダイアログからのリクエストの受け口の作成
  • パラメータを受け取り、完了メッセージを投稿する

Step1. Slashコマンドからダイアログを表示する

Slashコマンドの登録

Slashコマンドの登録は過去の記事に載せています。

今_回は複雑な処理が含まれるため、Integromatは使わずコーディングを行います。

以下のページを参考にSlashコマンドを作るまで行ったら次に進みます。

Slashコマンドの受け口の作成(dialog.open)

今回はLaravelで実装を行います。

ダイアログを開く処理はSlack内で完結しているような印象を持ってしまいますが、外部サーバーとの連携が必須です。

GCPやHerokuなどの、外部からアクセスを許可されている環境を用意してアプリケーションを用意してください。

参考のコードはこちら

// routes/api.php
Route::middleware('slack.verification')->post('/yukyu', 'SlackController@yukyuDialog')->name('yukyuDialog');
// SlackController.php
class SlackController extends Controller
{
    const BOT_TOKEN = 'xoxb-*******';

    /**
     * /yukyu のSlashコマンドを実行すると呼ばれる場所
     *
     * @param Request $request
     * @return Response
     */
    function yukyuDialog (Request $request) {
        $url = 'https://slack.com/api/dialog.open';
        $token = self::BOT_TOKEN;
        $dialog = $this->getYukyuDialog();
        $trigger_id = $request->input('trigger_id');

        $params = [
            'token' =>  $token,
            'dialog' => \GuzzleHttp\json_encode($dialog),
            'trigger_id' => $trigger_id
        ];

        $client = new Client();

        $response = $client->request(
            'POST',
            $url, // URLを設定
            ['query' => $params] // パラメーターがあれば設定
        );

// この書き方でもOK(JSON形式で送る)
//        $headers = [
//             'Content-type' => 'application/json',
//             'Authorization'  =>  'Bearer ' . $token
//        ];
//        $response = $client->request(
//            'POST',
//            $url, // URLを設定
//            [
//                'headers' => $headers,
//                'json' => $params
//            ] // パラメーターがあれば設定
//        );

        $log = \GuzzleHttp\json_decode($response->getBody()->getContents(), true);
        Log::info(print_r($log, true));

        return response('',200);
    }

    /**
     * ダイアログのテンプレートを作る
     *
     * @return array
     */
    function getYukyuDialog () {
        return [
            "callback_id" => "yukyu",
            "title" => "休暇申請ダイアログ",
            "submit_label" => "送信",
            "state" => "none",
            "elements" => [
                [
                    "type" => "select",
                    "label" => "休暇種類",
                    "name" =>  "holiday_type",
                    "options" => [
                        [
                            "label" => "有給休暇",
                            "value" => "type-full"
                        ],
                        [
                            "label" => "午前休暇",
                            "value" => "type-am"
                        ],
                        [
                            "label" => "午後休暇",
                            "value" => "type-pm"
                        ]
                    ],
                ],
                [
                    "type" => "text",
                    "label" => "休暇予定日",
                    "name" => "date",
                    "placeholder" => "2020-01-01",
                ],
                [
                    "type" => "textarea",
                    "label" => "休暇理由",
                    "name" =>  "reason",
                    "hint" => "理由を記入してください",
                ]
            ],
        ];
    }
}

dialog.openを使う際に気をつけるポイント

https://api.slack.com/methods/dialog.open

dialogを表示するAPIの解説の公式のページはこちらですが、必要なパラメータとエラーコードの説明となっています。

dialog.openを使う際、気をつけるポイントはたった1つです。

dialogのパラメータはJSON形式にエンコードすることです

$params = [
    'token' =>  $token,
    'dialog' => \GuzzleHttp\json_encode($dialog),
    'trigger_id' => $trigger_id
];

今回はGuzzleを使用していますが、エンコードをする方法は何でもOKです。

よくある失敗としては配列のままパラメータをセットして送信してしまうため気をつけましょう。

dialogの生成

dialogパラメータですが、公式の解説ページは以下のあります。

https://api.slack.com/dialogs

作り方が少し複雑なので、getYukyuDialog を元にポイントだけまとめます

$dialog = [
    "callback_id" => "yukyu",
    "title" => "休暇申請ダイアログ",
    "submit_label" => "送信",
    "state" => "none",
    "elements" => [
        [
            "type" => "select",
            "label" => "休暇種類",
            "name" =>  "holiday_type",
            "options" => [
                [
                    "label" => "有給休暇",
                    "value" => "type-full"
                ],
                [
                    "label" => "午前休暇",
                    "value" => "type-am"
                ],
                [
                    "label" => "午後休暇",
                    "value" => "type-pm"
                ]
            ],
        ],
        [
            "type" => "text",
            "label" => "休暇予定日",
            "name" => "date",
            "placeholder" => "2020-01-01",
        ],
        [
            "type" => "textarea",
            "label" => "休暇理由",
            "name" =>  "reason",
            "hint" => "理由を記入してください",
        ]
    ],
];
  • callback_id: ダイアログを使用した時にどのダイアログかを識別するID
  • title, submit_label: タイトルや送信ボタンで表示される文字列
  • notify_on_cancel: キャンセルした時もサーバーに通知を出すか(任意)
  • state: 状態…というよりも自由記述欄。3000文字まで使える(ただしおそらく半角での話)
  • elements: ダイアログの中身となる部分

ダイアログで入力方法に使用できる形式はたった3種類しかありません。

それぞれ texttextareaselect で、今回作った機能で全て使用しているので参考にしてください。

デフォルトで全て必須項目とされていますが、オプション設定にすることもでき、そういった詳細の設定方法は上記のリンクより確認してください。

ここまで実装したらサーバーにアップロードして、ダイアログが表示されるかの確認してみましょう!

Step2. ダイアログからのパラメータをサーバーで受け取り処理を行う

次にダイアログで送信されたパラメータを受け取る部分の実装を行っていきます。

InteractiveComponentsの登録

IntenteractiveComponentsという機能を使うため↑の Interactive Componentsを設定する を参考にSlackAppsを設定します。

CallBackIdなどは設定は不要で、

  • InteractivityがONになっていること
  • WebhookURLが設定されていること

が満たされていれば対応完了です。

ダイアログからのリクエストの受け口の作成 

リクエストの受け口は以下のような形にしています。

// routes/api.php
Route::middleware('slack.verification')->post('/interactiveMessage', 'SlackController@interactiveMessage')->name('interactiveMessage');
// SlackController.php
class SlackController extends Controller
{
    /**
     * ダイアログから送られてくるリクエストを受け取る
     * 他のリクエストからの受け口になるので callback_id などで場合分けする必要がある
     *
     * @param Request $request
     * @return Response
     */
    function interactiveMessage (Request $request) {
        // 早期レスポンス
        response('',200)->send();
        
        $payload = $request->input('payload');
        $postData = \GuzzleHttp\json_decode($payload, true);

        // 今回の対応以外にinteractiveMessageを使用している場合、
        // callback_idを使って処理を分岐させてください

     //////////////////////
        // 自由に処理を記述する //
        //////////////////////

        $text = '以下の内容で受け付けました!';
        $submission = $postData['submission'];
        $dialog = $this->getYukyuDialog(); // ダイアログ生成の時に使用したもの
        $attachments = $this->makeAttachments($dialog, $submission);

        $client = new Client();
        $params = [
            'text' => $text,
            'attachments' => \GuzzleHttp\json_encode($attachments)
        ];

        $response = $client->request(
            'POST',
            $postData['response_url'],
            ['json' => $params] // jsonにする
        );
        
        $log = \GuzzleHttp\json_decode($response->getBody()->getContents(), true);
        Log::info(print_r($log, true));

        return response('',200);
    }

    /**
     * 回答を受け取った結果をまとめる
     *
     * @param $dialog
     * @param $submission
     * @return array
     */
    function makeAttachments ($dialog, $submission) {
        $elements = $dialog['elements'];
        $attachments = [];

        $text =  "";
        foreach ($submission as $key => $value) {
            foreach ($elements as $element) {
                if ($element['name'] === $key) {
                    $label = $element['label'];
                    $answer = '';
                    if (isset($element['options'])) {
                        $options = $element['options'];
                        foreach ($options as $option) {
                            if ($option['value'] === $value) {
                                $answer = $option['label'];
                            }
                        }
                    } else {
                        $answer = $value;
                    }
                    $text .= $label . ' : ' . $answer. "\n";
                }
            }
        }

        $attachments[] = [
            'text' => $text,
        ];

        return $attachment;
    }
}

InteractiveMessageを受け取る際に気をつけるポイント

まず1つ目はresponse時間の制限です。

セキュリティ上、InteractiveMessageを受け取った後、3秒以内にresponseを返さないとエラーが返ってきてしまいます。

DBの更新やAPIの送受信を行っているとあっという間に3秒が経過してしまいます。

そのためLaravelのResponseクラス内のsendを用いて

// 早期レスポンス
response('',200)->send();

でとりあえず返してしまいましょう(値が正しいか確認してからの方が良いかも。。)

レスポンス結果を返す

200 レスポンスを返せばそれでダイアログ結果の受け取り処理は終了することが出来ます。

しかし、それではユーザーは正しくデータが送信されたのかがわかりません。

 $client = new Client();
 $params = [
     'text' => $text,
     'attachments' => \GuzzleHttp\json_encode($attachments)
 ];
 $response = $client->request(
     'POST',
     $postData['response_url'],
     ['json' => $params] // jsonにする
 );

そこでInteractiveMessageのデータに含まれるresponse_urlを使ってメッセージを送ります。

そもそも response_url の中身は

https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX

というSlackのIncomingWebhook形式のURLです。

https://api.slack.com/messaging/webhooks#posting_with_webhooks

Content-TypeをJSONにして送らなければならないのがポイントです。

今回作ったサンプルのようにattachmentsを付けることも出来ますが、Block Kit Builder を使った綺麗な表示も使うことが出来ます。

Slackでのダイアログ表示は難しそうで簡単

今回は

  • Slashコマンドでダイアログを表示すること
  • ダイアログからのデータを受け取る

ことをまとめてみました。

「ダイアログを表示してデータを受け取る」は難しく感じますが、機能を分割することで理解がぐっと楽になると思います。

唯一注意するポイントは「どんな形式でデータを送るか」を理解していることが重要です。

open.dialog は content-type はurlencodedとjsonの2種類が許可されていますが、response_urlの場合はjson形式でないとうまく動かないようです。

そこさえ理解していれば今回のSlashコマンドやダイアログに関連するAPI以外の他のSlackのAPIもすぐに使えるようになると思います!

ここまで読んで頂きありがとうございました!

コメント

タイトルとURLをコピーしました