Slackの新着絵文字と登録数ランキングをお知らせするBotをGASで作る(Enterprise Grid用)

# はじめに
[Slackの新着絵文字をお知らせするBotをGASで作る](https://qiita.com/7032/items/2e13c1321aae7d3215fe)の続きです👈
今回は[Slack Enterprise Grid](https://slack.com/intl/ja-jp/enterprise)固有の機能を使って、さらに `絵文字で盛り上がるインターナルコミュニケーション` の活用を模索します。

今回のメニューはこんな感じです。
1. 「この新着絵文字は**誰が追加したのか**」を可視化する
1. その月・その週など**特定の期間に作られた全絵文字を可視化**する
1. 「月間**絵文字登録者ランキング**」を作って社内を盛り上げる

尚、今回は **[Slack Enterprise Grid](https://slack.com/intl/ja-jp/enterprise)固有の機能を使います** ので、[Slack Enterprise Grid](https://slack.com/intl/ja-jp/enterprise)を契約されているワークスペースのみが対象となります。この点ご留意＆ご了承ください。

それでは早速参りましょう。

今回のソースコードは **[7032/gas_emojichecker_enterprise](https://github.com/7032/gas_emojichecker_enterprise)** です。

# 「この絵文字は誰が追加したのか」を取得する
**[Slack Enterprise Grid](https://slack.com/intl/ja-jp/enterprise)** 固有の機能のひとつとして **[Admin 向けAPI](https://api.slack.com/admins#apis)** があります。これは Slack のかなりの機能について Rest API で色々な中身を取得できるAPI群です。その網羅範囲は、これらを利用する為だけに Slack Enterprise Grid を契約してもお釣りがくるレベルかと思います。
## 絵文字の詳細を取得する
その多機能なAdmin向けAPI群の一つが**ワークスペースに登録されている絵文字の詳細を含むリストの取得**です。
Enterprise Grid 未満で使用できる [`emoji.list`](https://api.slack.com/methods/emoji.list) というAPIもありますが、これは *登録されている絵文字の一覧を返すのみ* です。
一方で、 [**`admin.emoji.list`**](https://api.slack.com/methods/admin.emoji.list)は、各絵文字を **「いつ」「誰が」登録したのか** といった詳細も取得することが出来ます。
```http:admin.emoji.list
GET https://slack.com/api/admin.emoji.list
```
[APIの説明に表示されている戻り値の例](https://api.slack.com/methods/admin.emoji.list#examples)
```json:admin.emoji.listの戻り値の例
{
    "ok": true,
    "emoji": {
        "workflow": {
            "url": "https://emoji.slack-edge.com/TM315QLU8/workflow/530de66adccc59c5.png",
            "date_created": 1591720632,
            "uploaded_by": "WLWLQDAL9"
        },
        "welcome": {
            "url": "https://emoji.slack-edge.com/TM315QLU8/welcome/763d3659699d2ef7.gif",
            "date_created": 1593383451,
            "uploaded_by": "WPU7MCTFH"
        },
 ```
では早速これを活用して、現在あなたのワークスペースに登録されている全ての絵文字の情報を取得してみましょう。
## admin.emoji.list を使う為の準備
[**`admin.emoji.list`**](https://api.slack.com/methods/admin.emoji.list) を呼ぶためには、Botに [**`admin.teams:read`**](https://api.slack.com/scopes/admin.teams:read)の権限が必要になります。
なのでまずはこの権限をBotに付与して、APIを使用できるOAuthトークンを取得しましょう。
- Bot選択画面[Your Apps](https://api.slack.com/apps)で、権限を付与するBotを選択する
- **`OAuth & Permissions`** を選択し、**User Token Scopes** に **admin.teams:read** を付与する
- Botをワークスペースにインストールする

これで、APIを呼ぶための **OAuth Token** を取得できます。
尚、**`Bot User OAuth Token`** なので、ワークスペースの管理者権限の方がログインした状態でOAuthによる「権限行使の承認」を行う必要があります。
OAuthでの承認が完了したら **`Bot User OAuth Token`** にトークンが入りますので、**スクリプトプロパティ** に値を保存しておきましょう。
下記のソースコードでは **`api-token`** という名前でスクリプトプロパティに保存されている前提としてあります。

```javascript:全絵文字の取得
//  get current all emoji
function  _peekAllCurrentEmoji() {
  var allList = {};
  var pageToken = "";
  var fLoop     = true;
  const apiToken  = PropertiesService.getScriptProperties().getProperty("api-token");
  for(;fLoop;) {
    var url = "https://slack.com/api/admin.emoji.list?limit=1000";
    if (pageToken.length > 0) {
      url +=  "&cursor="
          + pageToken;
    }
    const response  = UrlFetchApp.fetch(
                        url,
                        { "method" : "GET",
                          "headers" : {
                            "Authorization" : "Bearer " + apiToken
                          },
                          "muteHttpExceptions" : true
                        }
                      );
    const resultTxt = response.getContentText();
    const resultJson= JSON.parse(resultTxt);
    pageToken = resultJson['response_metadata']['next_cursor'];
    fLoop = false;
    if (pageToken.length > 0) {
      fLoop = true;
    }
    Object.assign(allList,resultJson['emoji']);
  }
  return  allList;
}
```
上記は、シンプルに [**`https://api.slack.com/methods/admin.emoji.list`**](https://api.slack.com/methods/admin.emoji.list) を呼んで、その結果を全て結合して返しているのみです。
[**`https://api.slack.com/methods/admin.emoji.list`**]は引数 ``limit`` で指定した数までしか最大で値を返しません。Enterprise Gird ともなると **数百～数千の絵文字が登録されていることも珍しくない** ので、APIを複数回呼ぶ必要があります。

取得した一覧は絵文字の名前をキーとして、その絵文字に関する詳細を値とする連想配列で格納されます。
```json:admin.emoji.listの戻り値の例
{
    "ok": true,
    "emoji": {
        "workflow": {
            "url": "https://emoji.slack-edge.com/TM315QLU8/workflow/530de66adccc59c5.png",
            "date_created": 1591720632,
            "uploaded_by": "WLWLQDAL9"
        },
    ....
}
```
上記の場合、キーとなる **`"emoji"`** が絵文字名で、 Slack上の表現では **:emoji:** で表現される絵文字についての詳細となります。
値部分には以下が入ります。
| キー | 値の内容 |
|---|---|
| **url** | 絵文字として登録された**画像のURL**。絵文字が画像の登録ではなくエイリアスの登録の場合は、**`"https://emoji...."`** という形式ではなく **`"alias:hoge"`** となり、**hoge** 部分がオリジナルの絵文字を指します。 |
| **date_created** | その絵文字が**登録された日時** (unix_time / UTC) |
| **uploaded_by** | その絵文字を **登録したアカウント**の Slack ID |

それでは、早速これを使って前回の [Slackの新着絵文字をお知らせするBotをGASで作る](https://qiita.com/7032/items/2e13c1321aae7d3215fe) を改良してみましょう。
まずは、更に以下の関数を追加します。
```javascript:この絵文字は誰が追加したかを返す関数
//  get the Slack account who did register the emoji.
//    propName : label of Script Property that stored the API token to access Admin API.
//    emoji    : emoji name
function  _getOriginalRegistererOfEmoji(propName,emoji) {
  const allList = _peekAllCurrentEmoji(propName); // 登録されている全部の絵文字を取得する
  const result  = allList[emoji]; // 指定された絵文字についての情報を取得する
  if (result != null) {
    if (result['uploaded_by']) {
      return  result['uploaded_by']; // どのアカウントが登録したかを返す
    }
  }
}
```
今回はシンプルに、一度全部の絵文字を取得した後に、その中から指定された絵文字についての情報を取得する様にしました。但し、あまりに多くの絵文字が登録されている場合、APIの呼び出し回数が多くなり、それだけ負荷もかかりますので、
- Event通知を或る程度溜めてから通知する
- 絵文字一覧は定期的に取得して別のDBへ保存するようにする

など工夫されると良いかと思います。
そして、取得したSlackアカウントをメッセージに挿入します。**Slack ID** は Incoming Webhook のメッセージ中で **`<@Slack ID>`** という形式で挿入すると、実際のメッセージ上ではメンションとして表示されますので、今回はこれをそのまま活用します。
```javascript:「誰が追加したか」を追加
        var   strMessage= "*[New EMOJI]* :"+emojiName+": is added as *`:"+emojiName+":`*";
        //  「この絵文字は誰が追加したか」の情報を付加する
        const updateBy  = _getOriginalRegistererOfEmoji("api-token",emojiName);
        if (updateBy != null) {
          strMessage  +=  " by <@" // Slack ID がメンションとして表示されるようにする
                      + updateBy
                      +   ">"
        }
        strMessage  +=  "!!!\n";
```
これで
**絵文字が新規で登録されたら「誰が登録したか」を含めて通知する** Botの出来上がりです。😉
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2998428/925c15fe-57ed-34b6-6d33-fe8368e7dd7b.png)

# その月・その週など**特定の期間に作られた全絵文字を可視化**する
せっかく「誰が」「いつ」「どの絵文字を」登録したのかの一覧を取得できるようになりましたので、通知に使うだけでは勿体なく思いませんか？というわけで是非これを他にも活用してみましょう。
## 取得した絵文字を登録日時でふるいにかける
取得した全絵文字一覧には、その絵文字が登録された日時も **unix time** で取得できます。
この値を使って、全絵文字一覧から「今週」や「今月」登録されたものはどれ？と選別することが出来ます。まずは、この部分を作ってみましょう。
また、折角ですので登録したユーザー別に整理してみましょう。
```javascript:全絵文字一覧から特定の日時以降のものを選別して取得する
function getEmojiRegistererDuringRange(range) {
  // 全絵文字を取得
  const allEmoji = peekAllCurrentEmoji('api-token');
  var allCount = {};
  // 取得した全絵文字を調べて条件に適合するものを抽出する
  for(var key in this.allEmoji) {
    const val = this.allEmoji[key];
    const user = val['uploaded_by'];
    // 絵文字名、url、登録日時を1つのオブジェクトとしてまとめる。
    const item  = {
      "name": key,
      "url":  val['url'],
      "date_created": val['date_created']
    }
    var fAdd  = ((range == null) ? true : false);
    // この絵文字は指定した範囲の日時に登録されたものであれば集める対象とする(fAddをtrueとする)
    // range が指定されておらず null の場合は全ての絵文字を対象とする
    if (range) {
      if (item.date_created >= range) {
        fAdd  = true;
      }
    }
    // 集める対象の絵文字を登録者のSlack ID別に配列に格納する
    if (fAdd) {
      const keys  = Object.keys(allCount);
      if (keys.indexOf(user) == -1) {
        allCount[user]  = [ item ];
      } else {
        allCount[user].push(item);
      }
    }
  }
  return allCount;
}
```
これで、下記のようなJSONを取得することが出来ます。
```json:登録者別・指定日時の範囲に登録された絵文字
{ "<Slack ID 1>" :
  [
    {
      "name" : "絵文字名"
      "url" : "絵の元画像のURL,またはエイリアスの元の名前",
      "date_created" : 1591720632 // 登録日時(unix time)
    },
    ...
  ],
  "<Slack ID 2>" : ...
  ...
}
```

# 「月間**絵文字登録者ランキング**」を作って社内を盛り上げる
特定期間に登録された絵文字を登録者別に取得することが出来ました。
これを「登録された数順に登録者を並び替える」ことをすれば「登録数ランキング」の出来上がりです。
```javascript:登録者を登録数別でソートし直す
  var ranking = [];
  // 上記で取得した「対象期間中の登録者別絵文字一覧」を一次配列とする
  for(var i in allCount) {
    const item  = allCount[i];
    ranking.push(
      { "owner":    i,
        "contents": item
      }
    )
  }
  // 登録されている絵文字の数順（降順）にソートする
  ranking.sort(
    function(a,b) {
      const numA  = a['contents'].length;
      const numB  = b['contents'].length;
      return  numB  - numA;
    }
  );
  return ranking;
```
これで、「登録された絵文字の数が多い順にソートされた登録者別の絵文字一覧」の配列が戻ってきます。
```json:登録された絵文字の数が多い順にソートされた登録者別の絵文字一覧
[
  // 1位
  {
    "owner" : "<Slack ID>", // 登録者のSlack ID
    "contents" : [ // その登録者が登録した絵文字一覧
      {
        "name" : "絵文字名"
        "url" : "絵の元画像のURL,またはエイリアスの元の名前",
        "date_created" : 1591720632 // 登録日時(unix time)
      },
      ...
    ]
  },
  ....
]
```
あとはこのJSONの中身をそのまま Incoming Webhook で通知すれば「絵文字登録数ランキング」を通知できますね。😉
```javascript:絵文字登録数ランキングの通知
  const beginDate = new Date();
  const lastMonth = beginDate.setMonth(beginDate.getMonth() - 1); //  1ヵ月前 = 月間ランキング.
  const ranking = getEmojiRegistererRanking(lastMonth/1000);  //  msec to sec.
  var strMsg  = "*This month EMOJI register ranking!*\n";
  
  for(var i in ranking) {
    //  show the registerer
    strMsg  +=  (Number(i)+1).toString()
            + " : <@"
            + ranking[i].owner
            + "> *("
            + ranking[i].contents.length
            + ")* = ";
    //  arrange all items those the ranker registered.
    for(var j in ranking[i].contents) {
      const item  = ranking[i].contents[j];
      strMsg  +=  ":"
              + item.name
              + ": ";
    }
    strMsg  +=  "\n";
  }
  sendMessageToSlack(
    "incoming-webhook-url",
    strMsg
  )
```
**実行結果**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2998428/bafaf7e6-7880-742f-8a78-e90147d5f100.png)

# さいごに
GASのトリガーで月初に登録しておくと、毎月頭に先月の「登録者数ランキング」が通知されます。
絵文字は登録数を競うものではないとは思いますが、コミュニケーションの目安として、また社内コミュニケーションを盛り上げるネタのひとつとして話題にするなどしてもらえば幸いです。
尚、[今回 GitHub に登録しているソース](https://github.com/7032/gas_emojichecker_enterprise/blob/main/ranking.gs)は **クラス化** してありますので、ご参考ください。
また、今回は社内で実際に利用しているコードを改変した上で私のプライベートアカウント上で公開しています。
※社内用のスクリプトは他の処理も含まれているため
もし「動かない」などありましたら、コメント頂くかプルリク頂けると色々な方が更に幸せになりますので、是非宜しくお願いします。🙇‍♀️