3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Slack をフル活用するために Slack の拡張機能を調べてみた

Last updated at Posted at 2023-12-06

この記事の目的

Slack AI が発表され,今後も利用が普及する見込みであることから,我が家にも Slack を導入する機運が高まりました.
Slack をフリープランでもフル活用するために今後拡張していけそうな機能を調べたので,備忘録のためにこの記事にまとめます.

こちらの記事では Slack API 公式ドキュメント に準じて動作確認を行っています.

こちらの記事では無料で活用できる範囲の検証しか行っていません.
よって以下は取り扱っていません.

  • Workflows
  • Next generation Slack platform

Slack App は 10 個までの制限があることも念頭に置く必要があるかもしれません.

基本的に https://your-workspace.slack.com/apps から作業を行います.

slack-com-apps.png

自前の Slack Apps を利用した拡張

API を使うにあたって,自前の Slack App を作成してワークスペースにインストールする必要があります.
まず,自前の Slack App を作成する手順を説明します.

  1. https://your-workspace.slack.com/apps の右上にある Build をクリックして遷移した先から Create an App をクリック
  2. From scratch を選ぶ
  3. App Name に適当なアプリ名を入力し,アプリを利用するワークスペースを指定したら Create App を実行
  4. Basic Information が表示されたら Install your app から ワークスペースにインストール
  5. インストールが完了したら OAuth & Permissions からトークン (xoxb-はBot用,xoxp-はユーザ用) が発行されていることを確認

make-slack-app.PNG

install-app-to-workspace.PNG

init-oauth-scop.PNG

作成した App に対して適切な権限やスコープを管理する1必要があります.
デフォルトではどのチャンネルにも接続はできません.
ここでは公式ドキュメント2に従って,OAuth & Permissions > Scopes から Bot 用とユーザ用に channels:read, channels:history, chat:writeをつけておきます.

トークンを利用してパブリックなチャンネルの情報を取得 (api/conversations.list3) できるか試すことで,アプリがワークスペースにインストールできたことを確認できます.
以下では特にチャンネル名とチャンネル ID を返してもらうようにしています.

curl -X POST 'https://slack.com/api/conversations.list' \
-H "Authorization: Bearer xoxp-xxxxxxxxxxxxxxxxxxxx" \
| jq '[.channels[].name]','[.channels[].id]'

特定のチャンネル・チャットにメッセージを送る

api/chat.postMessage

cURL を使ってユーザの代わりにメッセージを送る場合の例を以下に示します.
"channel" の値はチャンネル名でもチャンネル ID でも OK です.

curl -X POST 'https://slack.com/api/chat.postMessage' \
-H "Content-Type: application/json" \
-H "Authorization: Bearer xoxp-xxxxxxxxxxxxxxxxxxxx" \
-d @- << EOF
{
  "channel": "CHANNEL-NAME-OR-ID",
  "text": "Hello world :tada:"
}
EOF

Bot で送る場合は予めチャンネルに App を登録しておく必要があります.

add-app.png

Add an App が完了したら改めてPOSTを送ることで投稿が完了し,App 名の Bot ユーザからのチャット投稿が確認できます.

curl -X POST 'https://slack.com/api/chat.postMessage' \
-H "Content-Type: application/json" \
-H "Authorization: Bearer xoxb-xxxxxxxxxxxxxxxxxxxx" \
-d @- << EOF
{
  "channel": "CHANNEL-NAME-OR-ID",
  "text": "Hello world from Bot :tada:"
}
EOF

スレッド返信するには ts 値が必要です.
api/chat.postMessage でメッセージを送った場合はレスポンスに ts 値を持っているため,例えばあるサービスから順番に情報が送られる場合は ts 値を保持しておけば以下の POST でスレッド返信できます.

curl -X POST 'https://slack.com/api/chat.postMessage' \
-H "Content-Type: application/json" \
-H "Authorization: Bearer xoxp-xxxxxxxxxxxxxxxxxxxx" \
-d @- << EOF
{
  "channel": "CHANNEL-NAME-OR-ID",
  "thread_ts": "PARENT_MESSAGE_TS",
  "text": "Hello again! :tada:"
}
EOF

