Slack Bolt con Kotlin: construye tu propio bot de Slack

Fecha de publicación: 2023-11-30
Slack Bolt es el framework oficial de Slack para construir aplicaciones: bots, integraciones y workflows. Aunque la documentación oficial se enfoca principalmente en JavaScript y Python, el SDK de Bolt para JVM funciona perfectamente con Kotlin. En este tutorial construiremos un bot funcional paso a paso, integrando Bolt con Spring Boot.
Al finalizar tendrás un bot capaz de:
  • Responder a slash commands personalizados
  • Escuchar menciones y mensajes directos
  • Enviar mensajes con Block Kit (formato enriquecido)
  • Desplegarse en producción con Spring Boot

Paso 1: Crear la app en Slack

Antes de escribir una sola línea de código, necesitas registrar tu bot en Slack:
  • Ve a api.slack.com/apps y haz clic en Create New App, luego selecciona From Scratch.
  • Asigna un nombre a tu app y selecciona el workspace donde la instalarás.
  • En OAuth & Permissions, agrega los siguientes Bot Token Scopes:
    • chat:write: para que el bot pueda enviar mensajes
    • commands: para registrar slash commands
    • app_mentions:read: para escuchar cuando mencionan al bot
    • im:history: para leer mensajes directos
  • Instala la app en tu workspace haciendo clic en Install to Workspace.
  • Guarda el Bot Token (comienza con xoxb-) y el Signing Secret que encontrarás en Basic Information. Los necesitarás para configurar tu app.

Paso 2: Configurar el proyecto Kotlin

Configura tu build.gradle.kts con las dependencias de Spring Boot y Bolt para JVM:
kotlin
1plugins {
2    kotlin("jvm") version "1.9.22"
3    id("org.springframework.boot") version "3.2.2"
4    id("io.spring.dependency-management") version "1.1.4"
5    kotlin("plugin.spring") version "1.9.22"
6}
7
8dependencies {
9    implementation("org.springframework.boot:spring-boot-starter-web")
10    implementation("com.slack.api:bolt:1.36.1")
11    implementation("com.slack.api:bolt-servlet:1.36.1")
12    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
13}
Luego, define las variables de entorno en application.yml. Nunca escribas los tokens directamente en el código fuente:
yaml
1slack:
2  bot-token: ${SLACK_BOT_TOKEN}
3  signing-secret: ${SLACK_SIGNING_SECRET}

Paso 3: Configurar Bolt con Spring Boot

Crea una clase de configuración que inicialice el AppConfig, el App de Bolt y el servlet que recibirá los eventos de Slack:
kotlin
1@Configuration
2class SlackConfig(
3    @Value("${'$'}{slack.bot-token}") private val botToken: String,
4    @Value("${'$'}{slack.signing-secret}") private val signingSecret: String
5) {
6    @Bean
7    fun appConfig(): AppConfig {
8        val config = AppConfig()
9        config.singleTeamBotToken = botToken
10        config.signingSecret = signingSecret
11        return config
12    }
13
14    @Bean
15    fun slackApp(appConfig: AppConfig): App {
16        val app = App(appConfig)
17
18        // Registrar handlers aquí
19        registerCommands(app)
20        registerEvents(app)
21
22        return app
23    }
24
25    @Bean
26    fun slackServlet(app: App): SlackAppServlet = SlackAppServlet(app)
27}
El servlet de Bolt necesita estar mapeado a una ruta específica. Slack enviará todos sus eventos HTTP a esa ruta. Registra el servlet en Spring con un ServletRegistrationBean:
kotlin
1@Configuration
2class ServletConfig {
3    @Bean
4    fun slackServletRegistration(slackServlet: SlackAppServlet): ServletRegistrationBean<SlackAppServlet> {
5        return ServletRegistrationBean(slackServlet, "/slack/events")
6    }
7}

Paso 4: Slash Commands

