三行解説
- SlackイベントでSlackだけでプレゼンやったよ
- 主にInteractiveMessageとUpdate.postを使って作ったよ
- 色々応用が利くよ
何やったの?
日本のSlackユーザーコミュニティ:JCN にて、Slack開発やろうよのイベントで発表させていただきました。
SlackのイベントなのにSlack映さずにパワポだけで発表するのも味気ないなあ、と考えたのがきっかけです。
どんな話したのか気になるよーって方は、近日公開予定のYoutube動画をご視聴ください。
動作イメージ
SlackJCNのイベントで登壇しました。
— barusu (@Flat_flag_man) January 27, 2023
来てくださった方ありがとうございましたー。
これまでに作ったアプリの紹介とか
やっぱりスプレッドシート便利だよねって話とか
でもSlackアプリ作っていこーよって話とかを
パワポ一切使わずにSlackだけ見せてお話しました。#slackjcn #jcn pic.twitter.com/y8KIaF8GCq
処理フロー
全体的な処理フローはこんな感じです。

本題:どうやって作ったのか
Workatoを使って実装してます。
一見するとやや複雑な作りになっていますが、応用が利く処理機構になっているので是非流用してほしいです。
このブログで紹介するテクニック
- Workato
- LookupTableの作り方、使い方
- HTTP アクションの使い方
- Webhookトリガーを使ったレシピ節約
- 異なる経路のトリガーを一つにまとめる工夫
- Json Parser
- エラーハンドリング処理
- Slack
- Block Kit Builder
- BlockID / Action ID
- API
- Update.post
- chat.post
- Bot
- 作成、権限追加、インストール
- InteractiveMessage
- スラッシュコマンド
Slack機能と簡単解説
- スラッシュコマンド
- Workatoのレシピを呼び出す
- Interactive Messages
- 任意のボタンをクリックしたらHTTPリクエストを飛ばす
- Block Kit Builder
- ボタン埋め込み投稿とかのJsonを作る
- Update.post
- 投稿を書き換えるAPI
作り方解説
以後、Step By Stepで手順を記載します。
Slack:Botの基本設定
① Botを準備する
https://api.slack.com/apps にてCreate New APP をクリック

From scratch をクリック

アプリ名を入力し、インストール先のWorkspaceを選択してCreate Appをクリック

※忙しい人向けのマニフェスト
{
"display_information": {
"name": "須藤あどみん",
"description": "EventSub,Speaker'sSlide",
"background_color": "#d32600"
},
"features": {
"bot_user": {
"display_name": "sudo-admin",
"always_online": true
},
"slash_commands": [
{
"command": "/slide-start",
"url": "https:<スラッシュコマンド実行時のRequestURL>",
"description": "Slideを表示する",
"usage_hint": "[SlideNumber]",
"should_escape": false
}
]
},
"oauth_config": {
"redirect_urls": [
"https://www.workato.com/oauth/callback"
],
"scopes": {
"bot": [
"chat:write",
"chat:write.customize",
"chat:write.public",
"commands"
]
}
},
"settings": {
"interactivity": {
"is_enabled": true,
"request_url": "https:<ボタンクリック時のRequestURL>"
},
"org_deploy_enabled": false,
"socket_mode_enabled": false,
"token_rotation_enabled": false
}
}
② 権限追加
Slackアプリ管理画面から、OAuth & Permissions をクリック

画面下部 Scopes 欄のBot Token Scopes にて以下権限を追加

SlackBotをインストール
アプリ管理画面から、Install App をクリック

許可するをクリック

!)こんな画面が出たときは

App Homeをクリック

App Display Name のEdit をクリック

Bot Name、Defoult user nameを入力して[Save]をクリック

再度インストールを実施して完了
インストール後、OAuth & PermissionsのBot User OAuth Tokenの値を控えておく

Slack:スラッシュコマンドを設定する
処理フロー図の青枠部分に該当します

設定方法
アプリ管理画面から、Slash Commandsをクリック

Create New Command をクリックして各パラメータ入力

? 入力パラメータ
- Command
任意
- RequestURL
処理側で発行
リクエストを受け付けるURL
→ 後述の手順で設定するのでここでは適当なURLでOK
- Short Description
簡易的な説明
- UsageHint
引数などあれば
Slack:Interactive messagesを設定する
処理フロー図の青枠部分に該当します

設定方法
アプリ管理画面から、Interactivity & Shortcuts をクリック

Interactivity をOnにし、Request URL にリクエストを受け付けるURLを入力

※リクエストを受け付けるURLは、後行程で作成するWorkatoのレシピから取得します

