Slack API client
Choose an HTTP client backend
You have to choose a sttp backend
that supports scala.concurrent.Future
, cats.effect.Async/Effect
, monix.eval.Task
response wrappers:
- AsyncHttpClientFutureBackend
- AsyncHttpClientCatsBackend
- AsyncHttpClientFs2Backend
- AsyncHttpClientMonixBackend
- AkkaHttpBackend
- OkHttpFutureBackend
- OkHttpMonixBackend
- HttpClientFutureBackend
- HttpClientMonixBackend
- Http4sBackend
Add a dependency of your choice to your build.sbt
.
For instance, for Akka Http backend this is:
"com.softwaremill.sttp.client" %% "akka-http-backend" % sttpVersion
where sttpVersion
should be 2.0+
Create a client to Slack Web API methods
SlackApiClient provides access to all available of Slack Web API methods.
Future backend
// Import Slack Morphism Client
import org.latestbit.slack.morphism.client._
// Import STTP backend
// We're using Akka Http for this example
import sttp.client.akkahttp.AkkaHttpBackend
// Import support for scala.concurrent.Future implicits from cats (required if you use Future-based backend)
// If you bump into compilation errors that `Future` isn't a `cats.Monad` or `cats.MonadError`,
// you probably forgot to import this.
// Also, you can import all implicits from cats using `import cats.implicits._` instead of this
import cats.instances.future._
// Creating an STTP backend
implicit val sttpBackend = AkkaHttpBackend() // this is a Future-based backend
// Creating a client instance
val client = SlackApiClient.create() // or similarly SlackApiClient.create[Future]()
Cats Effect backend
// Import Slack Morphism Client
import org.latestbit.slack.morphism.client._
// Import STTP backend
// We're using AsyncHttpClientCatsBackend for this example
import sttp.client.asynchttpclient.cats.AsyncHttpClientCatsBackend
// Import support for cats-effect
import cats.effect._
// We should provide a ContextShift for Cats IO and STTP backend
implicit val cs: ContextShift[IO] = IO.contextShift( scala.concurrent.ExecutionContext.global )
for {
backend <- AsyncHttpClientCatsBackend[IO]() // Creating an STTP backend
client = SlackApiClient.build[IO]( backend ).create() // Create a Slack API client
result <- client.api.test( SlackApiTestRequest() ) // call an example method inside IO monad
} yield result
Monix backend
// Import Slack Morphism Client
import org.latestbit.slack.morphism.client._
// Import STTP backend
import sttp.client.asynchttpclient.monix.AsyncHttpClientMonixBackend
// Monix imports
import monix.eval._
import monix.execution.Scheduler.Implicits.global
for {
backend <- AsyncHttpClientMonixBackend() // Creating an STTP backend
client = SlackApiClient.build[Task]( backend ).create() // Create a Slack API client
result <- client.api.test( SlackApiTestRequest() ) // call an example method inside Task monad
} yield result
Make Web API methods calls
For most of Slack Web API methods (except for OAuth methods, Incoming Webhooks and event replies) you need a Slack token to make a call. For simple bots you can have it in your config files, or you can obtain workspace tokens using Slack OAuth.
There is an example implementation of Slack OAuth v2 in Akka Http Example.
Slack Morphism requires an implicit token specified as an instance of SlackApiToken:
In the example below, we’re using a hardcoded Slack token, but don’t do that for your production bots and apps. You should securely and properly store all of Slack tokens. Look at Slack recommendations.
import org.latestbit.slack.morphism.client._
import org.latestbit.slack.morphism.common._
import org.latestbit.slack.morphism.client.reqresp.chat._
import sttp.client.akkahttp.AkkaHttpBackend
import scala.concurrent._
import cats.instances.future._
implicit val sttpBackend = AkkaHttpBackend()
val client = SlackApiClient.create[Future]()
implicit val slackApiToken: SlackApiToken = SlackApiBotToken(SlackAccessTokenValue("xoxb-89....."))
client.chat.postMessage(
SlackApiChatPostMessageRequest(
channel = "#general",
text = "Hello Slack"
)
)
As you might noticed here, Slack Morphism API mimics Slack Web API method names, so that
https://slack.com/api/chat.postMessage
is client.chat.postMessage(...)
, or https://api.slack.com/methods/oauth.v2.access
is client.oauth.v2.access(...)
etc.
The complete list of all of the implemented Web API methods is available here.
There is also the auxiliary function client.withToken
to help with implicit tokens in Scala for-comprehension:
for {
backend <- AsyncHttpClientCatsBackend[IO]()
client = SlackApiClient.build[IO]( backend ).create()
result <- client.withToken( yourToken )( implicit token => _.api.test( SlackApiTestRequest() ) )
} yield result
Low-level HTTP API to Slack Web API
In case you didn’t find a method you need on the list above, or you need something different/undocumented/legacy, there is a low-level API for this scenario:
import org.latestbit.slack.morphism.client._
// Definition of your request and response as a case classes
case class YourRequest(...)
case class YourResponse(...)
// Definitions of JSON encoder/decoders
import io.circe.generic.semiauto._
implicit val yourRequestEncoder = deriveEncoder[YourRequest]
implicit val yourResponseDecoder = deriveEncoder[YourRequest]
// Need init a token before making calls
implicit slackApiToken: SlackApiToken = ...
// Make a call
client.http.post[YourRequest,YourResponse](
methodUri = "some.someMethod", // Slack relative Method URI
YourRequest()
)
Please, don’t hesitate to submit a PR with model updates if you find anything missing or find model inconsistency. This project is open to help each other, so any PRs are welcomed.
Avoid boilerplate making consequent non-blocking client requests with EitherT
This is completely optional and just a recommendation for you.
You might notice some boilerplate when you deal with consequent client requests that returns Future[ Either[ SlackApiClientError,SomeKindOfSlackResponse ] ]
.
To solve this in a better way, consider using an approach with EitherT[] from Cats, well described here.
For example, this example shows two consequence Web API calls: We make a first async call, find some result in the first response, and getting the response result of a next async call.
EitherT( slackApiClient.conversations.list( SlackApiConversationsListRequest() ) ).flatMap { channelsResp =>
channelsResp.channels
.find( _.flags.is_general.contains( true ) )
.map { generalChannel =>
EitherT(
client.chat.postMessage(
SlackApiChatPostMessageRequest(
channel = generalChannel.id,
text = "Hello"
)
)
).map { resp =>
resp.ts.some
}
}
.getOrElse(
EitherT[Future, SlackApiClientError, Option[SlackTs]](
Future.successful( None.asRight )
)
)
}
EitherT gives you the power to deal with those transformations and avoid boilerplate without sacrificing error handling.