ts 値がわからないがスレッド返信したい場合は,特定のチャンネルでメッセージを検索する を参照して検索してから実行するとよいかもしれません.

その他,一時的なメッセージを送る場合は chat.postEphemeral4,時間指定でメッセージを予約投稿する場合は chat.scheduleMessage5 を利用することができます.

Incoming Webhooks

Incoming Webhooks を有効化してメッセージを投稿する方法もあります.
この場合はチャンネルごとに宛先 URL を発行して POST します.

incoming-webhooks.PNG

curl -X POST 'https://hooks.slack.com/services/xxxxxxxxxxxxxxxxxxxx' \
-H "Content-Type: application/json" \
-d @- << EOF
{
  "text": "Hello world :tada:"
}
EOF

api/chat.postMessage では App に設定した権限やスコープに準じて,プライベートなチャンネルに投稿はできませんでした.
一方で Incoming Webhooks を有効化する際にプライベートチャンネルを指定した場合は,プライベートチャンネルでも指定 URL を使えば投稿できてしまうため,ユースケースに合せて api/chat.postMessage と選択する必要がありそうです.

また,Incoming Webhooks のレスポンスには ts 値は含みません.

特定のチャンネルでメッセージを検索する

api/conversations.history

会話の履歴を取得する場合は api/conversations.history を利用します.
ここで "channel" はチャンネル ID のみ指定可能で,チャンネル名を指定するとエラーになります.

curl -X POST 'https://slack.com/api/conversations.history' \
-H "Content-Type: application/json" \
-H "Authorization: Bearer xoxp-xxxxxxxxxxxxxxxxxxxx" \
-d @- << EOF
{
  "channel": "CHANNEL-ID",
  "limit": 1
}
EOF

limit を使って最新のチャットからいくつ取得するかを決めることができます.デフォルトは 100 です.
このレスポンスから text を対象にチャットの文字検索も可能であり,ts 値の特定も一応可能です.

特定のチャンネルからいろいろなサービスの処理を発生させる

Slash Commands

スラッシュコマンドを使うと,メッセージ作成ボックスからアプリを呼び出すことができます.
呼び出されたアプリからは,処理が行われた結果を Slack に返答するようにします.

スラッシュコマンドの後ろには任意の引数をつけることができます.
引数はコマンドの実行により送られるリクエスト JSON 中の text データに含まれており,適当にパースして処理に反映できます.

一方で,Slack がスラッシュコマンドを受信してから 3 秒以内に連携先アプリより HTTP 200 OK が返ってこなかった場合は failed with the error "operation_timeout" となります6
時間がかかる処理については仕様上相性が悪く,何らかの方法で非同期的に処理できるように実装しなければなりません.

ここでは例として Google Apps Script (GAS)WorldTimeAPI を叩くアプリを作成し,Slack のメッセージ作成ボックスから /time コマンドを打ち込むことで WorldTimeAPI の datetime を取得します.
また,引数には任意の timezones7 を付与できるものとし,引数がない場合は Asia/Tokyo で問い合わせを行うものとします.

リクエスト元の検証には Basic Information > App Credentials > Verification Token を参照して token を利用します.

verification-token.PNG

gas-postTime.png

postTime.gs
function doPost(e) {
  const url = "http://worldtimeapi.org/api/timezone/";
  const token = e.parameter.token;
  const timezone = e.parameters.text[0] || "Asia/Tokyo";

  // Verification
  if (token === "xxxxxxxxxxxxxxxxxxxx") {

    // Access WorldTimeAPI
    const res = UrlFetchApp.fetch(url + timezone);
    const jsonRes = JSON.parse(res);
    return ContentService.createTextOutput(JSON.stringify(jsonRes.datetime));

  } else {
    throw new Error("Invalid request.");
  }
}

注意

  • token 情報は e.parameter,引数の情報は e.parameters から取得しています.e.parameters 内の要素は全てリストなので注意してください.
  • 上記では token をハードコーディングしていますが,実装する際はハードコーディングしない工夫をしてください.

slash-commands.png

