インタラクティブなSlackAPP開発入門 ーUI編ー

2022.08.24

どうも新卒3期生のみなみです。

インターンの時にSlackAPPを作っていたのですが、その当時は情報がまとまってなかったので整理してみました。

想定読者

  • Slack API、Incoming Webhooksなどを使ってSlackに通知させてことがあるけど、インタラクティブなSlackAPPは作ったことがない人

出来るようになること

  • Slack APP開発の流れが掴める
  • モーダル、ホームタブの使い方がなんとなくわかる
  • Block kit Builderを使っていい感じのUIをデザインできる

構成図

サーバーレスでシンプルに作りたい場合はこのような構成がベストでしょう。
画面遷移時にデータを受け渡しすることで、データベース無しでもTODOアプリなどは作れそうですが、ほとんどの場合はデータベースも利用するかと思います。
通知だけ行う一方的なボットの場合はLambdaから直接chat.postMessageをPOSTしてあげるだけでメッセージを送信出来ますが、ユーザーからの操作を受け付けるためにAPI Gatewayを使っております。

1. 基礎知識・準備編

APIの種類

Slackには様々なAPIが用意されています。
挙げてみると

  • Incoming Webhooks (通知用のBot開発で使ったことある方もいるかと)
  • Slash command (スラッシュコマンド用のBot開発に使います)
  • RTM API
  • Event API
  • Web API

などが存在しますが、ボタンなどのインタラクティブなコンポーネントを利用したい場合はEvent APIRTM APIWeb APIのどれかを使うかと思います。
「どういう基準で選んだらいいんだよ」と思った方、Slackの公式ページにてめちゃくちゃ分かりやすいフローチャートが用意されています。
必要な Slack API はどれ? - Slack アプリの作成のためのヒント

一応このように用途で分かれていますが、特別な用件がない限りEvent APIを使うといいでしょう。Slack公式もそのように推奨しています。 理由としては

  • 一番新しいAPIであり、サブスクリプションモデルを利用していて、権限(スコープ)を細かく設定出来る

というのがあります。
今回の記事でもEvent APIを採用しております。

APIとLambdaの連携

まず初めにやることとして、Event APIを用いて Lambda ⇆ API Gateway ⇆ Slack の3つを連携させる必要があります。

Event Subscriptions

まずはEvent APIを設定する為に、このようにEvent SubscriptionsにAPI GatewayのURLを登録させる必要があります。

その際にチャレンジ&レスポンス認証が必要になります。
Slackとサーバー側で認証を行う為にSlack側から以下のようなリクエストがきます。

"body": { 
     "type": "url_verification",
     "token": "afafhui3798rt4n9way475h",
     "challenge": "faenuiohtn3unr489nru943867han8974ry785h789fat64"
}

リクエストがきたら、そのままchallengeを文字列で返してあげるだけでOKです。

def lambda_handler(event, context):

    # チャレンジ認証
    if "challenge" in event["body"]:
        return event["body"]["challenge"]

これに関してはこちらのブログが大変分かりやすいです。
権限の設定など、SlackAPPの初期設定に関しても説明されているのでそちらを見てから進むことをお勧めします。

SlackとAWSを繋ぐはじめの一歩~SlackのChallenge認証へチャレンジする

Interactivity & Shortcuts

ユーザーがボットに対してメンションを行ったり、この後も説明するホームタブを開いた際のリクエストはEvent Subscriptionsで設定したURLに送られます。
一方、ボタンなどのコンポーネントからの操作に関してはInteractivity & Shortcutsで設定したURLにリクエストが送られます。こちらも登録しましょう。


ちなみにInteractivity & ShortcutsではChallenge認証は必要ありません。

2. UI開発 基礎知識

Slack APPで利用できるUIにはホームタブモーダルメッセージの3つがあります。 簡単なUIの説明と実際に使用する際のフローについて解説します。

ホームタブ

ホームタブはユーザーとSlackを1対1で繋ぐことが出来るUIとなっています。
ホームタブには3つのタブがあり、アプリについての情報を見る為のワークスペース情報、ボットとダイレクトメッセージで会話出来るメッセージ、動的なコンテンツを提供することが出来るホームで構成されています。
一度も触ったことが無い方はGoogleカレンダーのAppを使ってみてください。

ホームタブは特にプライベートなコンテンツを提供するのに向いています。

アプリケーションフロー

実際にLambdaで開発する際の流れとしてはこのようになります。

  1. ユーザーがホームタブを開くことでapp_home_opendイベントが発生し、Event Subscriptions で設定したURLにリクエストが送られる。
  2. 送られてきたjsonリクエストをパースし、ペイロードからUserIDを抽出し、views.publishメソッドでボタンなどのUIのある初期画面を表示
  3. ユーザーがホームタブ内にあるエレメント(ボタンなど)を操作しリクエストをLambdaに送信
  4. ペイロードからview_submissionblock_actionsイベントを判別し、処理。処理結果をviews.publishメソッドで更新