Los slash commands son comandos que los usuarios escriben en Slack comenzando con /. Bolt los registra con app.command(). El handler recibe el payload de la solicitud y debe responder con ctx.ack():
kotlin
1fun registerCommands(app: App) {
2    // /hello: responder con un saludo personalizado
3    app.command("/hello") { req, ctx ->
4        val userName = req.payload.userName
5        ctx.ack("Hola $userName! Soy tu bot escrito en Kotlin.")
6    }
7
8    // /ticket: crear un ticket simple
9    app.command("/ticket") { req, ctx ->
10        val text = req.payload.text
11        if (text.isNullOrBlank()) {
12            ctx.ack("Uso: /ticket [descripción del problema]")
13        } else {
14            // Aquí podrías guardar en base de datos, crear un issue en Jira, etc.
15            ctx.ack("Ticket creado: *$text*
16Reportado por: <@${req.payload.userId}>")
17        }
18    }
19}
Slack espera una respuesta en menos de 3 segundos. Si tu handler necesita más tiempo (por ejemplo, llamar a una API externa), usa ctx.ackWithText() para responder de inmediato y luego envía un mensaje de seguimiento con ctx.respond() o ctx.client().

Paso 5: Escuchar eventos (menciones y mensajes directos)

Para escuchar eventos como menciones al bot o mensajes directos, usa app.event(). Asegúrate de haber habilitado Event Subscriptions en la consola de Slack y de haber suscrito los eventos correspondientes (app_mention y message.im):
kotlin
1fun registerEvents(app: App) {
2    // Responder cuando mencionan al bot en un canal
3    app.event(AppMentionEvent::class.java) { event, ctx ->
4        val text = event.event.text
5        val channel = event.event.channel
6        val user = event.event.user
7
8        ctx.client().chatPostMessage { msg ->
9            msg.channel(channel)
10                .text("Hola <@$user>! Me mencionaste diciendo: "$text"")
11        }
12
13        ctx.ack()
14    }
15
16    // Escuchar mensajes directos al bot
17    app.event(MessageEvent::class.java) { event, ctx ->
18        if (event.event.channelType == "im") {
19            ctx.client().chatPostMessage { msg ->
20                msg.channel(event.event.channel)
21                    .text("Recibí tu mensaje. Soy un bot, pero estoy escuchando.")
22            }
23        }
24        ctx.ack()
25    }
26}

Paso 6: Mensajes con Block Kit

Block Kit es el sistema de componentes de Slack para construir mensajes con formato enriquecido: secciones, divisores, botones, imágenes y contexto. Es especialmente útil para dashboards o notificaciones de estado. El siguiente ejemplo muestra un comando /status que reporta el estado de diferentes sistemas:
kotlin
1app.command("/status") { req, ctx ->
2    ctx.ack { res ->
3        res.blocks {
4            section {
5                markdownText("*Estado del Sistema*")
6            }
7            divider()
8            section {
9                markdownText(":white_check_mark: *API Gateway* - Operativo")
10            }
11            section {
12                markdownText(":white_check_mark: *Base de datos* - Operativo")
13            }
14            section {
15                markdownText(":warning: *Cache Redis* - Latencia elevada (250ms)")
16            }
17            context {
18                markdownText("Última actualización: ${java.time.LocalDateTime.now()}")
19            }
20        }
21    }
22}
El DSL de Block Kit en Kotlin es limpio y expresivo. Puedes construir bloques de manera declarativa sin manipular JSON directamente, lo que reduce errores y mejora la legibilidad.

Paso 7: Exponer con ngrok para desarrollo

Slack necesita una URL pública para enviar eventos HTTP a tu bot. Durante el desarrollo, puedes usar ngrok para crear un túnel desde internet hacia tu servidor local:
bash
1# Instalar ngrok (macOS con Homebrew)
2brew install ngrok
3
4# Exponer tu app local en el puerto 8080
5ngrok http 8080
ngrok te mostrará una URL pública como https://xxxx.ngrok.io. Copia esa URL y configúrala en dos lugares dentro de la consola de Slack:
  • Event Subscriptions → Request URL: https://xxxx.ngrok.io/slack/events
  • Slash Commands → Request URL (en cada comando): https://xxxx.ngrok.io/slack/events
Slack verificará la URL enviando un challenge. Bolt responde automáticamente a esta verificación, por lo que no necesitas escribir código adicional para ello.

Consideraciones para producción

Cuando lleves tu bot a producción, ten en cuenta los siguientes puntos:
  • Variables de entorno: nunca escribas tokens en el código fuente. Usa variables de entorno o un gestor de secretos como AWS Secrets Manager o Vault.
  • Rate limiting: Slack limita la cantidad de mensajes por segundo que puede enviar un bot. Implementa control de tasa en los handlers que generen muchos mensajes.
  • Socket Mode: si no quieres exponer un endpoint HTTP público, activa Socket Mode en la consola de Slack. Tu bot se conecta mediante WebSocket y recibe los eventos sin necesidad de una URL pública.
  • Logging: registra todos los eventos recibidos y las respuestas enviadas. Esto facilita el debugging cuando algo falle en producción.
  • Procesamiento asíncrono: para tareas costosas (consultas pesadas, llamadas a APIs externas), considera delegar el trabajo a una cola como Kafka o SQS. Responde a Slack de inmediato y procesa en segundo plano.

Conclusión

Slack Bolt con Kotlin es una combinación sólida para automatizar flujos de trabajo internos. El SDK de Bolt para JVM es maduro y cubre los casos de uso más comunes: slash commands, eventos, mensajes enriquecidos con Block Kit e interacciones con botones. Con Spring Boot como base, tienes un bot listo para producción con muy poco esfuerzo de configuración.

Posts que podrian interesarte