[Java] Getting Started JBot

2017.02.08

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

小室です。 Joobyの続きも書かねばならないのですが、また面白いフレームワークを紹介されたので触ってみました。

はじめに

JBotはJava製のBotフレームワークです。現在はSlackをサポートしており、非常に簡単にSlackのBotプログラムが作成可能です。SpringBootベースですのでサーバー構築が非常に容易です。

Facebook and Twitter coming soon

とあるように、TwitterやFacebookなどのSNSをサポートする予定のようです。

Slack Developers Kitを見てみると他にも多数のフレームワークがあるようです。

サンプルを動かす

動作を確認するために手っ取り早くサンプルを確認してみます。

前提

  • すでにSlackのTeamが一つあること
  • Java8がインストール済み

JBotを取得する

gitからCloneしましょう。

$ git clone git@github.com:ramswaroop/jbot.git
Cloning into 'jbot'...
remote: Counting objects: 1421, done.
remote: Compressing objects: 100% (16/16), done.
remote: Total 1421 (delta 2), reused 0 (delta 0), pack-reused 1394
Receiving objects: 100% (1421/1421), 241.66 KiB | 371.00 KiB/s, done.
Resolving deltas: 100% (582/582), done.

ディレクトリを確認してみます。 jbot がフレームワーク、jbot-example がサンプルコードです。

$ cd jbot
jbot/         jbot-example/

今回は jbot-example を利用します。

Botを作成する

Create a slack bot のリンクを実行します。 jbot-sample というユーザー名で登録します。

スクリーンショット 2017-01-31 23.15.43

Add Bot Integration ボタンを押すとAPI Tokenが発行されます。不安な場合は、regenerateしておくと良いかもしれません。Botのアイコンや表示名などを設定する項目がありますが、今回はAPI Tokenのみ必要なので割愛します。

API Tokenをコピーしておき、Save Integration をクリック。

設定ファイルを修正

resources/application.properties を修正します。まずは単純な機能から。DirectMessageやMention、Conversationのみ実行できるように設定しましょう。

# botkit
spring.jackson.property-naming-strategy=SNAKE_CASE

# slack integrations
rtmUrl=https://slack.com/api/rtm.start?token={token}&simple_latest&no_unreads
slackBotToken=xoxb-50014402slackbottokenx29U9X1bQ
slashCommandToken=X73Fv3Tokenx242CdpEq
slackIncomingWebhookUrl=https://hooks.slack.com/services/T025DUwebhookurloOYvPiHL7y6

デフォルトでは上記のように記載されています。6行目を書き換えます。当然ながらこのslackBotTokenは無効なので、先程コピーして値に書き換えましょう。

今回はBotが単純な反応をさせだけなので、書き換えるのは slackBotToken 値の変更のみでOKです。

設定項目について

  • botkit : プロパティの命名戦略なので特に変更する必要はないと思います。
  • rtmUrl : SlackのReal Time Messaging APIのエンドポイント設定です。こちらも特に触る必要はありません。 simple_latestno_unreads のオプションの意味はSlackのRTM APIドキュメントに記載されています。
  • slackBotToken : SlackBotの起動に必要なAPI Tokenです。こちらは、SlackのBot Configurationから設定及び再生成が可能です。
  • slashCommandToken : Slackの / で実行できるコマンドをカスタマイズすることができます。こちらは少々複雑なため、ローカルでのテストは難しそうです(Slashコマンド実行時のフックするURL等を指定する必要があるなど。)
  • slackIncomingWebHookUrl : Slackの特定のチャンネルにメッセージを配信することの出来るURL設定です。

実行

mavenコマンドで実行します。

$ mvn spring-boot:run
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building JBot Example 1.0.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] >>> spring-boot-maven-plugin:1.4.0.RELEASE:run (default-cli) > test-compile @ jbot-example >>>
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ jbot-example ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ jbot-example ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ jbot-example ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /Users/xxxxxx/Develop/spring/jbot/jbot-example/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ jbot-example ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] <<< spring-boot-maven-plugin:1.4.0.RELEASE:run (default-cli) < test-compile @ jbot-example <<<
[INFO]
[INFO] --- spring-boot-maven-plugin:1.4.0.RELEASE:run (default-cli) @ jbot-example ---

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.4.0.RELEASE)

