Slack 特定のメッセージにユーザーが自動リアクションする機能をGASで実装する

# はじめに
こんにちは！
相変わらずGASに魂を売っている @Keichan_15 です！笑 
(おそらくボチボチ沈静化していく気がしています…:frowning2:)

最近とある思い付きで、Slackのあるチャンネル内で特定のメッセージが投稿された際に、GASを使用して自動でリアクションしてみたいな～と思うことがありました。
リアクションされると皆な嬉しい気持ちになりますよね！そうですよね？？:relaxed:

実際に実装した時に中々GASで自動リアクションを行う記事や資料が見当たらなかったので、備忘録含めてまとめていきたいと思います！当方GAS初心者で手探りの状態 & 多くの方々の助けをお借りして作成することができました！ありがとうございます:bow:

# 完成写真
![2023-02-27 (2).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2798073/b62a36f1-08d5-2a02-b120-a624014d189c.jpeg)

このように「test」と入力した瞬間に、自動で元々設定していたリアクションをそのメッセージに付与するといった内容です！(動画で無くて申し訳ない:bow:)

ここで重要なのが、今回の記事では **リアクションするのはBotではなく、ご自身のユーザーで自動リアクション** します。この点、Botが自動でリアクションする記事は多く見つかったのですが、ユーザーが自動でリアクションする記事が無くてかなり手こずりました…。

# 実装
## 実装の流れ
まず、実装の流れについてまとめていきます。

1. 自動リアクション用のSlack Appを作成する
2. Outgoing Webhookを使用して、Slackで投稿された「メッセージ」「チャンネル」「ユーザーID」「投稿時間」を取得
3. GASで自動リアクション付与の実装

と、ざっくりこんな感じで実装できます。順番に見ていきましょう！

