Slack Bolt con Kotlin: construye tu propio bot de Slack

Jorge SaavedraJorge Saavedra
·15 de junio, 2024·4 min de lectura
kotlinspring-bootslackbotsintegraciones
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
plugins {
    kotlin("jvm") version "1.9.22"
    id("org.springframework.boot") version "3.2.2"
    id("io.spring.dependency-management") version "1.1.4"
    kotlin("plugin.spring") version "1.9.22"
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("com.slack.api:bolt:1.36.1")
    implementation("com.slack.api:bolt-servlet:1.36.1")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
}
Luego, define las variables de entorno en application.yml. Nunca escribas los tokens directamente en el código fuente:
yaml
slack:
  bot-token: ${SLACK_BOT_TOKEN}
  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
@Configuration
class SlackConfig(
    @Value("${'$'}{slack.bot-token}") private val botToken: String,
    @Value("${'$'}{slack.signing-secret}") private val signingSecret: String
) {
    @Bean
    fun appConfig(): AppConfig {
        val config = AppConfig()
        config.singleTeamBotToken = botToken
        config.signingSecret = signingSecret
        return config
    }

    @Bean
    fun slackApp(appConfig: AppConfig): App {
        val app = App(appConfig)

        // Registrar handlers aquí
        registerCommands(app)
        registerEvents(app)

        return app
    }

    @Bean
    fun slackServlet(app: App): SlackAppServlet = SlackAppServlet(app)
}
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
@Configuration
class ServletConfig {
    @Bean
    fun slackServletRegistration(slackServlet: SlackAppServlet): ServletRegistrationBean<SlackAppServlet> {
        return ServletRegistrationBean(slackServlet, "/slack/events")
    }
}

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
fun registerCommands(app: App) {
    // /hello: responder con un saludo personalizado
    app.command("/hello") { req, ctx ->
        val userName = req.payload.userName
        ctx.ack("Hola $userName! Soy tu bot escrito en Kotlin.")
    }

    // /ticket: crear un ticket simple
    app.command("/ticket") { req, ctx ->
        val text = req.payload.text
        if (text.isNullOrBlank()) {
            ctx.ack("Uso: /ticket [descripción del problema]")
        } else {
            // Aquí podrías guardar en base de datos, crear un issue en Jira, etc.
            ctx.ack("Ticket creado: *$text*
Reportado por: <@${req.payload.userId}>")
        }
    }
}
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
fun registerEvents(app: App) {
    // Responder cuando mencionan al bot en un canal
    app.event(AppMentionEvent::class.java) { event, ctx ->
        val text = event.event.text
        val channel = event.event.channel
        val user = event.event.user

        ctx.client().chatPostMessage { msg ->
            msg.channel(channel)
                .text("Hola <@$user>! Me mencionaste diciendo: "$text"")
        }

        ctx.ack()
    }

    // Escuchar mensajes directos al bot
    app.event(MessageEvent::class.java) { event, ctx ->
        if (event.event.channelType == "im") {
            ctx.client().chatPostMessage { msg ->
                msg.channel(event.event.channel)
                    .text("Recibí tu mensaje. Soy un bot, pero estoy escuchando.")
            }
        }
        ctx.ack()
    }
}

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
app.command("/status") { req, ctx ->
    ctx.ack { res ->
        res.blocks {
            section {
                markdownText("*Estado del Sistema*")
            }
            divider()
            section {
                markdownText(":white_check_mark: *API Gateway* - Operativo")
            }
            section {
                markdownText(":white_check_mark: *Base de datos* - Operativo")
            }
            section {
                markdownText(":warning: *Cache Redis* - Latencia elevada (250ms)")
            }
            context {
                markdownText("Última actualización: ${java.time.LocalDateTime.now()}")
            }
        }
    }
}
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
# Instalar ngrok (macOS con Homebrew)
brew install ngrok

# Exponer tu app local en el puerto 8080
ngrok 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