Slackの新着絵文字をお知らせするBotをGASで作る

# はじめに

特定の個人を攻撃したり風紀を乱すような絵文字を追加する**従業員の良からぬ行動**によって、社内の**Slackのモラルが下がる**、場合や内容によっては**ハラスメントにつながる**、というお話を小耳に挟みまして、こまめに絵文字を監視するより「絵文字追加したら皆に知れ渡るよ」ということで防止する、というアイディアを目の当たりにしたので、モラル崩壊云々はさておきネタとしても面白く、これは良いと思い早速作ってみました。

## 今回の流れ

1. **Google Cloud Platform**(以下**GCP**) でプロジェクトを作る
    1. OAuth同意画面を作る
1. **Google Apps Script**(以下 **GAS** )で外部から呼び出し可能なプロジェクトを作る
    1. GASのプロジェクトを新規作成する
    1. GCPのプロジェクトを関連付ける
    1. GASでHTTPのPOSTリクエストを受け付けるコードを用意する
    1. GASをウェブアプリとしてデプロイして外部から呼ばれるようにする
1. GASで**Slack Event**を受信して**Incoming Webhook**で通知する
    1.  **Slack**で**Slack App**を作る
    1. **Incoming Webhook**を有効化する
    1. **Event Subscriptions**を有効化して**Slack Events**をGASで受信できるようにしておく
    1. 受信した**Slack Event**をGASで処理する