## Slack アプリ作成
まずは自動リアクション用のSlack アプリを作成します。作成の仕方は過去に僕が執筆した [別記事](https://qiita.com/Keichan_15/items/a4facca87d178f356397#1-1-slack%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AE%E4%BD%9C%E6%88%90) を参考に導入をお願い致します！:ok_hand:

https://qiita.com/Keichan_15/items/a4facca87d178f356397#1-1-slack%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AE%E4%BD%9C%E6%88%90

上記の記事のなかで、今回一部設定を変更しなければならない箇所があります。

まずScopesの項目ですが、今回は **User Token Scopes** 側でScopeを追加する必要があります。Bot Token Scopesの場合、返信は **Botが返信** することになりますが、User Token Scopesの場合は **ユーザーが返信** するようになります。

また、追加するScopeに一部変更があります。今回追加するScopeは、

- **channels:history**
- **reactions:read**
- **reactions:write**

の3つとなります。

![2023-03-05 (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2798073/03a18ea6-f7dc-b69b-19d5-eb615fefe24b.png)

workspaceへSlackアプリをインストールするとTokenが発行されますが、今回使用するのは **User OAuth Token** のTokenになるので注意が必要です。

![2023-03-05 (3).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2798073/a7bcf132-ae07-8889-d8e8-dc2f20476ef0.jpeg)

それではGASを書いていきましょう～！

## GAS 実装
今回使用するのは、Slack APIの **reactions.add** になります。

https://api.slack.com/methods/reactions.add

引数で必須となるのは主に4つです。

- **token** (先ほど生成した User OAuth Token)
- **channel** (リアクションを付与したいチャンネルID)
- **name** (リアクション名)
- **timestamp** (リアクションを付与するメッセージの投稿時刻)

中でも **timestamp** ですが、取得したエポックマイクロ秒を **小数点込みのエポックマイクロ秒** に変換しなければなりません。

> _Timestamp of the message to add reaction to._
>
> _Example_
> _1234567890.123456_

ex) ``1234567890123456 → 1234567890.123456``

~~今回は取得したtimestampを ``timestamp / 1000000`` することで、小数点付エポックマイクロ秒に変換します。~~
→ こちらの `e.parameter.timestamp` 自動的に小数点付エポックマイクロ秒で取得してました…。この前まで整数値で取得してた気が…。僕の勘違いかな、なんで？？:frowning2:(3/6現在)

それでは早速実装していきましょう！GASに下記コードを実装してください！

```javascript:hogehoge.gs
const prop = PropertiesService.getScriptProperties().getProperties();
const token = prop.TOKEN;
const channelID = prop.CHANNEL;

const lock = LockService.getScriptLock();

function doPost(e) {
  const message = e.parameter.text;
  const thread_ts = e.parameter.timestamp;

  if (message != "test") return;

  lock.tryLock(10000);

  const emojis = ['otsukaresamadesu', 'arigato', 'mario', 'parrot_dad', 'f_like', 'kirby', 'onepiece'];

  // 2 ~ 6 までの整数値をランダムで生成
  let num = Math.floor(Math.random() * 5) + 2; 

  for (let i = 0; i < num; i++) {

    const channel = `https://slack.com/api/reactions.add?channel=${channelID}&name=${emojis[i]}&timestamp=${thread_ts}&pretty=1`;
    const options = { "headers": { "Authorization": `Bearer ${token}` }, "method": "post" };

    UrlFetchApp.fetch(channel, options);

  }

  lock.releaseLock();
}
```

## 解説

実装コードについて順番に解説します。

```javascript:hogehoge.gs
const prop = PropertiesService.getScriptProperties().getProperties();
const token = prop.TOKEN;
const channelID = prop.CHANNEL;
```

``PropertiesService.getScriptProperties().getProperties();`` はスクリプトプロパティを取得する際に必要な記述になります。
今回登録するスクリプトプロパティは2つです。

- プロパティ名: **TOKEN** / 値: **User OAuth Token**
- プロパティ名: **CHANNEL** / 値: **リアクションを付与する ``channel_id``**

channel_idの確認方法はいくつか存在しますが、手っ取り早いのはSlackの ``チャンネル名をクリック → チャンネルID をコピー`` だと思います。

![2023-03-05 (6).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2798073/830a5498-d2c2-967d-f39d-7ebbf38ca5bc.jpeg)

そしてスクリプトプロパティに設定した値を、それぞれ ``token`` と ``channelID`` の変数に格納しています。

```javascript:hogehoge.gs
const lock = LockService.getScriptLock();
```

排他処理を行っています。調べてみたところ、GASには **LockService** と呼ばれるクラスがありました。

例えば大規模なSlackチャンネルの場合、リアクションを付与したいメッセージが同時に複数投稿された際にリアクションが付与されない問題が発生します(処理が上書きされる)
これを防ぐために、ある時間内においては1つの処理のみ動くようにすることで他のユーザーの処理を排他しています。

```javascript:hogehoge.gs
function doPost(e) {
  const message = e.parameter.text;
  const thread_ts = e.parameter.timestamp;
```

`doPost` 関数を定義し、後に紹介するOutgoing Webhookで受け取った各データを変数に格納しています。 `doPost` 関数の引数である `e` から、入力したメッセージ内容が登録されている `text` と、メッセージの投稿時刻である `timestamp` を取得しています。

~~さらに `timestamp` は小数点付エポックマイクロ秒への変換が必要なため、1000000で割った結果を変数に格納することで解決しています。~~

```javascript:hogehoge.gs
  if (message != "test") return;
```

ここでは **test** というメッセージ **以外** が投稿された場合は return するようにしています。要するに **完全一致** としています。この部分に関しては別途 `include` などを使用するなど、かなりカスタマイズできるのではないでしょうか。

```javascript:hogehoge.gs
  lock.tryLock(10000);
```

他の処理が終わる待ち時間を設定します。ここでは10秒間にしています。

```javascript:hogehoge.gs
const emojis = ['otsukaresamadesu', 'arigato', 'mario', 'parrot_dad', 'f_like', 'kirby', 'onepiece'];
```

付与する絵文字をあらかじめ配列で登録しておきます。付与したい絵文字名の確認方法ですが、何かしら適当にメッセージをチャンネルに投稿し、そのメッセージにリアクションを行った後にリアクションにカーソルを重ねると絵文字の名前が確認できます(写真で言うところの `arigato`)

![2023-03-05 (8).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2798073/144d1ca1-71f9-dc54-0861-2d8735276369.png)

```javascript:hogehoge.gs
  let num = Math.floor(Math.random() * 5) + 2; 
```

ここは個人的なギミック部分です。2 ~ 6までの整数をランダムで生成し、結果を変数に代入しています。
整数の値が2以上である理由は後述のコードから確認できますが、配列の要素[0]番目と[1]番目を一律リアクション付与させるためです。日頃の運次第ではMAX6つのリアクションを獲得することができるということですね！笑

```javascript:hogehoge.gs
  for (let i = 0; i < num; i++) {

    const channel = `https://slack.com/api/reactions.add?channel=${channelID}&name=${emojis[i]}&timestamp=${thread_ts}&pretty=1`;
    const options = { "headers": { "Authorization": `Bearer ${token}` }, "method": "post" };

    UrlFetchApp.fetch(channel, options);

  }
```

まず乱数生成で得た整数を上限値として、for文でリアクションをループさせています。これにより連続して複数のリアクションが可能になります。

そして、APIを叩くにあたって必要な `URL` と `option` について設定しています。
URLに関しては、事前に代入していた変数をURLの各部分に変数展開を用いて設定しました。JavaScriptにも変数展開ってあったんですね…。普段Rubyを触っていたので初めて知り勉強になりました。

ちなみにSlack APIにはTesterがあるので、試しに叩いてみるとより分かりやすいかもしれません。Testerの使用方法については本記事の趣旨から逸れるため、申し訳ないのですが今回は省略します。

https://api.slack.com/methods/reactions.add/test

それぞれ設定ができたら、`UrlFetchApp.fetch` でHTTPリクエストを送信します。引数に指定したURLにHTTPリクエストが実行され、サーバーからのレスポンスが戻り値として取得できます。

https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app?hl=ja

```javascript:hogehoge.gs
  lock.releaseLock();
```

最後に排他処理のロックを開放してあげましょう。GASの排他処理については下記の記事がかなり参考になりました。

https://qiita.com/kyamadahoge/items/f5d3fafb2eea97af42fe

## デプロイしてOutgoing Webhookに設定

こちらも [別記事](https://qiita.com/Keichan_15/items/a4facca87d178f356397#4-%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4url%E3%82%92outgoing-webhook%E3%81%AB%E8%A8%AD%E5%AE%9A) に方法を記載していますので、こちらの通りに行って頂ければ問題ありません！

https://qiita.com/Keichan_15/items/a4facca87d178f356397#4-%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4url%E3%82%92outgoing-webhook%E3%81%AB%E8%A8%AD%E5%AE%9A

こちらで完成です！testと投稿して頂くと、ユーザーが自動でリアクションしてくれるようになりました！:clap:

![2023-03-05 (10).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2798073/1611a04c-d199-d7b2-30c9-d2dd9aafcab2.jpeg)

# おわりに
今回はGASでの自動リアクションについて解説してきました。

SlackやTeamsなどの顔の見えないチャットツールにおいて、リアクションは心理的安全性を高める上で非常に効果的です。実際の業務において退勤時のメッセージ投稿に自動リアクションする機能を実装したところ、職場の方々からは「リアクションがあることでどこか嬉しい気持ちになる」と嬉しいお言葉も頂きました。やって良かったナ…。:relaxed:

反応は大袈裟なくらいが丁度良いって言葉もありますけど、現代のチャットツール文化においては必然的なことなのかもしれませんね。

おしまい。