Event API

イベント API を使うと,チャンネルの設定に変更が行われたりメッセージが送られた (=トリガーとして設定したあるイベントが発生した) 場合に,行われた操作の情報を指定サーバーに送ることができます.

スラッシュコマンドと異なり毎回 Slack にメッセージを送り返す仕様ではないため,応答を Slack に返したい場合は 特定のチャンネル・チャットにメッセージを送る を参照して応答できるようにしてあげる必要があります.

また,引数を渡して処理を分岐させることもできません.
ただし,例えばチャンネルへの投稿をトリガとした (message.channels8) 場合に event.blocks.elements.elements.text に投稿したチャットの内容を含むため,うまく扱えばスラッシュコマンドのように引数として扱えるかもしれませんが,管理はかなり煩雑になると思われます.

接続先に指定できるサーバーの数も1つの Slack App につき 1 つまでのため,無料の範囲で量産するのも難しそうです.

  • あるチャンネルで投稿される全ての投稿を監視する
  • 特定の用途にのみ用いるチャンネルで処理を組み込む

といった実装の仕方が主なユースケースかもしれません.

Slack からの情報を受信する側は,接続検証のためにハンドシェイクを実装してあげる必要があります9

ここでは,トリガ発火によって Slack からサーバに POST されるデータをそのまま Slack の別のチャネルに送り返す実装をしてみます.

検証に使われる token は,スラッシュコマンドと同様に Basic Information > App Credentials > Verification Token を参照します.

postChat.gs
function doPost(e) {
  const jsonReq = JSON.parse(e.postData.contents);
  
  // Verification
  if (jsonReq.token == "xxxxxxxxxxxxxxxxxxxx") {
    
    // Post a chat
    const strReq = JSON.stringify(jsonReq);
    const jsonText = {"text": strReq};
    const jsonPayload = JSON.stringify(jsonText);

    UrlFetchApp.fetch("https://hooks.slack.com/services/xxxxxxxxxxxxxxxxxxxx", {
      "method": "post",
      "contentType": "application/json", 
      "payload": jsonPayload
    });

    // Handshake
    return ContentService.createTextOutput(jsonReq.challenge);
    
  } else {    
    throw new Error("Invalid request.");
  }
}

注意

  • UrlFetchApp.fetch の宛先に Slack Apps がイベントをリッスンしているチャネルを指定してしまうと無限ループが起きるので注意してください.
  • 上記では tokenWebhook URL をハードコーディングしていますが,実装する際はハードコーディングしない工夫をしてください.
  • 上記のまま実装すると token 等も含まれて投稿されるため,動作確認を行うチャンネルにはご注意ください.

今回トリガとなるイベントには message.channels8 を利用して,Slack App を適用したチャンネルに投稿があった場合に稼働するようにします.

event-subscriptions.PNG

Interactivity & Shortcuts

より高級なメッセージ投稿を行うことで,双方向的なやり取りを実装できます.
また自作アプリとの連携によって新しいモーダルを実装したり10,App Home に追加機能を持たせたり11することができます.

作りたいメッセージ/モーダル/App Home のイメージは,Block Kit Builder を用いることで簡単に膨らませることができます.

block-kit-builder.png

Slack 自体は適切に作成された blocks 要素を受信して高級な表示を行うだけですが,Block Kit Builder では左バーからボタンをクリックしたり,レビュー中の要素をドラッグ&ドロップしたりすることで簡単に blocks 要素を自動生成することができます.

Desktop/Mobile を切り替えれば,PC/スマホでどう見えるのかチェックしながら作成を行えます.
また,作ったメッセージやモーダルを Slack 上で確認したい場合は Send to Slack をクリックすることでテスト送信もできます.

ここでは Block Kit Builder で作成した blocks 要素をイベント API で設定した自動応答に含める実装をしてみます.
詳しい手順は Event API を参照してください.