画面右下のSave Changes をクリック

以上、ここまででSlackBotの設定は完了です。
SlashCommand、InteractiveMessageのURLはWorkatoレシピ作成後に生成されるURLに差し替えてください。
Workato:事前準備
事前準備①:押すボタンを定義する
今回はこのように定義しています
- ▷:次のスライドに進む
- ◁:前のスライドに戻る
- Agenda:アジェンダに遷移する
- End:スライドを終了する
図にするとこんな構成

この内容に合わせてテーブルを作っていきます。
事前準備②:LookupTableを作る
Tools → LookupTables をクリック

New Tableをクリック

Add entries manually をクリック

各列名を定義する
※ここではSlideNumber、Json を定義しています

LookupTableに入れるデータを作る
Block Kit Builder を利用してJsonを作成してます。
Block Kit Builderでスライドの内容を作成する

画面右側のJsonの”blocks”:[~~]の範囲をコピーする。
Workatoから投稿する際、”blocks”: [<->] の部分だけ動的にするため。
action-idやblock-idを忘れずに設定しておく。
"blocks": [
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "End",
"emoji": true
},
"value": "previous",
"action_id": "button_end"
},
{
"type": "button",
"text": {
"type": "plain_text",
"text": "◁",
"emoji": true
},
"value": "previous",
"action_id": "button_previous"
},
{
"type": "button",
"text": {
"type": "plain_text",
"text": "▷",
"emoji": true
},
"value": "next",
"action_id": "button_next"
},
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Agenda",
"emoji": true
},
"value": "next",
"action_id": "button_agenda"
}
]
}
]
Add Entry をクリック

値を入力して✓をクリック

入力する値
- SlideNumber:1(最初のスライドなので1とします)
- Json:Block Kit Builderで作成したJsonを貼り付ける
完了!

以後、同じようにBlock部分のJsonを作成してテーブルに追加していく

Workato:レシピ作成
このレシピは2つの処理を1つにまとめています
- スラッシュコマンド実行処理
- 元となるボタン付き投稿をPost
- ボタンクリック後の処理
- 押されたボタンを元に、対応する内容を取得
- Slackの投稿を編集する
処理フロー図の青枠部分2つの作成手順を解説します

レシピ新規作成〜エラー処理部分を作る

トリガーの設定
- Payload configuration のWebhook type を`PUT/POST with FORM encoded payload` にする

Webhook受信時のリクエスト内容を定義する
※ この状態からschemaで直接入力が出来ないので適当に一つ項目を追加する
Add Fields manuallyから一つ項目を追加してAdd fieldをクリック

Edit Schema をクリック

後述のSchemaをコピペして貼り付けてUpdateをクリック

[
{
"name": "payload",
"type": "string",
"optional": true,
"control_type": "text"
},
{
"name": "command",
"type": "string",
"optional": true,
"control_type": "text"
},
{
"name": "channel_id",
"type": "string",
"optional": true,
"control_type": "text"
},
{
"name": "user_id",
"type": "string",
"optional": true,
"control_type": "text"
},
{
"name": "token",
"type": "string",
"optional": true,
"control_type": "text"
},
{
"name": "response_url",
"type": "string",
"optional": true,
"control_type": "text"
},
{
"name": "trigger_id",
"type": "string",
"optional": true,
"control_type": "text"
},
{
"name": "enterprise_id",
"type": "string",
"optional": true,
"control_type": "text"
},
{
"name": "text",
"type": "string",
"optional": true,
"control_type": "text"
}
]
※ Query Params,Headers は定義不要なのでスキップ
エラー処理を作る
Handle Errors をクリック

エラー時の共通処理を作る

Sample:Slackに通知させる処理を追加する

以後、通常処理はStep3〜4の間に作成していきます。
スラッシュコマンド実行処理
フロー図の青枠部分を作ります

Step4:IF分岐を作成

▼条件設定
項目名 | 値 |
Data field | Step1 の Command |
Condition | is Presant |

Step5:さらにIf分岐を作成

▼条件設定
項目名 | 値 |
Data field | Step1 の Command |
Condition | Equals |
Value | 設定したスラッシュコマンド( ここでは[/slide-start]) |

Step6:LookupTableを呼び出す
Lookup entry を選択

作成したLookupTableを選択し、SlideNumber欄は 1 と入力

Step7:Slackに投稿する
HTTPアクションを追加

それぞれ値を入力

▼設定箇所と値
項目名 | 値 |
Request name | (任意) |
Method* | POST |
Request URL* | https://slack.com/api/chat.postMessage |
Request content type | JSON |
Request bodyはこのように設定します