1.1～3.2までは[**色々と検索したり記事を参照して**](https://qiita.com/search?sort=&q=GAS+%E3%82%A6%E3%82%A7%E3%83%96%E3%82%A2%E3%83%97%E3%83%AA+)各自で頑張ってみてください。🙋‍♀️
また、先にGitHubのソースを公開しておきます。
- **[7032/gas_emojichecker](https://github.com/7032/gas_emojichecker)**

以下、本記事は下記(及びそれらを準備する知識)が揃っている前提となります。
### 前提として揃っているもの
- ウェブアプリとしてデプロイできる**GAS**
- **Bot**として`Incoming Webhookが出来る`**Slack App**

# 実際にGASでBotを作ってみる
## GAS側でSlack Eventに対応する準備をする
手始めに、GASの `doPost` 関数を以下のようにしてください。
```javascript:doPost
function  doPost(e) {
  const rowData = e.postData.getDataAsString();
  const jsonData = JSON.parse(rowData);

  //  return a response as HTML to confirm the challenge
  if (jsonData['challenge']) {
    const output = ContentService.createTextOutput(jsonData['challenge']);
    output.setMimeType(ContentService.MimeType.TEXT);
    return output;
  }
  const output = ContentService.createTextOutput("ok");
  output.setMimeType(ContentService.MimeType.TEXT);
  return output;
}
```
### コード解説
`doPost` は引数で受け取ったオブジェクトにPOSTで受信したデータが含まれているので、これをテキストとして読み込み、JSONとしてパースしてオブジェクトとして扱います。
```javascript
  const rowData = e.postData.getDataAsString();
  const jsonData = JSON.parse(rowData);
```
`Slack Bot` の **[Events API](Slack Events API)** はイベント通知先として指定したURLが正しいものかどうかの確認で下記のようなJSONを渡してくるので
```json
{
    "token": "Jhj5dZrVaK7ZwHHjRyZWjbDl",
    "challenge": "3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P",
    "type": "url_verification"
}
```
この `challenge` の部分をそっくりそのまま返す処理が必要になります。
```javascript
  if (jsonData['challenge']) {
    const output = ContentService.createTextOutput(jsonData['challenge']);
    output.setMimeType(ContentService.MimeType.TEXT);
    return output;
  }
```

💡GASを書き換えたら必ず**デプロイ**して新しいコードで書かれたウェブアプリを更新しましょう。

## Slack App で Event Subscriptions を有効化する
### Slack Events を GAS で受信できるようにする
1. **[Slack Appの一覧](https://api.slack.com/apps)**で、対応させる**Slack App**を選びます。
1. 左側メニュー `Event Subscriptions` をクリックしイベント設定画面へ入ります。
1. `Enable Events` を**ON**にし、`Request URL` に、GASでデプロイしたウェブアプリのURLを入力します。
ここでGASがJSONに含まれるチャレンジを正しく返していれば、入力したURLに `Verified` と表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2998428/520a0c3c-6be2-1dbf-64de-e8d741626c88.png)

### 受信するイベントを指定する
**Subscribe to bot events**をクリックして開き、`Add Bot User Event`のボタンをクリックしてイベントを追加します。
追加するイベントは**emoji_changed**です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2998428/dbf0cfe9-5508-97ed-07c9-dc4fa4504010.png)

イベントが追加されると、OAuthのスコープに `emoji:read` が自動的に追加されます。

以上で、Slackワークスペースに絵文字が追加されたら、**Slackイベント**`emoji_changed`が発生し、GASで作られたウェブアプリへPOSTされるところまで出来ました。
あとは、このPOSTを元に新しい絵文字が来たことを**Incoming Webhook**を使って通知するだけですね。😉

## イベントの内容をSlackへ通知する
**Slack App**からSlackへ通知を行うには**Incoming Webhook**を使うだけでOKです。
では早速、GASのコードを以下にまるっと置き換えてみましょう。
**[7032/gas_emojichecker/main.gs](https://github.com/7032/gas_emojichecker/blob/main/main.gs)**

### GASのスクリプトプロパティを設定する
スクリプトプロパティ `Webhook-EMOJI` に、Slack Appで設定した **`Incoming Webhook`のURL**を設定してください。

### SlackへのIncoming Webhookによる通知
関数 `_SendMessageToSlack(propName,message)` は引数を2つとり、以下の処理を行います。
- `propName`で指定されたスクリプトプロパティから値を読み出す
- 読み出したスクリプトプロパティで指定されたURLへSlackメッセージとして`message`で指定された内容を通知する
    - HTTPヘッダー `Content-Type` に `application/json` を指定する
    - JSONの下記の形式をボディとしてPOSTで送信する
```json:POSTのボディとして送信する内容(JSON)
{ "text" : "送信したいメッセージの内容" }
```
実際の処理は以下の部分となります。
```javascript:
function  _SendMessageToSlack(propName,message) {
  const propService = PropertiesService.getScriptProperties();
  const url  = propService.getProperty(propName);
  try {
    var response  = UrlFetchApp.fetch(
                      //  URL stored in the property
                      url,
                      //  Options set to header and payload
                      {
                        "method" :            "POST",
                        "headers":            { "Content-Type" : "application/json", },
                        "payload":            JSON.stringify({ "text" : message }),
                        "muteHttpExceptions": true
                      }
                    );
  }
  catch (e) {
    console.log("[Slack:WebHook error]"+JSON.stringify(e)+"\nURL:"+url);
  }
}
```

### 受信したSlack Eventを処理してSlackへ通知する処理
Slackイベント `emoji_changed` は、以下のようなJSONをPOSTしてきます。
```json
{ "event" : {
    "type": "emoji_changed",
    "name": "絵文字の名前。コロンで挟まれた部分",
    "value": "新しい絵文字が投稿された場合のURL、またはエイリアス元の絵文字名"
  }
}
```
| JSONキー | 内容 |
|---|---|
| event.type | 絵文字、絵文字のエイリアスが追加された場合、`emoji_changed` が入ります。 |
| event.name | 絵文字の名前 = コロンで挟まれた `:hogehoge:` の `hoge` 部分が入ります。|
| event.value | 新規で絵文字が追加された場合は、その画像の保存先のURLが入ります。エイリアスの場合は `alias:hoge` という形式の文字列が入ります。 `alias:` というプレフィックスがついてエイリアス元の絵文字名が入ってくるので、ここで「新規で投稿された絵文字」か「エイリアスが追加されたのみ」かを判断します。|

以上を元に通知メッセージを構築して、先述の `_SendMessageToSlack` 関数へ渡して通知を行います。
この処理のメインとなる部分が以下です。
```javascript
if (jsonData["event"]) {
  if (jsonData["event"]["type"]) {
    switch(jsonData.event.type) {
    //  EMOJI
    case  "emoji_changed": {
      //  Add EMOJI
      const emojiName = jsonData.event.name;
      const emojiVal  = jsonData.event.value;
      const emojiAli  = "alias:";
      var   strMessage= "*[New EMOJI]* :"+emojiName+": is added as *`:"+emojiName+":`* !!!\n";
      if (emojiVal.slice(0,emojiAli.length) == emojiAli) {
        const emojiOrginal  = emojiVal.slice(emojiAli.length);
        strMessage  +=  "This is just an *alias* of *`:"+emojiOrginal+":`* :"+emojiOrginal+":";
      } else {
        strMessage  +=  "Original picture is "+emojiVal;
      }
      _SendMessageToSlack("Webhook-EMOJI",strMessage);
      }
      break;
    }
  }
}
```


以上で、
- 絵文字が追加されると **Slack Event** `emoji_changed` が発生する
- **Slack App**が上記の**Slack Event**を受け取る
- 受け取った内容を元に**Incoming Webhook**で通知する
という流れで、**ワークスペースへ絵文字が追加されたら特定のチャンネルで通知する**ということが出来る様になりました。🙌🙌🙌

# さいごに
ITのお知らせのチャンネルなどに流しても良いですし、絵文字の追加が活発なワークスペースであればネタチャンネルとして新着絵文字専用チャンネルなどを作っても良いかもしれませんね。