Builder で作成した blocks
{
	"blocks": [
		{
			"type": "image",
			"title": {
				"type": "plain_text",
				"text": "I Need a Marg",
				"emoji": true
			},
			"image_url": "https://assets3.thrillist.com/v1/image/1682388/size/tl-horizontal_main.jpg",
			"alt_text": "marg"
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "This is a section block with an accessory image."
			},
			"accessory": {
				"type": "image",
				"image_url": "https://pbs.twimg.com/profile_images/625633822235693056/lNGUneLX_400x400.jpg",
				"alt_text": "cute cat"
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "Test block with users select"
			},
			"accessory": {
				"type": "users_select",
				"placeholder": {
					"type": "plain_text",
					"text": "Select a user",
					"emoji": true
				},
				"action_id": "users_select-action"
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "Section block with a timepicker"
			},
			"accessory": {
				"type": "timepicker",
				"initial_time": "13:37",
				"placeholder": {
					"type": "plain_text",
					"text": "Select time",
					"emoji": true
				},
				"action_id": "timepicker-action"
			}
		},
		{
			"type": "actions",
			"elements": [
				{
					"type": "button",
					"text": {
						"type": "plain_text",
						"text": "Click Me",
						"emoji": true
					},
					"value": "click_me_123",
					"action_id": "actionId-0"
				}
			]
		},
		{
			"type": "divider"
		}
	]
}
postGreatChat.gs
function doPost(e) {
  const jsonReq = JSON.parse(e.postData.contents);
  
  // Verification
  if (jsonReq.token == "xxxxxxxxxxxxxxxxxxxx") {
    
    // Post a great chat
    const jsonBlocks = {
      "blocks": [
        {
          "type": "image",
          "title": {
				  "type": "plain_text",
				  "text": "I Need a Marg",
				  "emoji": true
			    },
			    "image_url": "https://assets3.thrillist.com/v1/image/1682388/size/tl-horizontal_main.jpg",
			    "alt_text": "marg"
		    },
		    {
			    "type": "section",
			    "text": {
				    "type": "mrkdwn",
				    "text": "This is a section block with an accessory image."
			    },
			    "accessory": {
				    "type": "image",
				    "image_url": "https://pbs.twimg.com/profile_images/625633822235693056/lNGUneLX_400x400.jpg",
				    "alt_text": "cute cat"
			    }
		    },
		    {
			    "type": "section",
			    "text": {
				    "type": "mrkdwn",
				    "text": "Test block with users select"
			    },
			    "accessory": {
				    "type": "users_select",
				    "placeholder": {
					    "type": "plain_text",
					    "text": "Select a user",
					    "emoji": true
				    },
				    "action_id": "users_select-action"
			    }
		    },
		    {
			    "type": "section",
			    "text": {
				    "type": "mrkdwn",
				    "text": "Section block with a timepicker"
			    },
			    "accessory": {
				    "type": "timepicker",
				    "initial_time": "13:37",
				    "placeholder": {
					    "type": "plain_text",
					    "text": "Select time",
					    "emoji": true
				    },
				    "action_id": "timepicker-action"
			    }
		    },
		    {
			    "type": "actions",
			    "elements": [
				    {
					    "type": "button",
					    "text": {
						    "type": "plain_text",
						    "text": "Click Me",
						    "emoji": true
					    },
					    "value": "click_me_123",
					    "action_id": "actionId-0"
				    }
			    ]
		    },
		    {
			    "type": "divider"
		    }
	    ]
    };

    const jsonPayload = JSON.stringify(jsonBlocks);

    UrlFetchApp.fetch("https://hooks.slack.com/services/xxxxxxxxxxxxxxxxxxxx", {
      "method": "post",
      "contentType": "application/json", 
      "payload": jsonPayload
    });

    // Handshake
    return ContentService.createTextOutput(jsonReq.challenge);
    
  } else {    
    throw new Error("Invalid request.");
  }
}

以上で Block Kit で作成した高級なメッセージが自動投稿されるようになりますが,更にアクションを追加して機能を拡張します.

Slack App では,ショートカットやモーダル,ボタンやメニューなどでアクションが Slack 上で行われた際に処理を行うサーバーを1つだけ指定することができます12

ここでは GAS を使って先ほど自動送信されたメッセージの "type": "button" 要素に対するアクションを処理するサーバーも新たに作成します.