といった流れになります。図で表すとこんな感じ

モーダル

モーダルを使う利点として、ユーザーに対して半ば強制的にリアクションさせることが出来ます。
また、下で説明するメッセージでは、ユーザーからの文字入力を受け付けるinputブロックを使用することが出来ない為、メッセージで対話している最中に、ユーザーからの入力が必要な際に部分的に利用されることもあります。

アプリケーションフロー

流れとしては以下のようになります。

  1. ユーザーがエレメントを操作しペイロードがLambdaに送信される
  2. ペイロードのview_submissionblock_actionsからtrigger_idを抽出してviews.openメソッドでモーダルを表示
  3. ユーザーがエレメントを操作しペイロードがLambdaに送信される
  4. views.updateメソッドでモーダルを更新、もしくはtrigger_idを抽出してviews.pushメソッドで新しいモーダルを重ねて表示
  5. ユーザーがエレメントを操作しペイロードがLambdaに送信される
  6. Lambdaから{"response_action": "clear"}のレスポンスを返すことで全てのモーダルを閉じて終了


このようにモーダルは更新または上に新しいモーダルを重ねる(3つまで)ことで画面遷移を可能としています。
また、重なっている状態で表示されるモーダルは一番上だけですが、下にあるモーダルは上にあるモーダルを閉じることで、以前の状態を維持したままモーダルを再表示させることができます。

レスポンスのbodyが{"response_action": "clear"}のレスポンスの場合は全てのモーダルを削除、空の200レスポンスの場合はリクエストがあったモーダル(一番上)を削除します。

メッセージ

メッセージはユーザーの投稿のようにチャンネル内で表示させることができます。
メッセージを選択する利点として、チャンネルに参加している複数人に一度で通知、リアクションをしてもらいたい場合に使われることが多いです。
注意点としてメッセージ単体ではユーザーからの文字入力を受け付けることが出来ないので、モーダルを入力フォームとして利用したり、メンションを活用する必要があります。

アプリケーションフロー

例としてメンションを受けた場合の流れについて説明します。

  1. ユーザーがAPPに対してメンションすることでapp_mentionイベントが発生し、Event Subscriptions で設定したURLにリクエストが送られる。
  2. channelを取得し、chat.postMessageメソッドを使いメッセージを送信
  3. ユーザーがエレメントを操作しペイロードがLambdaに送信される
  4. channelを取得し、chat.postMessageメソッドを使い新しいメッセージを送信

モーダルやホームタブと比べるとシンプルです。

3. UI開発 実践編

Block Kit Builderを使っていい感じのUIを作ってみる

Slack公式のBlock Kit Builderを使うことでブラウザ上でUIを簡単にデザインすることができます。
生成されたViewは右側のjsonオブジェクトとして変換されます。
実際に使用する場合もjsonをそのまま利用することできるので、フロントエンドの知識がなくても直感的にいい感じのUIを作ることができるのでおすすめです。

また、便利な機能として、画面中央のActions Previewからボタンなどユーザーからの操作を受け取った時のリクエストを確認することができますので、Lambdaを書くときも重宝しますね。

プレビュー画面をいじるだけでもそれなりに開発することも出来るのですが、jsonを直接いじって編集することで出来ることも色々あるので、viewの構造について詳細に説明します。

viewは以下のようにブロックがあり、その中にボタンなどのエレメントを配置する構造となっています。

blocks以外に関しては必要なプロパティがモーダルホームタブメッセージで少々異なります。

メッセージ

メッセージはblockのみで大丈夫です。

{
    "blocks": [
        {
            "type": "section",
            "text": {
                "type": "plain_text",
                "text": "This is a plain text section block.",
                "emoji": true
            }
        },
        {
            "type": "actions",
            "elements": [
                {
                    "type": "button",
                    "text": {
                        "type": "plain_text",
                        "emoji": true,
                        "text": "Approve"
                    },
                    "style": "primary",
                    "value": "click_me_123"
                },
                {
                    "type": "button",
                    "text": {
                        "type": "plain_text",
                        "emoji": true,
                        "text": "Deny"
                    },
                    "style": "danger",
                    "value": "click_me_123"
                }
            ]
        }
    ]
}

モーダル

モーダルではtypetitlesubmitcloseが必須となります。

