Slack SDK for Javaを使ってメッセージを投稿する

2020.03.24

はじめに

最近はリングフィットアドベンチャーのワイドスクワットが辛くてたまらない佐々木です。 今回は先日リリースされたSlack SDK for Javaを試してみました。

UPDATE 2020/3/25

エラー時の挙動についてライブラリ作者の@seratch_jaさんより以下のようなご指摘をいただきました。ご指摘いただいた内容に基づいてエラーハンドリングについての項目を訂正、追記しています。

@seratch_jaさんありがとうございます!

背景

私は個人的にSlackへ定期的にリマインドメッセージを送信するAWS Lambda関数を開発しています。そのプログラムはScalaで実装していて、メッセージの送信にはHTTPクライアントHammockを使ってSlack APIを直接コールしていました。今回リリースされたSDKはこのプログラムのさらなる機能追加にぴったりなのでまずはメッセージ送信機能に取り入れてみることにしました。

環境

今回使ったライブラリのバージョンなどは下記の通りです。

  • scala 2.12.8
  • io.monix:monix-eval:3.0.0
  • com.slack.api:bolt-aws-lambda:1.0.1

やること

ざっくり下記のようなステップが必要です。

  1. Slack Appの作成とアクセストークンの取得
  2. アクセストークンを使ってメッセージを送信するプログラムを書いて動かす

Slack Appの作成とアクセストークンの取得

このステップについてはBolt入門ガイドがわかりやすいのでこちらを参照することをおすすめします。

必要な操作は下記の通りです。

  • アプリを任意のワークスペースにインストールする
  • Bot Token Scopesにchar:writeスコープを指定する
  • Bot User OAuth Access Tokenをメモする
  • 作成したボットユーザーをメッセージを送信したいチャネルに招待する

コード

上記で生成したトークンを使ってメッセージを送信するコードは以下のようになります。

package example

import cats.effect.Effect
import cats.implicits._
import com.slack.api.Slack
import com.slack.api.methods.request.chat.ChatPostMessageRequest.ChatPostMessageRequestBuilder
import com.slack.api.methods.response.chat.ChatPostMessageResponse
import monix.eval.Task
import monix.execution.Scheduler.Implicits.global

object PostMessageExample extends App {

  val BOT_TOKEN = "xoxb-XXXXXXXXXXXXXXXXXXXXXXXXX"
  val CHANNEL_NAME = "#general"

  def postMessage[F[_]: Effect]: F[ChatPostMessageResponse] =
    Effect[F]
      .delay {
        Slack
          .getInstance()
          .methods(BOT_TOKEN)
          .chatPostMessage(
            (builder: ChatPostMessageRequestBuilder) =>
              builder
                .channel(CHANNEL_NAME)
                .text("Hello slack world !")
          )
      }
      .flatMap { res =>
        if (res.isOk) Effect[F].pure(res)
        else Effect[F].raiseError(new RuntimeException(res.getError))
      }

  println(postMessage[Task].runSyncUnsafe())
  //ChatPostMessageResponse(ok=true, warning=null, error=null, needed=null, provided=null, deprecatedArgument=null, responseMetadata=null, channel=C6D0G1KEU, ts=1585043572.000800, message=Message(type=message, subtype=null, team=T6DHJSZ6F, channel=null, user=U010AAUEKMH, username=null, text=Hello slack world !, blocks=null, attachments=null, ts=1585043572.000800, threadTs=null, intro=false, starred=false, wibblr=false, pinnedTo=null, reactions=null, botId=B010PDMM5P0, botLink=null, displayAsBot=false, botProfile=BotProfile(id=B010PDMM5P0, deleted=false, name=dev-io-example, updated=1585043487, appId=A010MQAN4JC, icons=BotProfile.Icons(image36=https://a.slack-edge.com/80588/img/plugins/app/bot_36.png, image48=https://a.slack-edge.com/80588/img/plugins/app/bot_48.png, image72=https://a.slack-edge.com/80588/img/plugins/app/service_72.png), teamId=T6DHJSZ6F), icons=null, file=null, files=null, upload=false, parentUserId=null, inviter=null, clientMsgId=null, comment=null, topic=null, purpose=null, edited=null, unfurlLinks=false, unfurlMedia=false, threadBroadcast=false, replies=null, replyCount=null, replyUsers=null, replyUsersCount=null, latestReply=null, subscribed=false, xFiles=null, lastRead=null, root=null, itemType=null, item=null))
}

エラーハンドリング

MethodsClient#chatPostMessage は認証エラーなどで送信に失敗したときでも例外を送出しないので、ChatPostMessageResponse#isOk で成否を判定して#raiseErrorしています。

Slack APIのレスポンスボディにはリクエストの成否を表すok プロパティがあり、それがfalseの場合にはAPIクライアントは例外を送出しません。そのためここではChatPostMessageResponse#isOk で成否を判定して#raiseErrorしています。 APIクライアントのエラー処理の詳細についてはAPIクライアントの基本の「レスポンスの扱い」にて説明されていますが、以下の3種類があり、2と3の場合は例外がスローされるとのことでした。

API メソッドを呼び出すとき、様々な理由からエラーが発生することがあります。

  1. 成功のレスポンスを受け取っても、そのレスポンスボディの "ok" プロパティが false で "error" プロパティが channel_not_found のような値で設定されていることがあります。このエラーコードの意味・理由は API メソッドのドキュメントで確認できます。
  2. ネットワーク・Slack API サーバーとの接続の問題で java.io.IOException が throw されることがあります。
  3. Slack API サーバーから 20x 以外の HTTP ステータスで応答された場合に com.slack.api.methods.SlackApiException が throw されることがあります。

また認証エラーの場合には下記のようなログが出力されました(注: エンコーダーはLogstashEncoder)

{"@timestamp":"2020-03-24T19:22:26.720+09:00","@version":"1","message":"Got an unsuccessful response from auth.test API (error: invalid_auth)","logger_name":"com.slack.api.methods.impl.TeamIdCache","thread_name":"scala-execution-context-global-18","level":"ERROR","level_value":40000}

実行結果

実行すると下記のようにメッセージが投稿されます。

最後に

Slack SDK for Javaを使ってチャネルへメッセージ送信してみました。 次回はBoltを使ってスラッシュコマンドを実装してみたいと思います。