postAnotehrChat.gs
function doPost(e) {
  const jsonReq = JSON.parse(e.parameter.payload);
  
  // Verification
  if (jsonReq.token == "xxxxxxxxxxxxxxxxxxxx") {
    
    // Post a chat
    const ts = jsonReq.message.ts;
    // const webhookURL = jsonReq.response_url;
    const webhookURL = "https://hooks.slack.com/services/xxxxxxxxxxxxxxxxxxxx";
    const action_id = jsonReq.actions[0].action_id;
    const action_value = jsonReq.actions[0].value;

    if (action_id == "actionId-0") {
      const message = action_value + "が送信されました";
      const jsonText = {
        "text": message,
        "thread_ts": ts
      }

      const jsonPayload = JSON.stringify(jsonText);

      UrlFetchApp.fetch(webhookURL, {
        "method": "post",
        "contentType": "application/json", 
        "payload": jsonPayload
      }); 
    }

  } else {    
    throw new Error("Invalid request.");
  }
}

注意

  • イベント API では e.postData.contents を使っていましたが,Interactivity では e.parameter.payloadを使います.
  • const webhookURL = jsonReq.response_url; とすると,アクションを発生させたメッセージを上書きすることができます.
  • jsonReq.actions[0] のように actions はリストなので注意してください.送られてくる リクエストの中身を確認しながら実装するのが良いと思います.

greatChat.png

既存の Slack Apps を利用した拡張

独自の Slack App を利用した API の活用方法を先に書きましたが,基本的には既存の Slack Apps を優先的に使うことが推奨されます.

個人的に使いたいかも?と感じたものについて調べてみたのでこちらにまとめます.

Slack Developer Tools

sdt.PNG

Block Kit Builder からモーダルの見た目を確認するために必要になります.
その他にも /sdt whoami コマンドでワークスペース ID やユーザ ID を調査したり,/sdt docs で API ドキュメント13を Slack 上で参照することができます.

RSS

rss.PNG

言わずと知れた Slack へのニュース配信設定です.
App 追加後,受信したいチャンネルで /feed subscribe するだけで設定が完了します.
様々なサイトを登録できますが,登録しすぎると埋もれて読めなくなるだけだったりするので適当に数を絞ったり配信するチャンネルを変えたりするのが良いと思います.

また,日経電子版のトップニュースは NIKKEI for Slack を使うとよいと思います.

subscribe 用のコマンドを残しておきます.

/feed subscribe コマンド
# 日本のニュース系
## NHKニュース - 主要ニュース
/feed subscribe https://www.nhk.or.jp/rss/news/cat0.xml
## Nikkei Asia
/feed subscribe https://asia.nikkei.com/rss/feed/nar?_gl=1*17haoij*_ga*MTQ3MzYyOTE4OS4xNjkxMDM0MTg1*_ga_5H36ZEETNT*MTcwMTIyNTAwMS4xLjEuMTcwMTIyNTExNi4xOC4wLjA.
## 日テレニュース 
/feed subscribe https://news.ntv.co.jp/rss/index.rdf

# 世界のニュース系
## BBC - Top Stories
/feed subscribe https://feeds.bbci.co.uk/news/rss.xml
## CNN - Top Stories
/feed subscribe http://rss.cnn.com/rss/edition.rss
## CNBC - Top News
/feed subscribe https://search.cnbc.com/rs/search/combinedcms/view.xml?partnerId=wrss01&id=1

# 趣味・興味系
## Publickey1
/feed subscribe https://www.publickey1.jp/atom.xml
## ITmedia
/feed subscribe https://rss.itmedia.co.jp/rss/2.0/topstory.xml
## Gigazine
/feed subscribe https://gigazine.net/news/rss_2.0/

GitHub

github.png

GitHub と Slack を簡単に連携でき,リポジトリの更新を通知したりログを残すことができます.

詳しい機能や設定手順はこちらの記事が参考になります.

GitLab

gitlab.png

gitlab.com を利用している場合は,こちらの App を追加することで簡単に連携することができます.