2017-01-31 23:21:19.022  INFO 44078 --- [           main] example.jbot.JBotApplication             : Starting JBotApplication on xxxxx.local with PID 44078 (/Users/xxxxxx/Develop/spring/jbot/jbot-example/target/classes started by xxxxxxxx in /Users/xxxxxxx/Develop/spring/jbot/jbot-example)

(長いので省略)

2017-01-31 23:21:24.303  INFO 44078 --- [cTaskExecutor-1] o.s.w.s.c.WebSocketConnectionManager     : Successfully connected
2017-01-31 23:21:24.399  INFO 44078 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2017-01-31 23:21:24.473  INFO 44078 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-01-31 23:21:24.479  INFO 44078 --- [           main] example.jbot.JBotApplication             : Started JBotApplication in 6.681 seconds (JVM running for 12.893)

依存ライブラリ等がダウンロードされ実行されました。ちなみに無効なslackBotTokenを指定すると、以下のようなエラーが出ます。以下のエラーが出た場合は、ビルドのキャッシュクリーンやTokenをRegenerateするなどをすると、解消されることがあります。

2017-01-29 15:10:53.197 ERROR 41465 --- [           main] me.ramswaroop.jbot.core.slack.SlackDao   : Error de-serializing RTM.start():

java.lang.NullPointerException: null
    at me.ramswaroop.jbot.core.slack.SlackDao$1.deserialize(SlackDao.java:61) [jbot-3.0.2.jar:3.0.2]
    at me.ramswaroop.jbot.core.slack.SlackDao$1.deserialize(SlackDao.java:55) [jbot-3.0.2.jar:3.0.2]
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3789) [jackson-databind-2.8.1.jar:2.8.1]
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2913) [jackson-databind-2.8.1.jar:2.8.1]
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:225) [spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:213) [spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]

SlackのRTM(Real Time Messaging API)への接続が失敗し、NullPoitnerException が発生するようです(これ良いの・・・?)

SlackからBotに話しかける

DirectMessageで話しかけてみます。

スクリーンショット 2017-02-08 12.29.57

Conversation(会話)を試してみましょう。private channelを作成し、BotをInviteしておきます。決まったメッセージを送ると会話をChainしてくれます。

スクリーンショット 2017-02-08 12.31.50

コードを確認

今回確認した動作は example.jbot.slack.SlackBot.java に記載してあります。

@Component
public class SlackBot extends Bot {
}

SpringのComponentScanで引っ掛けるために @Component が付与されています。Botというabstractクラスを継承しています。

設定情報

@Value("${slackBotToken}")
private String slackToken;

必要な設定情報は slackBotToken だけです。

Direct Message, Direct Mention

Direct Messageや直接メンションをもらった場合の動作が定義してあります。

@Controller(events = {EventType.DIRECT_MENTION, EventType.DIRECT_MESSAGE})
public void onReceiveDM(WebSocketSession session, Event event) {
    reply(session, event, new Message("Hi, I am " + slackService.getCurrentUser().getName()));
}

今回は固定メッセージで自分の名前を返しています。いわゆる村の入口にいる村人状態。

メッセージを解析する

正規表現パターンにマッチするメッセージがあれば自動的に反応します。

@Controller(events = EventType.MESSAGE, pattern = "^([a-z ]{2})(\\d+)([a-z ]{2})$")
public void onReceiveMessage(WebSocketSession session, Event event, Matcher matcher) {
    reply(session, event, new Message("First group: " + matcher.group(0) + "\n" +
            "Second group: " + matcher.group(1) + "\n" +
            "Third group: " + matcher.group(2) + "\n" +
            "Fourth group: " + matcher.group(3)));
}

Matcherの結果を利用することも出来るので、ユーザーが投稿したメッセージの内容もうまく利用することができそうです。

スクリーンショット 2017-02-08 12.47.51

PINイベント

メッセージをPINすると反応します。

