Slack BotをJavaでお手軽に作る

SlackBotをJavaでお手軽に作る方法をご紹介します  

紹介したソースコード一式は https://github.com/riversun/slacklet-examples.git にあります

# BOT用API Tokenの取得
まず、こちらに行き、BOT用のAPI Tokenを取得します
https://my.slack.com/services/new/bot

- BOTのusernameを決めたら、[Add bot integration]をクリックします

![slack_reg_01.png](https://qiita-image-store.s3.amazonaws.com/0/170905/3c469184-19c6-42ba-426c-0dfd5a67f48f.png)

- これで、API Tokenが生成されました。今後このAPI Tokenを使ってSlackBotを作っていきます。
![slack_reg_02.png](https://qiita-image-store.s3.amazonaws.com/0/170905/de69388f-e613-2a14-bee3-683ec472da18.png)

# Slackのプロトコルと事前準備
プロトコルはWebsocketベースのSlackのRTM([Realtime Messaging API](https://api.slack.com/rtm))で、ライブラリには[Simple Slack API](https://github.com/Ullink/simple-slack-api.git)をベースにした[Slacklet](https://github.com/riversun/slacklet.git)というラッパーライブラリを使います。

**Slacklet**
Slackletは以下からmaven/gradleまたは、jarの直接ダウンロードで取得可能です。
[maven/gradle](https://search.maven.org/artifact/org.riversun/slacklet/1.0.4/jar)
[slacklet.jarを直接ダウンロード](http://repo1.maven.org/maven2/org/riversun/slacklet/1.0.4/slacklet-1.0.4.jar)

# BOTをチャンネルに参加させる
まず、さきほど作成したBOTをinviteして任意のチャンネルに参加させます。

![ex00_00_join.png](https://qiita-image-store.s3.amazonaws.com/0/170905/ccdc4772-e533-bf1d-1c01-fcff92bc71f5.png)


# １．エコーを返すSlack bot

誰かが発言したら、そのままエコーを返すシンプルな例です。

```java:Example00.java

public class Example00 {

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

		String botToken ="取得したAPI Token" ;

		SlackletService slackService = new SlackletService(botToken);

		// slackletを追加する
		slackService.addSlacklet(new Slacklet() {

			@Override
			public void onMessagePosted(SlackletRequest req, SlackletResponse resp) {
				// メッセージがユーザーからポストされた

				// メッセージがポストされたチャンネルを取得する
				SlackChannel channel = req.getChannel();

				if ("random".equals(channel.getName())) {
					// #randomチャンネルだった場合

					// メッセージ本文を取得
					String content = req.getContent();

					// メッセージがポストされたチャンネルに対して、BOTからメッセージを送る
					resp.reply("「" + content + "」て言いましたね。");

				}

			}
		});

		// slackletserviceを開始(slackに接続)
		slackService.start();

	}

}
```

[このサンプルのソースコード](https://github.com/riversun/slacklet-examples/blob/master/src/main/java/org/example/Example00.java)

## 実行例
Slack上で「こんにちは」といったら、BOTからエコーが返ってきます。
![ex00_01.png](https://qiita-image-store.s3.amazonaws.com/0/170905/2343f9c8-6852-7225-a13d-e2b010b8efbc.png)


## 説明
上のように、**SlackletService**をつくり、**#addSlacklet**で**Slacklet**を追加します。
**Slacklet**は**Servlet**みたいなもので、必要なメソッドをオーバーライドしてつかいます。

**#onMessagePosted**はチャンネルにユーザーがメッセージをポストしたときに呼び出されます。
チャンネルを限定して返信したいときは上の例のようチャンネル名を識別して返信をします。

# ２．ダイレクトメッセージ(Direct Message)を受け取る
ユーザーがBOTに対してポストしたダイレクトメッセージを受け取りたい場合は以下のように**Slacklet#onDirectMessagePosted**をオーバーライドします

```java
@Override
public void onDirectMessagePosted(SlackletRequest req, SlackletResponse resp) {
	// BOT宛のダイレクトメッセージがポストされた

	String content = req.getContent();

	// メッセージを送信したユーザーのメンションを取得する
	String mention = req.getUserDisp();

	// ダイレクトメッセージを送信したユーザーに対して返信する
	resp.reply(mention + "さん、ダイレクトメッセージありがとう。\n「" + content + "」って言いましたね");
}

```
[このサンプルのソースコード](https://github.com/riversun/slacklet-examples/blob/master/src/main/java/org/example/Example01.java)

## 実行例
以下のように、BOTに対してダイレクトメッセージを送ると、返事がきます。

![ex01_00.png](https://qiita-image-store.s3.amazonaws.com/0/170905/4e70d913-cdd2-fb2b-f170-859626f8b927.png)

# ３．＠メンションされた場合のみ反応する

ユーザーがBOTをメンションしたメッセージ、たとえば**「@smilebot こんにちは」**してポストしたメッセージを受け取りたい場合は以下のように**Slacklet#onMentionedMessagePosted**をオーバーライドします

```java
@Override
public void onMentionedMessagePosted(SlackletRequest req, SlackletResponse resp) {
	// あるチャンネルでこのBOTへのメンション付きメッセージがポストされた(例　「@smilebot おはよう」）

	String content = req.getContent();

	String mention = req.getUserDisp();
	resp.reply("こんにちは、" + mention + "さん。「" + content + "」って言いましたね。");
}
```

[このサンプルのソースコード](https://github.com/riversun/slacklet-examples/blob/master/src/main/java/org/example/Example01.java)

## 実行例
**＠ユーザー名**でBOTの名前を指定してメンションしたときだけ、返事がきます。

![ex01_01_mentioned.png](https://qiita-image-store.s3.amazonaws.com/0/170905/d9c4935b-ed14-e449-d17f-9ab6c01da328.png)

# ４．ユーザーにダイレクトメッセージを送る
BOTからユーザーにダイレクトメッセージ(direct message)を送る例です。
ユーザーからのメッセージへの返信ではなく、BOTから先にメッセージを送りたい場合につかいます。

```java

public class Example02 {

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

		String botToken ="取得したAPI Token"; 

		SlackletService slackService = new SlackletService(botToken);
		slackService.start();

		// ユーザーに対して、（返信ではなく）ダイレクトメッセージを送る
		String userName = "riversun";
		slackService.sendDirectMessageTo(userName, "こんちは～");

		// slackとの接続を終了
		slackService.stop();

	}

}

```

[このサンプルのソースコード](https://github.com/riversun/slacklet-examples/blob/master/src/main/java/org/example/Example02.java)

## 実行例
実行すると、このようにBOTからのダイレクトメッセージが届きます。

![ex02_00_direct_left.png](https://qiita-image-store.s3.amazonaws.com/0/170905/e98b9502-1310-f319-4570-1005f015de37.png)

![ex02_01_direct.png](https://qiita-image-store.s3.amazonaws.com/0/170905/7fa17362-9fed-bbc6-c4fb-71eab770ea8a.png)

# ５．指定したチャンネルにメッセージを送る

```java
public class Example03 {

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

		String botToken = ResourceBundle.getBundle("credentials").getString("slack.bot_api_token");

		SlackletService slackService = new SlackletService(botToken);
		slackService.start();

		// チャンネルに対して、（返信ではなく）メッセージを送る
		String channelName = "random";
		slackService.sendMessageTo(channelName, "randomチャンネルの皆様、こんにちは！");

		slackService.stop();

	}

}

```
[このサンプルのソースコード](https://github.com/riversun/slacklet-examples/blob/master/src/main/java/org/example/Example03.java)

## 実行例
**#random**チャンネルに対してBOTがメッセージを送ります。
![ex03.png](https://qiita-image-store.s3.amazonaws.com/0/170905/a63f520c-c07d-36f1-ca4f-c08e00a7ae06.png)


# ６．セッション機能（ユーザー固有のコンテクスト）を使う
Slackletはマルチユーザーを意識した設計にしています。あるユーザーがBOTと対話しているときに、他のユーザーも独立して対話が成り立つようにします。  
これを実現するためユーザー毎の各種状態を保持するために、session機能があります。servletと同じく**SlackletRequest .getSession**でセッションを取得し、その中に状態を保持します。

```java

@Override
public void onDirectMessagePosted(SlackletRequest req, SlackletResponse resp) {

	String content = req.getContent();

	// セッションを取得する（セッションはユーザー毎に固有）
	SlackletSession session = req.getSession();

	// 発言回数カウント用のintegerをセッションから取得する。未だ何も入れていないときは、デフォルト値１とする
	Integer num = (Integer) session.getAttribute("num", 1);

	resp.reply(req.getUserDisp() + "さんは" + num + "回目に「" + content + "」って言いました。");

	// 回更をインクリメントして、セッションを更新する
	num++;
	session.setAttribute("num", num);

}
```

[このサンプルのソースコード](https://github.com/riversun/slacklet-examples/blob/master/src/main/java/org/example/Example04.java)

## 実行例
対話の回数をセッションに記憶するので対話が継続しているようにみえます。
ユーザー毎に固有に記憶されます。
![ex04.png](https://qiita-image-store.s3.amazonaws.com/0/170905/b518f6dd-5ad2-1487-7d41-f5675ee6818f.png)



## 拡張のヒント

【**セッションの永続化**】
本格的な対話を実装するときには確実に必要になるのが**コンテキスト**です。
**コンテキスト**は対話状態、各種ステータスなどを入れておきます。

Slackletのデフォルト実装では、セッションはオンメモリに保持されますので、プログラムを終了すると忘れてしまいます。セッションを永続化したい場合は永続化コードを含む[SletPersistManager](https://github.com/riversun/slacklet/blob/master/src/main/java/org/riversun/slacklet/SletPersistManager.java)を実装し、**SlackletService#setSessionPersistenceManager**でセットします。


【**マルチユーザー対応**】
複数のユーザーとの対話を同時処理できるよう、ユーザー毎に別スレッドで処理していますが、BOTが扱うユーザー数に応じて、executorやスレッドプールの大きさを適宜調節すると良いとおもいます。


【**ログを表示する**】
以下のようにするとログをstderrに出力します

```java
org.riversun.xternal.log.Logger.setEnabled(true);
```


【**さらに深い拡張**】
Slackletの下回りは[Simple Slack API](https://github.com/Ullink/simple-slack-api)を拡張して作られています。より深い拡張をしたい場合は[Simple Slack API](https://github.com/Ullink/simple-slack-api)を参考にされると良いとおもいます。