▼設定する値とポイント解説
項目名 | 値 |
channel | [Step1 Output] の Channel ID |
text | “1” ※これが現在表示されるスライドの内部番号になります |
Json | LookupTableから取得したJson |
Request content type | JSON |
Request headers

▼ポイント
- <Your-Token> 部分は作成したSlack Botのトークンを取得して入力します
- ハードコード回避の方法は別途ありますが、今回は省略します
Step8:Stop Job を追加

In job report, mark stopped job as* はSuccessful と設定

ここまでで、スラッシュコマンド実行→最初の投稿(ボタン付き)が完成しました
ボタンクリック処理
フロー図の青枠部分を作ります

Step9:Jsonをパースする処理を追加
スラッシュコマンドからの実行とボタンクリック時のInteractiveMessageによるHTTPリクエストでは、トリガーに渡されるJsonの体裁が異なるため、そのままではボタンクリック時の処理が上手く作れません
ので、JsonParserを使ってリクエストで渡されるJsonをデータピルに変換します

Sample document に以下の値を入れる
{"type":"block_actions",
"token": "NLfjXXXXXXXXXXXXXXX",
"team_id": "T02XXXXXXX",
"team_domain": "cloudnative-sandbox",
"channel_id": "C0XXXXXXXX",
"channel_name": "test-dmbot",
"user_id": "U01KJS6TA2H",
"user_name": "subaru",
"command": "/zundoko",
"text": "",
"api_app_id": "A0282XXXXX",
"is_enterprise_install": "false",
"enterprise_id": "E9KXXXXX",
"enterprise_name": "sudachi.enterprise",
"response_url": "https://hooks.slack.com/commands/T026J17UQ03/2837303000951/v0mXXXXXXXXXXXXXXXXXXXXX",
"trigger_id": "2848987036581.2222041976003.XXXXXXXXXXXXXXXXXXXXXXXXX",
"user": {
"id": "U01KJS6TA2H",
"username": "subaru",
"name": "subaru",
"team_id": "T026J17UQ03"
},
"api_app_id": "A0282XXXXX",
"token": "NLfjGOFELXXXXXXXXXX",
"container": {
"type": "message",
"message_ts": "1639677931.007100",
"channel_id": "C026WGZAR25",
"is_ephemeral": false
},
"trigger_id": "2864722859649.XXXXXXXXXXXXX.XXXXXXXXXXXXXXXXXXXXXXXXX",
"team": {
"id": "T026J17UQ03",
"domain": "cloudnative-sandbox",
"enterprise_id": "E9KXXXXX",
"enterprise_name": "sudachi.enterprise"
},
"enterprise": {
"id": "E9KEEL8NN",
"name": "sudachi.enterprise"
},
"is_enterprise_install": false,
"channel": {
"id": "C026WGZAR25",
"name": "test-dmbot"
},
"message": {
"bot_id": "B02XXXXXX",
"type": "message",
"text": "aaaa",
"user": "U02XXXXXXX",
"ts": "1639677931.007100",
"team": "T026J17UQ03",
"blocks": [
{
"type": "section",
"block_id": "lU6",
"text": {
"type": "mrkdwn",
"text": "Slect Your Answer.",
"verbatim": false
}
},
{
"type": "divider",
"block_id": "1bc"
},
{
"type": "actions",
"block_id": "b+HM7",
"elements": [
{
"type": "button",
"action_id": "actionId-1",
"text": {
"type": "plain_text",
"text": "1",
"emoji": true
},
"value": "click_me_1"
},
{
"type": "button",
"action_id": "actionId-2",
"text": {
"type": "plain_text",
"text": "2",
"emoji": true
},
"value": "click_me_2"
}
]
}
]
},
"state": {
"values": {
}
},
"response_url": "https://hooks.slack.com/actions/XXXXXXXX/XXXXXXXXXX/XXXXXXXXXXXXXXX",
"actions": [
{
"action_id": "actionId-2",
"block_id": "b+HM7",
"text": {
"type": "plain_text",
"text": "2",
"emoji": true
},
"value": "click_me_2",
"type": "button",
"action_ts": "1639678262.5XXXXX"
}
]
}
DocumentにStep1のPayload を指定

Step10:Logger アクションを追加
主にデバッグ目的で追加します
JsonParserのOutputはJob履歴画面ではJsonが省略されるので、ここでLoggerを追加してJsonの中身が表示されるようにします

Message に Step9のOutputを配置

