Reach

Grow

Manage

Automate

Reach

Grow

Manage

Automate

Cómo construir un bot de WhatsApp para listas de tareas utilizando la API de Conversaciones Programables de Bird.

Pájaro

5 feb 2020

WhatsApp

1 min read

Cómo construir un bot de WhatsApp para listas de tareas utilizando la API de Conversaciones Programables de Bird.

Pájaro

5 feb 2020

WhatsApp

1 min read

Cómo construir un bot de WhatsApp para listas de tareas utilizando la API de Conversaciones Programables de Bird.

Bird ha lanzado recientemente Conversaciones Programables. Permite a las empresas combinar plataformas de comunicación como WhatsApp, Messenger y SMS en sus sistemas, utilizando una única API.

Business in a box.

Descubre nuestras soluciones.

Bird lanzó recientemente Programmable Conversations. Permite a las empresas integrar plataformas de comunicación como WhatsApp, Messenger y SMS en sus sistemas utilizando una sola API.

Quería probarlo, así que construí un bot de lista de tareas para WhatsApp, porque ¿quién no necesita una lista de tareas automatizada para ayudar a organizar su día? Puede sonar complicado, pero en realidad fue fácil, y me gustaría contarte todo al respecto.

Ahora, trabajo en MessageBird, así que podía sumergirme y comenzar a construir. Si lo intentas, necesitarás solicitar acceso anticipado. Pero una vez que estés configurado con un canal de WhatsApp, puedes iniciar sesión en el Dashboard en el sitio web de MessageBird y comenzar.

Lo primero que hice fue leer la documentación. Aprendí que, para recibir mensajes del bot, tendría que usar un webhook. Esto significaba que mi bot necesitaría ser accesible desde Internet. Al construir APIs como esta, es importante seguir las mejores prácticas de versionado de API para la mantenibilidad. Como estaba comenzando a codificar, decidí usar ngrok. Crea un túnel desde la internet pública a tu querido localhost en el puerto 5007. ¡Engancha!

ngrok http 5007 -region eu -subdomain todobot

Luego, necesitaba hacer una llamada a la API de Programmable Conversations para crear el webhook. Es un POST a https://conversations.messagebird.com/v1/webhooks y se ve algo así:


func main() {// define the webhook json payload
  wh := struct {
         Events    []string `json:"events"`
         ChannelID string   `json:"channelId"`
         URL       string   `json:"url"`
  } {
    // we would like to be notified on the URL
    URL:       "https://todobot.eu.ngrok.io/create-hook",
    // whenever a message gets created
    Events:    []string{"message.created"},
    // on the WhatsApp channel with ID
    ChannelID: "23a780701b8849f7b974d8620a89a279",
  }
  
  // encode the payload to json
  var b bytes.Buffer
  err := json.NewEncoder(&b).Encode(&wh)
  if err != nil {
    panic(err)
  }
  
  // create the http request and set authorization header
  req, err := http.NewRequest("POST", "https://conversations.messagebird.com/v1/webhooks", &b)
  req.Header.Set("Authorization", "AccessKey todo-your-access-key")
  req.Header.Set("Content-Type", "application/json") // fire the http request
  client := &http.Client{}
  resp, err := client.Do(req)
  if err != nil {
         panic(err)
  }
  defer resp.Body.Close()// is everything ok?
  body, _ := ioutil.ReadAll(resp.Body)
  if resp.StatusCode >= http.StatusBadRequest {
    panic(fmt.Errorf("Bad response code from api when trying to create webhook: %s. Body: %s", resp.Status, string(body)))
  } else {
    log.Println("All good. response body: ", string(body))
  }
}

Genial. Ahora la API de Conversations hará una solicitud POST a:

https://todobot.eu.ngrok.io/create-hook cada vez que se crea un nuevo mensaje en el canal de WhatsApp que configuraste antes.

Así es como se ve una carga útil de webhook:


