Probando un bot de Slack, sin Slack
Tuve que ir a la playa el finde 🏖
Una tranquila mañana de sábado para un jovenzuelo arquitecto que se encontraba esperando el bus, soñando despierto sobre el refactoring del Enerbot que le traería la tan esperada paz mental. Sin sospechar, de lo que le iba a ocurrir al llegar.
Antes de contar la historia de mi fin de semana, hablemos sobre Enerbot.
La historia de un pequeño bot
Hace algunos meses, en un espacio de Slack muy, muy lejano, en un canal bastante seguro, surge Enerbot, un 🤖 fuerte e independiente que no necesita un framework como Hubot, Errbot o Slack Ruby Bot para funcionar la mayor parte de las veces.
Este utiliza la gema de Slack Ruby Client para interactuar con la API de Real Time Messaging, lo cual implica que el bot “lee” todo lo escrito en los canales que este se encuentre.
A varios puede levantarles una 🚩, a mi gusto, eso es compensado con la naturalidad y cierto grado de carisma que le entrega, al no requerir necesariamente a una invocación estilo “@otherbots” pasando a ser un montón de REGEX, pero con carisma.
Algo así como el Albert Eistein de La Saga de los Heechee.
Continuando con la historia… el tiempo pasó, le fuimos incorporando nuevas funcionalidades, validaciones, mejoras, permitiendo adaptarlo a las necesidades que van surgiendo en la comunidad. Todo esto claramente en nuestro tiempo libre, lo cual provocó que algunos cambios fueran hechos a la rápida o simplemente tener que alterar la estructura en vuelo, haciendo que recurriésemos a workarounds, convirtiéndose tan paulatinamente en un Legacy que ni nos dimos cuenta.
De ahí la necesidad de mejorarlo o al menos empezar a avanzar en ello, cosa que había planeado hacer este finde.
Volviendo a mi fin de semana
La cobertura de cierta compañía telefónica*, no me facilitó el poder conectarme y si me llegaba señal, no era suficiente para que el Enerbot local se conectase, por lo tanto, nada de pruebas.
Tenía dos opciones, encontrar otra forma de pasar el tiempo hasta el domingo en la noche o ver una forma rebuscada para conversar con el bot.
Your friendy local neighbor Enerbot
Para utilizarlo localmente, hay que reemplazar los elementos de la gema que permiten recibir y envían información a Slack.
En su estructura más simple, se vería algo como esto:
require 'slack-ruby-client'
Slack.configure do |config|
config.token = ''
config.raise 'Missing token!' unless config.token
end
client = Slack::RealTime::Client.new
client.on :message do |data|
text = data.text
chan = data.channel
case text
when /^enerborg hola/
client.web_client.chat_postMessage channel: chan,
text: 'Holiwis'
end
end
client.start!
Realiza lo siguiente:
- Carga la gema de ‘slack-ruby-client’ que contiene toda la magia para interactuar con Slack.
- Realiza la configuración del token de autenticación.
- Define la inicialización del cliente para ser utilizada en el hermoso loop al recibir un mensaje
client.on :message
, que es básicamente “lee todo lo que escriba en los canales donde te encuentres”. - Toda la información recibida se asigna a
data
, del cual se obtiene información sobre quién escribió algo, en qué canal y más. - Ante algún match con el Case responderá el mensaje mediante
chat_postMessage
entregando el valor asignado.
En este post se llamará Enerborg por temas de Compliance.
Recibiendo y enviando un mensaje
Al escribir en Slack, el bot lo lee de la siguiente forma:
#<Slack::Messages::Message channel="###" client_msg_id="###" event_ts="###" team="###" text="enerborg hola" ts="###" type="message" user="###">
Toda esta información se define en data
de la cual puedes obtener información como de donde se invoca al bot, si es desde un thread, que usuario, incluye un archivo, etc.
Por ejemplo, un respuesta dentro del mismo canal, el método de respuesta, normalmente esperaría el valor data.channel
.
⭐️ TIP: si quieres responder y/o enviar a algún otro canal, puedes utilizar el ID o nomenclatura en vez del valor de data.channel
en el chat.postMessage.
Para el case del ejemplo, este evalúa el data.text
y el REGEX hace el match con el “hola” dentro del texto “enerborg hola”.
Seguir usando esta forma de recibir información evitará tener que reestructurar posibles cambios debido a qué tal método, esperaba que data
incluyese .channel
y no el valor de #party_parrot_squad
directamente.
⭐️ TIP: para una mejor troubleshooting, un p data
dentro del case
o en el chat.postMessage ante algún match. Hará tu día mejor.
Ya teniendo todo eso en mente, comencemos por la definición para el valor donde obtendrá la info, será del modulo SlackIn:
module SlackIn
def self.channel
'#party_parrot_squad'
end
def self.text
'enerborg hola'
end
def self.user
'lucio'
end
end
Para obtener el valor de cada uno de los métodos utilizados por este ejemplo, de la siguiente forma:
text = SlackIn.text
chan = SlackIn.channel
user = SlackIn.user
Ya que tenemos el módulo que imita un mensaje escrito en Slack, esto nos libra de utilizar:
require 'slack-ruby-client'
Slack.configure do |config|
config.token = ''
config.raise 'Missing token!' unless config.token
end
client = Slack::RealTime::Client.newclient.on :message do |data|
endclient.start!
Ahora solamente queda el Case, el cual podemos mantenerlo, ya que no se encuentra sujeto a la gema de Slack y el chat.postMessage, que define los parámetros de cómo y dónde se escribirá el bot en Slack.
Este puede incluir valores que sobre-escriban los por default como el ícono y nombre del bot.
client.web_client.chat_postMessage channel: chan,
text: 'Holiwis',
icon_emoji: ':enerbot:',
username: 'ENERBORG'
⭐️ TIP: como lifehack, si quieres escribir a través del bot en un thread, solamente debes agregarle el parámetro y valor del Time Stamp (ts) al chat.postMessage.
Lo cual perfectamente queda como:
p "Reply on channel: #{chan} | With Text: Hola #{user}"
Recibiendo y enviando un mensaje
Bien, del primer ejemplo pasamos a lo siguiente:
module SlackIn
def self.channel
'#party_parrot_squad'
end
def self.text
'enerborg hola'
end
def self.user
'lucio'
end
end
text = SlackIn.text
chan = SlackIn.channel
user = SlackIn.user
case text
when /^enerborg hola/
p "Reply on channel: #{chan} | With Text: Hola #{user}"
end
Al ejecutar, nos entrega lo siguiente:
“Reply on channel: #party_parrot_squad | With Text: Hola lucio”
Equivalente al bot escribiendo "Hola #{tu_saurio}"
en Slack.
¿No salía mejor hacer un script?
SNO, si fuesen funcionalidades (scripts) sería mejor. En este caso se apuntaba a modificar gran parte de la estructura que maneja la interacción con Slack, cosa de utilizar clases y módulos para jugar con las evaluaciones de permisos, entre otras cosas.
Lo siguiente es parte de los cambios que quería y del repositorio que estaba usando para pruebas sin Slack.
La inicializar la clase Reply
, esta recibe los valores de data
(SlackIn
) y el reply
(data.text
). De ahí surge la necesidad de usar la definición engorrosa del modulo, debido a que al definir variables individuales, no representaría bien la forma en que recibe información el bot desde Slack.
Resumiendo
Con un módulo puedes hacer pruebas simples sobre tu bot sin conexión a internet, lo cual es poco probable que ocurra en esta época, en cualquier caso, fuiste advertido.
La documentación de la gema de Slack Ruby Client le faltan algunas cosas, ya que el resto se basa en que leas la documentación de la API Slack. Con eso, tienes un montón de cosas entretenidas por hacer.
Un ⭐️ TIP, podría ser que para obtener información de usuarios, canales y otras cosas, debes utilizar el cliente Web y no el RealTime Messaging.
Dejo el repo del Enerbot y del Enerborg local. Dudas, comentarios, sugerencias, siempre bienvenidas en este post o en los repositorios respectivos.
Aguante Ruby.