Step11:If分岐を設定
目次の各ボタン※ 画像参照 を押した時に分岐する処理を作る

If分岐を追加

▼条件設定
項目名 | 値 |
Data field | Step9 のActions > Action ID |
Condition | starts with |
Value | Action ID に設定した値(ここではbutton_agenda) |
設定後の画面はこんな感じ

▼ポイント解説
このStep11とStep28はIf分岐をネストしており、冗長的になってます。
ネストさせなくても条件分岐を正しく設定すれば動作に問題はないのですが…レシピ修正の際の小技的に、処理群をIfでまとめると畳めるのでこうしてます。

目次ボタンごとの分岐処理
目次の各ボタン※ 画像参照 をそれぞれ押した時に、ボタンごとに分岐する処理を作る

Step11の配下にIf分岐を追加

▼条件設定
項目名 | 値 |
Data field | Step9 の Actions → Text→ Text |
Condition | equals |
Value | 設定したボタンのテキスト( ここでは[1.アプリ紹介]) |
設定後の画面はこんな感じ

Step13:Lookup Tableを追加してLookup entry を選択
!)ここの処理で指定する番号が、それぞれのボタンをクリックした際に表示する番号となります

作成したLookupTableを選択し、SlideNumber欄は対応する数字を入力(例:100)

Step14:Slack投稿を書き換える
HTTP アクションを追加してそれぞれ値を入力

▼設定する項目と値
項目名 | 値 |
Request name | (任意) |
Method | POST |
Request URL | https://slack.com/api/chat.postMessage |
Value | JSON |
Request bodyはこのように設定する

▼設定する値とポイント解説
項目名 | 値 |
channel | Step9 のContainer→ Channel IDを指定 |
ts | Step9 のMessage→Ts を指定 |
text | Step13 のSlideNumber を指定 ※これが現在表示されるスライドの内部番号になります |
Textの下の[json] | Step13 の json を指定 ※末尾にカンマを忘れずに |
Request headers

ポイント
- <Your-Token>部分は作成したSlack Botのトークンを取得して入力します
- ハードコード回避の方法は別途ありますが、今回は省略します
Step15:Stop Job を追加

In job report, mark stopped job as* はSuccessful で設定

Step16〜 27 : Step12〜15をコピーし、適宜値を変更して設定

▼設定変更箇所
元となるStep | 設定箇所 | 変更内容 |
Step12:If分岐アクション | Condition | Valueを各ボタンの内容に置き換えて変更する |
Step13:LookupTable | SlideNumber | 対応させたいスライドの番号 |
備え付けの各ボタンごとの分岐処理
備え付けの各ボタン※ 画像参照 をそれぞれ押した時に、ボタンごとに分岐する処理を作る


▼条件設定
項目名 | 値 |
Data field | Step9 のActions > Action ID |
Condition | equals |
Value | 設定したボタンのActionID(button_previous、button_end、button_next、button_agenda) OR で作成する |
設定後の画面

Step29:進むボタンの処理
更にIf分岐を設定

▼条件設定
項目名 | 値 |
Data field | Step9 の Actions → Text→ Text |
Condition | equals |
Value | 設定したボタンのテキスト(▷) |
Step30:Logger を追加
後続のStep32でSlackのリクエストBodyでは数式埋め込みができないため、Loggerアクションでデータピルを作成します
Loggerアクションを追加してFormula に変更し、Step9 のText を選択して以下数式にする
([Step9 Text].to_i)+1

Step31:Lookup Tableを追加してLookup entry を選択

作成したLookupTableを選択し、SlideNumber欄はStep 30 のMessageを選択

Step32:Slack投稿を書き換える
HTTP アクションを追加(Step14をコピーすると楽です)

▼設定する項目と値
項目名 | 値 |
Request name | (任意) |
Method | POST |
Request URL | https://slack.com/api/chat.postMessage |
Value | JSON |
Request bodyはこのように設定する

▼設定する値とポイント解説
項目名 | 値 |
channel | Step9 のContainer→ Channel IDを指定 |
ts | Step9 のMessage→Ts を指定 |
text | Step13 のSlideNumber を指定 ※これが現在表示されるスライドの内部番号になります ※内部番号を加減することで対応するスライド番号を呼び出せるようになります |
Textの下の[json] | Step13 の json を指定 ※末尾にカンマを忘れずに |
Request headers

ポイント
- <Your-Token>部分は作成したSlack Botのトークンを取得して入力します
- ハードコード回避の方法は別途ありますが、今回は省略します
Step33:Stop Jobを設定

