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

## この記事の目的

[Slack AI](https://slack.com/intl/ja-jp/blog/productivity/product-innovations-dreamforce-2023) が発表され，今後も利用が普及する見込みであることから，我が家にも Slack を導入する機運が高まりました．
Slack を**フリープランでも**フル活用するために今後拡張していけそうな機能を調べたので，備忘録のためにこの記事にまとめます．

:::note info
こちらの記事では [Slack API 公式ドキュメント](https://api.slack.com/) に準じて動作確認を行っています．
:::

:::note warn
こちらの記事では[無料で活用できる範囲](https://app.slack.com/plans)の検証しか行っていません．
よって以下は取り扱っていません．

- Workflows
- Next generation Slack platform

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

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

![slack-com-apps.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/44021a10-5e08-ff08-a56e-b2e2493b1dec.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/91abf660-ab5a-def3-ee67-b4917678c9f2.png)

![install-app-to-workspace.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/e1871ea8-4af0-0b3e-c608-730102ab025f.png)

![init-oauth-scop.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/ef391429-6790-3db7-fd77-606711e5139a.png)

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

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

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

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

#### [```api/chat.postMessage```](https://api.slack.com/methods/chat.postMessage)

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

```shell
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/d4d04e84-98d6-177a-37f5-34baf9774476.png)

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

```shell
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``` でスレッド返信できます．

```shell
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.postEphemeral```[^SLACK/API/chat.postEphemeral]，時間指定でメッセージを予約投稿する場合は ```chat.scheduleMessage```[^SLACK/API/chat.scheduleMessage] を利用することができます．

#### [Incoming Webhooks](https://api.slack.com/messaging/webhooks)

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

![incoming-webhooks.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/276baca4-0c9e-353a-435c-5ef813a39c6f.png)

```shell
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```](https://api.slack.com/methods/conversations.history)

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

```shell
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](https://api.slack.com/interactivity/slash-commands)

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

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

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

ここでは例として [Google Apps Script (GAS)](https://script.google.com/home) で [WorldTimeAPI](https://worldtimeapi.org/) を叩くアプリを作成し，Slack のメッセージ作成ボックスから ```/time``` コマンドを打ち込むことで WorldTimeAPI の ```datetime``` を取得します．
また，引数には任意の timezones[^TIMEAPI/TIMEZONES] を付与できるものとし，引数がない場合は ```Asia/Tokyo``` で問い合わせを行うものとします．

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

![verification-token.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/7b8f8068-ab15-7454-6f59-633279ed518e.png)

![gas-postTime.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/e3f2b9c0-2805-aaed-5643-5cf98ecfbcc4.png)

```javascript: 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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/969fd6f1-ed62-2e26-4e89-0744d71ff442.png)

#### [Event API](https://api.slack.com/apis/connections/events-api)

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

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

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

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

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

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

Slack からの情報を受信する側は，接続検証のためにハンドシェイクを実装してあげる必要があります[^SLACK/EVENT-API-HANDSHAKE]．

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

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

```javascript: 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 がイベントをリッスンしているチャネルを指定してしまうと無限ループが起きるので注意してください．
- 上記では ```token``` と ```Webhook URL``` をハードコーディングしていますが，実装する際はハードコーディングしない工夫をしてください．
- 上記のまま実装すると ```token``` 等も含まれて投稿されるため，動作確認を行うチャンネルにはご注意ください．

今回トリガとなるイベントには ```message.channels```[^SLACK/EVENT-API-MESSAGE] を利用して，Slack App を適用したチャンネルに投稿があった場合に稼働するようにします．

![event-subscriptions.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/75f377ba-55b8-ab5c-42f3-bef98e7438d8.png)

#### Interactivity & Shortcuts

より高級なメッセージ投稿を行うことで，双方向的なやり取りを実装できます．
また自作アプリとの連携によって新しいモーダルを実装したり[^SLACK/INTERACTIVE-MODAL]，App Home に追加機能を持たせたり[^SLACK/APPHOME]することができます．

作りたいメッセージ/モーダル/App Home のイメージは，[Block Kit Builder](https://app.slack.com/block-kit-builder/) を用いることで簡単に膨らませることができます．

![block-kit-builder.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/d5df860e-5e63-1d8f-4295-033dfd4cae83.png)

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

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

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

<details><summary>Builder で作成した blocks</summary><div>

```json
{
	"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"
		}
	]
}
```

</div></details>

```javascript: 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つだけ指定することができます[^SLACK/INTERACTIVITY]．

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

```javascript: 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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/5913a3f1-a380-5ad2-c8a7-5a437776f1a8.png)

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

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

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

### [Slack Developer Tools](https://sdt.builtbyslack.com/)

![sdt.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/bff004af-aba0-04cf-e9c9-f9a08c50d582.png)

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

### RSS

![rss.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/0f807693-ef19-6976-f4ff-a806b4d9bce5.png)

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

また，日経電子版のトップニュースは [NIKKEI for Slack](https://chatbot.nikkei.com/) を使うとよいと思います．

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

```shell: /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](https://slack.github.com/)

![github.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/b5f6c11e-9c32-2a61-2ed6-bac5224eb892.png)

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

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

https://qiita.com/unsoluble_sugar/items/e42ac352400598a24915

### [GitLab](https://about.gitlab.com/solutions/slack/)

![gitlab.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/46cecbce-a787-67df-d454-abdbd562c529.png)

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

**self-managed な GitLab と連携するには 16.2 移行のバージョンを利用していてかつ Slack と疎通できる環境にある条件があり，自分で manifest から Slack App を作成[^SLACK/MANIFESTS]しなければ使えません．**[^GITLAB/SLACKAPP]

※ 一応，Slack 非公式な App で self-managed な GitLab と連携する方法もあるみたいですが，自分で作成した方が良いと思います...[^GITLAB/issues-28164]

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

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

- GitLab の ```Admin Area``` で ```Setting``` > ```General``` > ```GitLab for Slack App``` を開きます．```Create Slack App``` を押します．Slack API のページに移動しますが，URL を見ると manifest が送られていることが確認できます．

![gitlab-slack-app-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/99947f86-443f-bf68-b209-1541af3c7ba8.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/78cf65e8-3046-f033-4fea-d3598c1cc3a3.png)

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

### [Axe AI](https://axe-ai.com/)

![axe-ai.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/503452a8-dff9-7dfb-622e-e8244b9bd5ee.png)

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

![axe-ai-chats.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/e9543a10-dc80-a131-48b0-7a5572da8aad.png)

Slack と ChatGPT の連携は [Open AI API](https://platform.openai.com/docs/api-reference/introduction) と [特定のチャンネルからいろいろなサービスの処理を発生させる](#特定のチャンネルからいろいろなサービスの処理を発生させる) を参照して自分で実装してもよいのですが，自前で ```"operation_timeout"``` の対応をしないといけないのが面倒で，かつ 1 日に何回も利用するわけでもなかったので検討しました．

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

### [Open Poll](https://openpoll.slack.alcor.space/)

![open-poll.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/b586cb22-f385-0ed4-ee73-6d8bcf804fa2.png)

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

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

## Workspace に設定する拡張

### [Slackbot](https://slack.com/intl/ja-jp/help/articles/202026038-Slackbot-%E5%85%A5%E9%96%80)

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

https://ascii.jp/elem/000/004/073/4073306/

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

![slackbot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/278a7d25-66cc-ea0c-817f-d2c8a3fe48df.png)

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

## 所感

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

一方，[スラッシュコマンド](#slash-commands) と [Interactivity & Shortcuts](#interactivity--shortcuts) でリクエストに含まれるデータの構造が異なるなど多少の癖があることもわかりました．

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

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

[^SLACK/AUTH-GUIDE]: https://api.slack.com/authentication
[^SLACK/SEND-MESSAGE]: https://api.slack.com/messaging/sending
[^SLACK/API/conversations.list]: https://api.slack.com/methods/conversations.list
[^SLACK/API/chat.postEphemeral]: https://api.slack.com/methods/chat.postEphemeral
[^SLACK/API/chat.scheduleMessage]: https://api.slack.com/methods/chat.scheduleMessage
[^SLACK/ACK_RESPONSE]: https://api.slack.com/interactivity/handling#acknowledgment_response
[^TIMEAPI/TIMEZONES]: https://worldtimeapi.org/timezones
[^SLACK/EVENT-API-MESSAGE]: https://api.slack.com/events/message.channels
[^SLACK/EVENT-API-HANDSHAKE]: https://api.slack.com/apis/connections/events-api#handshake
[^SLACK/INTERACTIVE-MODAL]: https://api.slack.com/automation/interactive-modals
[^SLACK/APPHOME]: https://api.slack.com/lang/ja-jp/app-home-with-modal
[^SLACK/INTERACTIVITY]: https://api.slack.com/messaging/interactivity
[^SLACK/API]: https://api.slack.com/methods
[^GITLAB/SLACKAPP]: https://docs.gitlab.com/ee/administration/settings/slack_app.html
[^SLACK/MANIFESTS]: https://api.slack.com/reference/manifests#creating_apps
[^GITLAB/issues-28164]: https://gitlab.com/gitlab-org/gitlab/-/issues/28164