@Controller(events = EventType.PIN_ADDED)
public void onPinAdded(WebSocketSession session, Event event) {
    reply(session, event, new Message("Thanks for the pin! You can find all pinned items under channel details."));
}

メッセージをPinすると、イベントに反応してメッセージが投稿されます。

スクリーンショット 2017-02-08 17.55.06

ファイルのアップロードイベント

@Controller(events = EventType.FILE_SHARED)
public void onFileShared(WebSocketSession session, Event event) {
    logger.info("File shared: {}", event);
}

ファイルをシェアするとLoggerに書き込まれました。

スクリーンショット 2017-02-08 17.59.24

2017-02-08 17:57:53.153  INFO 22028 --- [ient-SecureIO-1] example.jbot.slack.SlackBot              : File shared: me.ramswaroop.jbot.core.slack.models.Event@58ec327

会話機能

会話はメソッドをチェインして呼ぶことができます。サンプルでの会話は、 「setup meeting」というメッセージを投稿すると開始されます。

スクリーンショット 2017-02-08 18.20.40

会話の開始

@Controller(pattern = "(setup meeting)", next = "confirmTiming")
public void setupMeeting(WebSocketSession session, Event event) {
    startConversation(event, "confirmTiming");   // start conversation
    reply(session, event, new Message("Cool! At what time (ex. 15:30) do you want me to set up the meeting?"));
}

startConversation を呼ぶと会話のチェインが開始されます。こちらのメソッドを抜けるとユーザーの入力を待ち、メッセージが投稿されたのを確認すると next で指定された confirmTiming() メソッドがコールされます。

会話のチェイン1(何もせず遷移)

@Controller(next = "askTimeForMeeting")
public void confirmTiming(WebSocketSession session, Event event) {
    reply(session, event, new Message("Your meeting is set at " + event.getText() +
            ". Would you like to repeat it tomorrow?"));
    nextConversation(event);    // jump to next question in conversation
}

このメソッドでは入力された文字列をメッセージに埋め込み、表示します。特に受信したメッセージの検査はしていないので、実際は時刻表記の文字列ではなくても会話が進んでしまいます。nextConversation() でアノテーションで指定した next メソッドへ遷移します。

会話のチェイン2(yesかどうかをチェック)

@Controller(next = "askWhetherToRepeat")
public void askTimeForMeeting(WebSocketSession session, Event event) {
    if (event.getText().contains("yes")) {
        reply(session, event, new Message("Okay. Would you like me to set a reminder for you?"));
        nextConversation(event);    // jump to next question in conversation  
    } else {
        reply(session, event, new Message("No problem. You can always schedule one with 'setup meeting' command."));
        stopConversation(event);    // stop conversation only if user says no
    }
}

入力されたメッセージを確認し、 yes が含まれているかをチェックします。どこかにでも yes が入っていれば次の会話へ遷移します。それ以外は、会話を中止します。この場合、日本語で「はい」などを入力するとNGです。

  • 変なメッセージになった例 スクリーンショット 2017-02-08 18.15.27

会話の終了

@Controller
public void askWhetherToRepeat(WebSocketSession session, Event event) {
    if (event.getText().contains("yes")) {
        reply(session, event, new Message("Great! I will remind you tomorrow before the meeting."));
    } else {
        reply(session, event, new Message("Oh! my boss is smart enough to remind himself :)"));
    }
    stopConversation(event);    // stop conversation
}

最後は同じ構成なので省略します。yes かそれ以外でメッセージを変えているだけ。

会話機能は以下のように構成します。

  • startConversation() で会話処理フロー開始
  • nextConversation() でアノテーションで指定した次のメソッドへ繊維
  • stopConversation() で会話処理フロー停止

まとめ

JBotはSpringBootベースのフレームワークなので、非常に簡単にBotサーバーが構築できました。Tokenを入力するだけなので非常に簡単です。工夫によっては様々なタスクを肩代わりさせることができそうです。

Javaでお手軽に書けるので他のWebサービスと連携させるなど工夫のしがいがありそうです。他にもSlashCommand(/) や InCommingWebHookなどの機能も現時点では実装済みです。

参照