self-managed な GitLab と連携するには 16.2 移行のバージョンを利用していてかつ Slack と疎通できる環境にある条件があり,自分で manifest から Slack App を作成14しなければ使えません.15

※ 一応,Slack 非公式な App で self-managed な GitLab と連携する方法もあるみたいですが,自分で作成した方が良いと思います...16

こちらでは,公式ドキュメントに沿って 16.5.0-ee.0 の GitLab との連携手順を示します.

GitLab の Admin Area のリンクから App を作成することで manifest を利用した自前の Slack App を簡単に作成できます.

  • GitLab の Admin AreaSetting > General > GitLab for Slack App を開きます.Create Slack App を押します.Slack API のページに移動しますが,URL を見ると manifest が送られていることが確認できます.

gitlab-slack-app-1.png

  • Slack API のページから Create an App を押し,利用する Slack ワークスペースを指定して App を作成します.作成後,ワークスペースにインストールします.
  • GitLab から送られた manifest を確認・編集したい場合は,App の App Manifest から行えます.
  • GitLab に戻り Enable GitLab for Slack app にチェック後,Slack App の App Credentials の値を書き写して Save changes を行うことで設定完了です.

gitlab-slack-app-2.png

個人的には GitLab を閉じた環境で利用しているためうまく使えそうにないですが...

Axe AI

axe-ai.PNG

OpenAI の GPT-3.5 および GPT-4 で構築された自然言語チャットボットです.
1 日に 10 回の質問までは無料で利用できます.
連続で投げた質問は関連性を保持して会話として返答してくれるみたいです.
また,日本語で聞いた場合は日本語でも返答してくれるみたいです.

axe-ai-chats.PNG

Slack と ChatGPT の連携は Open AI API特定のチャンネルからいろいろなサービスの処理を発生させる を参照して自分で実装してもよいのですが,自前で "operation_timeout" の対応をしないといけないのが面倒で,かつ 1 日に何回も利用するわけでもなかったので検討しました.

会話したデータは 1 年間保持され,削除依頼をすることもできるようですが,個人情報を流さないように十分に気を付ける必要があります.

Open Poll

open-poll.PNG

Slack で投票を簡単に行うことができます.

投票コマンド例
/poll "What's you favourite color ?" "Red" "Green" "Blue" "Yellow"

Workspace に設定する拡張

Slackbot

Slackbot は Slack App ではありませんが Slack を機能拡張する1つの方法となり得る感じたのでこちらに記載します.
アイディアはこちらの記事に基づきます.

Slackbot の設定は https://your-workspace.slack.com.slack.com/customize/slackbot から行います.

slackbot.png

個人情報や機密情報を覚えさせることは推奨できませんが,ランダムな応答を得たり備忘録を残すには役立つと感じました.

所感

長くなってしまいましたが,Slack には制限がありつつも様々な方法でチャットツール統合・拡張を行う手段があることがわかりました.

一方,スラッシュコマンドInteractivity & Shortcuts でリクエストに含まれるデータの構造が異なるなど多少の癖があることもわかりました.

また,閉じた環境で使っている self-managed なサーバとの連携は難しいと感じました.

これからどう応用できるか考えてみたいと思います.

  1. https://api.slack.com/authentication

  2. https://api.slack.com/messaging/sending

  3. https://api.slack.com/methods/conversations.list

  4. https://api.slack.com/methods/chat.postEphemeral

  5. https://api.slack.com/methods/chat.scheduleMessage

  6. https://api.slack.com/interactivity/handling#acknowledgment_response

  7. https://worldtimeapi.org/timezones

  8. https://api.slack.com/events/message.channels 2

  9. https://api.slack.com/apis/connections/events-api#handshake

  10. https://api.slack.com/automation/interactive-modals

  11. https://api.slack.com/lang/ja-jp/app-home-with-modal

  12. https://api.slack.com/messaging/interactivity

  13. https://api.slack.com/methods

  14. https://api.slack.com/reference/manifests#creating_apps

  15. https://docs.gitlab.com/ee/administration/settings/slack_app.html

  16. https://gitlab.com/gitlab-org/gitlab/-/issues/28164

3
1
2

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
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?