SlackのプレゼンスAPI(presence_change)の仕様が変更(presence_sub)になっていたので対策した件

- Slack APIの[User Presence API](https://api.slack.com/docs/presence-and-status#user_presence)が変更になり、従来の方法ではユーザーのプレゼンス(ACTIVE,AWAY)変化を検出できなくなったので、対策したときのメモです。

- Javaベースのコード例も掲載します

- [この記事](https://qiita.com/riversun/items/25f64f285699223a992d)でも触れた[Slacklet Javaライブラリ](https://github.com/riversun/slacklet)にも反映しました。

# Presence APIの変化点

**いままで**
ユーザーのプレゼンス状態が変化したときに、**presence_change** イベントが発生していた。

**これから**(2017年11月15日以降)
ユーザーのプレゼンス状態が変化したときに、**presence_change** イベントを受信したい場合にはslackセッション確立後に**presence_sub** メッセージを発行して、**presence_change** イベントを購読することを事前に宣言する必要がある。

# 実際の対策

公式にある記事をもとに、実装した
https://api.slack.com/docs/presence-and-status#user_presence

## presence_sub の中身
つまるところ、ユーザーのプレゼンスが変化を検出したければ、事前にSlackのWebSocketセッションで**presence_sub** メッセージを発行しておけば良い、ということ。

**presence_sub**メッセージの中身は以下の通り

```json
{
    "type": "presence_sub",
    "ids": [
        "U123456",
        "W123456"
    ]
}
```

**ids**には、プレゼンス変化を検出したいSlackユーザーIDをすべていれる。


## presence_subをどのタイミングで発行すればよいのか

実はここで少しハマった。
WebSocketの接続確立のタイミングでWebSocketに対して、上述のJSONを送信してみたが、どうもうまく動作しなかった。

そこでプロトコルを分析してみると、SlackとのWebSocketセッションの確立後に **hello**　という挨拶？メッセージがSlackから送られてくることがわかった。
（このhelloメッセージは以前は無かったとおもうので、最近追加されたらしい。）

```json
"type":"hello"
```

そこで、このメッセージ受信のタイミングで、
- ユーザーID一覧を取得し
- **presence_sub**メッセージを送ってみた
ところ、**presence_change**がコールバックされるようになった。

タイミングについてはさらに精査したいが、いまのところ実用上この実装で不具合はでていない。

# Java実装(ここはライブラリ側。サンプルはこの次の節で）

1. **hello**メッセージを受け取った時点で以下のように、ユーザーIDの一覧を取得する

```java
//ユーザーID一覧を取得する
      Collection<SlackUser> slackUsers = getUsers();
      List<String> userIdList=new ArrayList<String>();
      for(SlackUser u:slackUsers) {
        userIdList.add(u.getId());
      }
//ユーザーIDを引数にして、presence_subを呼び出す
      sendPresenceSubMessageOverWebSocket(userIdList.toArray(new String[] {}));
```
（[上記コードへのポインタ](https://github.com/riversun/slacklet/blob/df4607db0df7a32a700d417876d9da89ea2c7436/src/main/java/org/riversun/xternal/simpleslackapi/impl/SlackWebSocketSessionImpl.java#L916)）


2.そして、以下のようにwebSocketに対して、**type**が**presence_sub**なメッセージを送る

```java
      JsonArray userIdArray = new JsonArray();
      for (String userId : ids) {
        userIdArray.add(userId);
      }
      JsonObject messageJSON = new JsonObject();
      messageJSON.addProperty("type", "presence_sub");
      messageJSON.add("ids", userIdArray);
      websocketSession.getBasicRemote().sendText(messageJSON.toString());
```
（[上記コードへのポインタ](https://github.com/riversun/slacklet/blob/df4607db0df7a32a700d417876d9da89ea2c7436/src/main/java/org/riversun/xternal/simpleslackapi/impl/SlackWebSocketSessionImpl.java#L803)）

まだアラはあるが、この実装で動作確認できた。

# サンプルコード(Slacklet Javaライブラリでの利用法)

## サンプルコード

以下がSlackletライブラリ利用時のプレゼンス変化コールバックのサンプルコード。
ライブラリ側で **presence_sub**を実施しているため、特に何も意識しなくてOK

```java:CheckUserPresence.java
package org.example;

import java.io.IOException;

import org.riversun.slacklet.SlackletService;
import org.riversun.xternal.simpleslackapi.SlackSession;
import org.riversun.xternal.simpleslackapi.SlackUser;
import org.riversun.xternal.simpleslackapi.events.PresenceChange;
import org.riversun.xternal.simpleslackapi.listeners.PresenceChangeListener;

public class CheckUserPresence {

  public static void main(String[] args) throws IOException {

    SlackletService slackService = new SlackletService("[your_bot_token_here]");
    slackService.start();

    SlackSession slackSession = slackService.getSlackSession();

    slackSession.addPresenceChangeListener(new PresenceChangeListener() {

      @Override
      public void onEvent(PresenceChange event, SlackSession session) {

        // ユーザーIDを取得
        String userId = event.getUserId();
        SlackUser slackUser = session.findUserById(userId);

        System.out.println("「" + slackUser.getUserName() + "」が「" + event.getPresence() + "」になりました");

      }
    });

  }

}
```

## Maven依存関係

```xml:POM.xmlに以下を追加
<dependency>
	<groupId>org.riversun</groupId>
	<artifactId>slacklet</artifactId>
	<version>1.0.4</version>
</dependency>
```

##実行

- サンプルを実行すると以下のようになる
まず最初に、すべてのユーザーのプレゼンスがコールバックされる

```
「smilebot」が「ACTIVE」になりました
「slackbot」が「AWAY」になりました
「riversun」が「AWAY」になりました
```

- サンプル起動後に、ユーザーのプレゼンスが変化すると、変化したユーザーのプレゼンスがコールバックされる

```
「riversun」が「ACTIVE」になりました
```

## 注意点
一度に**presence_sub**できるのは500ユーザーまでなので、500ユーザー以上いる場合には、必要な対象を500ユーザーにしぼるか、後述する**presence_query**をつかって500ユーザー毎にグループ分けしてプレゼンス状態を同期的に確認するかが必要となる。

# ユーザーのプレゼンスを取得する他の方法

Slack APIには、上述の方法以外にもプレゼンスを取得する方法がある。

- presence_query を使う方法
  WebSocketセッション確立後に**presence_query**を発行すると、**presence_change**を強制的に発火させることができる。**ids**にはプレゼンスを知りたいユーザーIDを指定する。

```json
{
    "type": "presence_query",
    "ids": [
        "U123456",
        "W123456"
    ]
}
```

- users.getPresenceを使う方法
あるユーザーの現在のプレゼンスを取得したい場合は、ユーザー毎に **users.getPresence**メソッドを利用する方法もある。

詳しくはこちら
https://api.slack.com/methods/users.getPresence