AckCord

You do what you want, exactly how you want it.

AckCord is a Scala library for Discord, using Akka. AckCord’s focus is on letting you choose the level of abstraction you want. Want to work with the raw events from the gateway? Works for that. Maybe you don’t want to bother with any of the underlying implementation and technicalities. Works for that too. Only interested in the REST requests? Pull in that module and ignore the rest.

While AckCord is still in active development, you can try AckCord by adding some of these to your build.sbt file.

libraryDependencies += "net.katsstuff" %% "ackcord"                 % "0.11.0" //For high level API, includes all the other modules
libraryDependencies += "net.katsstuff" %% "ackcord-core"            % "0.11.0" //Low level core API
libraryDependencies += "net.katsstuff" %% "ackcord-commands-core"   % "0.11.0" //Low to mid level Commands API
libraryDependencies += "net.katsstuff" %% "ackcord-lavaplayer-core" % "0.11.0" //Low level lavaplayer API

From there on you go with the API you have decided to use.

Should I use the low level or high level API?

It depends on your knowledge with Scala and Akka. If you know what Akka Streams is, and have no problem using it, then I’d say go for the low level API. If you’re familiar with other stream libraries, you might also want to try the low level API. Lastly, if you want to use AckCord the way it was meant to be used, and don’t mind reading up on the documentation for Akka, then again go with the low level API. If none of those apply to you, then go with the high level API.

The simplest bot

Most of these examples assume these two imports.

import net.katsstuff.ackcord._
import net.katsstuff.ackcord.data._
import cats.Id

AckCord comes with two (3 if you count handling dispatch events yourself) major APIs. A high level and a low level API. Let’s see how you would log in from both the high level and the low level API.

First we need a token.

scala> val token = "<token>" //Your Discord token
token: String = <token>

NOTE: For all codeblocks in this documentation, I’ll comment out all logins as this is real code being run.

Logging in from the high level API

To use the high level API of AckCord, you create the ClientSettings you want to use, and call build. This returns a future client. Using that client you can then listen for specific messages and events. Once you have set everything up, you call login.

scala> val clientSettings = ClientSettings(token) //Keep your settings around. It contains useful objects.
clientSettings: net.katsstuff.ackcord.ClientSettings = ClientSettings(<token>, 100, 0, 1, None, None, Online, false, Dispatcher[akka.actor.default-dispatcher], akka://AckCord, CommandSettings(true,Set()), RequestSettings(4,32,3,Backpressure,2 minutes))

scala> import clientSettings.executionContext
import clientSettings.executionContext

scala> val futureClient = clientSettings.createClient()
futureClient: scala.concurrent.Future[net.katsstuff.ackcord.DiscordClient[cats.Id]] = Future(<not completed>)

scala> futureClient.foreach { client =>
     |   client.onEvent[Id] {
     |     case APIMessage.Ready(_) => println("Now ready")
     |     case _                   =>
     |   }
     |   
     |   //client.login()
     | }

Logging in from the low level API

When working with the low level API, you’re the one responsible for setting stuff up, and knowing how it works.

First we need an actor system and materializer.

import akka.actor.{ActorSystem, ActorRef}
import akka.stream.{ActorMaterializer, Materializer}
import akka.stream.scaladsl.Sink

implicit val system: ActorSystem  = ActorSystem("AckCord")
//I'd recommend using a supplying a custom supervision to log exceptions here
implicit val mat: Materializer = ActorMaterializer()
import system.dispatcher

Next we create the Cache, and the RequestHelper. The Cache helps you know when stuff happens, and keeps around the changes from old things that have happened. The RequestHelper helps you make stuff happen. I’d recommend looking into the settings used when creating both the Cache and RequestHelper if you want to fine tune your bot.

scala> val cache = Cache.create
cache: net.katsstuff.ackcord.Cache = Cache(Sink(SinkShape(MergeHub.in(296233688))),Source(SourceShape(BroadcastHub.out(1489133475))),Sink(SinkShape(MergeHub.in(1587137524))),Source(SourceShape(BroadcastHub.out(26615916))))

scala> val requests = RequestHelper.create(BotAuthentication(token))
requests: net.katsstuff.ackcord.http.requests.RequestHelper = RequestHelper(Bot <token>,Actor[akka://AckCord/user/$a#-1635711911],4,3,32,Backpressure,2 minutes)

Now that we have all the pieces we want, we can create our event listener. In the low level API, events are represented as a Source you can materialize as many times as you want.

cache.subscribeAPI.collect {
  case APIMessage.Ready(c) => c
}.to(Sink.foreach(_ => println("Now ready"))).run()

Finally we can create our GatewaySettings and start the shard.

scala> val gatewaySettings = GatewaySettings(token)
gatewaySettings: net.katsstuff.ackcord.websocket.gateway.GatewaySettings = GatewaySettings(<token>,50,0,1,None,None,Online,false)

scala> DiscordShard.fetchWsGateway.foreach { wsUri =>
     |  val shard = DiscordShard.connect(wsUri, gatewaySettings, cache, actorName = "DiscordShard")
     |  //shard ! DiscordShard.StartShard
     | }

Access to the low level API from the high level API

Accessing the low level API from the high level API is simple. The shard actors can be gotten from the shards method on the DiscordClient[F]. The cache can be gotten from the cache method on the DiscordClient[F].

scala> futureClient.foreach { client =>
     |   val shards: Seq[ActorRef] = client.shards
     |   val cache: Cache = client.cache
     | }