{
  "conversation":{
    "id":"55c66895c22a40e39a8e6bd321ec192e",
    "contactId":"db4dd5087fb343738e968a323f640576",
    "status":"active",
    "createdDatetime":"2018-08-17T10:14:14Z",
    "updatedDatetime":"2018-08-17T14:30:31.915292912Z",
    "lastReceivedDatetime":"2018-08-17T14:30:31.898389294Z"
  },
  "message":{
    "id":"ddb150149e2c4036a48f581544e22cfe",
    "conversationId":"55c66895c22a40e39a8e6bd321ec192e",
    "channelId":"23a780701b8849f7b974d8620a89a279",
    "status":"received",
    "type":"text",
    "direction":"received",
    "content":{
      "text":"add buy milk"
    },
    "createdDatetime":"2018-08-17T14:30:31.898389294Z",
    "updatedDatetime":"2018-08-17T14:30:31.915292912Z"
  },
  "type":"message.created"
}

Queremos responder a esos mensajes. Comencemos por devolverlos, ¿qué dices?


// define the structs where we'll parse the webhook payload into
type whPayload struct {
  Conversation conversation `json:"conversation"`
  Message      message      `json:"message"`
  Type         string       `json:"type"`
}

type message struct {
  ID        string  `json:"id"`
  Direction string  `json:"direction"`
  Type      string  `json:"type"`
  Content   content `json:"content"`
}

type content struct {
  Text string `json:"text"`
}

type conversation struct {
  ID string `json:"id"`
}

func main() {
  http.HandleFunc("/create-hook", createHookHandler)
  log.Fatal(http.ListenAndServe(*httpListenAddress, nil))
}
// createHookHandler is an http handler that will handle webhook requests
func createHookHandler(w http.ResponseWriter, r *http.Request) {
  // parse the incoming json payload
  whp := &whPayload{}
  err := json.NewDecoder(r.Body).Decode(whp)
  if err != nil {
    log.Println("Err: got weird body on the webhook")
    w.WriteHeader(http.StatusInternalServerError)
    fmt.Fprintf(w, "Internal Server Error")
    return
  } 
  if whp.Message.Direction != "received" {
    // you will get *all* messages on the webhook. Even the ones this bot sends to the channel. We don't want to answer those.
    fmt.Fprintf(w, "ok")
    return
  } // echo: respond what we get
  err = respond(whp.Conversation.ID, whp.Message.Content.Text)
  
  if err != nil {
    log.Println("Err: ", err)
    w.WriteHeader(http.StatusInternalServerError)
    fmt.Fprintf(w, "Internal Server Error")return
  }
  w.WriteHeader(http.StatusOK)
  fmt.Fprintf(w, "ok")
}

Ahora, para la parte interesante. Haz una solicitud POST a:

https://conversations.messagebird.com/v1/conversations/<conversationID>/messages para responder a la solicitud.


func respond(conversationID, responseBody string) error {
  u := fmt.Sprintf("https://conversations.messagebird.com/v1/conversations/%s/messages", conversationID)msg := message{
  Content: content{
    Text: responseBody,
  },
  Type: "text",
  }
  var b bytes.Buffer
  err := json.NewEncoder(&b).Encode(&msg)
  if err != nil {
    return fmt.Errorf("Error encoding buffer: %v", err)
  }
  req, err := http.NewRequest("POST", u.String(), &b)
  req.Header.Set("Authorization", "AccessKey todo-your-access-key")
  req.Header.Set("Content-Type", "application/json")client := &http.Client{}
  resp, err := client.Do(req)
  if err != nil {
    return err
  }
  defer resp.Body.Close()body, _ := ioutil.ReadAll(resp.Body)
  if resp.StatusCode != http.StatusCreated {
    return fmt.Errorf("Bad response code from api when trying to create message: %s. Body: %s", resp.Status, string(body))
  }
  log.Println("All good. Response body: ", string(body))
  return nil
}

Ahí está. Esto es todo lo que necesitas para crear un bot que actúe como un humano de 5 años.

Ahora, avancemos hacia la construcción de toda la lista de tareas. Primero, modifica un poco la función createHookHandler para que llame a la nueva función handleMessage en lugar de responder.

func createHookHandler(w http.ResponseWriter, r *http.Request) {
  ...
  err = handleMessage(whp)
  ...
}

handle analizará simplistamente los mensajes, hará algún trabajo, y seleccionará la respuesta. Veamos el comando “add”:

func handleMessage(whp *whPayload) error {
  // every conversation has a todo list
  list := manager.fetch(whp.Conversation.ID)
  // parse the command from the message body: it's the first word
  text := whp.Message.Content.Text
  text = regexp.MustCompile(" +").ReplaceAllString(text, " ")
  parts := strings.Split(text, " ")
  command := strings.ToLower(parts[0])
  // default message
  responseBody := "I don't understand. Type 'help' to get help."
  switch command {
  ...
  case "add":
    if len(parts) < 2 {
           return respond(whp.Conversation.ID, "err... the 'add' command needs a second param: the todo item you want to save. Something like 'add buy milk'.")
    }
    // get the item from the message body
    item := strings.Join(parts[1:], " ")list.add(item)
    responseBody = "added."
  ...
  return respond(whp.Conversation.ID, responseBody)
}

Aquí, configuramos: list := manager.fetch(whp.Conversation.ID). Básicamente, “manager” es un mapa seguro para la concurrencia que asigna IDs de conversación a listas de tareas.

Una lista de tareas es una porción de cadena segura para la concurrencia. ¡Todo en memoria!

¡Otra cosa importante! Puedes archivar conversaciones. En algunas aplicaciones, como CRMs, es importante realizar un seguimiento de ciertas interacciones, como para evaluar la efectividad de los empleados de atención al cliente, por ejemplo. La API de Conversations te permite archivar una conversación para “cerrar” el tema. Si el usuario/cliente envía otro mensaje, la API de Conversations abrirá automáticamente un nuevo tema.

Además. Hacer una solicitud PATCH a https://conversations.messagebird.com/v1/conversations/{id} con el estado correcto en el cuerpo te permite archivar la conversación con ese id. Hacemos esto con el comando “bye”:

case "bye":
  archiveConversation(whp.Conversation.ID)
  manager.close(whp.Conversation.ID)
  responseBody = "bye!"

archiveConversation hará la solicitud PATCH y manager.close(whp.Conversation.ID) eliminará la conversación de la lista de tareas.

Pero oye, Programmable Conversations es una solución multicanal. ¿Qué pasa si quisieras reutilizar el código del bot para otra plataforma, como WeChat? Este enfoque multicanal es parte de la estrategia de desviar consultas a canales de menor costo. ¿Cómo lo harías?

¡Simplemente crea un nuevo webhook para apuntar a ese canal! ¡Un webhook que envíe solicitudes a la misma url https://todobot.eu.ngrok.io/create-hook que usamos para WhatsApp!

Esto funcionará porque el código del manejador siempre utiliza el conversationID de la carga útil del webhook para responder a los mensajes en lugar de un channelID codificado. La API de Conversations de MessageBird determinará automáticamente el canal para la conversación para enviar tu mensaje.

¿Quieres construir tu propio bot? Echa un vistazo al código completo en Github: https://github.com/marcelcorso/wabot, solicita acceso anticipado a WhatsApp a través de este enlace y comienza a construir directamente. ¡Feliz creación de bots!

Conectémosle con un experto de Bird.
Vea el poder completo del Bird en 30 minutos.

Al enviar, aceptas que Bird pueda contactarte sobre nuestros productos y servicios.

Puedes darte de baja en cualquier momento. Consulta el Aviso de Privacidad de Bird para obtener detalles sobre el procesamiento de datos.

Company

Newsletter

Mantente al día con Bird a través de actualizaciones semanales en tu buzón.

Conectémosle con un experto de Bird.
Vea el poder completo del Bird en 30 minutos.

Al enviar, aceptas que Bird pueda contactarte sobre nuestros productos y servicios.

Puedes darte de baja en cualquier momento. Consulta el Aviso de Privacidad de Bird para obtener detalles sobre el procesamiento de datos.

Company

Newsletter

Mantente al día con Bird a través de actualizaciones semanales en tu buzón.

Conectémosle con un experto de Bird.
Vea el poder completo del Bird en 30 minutos.

Al enviar, aceptas que Bird pueda contactarte sobre nuestros productos y servicios.

Puedes darte de baja en cualquier momento. Consulta el Aviso de Privacidad de Bird para obtener detalles sobre el procesamiento de datos.

R

Reach

G

Grow

M

Manage

A

Automate

Company

Newsletter

Mantente al día con Bird a través de actualizaciones semanales en tu buzón.