{
    "type": "modal",
    "title": {
        "type": "plain_text",
        "text": "My App",
        "emoji": true
    },
    "submit": {
        "type": "plain_text",
        "text": "Submit",
        "emoji": true
    },
    "close": {
        "type": "plain_text",
        "text": "Cancel",
        "emoji": true
    },
    "blocks": [
        {
            "type": "section",
            "text": {
                "type": "plain_text",
                "text": "This is a plain text section block.",
                "emoji": true
            }
        },
        {
            "type": "actions",
            "elements": [

ホームタブ

ホームタブはtypeを追加する必要があります。

{
    "type": "home",
    "blocks": [
        {
            "type": "section",
            "text": {
                "type": "plain_text",
                "text": "This is a plain text section block.",
                "emoji": true
            }
        },
        {
            "type": "actions",
            "elements": [

ブロックタイプについて

blocksに追加できるブロックとして全8種類のBlock typeが存在しています。
利用頻度の高いInputブロックがメッセージでは利用できないという注意点があります。

ブロックタイプ モーダル ホームタブ メッセージ 説明
Actions インタラクティブなエレメントを利用できます。
Context 画像とテキストの両方を含むことが出来るメッセージコンテキストを表示します。 
Divider hrタグのようにブロックを仕切ることができます。
File × × リモートファイルを利用する場合
Header 大きくて太文字のフォントを利用したテキストを表示させます。
Image シンプルに画像を表示できます。
Input × ユーザーからの入力を受け付けるエレメントを利用できます。
Section テキストとボタンなどテキストと他のエレメントを一度に表示することができます。

ブロックによって、利用できるフィールドは少々異なりますので、詳細は公式リファレンスを確認してください。
Reference: Layout blocks

ここではよく使うActionsSectionブロックのみ説明します。

Actionsブロック

一番よく使われる基礎的なブロックになります。また、ほとんどのブロックが以下のような構造となっています。
block_idはブロックに対して一意に設定されるIDとなります。設定していない場合はSlack側でランダムな文字列を設定されてしまうので自分で設定しましょう。
elements配列に様々なエレメントを追加することでボタンなどを表示することができます。

{
  "type": "actions",
  "block_id": "actions1",
  "elements": [
  ]
}

Sectionsブロック

Sectionsブロックはテキストとボタンなどの要素を組み合わせて使う場合に使われます。 textfieldsなどのテキストが必須となります。どちらか片方は最低限必要です。

プレビュー

{
    "type": "section",
    "fields": [
        {
            "type": "plain_text",
            "text": "*this is plain_text text*",
            "emoji": true
        },
        {
            "type": "plain_text",
            "text": "*this is plain_text text*",
            "emoji": true
        },
        {
            "type": "plain_text",
            "text": "*this is plain_text text*",
            "emoji": true
        },
        {
            "type": "plain_text",
            "text": "*this is plain_text text*",
            "emoji": true
        }
    ],
    "text": {
        "type": "plain_text",
        "text": "This is a plain text section block.",
        "emoji": true
    },
    "accessory": {
        "type": "button",
        "text": {
            "type": "plain_text",
            "text": "Click Me",
            "emoji": true
        },
        "value": "click_me_123",
        "action_id": "button-action"
    }
}

エレメントについて

ブロックに追加出来るエレメントは全10種類あります。ブロックによっては利用できないエレメントもあるので、注意してください。
Reference: Block elements

エレメント Sectionブロック Actionsブロック Inputブロック
Button ×
Checkbox
Date picker
Image ×
Multi-select menu ×
Overflow menu ×
Plain-text input × ×
Radio button
Select menu
Time picker


使用頻度の高いButtonについて説明します。


Button

ユーザーがボタンを押すことで予め設定していた値をリクエストとし受け取ることができたり、URLを設定することでボタンを押した際にユーザーのブラウザで指定したページを表示させることができます。

またconfirmを追加することで、以下のようにボタンを押した際の確認画面を表示させることもできます。


全てのエレメントに対してaction_idというフィールドを設定できます。ブロック内で一意のエレメントを識別する為に必要で、実際に入力を使う際に利用するので自分で設定しておきましょう。(GUIで作る場合はランダムなIDが設定されます。)
confirmに関してもほぼ全てのエレメントで利用できます。
プレビュー

"elements": [
    {
        "type": "button",
        "text": {
            "type": "plain_text",
            "text": "Click Me",
            "emoji": true
        },
        "confirm": {
            "title": {
                "type": "plain_text",
                "text": "ちゃんと確認してな"
            },
            "text": {
                "type": "mrkdwn",
                "text": "送信するけど大丈夫?"
            },
            "confirm": {
                "type": "plain_text",
                "text": "問題なし"
            },
            "deny": {
                "type": "plain_text",
                "text": "やっぱやめとく"
            }
        },
        "value": "click_me_123",
        "action_id": "actionId-0"
    }
]


最後に

今回の記事ではSlack APP開発の流れやUIの部分について説明しました。次回はサーバーサイド側を作る際の注意点などまとめていきますのでお楽しみに。