In job report, mark stopped job as* はSuccessful で設定

Step35〜46:Step29〜33をコピーしてPasteし、条件を適宜変更する

それぞれ定義したボタンごとに、呼び出すLookupTableのレコードを変更する
▼設定変更箇所
ボタン名 | 元となるStep | 設定箇所 | 変更内容(例) |
戻る | Step29:If分岐 | Value | 設定したボタンのテキスト(◁) |
Step30:Logger | Message | ([Step9 Text].to_i)+1 | |
Agenda | Step29:If分岐 | Value | 設定したボタンのテキスト(Agenda) |
Step31:LookupTable | 項目:SlideNumber | 対応させたいスライドの番号(3) | |
End | Step29:If分岐 | Value | 設定したボタンのテキスト(End) |
Step31:LookupTable | 項目:SlideNumber | 対応させたいスライドの番号(0) |
…めっちゃ長くなりましたが以上で完了です!
お疲れ様でした!あとはレシピをStartして、スラッシュコマンド実行すれば動きます
余談
スライドに画像を入れたい場合、パブリックにアクセスできる画像URLでないと表示できません。
やり方はいくらでもありますがざっくりこんなやり方があります
もちろんですが、公開不可な画像は表示したら(‘ω’乂)ダメーです
サービス | やり方 | barusu主観 |
Github Issue | Issueに画像置くとパブリックURLが作れる | お手軽 |
Notion | 公開ページ作ってそこに画像配置して画像のURLを取得する | ちょっとめんどい |
Amazon S3 | 画像置いてパブリックURL作成する | ちょっとめんどい |
GoogleDrive/Box/OneDriveなど | 公開リンク作成 | お手軽だけど会社ルール次第でNG食らう |
感想
使ったときの感想
- インパクトは抜群
- これどうやって作ったんすかって質問が多々ありました
- インパクトありすぎて発表の内容をあまり聞いてもらえてなかったかも
- 使い回しはとてもめんどい
- スライドの内容変えたいとかなるとJsonを変更してLookupTableに入れてってやるので、慣れが必要
- そもそもパワポで良いじゃんってなる
作ったときの感想
- 応用性が高い
- XしたらYのJsonを取得する、という形式にしたことにより他の機能でも活かせる
- Slackが提供できない画面を擬似的に作れ、ユーザーの入力や選択を受け付けられる
- 実際にこれを応用したのが、承認プロセスを作った話やSalesforce入力をどうにかして簡略化した話につながっていきます
- Interactive Messageが便利
- 非同期的に処理ができる
- Modal Viewとかの処理は呼び出し→入力 が同期処理のため、ユーザー側で呼び出すトリガーでしか実現ができなかった
- 一方で、Interactive Message は投稿して処理完了にできる
- 投稿→入力受付 の処理を非同期で受付できる
Workatoでやる理由は何?
- LookupTable参照が割と簡単
- GoogleSheetでやるのも良い。あっちは関数が使えるからね
- Jobの再実行が簡単なのが強い
- テストがしやすいのと、エラーが起きた際の修正後確認やらがとても楽
- 作りやすさ
- 簡略化したとはいえ、処理が複雑になるのでトータルで見るとWorkatoが作りやすいかも
- GASは最初書くのを頑張れば良いが、後から修正や改善、機能追加するのがしんどい
認識している課題
- Slackからのリクエストに対し、レスポンス速度を早める工夫が必要
- Slackのリクエスト再送処理のケアがWorkato(のWebhookTrigger)ではできない
- 一次レスポンスを返すなどの工夫が要る
- Slackのリクエスト再送処理のケアがWorkato(のWebhookTrigger)ではできない
- スライドをぽんぽん流すだけならPowerPointで良い
- 動きを付ける、縦長レイアウトにしたいなどのニーズがあればこっちが良い
- HTTPアクションを多用したので、Jsonエラーなどの修正が超煩雑
- 認証もTokenをハードコードしてるので管理しにくい
- これらの課題は今後SlackのカスタムSDKを作成して置き換えて解決する予定です
おわりに
今回はSlackのInteractive Messageその他機能を使った活用例の一つとしてご紹介しました。
Workatoの利用イメージも伝えたかったので、かなり冗長な内容になるのを承知でStep By Step で作成手順を添えています。
特にSlackのInteractiveMessageを使うと、通知をカスタマイズして利用する際にちょっと一手間加えるだけで後続タスクまで自動化できる足がかりになります。
今回ご紹介したレシピは応用が利くので、Workatoご利用中の方は色々作り変えて使ってみてください。
ではでは。