Last active
March 26, 2023 14:17
-
-
Save seratch/46c404c6141d34060a1c607822dd25d0 to your computer and use it in GitHub Desktop.
Slack app built with Play Framework (Scala)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name := """bolt-play-scala""" | |
organization := "com.example" | |
version := "1.0-SNAPSHOT" | |
lazy val root = (project in file(".")).enablePlugins(PlayScala) | |
scalaVersion := "2.13.2" | |
libraryDependencies += guice | |
libraryDependencies += "com.slack.api" % "bolt-servlet" % "1.0.8" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package controllers | |
import javax.inject._ | |
import play.api.mvc._ | |
@Singleton | |
class HomeController @Inject()(val controllerComponents: ControllerComponents) extends BaseController { | |
def index() = Action { implicit request: Request[AnyContent] => | |
Ok("Hello World!") | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.2") | |
addSbtPlugin("org.foundweekends.giter8" % "sbt-giter8-scaffold" % "0.11.0") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Routes | |
# This file defines all application routes (Higher priority routes first) | |
# https://www.playframework.com/documentation/latest/ScalaRouting | |
# ~~~~ | |
# An example controller showing a sample home page | |
GET / controllers.HomeController.index | |
POST /slack/events controllers.SlackAppController.index | |
# Map static resources from the /public folder to the /assets URL path | |
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package controllers | |
import javax.inject._ | |
import play.api.mvc._ | |
import services.SlackAppService | |
@Singleton | |
class SlackAppController @Inject()( | |
val controllerComponents: ControllerComponents, | |
service: SlackAppService) extends BaseController { | |
def index() = Action(parse.raw)(service.run(_)) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package services | |
import akka.util.ByteString | |
import com.slack.api.bolt.handler.builtin.SlashCommandHandler | |
import com.slack.api.bolt.request.{RequestHeaders, Request => SlackRequest} | |
import com.slack.api.bolt.response.{Response => SlackResponse} | |
import com.slack.api.bolt.util.SlackRequestParser | |
import com.slack.api.bolt.{App => SlackApp} | |
import javax.inject.Singleton | |
import play.api.http.HttpEntity | |
import play.api.mvc._ | |
import scala.jdk.CollectionConverters._ | |
@Singleton | |
class SlackAppService { | |
private[this] lazy val app = new SlackApp() | |
private[this] lazy val parser = new SlackRequestParser(app.config) | |
val makeRequestHandler: SlashCommandHandler = (req, ctx) => { | |
ctx.ack(s"Thanks <@${req.getPayload.getUserId}>!") | |
} | |
app.command("/make-request", makeRequestHandler) | |
def run(implicit request: Request[RawBuffer]): Result = { | |
toPlayResponse(app.run(parseRequest(request))) | |
} | |
def parseRequest(implicit request: Request[RawBuffer]): SlackRequest[_] = { | |
parser.parse(SlackRequestParser.HttpRequest.builder() | |
.requestUri(request.uri) | |
.queryString(request.queryString.map { case (k, vs) => k -> vs.asJava }.asJava) | |
.headers(new RequestHeaders(request.headers.toMap.map { case (k, vs) => k -> vs.asJava }.asJava)) | |
.requestBody(request.body.asBytes().map(_.utf8String).getOrElse("")) | |
.remoteAddress(request.remoteAddress) | |
.build()) | |
} | |
def toPlayResponse(response: SlackResponse): Result = { | |
val headers = response.getHeaders.asScala | |
.filter { case (_, v) => v != null && v.size() > 0 } | |
.map { case (k, v) => k -> v.get(0) } | |
.toMap | |
Result( | |
header = ResponseHeader(response.getStatusCode, headers), | |
body = HttpEntity.Strict(ByteString(response.getBody), Some(response.getContentType)) | |
) | |
} | |
} |
How to set SLACK_BOT_TOKEN and SLACK_SIGNING_SECRET?
Try this:
private val signingSecret = conf.get[String]("com.slack.signingSecret")
private lazy val app = {
val appConfig = new AppConfig()
// These need to be set to null, otherwise Slack auto-detects that we're an OAuth app which causes problems
// This means is that you can't use ctx.client() in the handlers
appConfig.setClientId(null)
appConfig.setClientSecret(null)
appConfig.setSigningSecret(signingSecret)
new SlackApp(appConfig)
}
The bot token is per workspace, so you want to set that dynamically when you're interacting with Slack.
Can we set Bot User OAuth Token? I am not sure it is setSingleTeamBotToken or not.
Do you know how to create unit test with bolt?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I just tried this with the latest version of Bolt (1.3) and it works fine except it appears that
response.getBody
can sometimes returnnull
, causing aNullPointerException
.It can be easily fixed with this extra line: