Hoe bouw je een WhatsApp-bot voor takenlijstjes met de programmeerbare conversaties-API van MessageBird?

Hoe bouw je een WhatsApp-bot voor takenlijstjes met de programmeerbare conversaties-API van MessageBird?

Hoe bouw je een WhatsApp-bot voor takenlijstjes met de programmeerbare conversaties-API van MessageBird?

Feb 5, 2020

Gepubliceerd door

Gepubliceerd door

Bird

Bird

-

Categorie:

Categorie:

WhatsApp

WhatsApp

Ready to see Bird
in action?

Ready to see Bird
in action?

How to build a WhatsApp bot for to-do lists using MessageBird’s Programmable Conversations API

MessageBird heeft onlangs Programmable Conversations gelanceerd. Hiermee kunnen bedrijven communicatieplatforms als WhatsApp, Messenger en SMS in hun systemen integreren - met behulp van één enkele API.

Ik wilde het wel eens proberen, dus maakte ik een WhatsApp bot to-do list, want wie heeft er geen geautomatiseerde to-do list nodig om zijn dag te organiseren? Het klinkt misschien ingewikkeld, maar het was eigenlijk eenvoudig, en ik vertel je er graag alles over.

Now, I work at MessageBird, so I could just dive in and start building. If you try this, you’ll need to vroege toegang aanvragen. But once you’re set up with a WhatsApp channel, you can log on naar de Dashboard on the MessageBird website and get started.

De first thing I did was read the docs. I learned that, in order to get messages from the bot, I would have to use a webhook. This meant that my bot would need to be accessible from the internet. Since I was just starting to code it, I decided to use ngrok. It creates a tunnel from the public internet to your dear localhost port 5007. Engage!

ngrok http 5007 -regio eu -subdomein todobot

Next, I needed to do a call naar de Programmable Conversations API to create the webhook. It’s a POST to https://conversations.messagebird.com/v1/webhooks and it looks something like this:

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))
       }
}

Mooi. Nu gaat de Gesprekken API een POST verzoek doen naar:

https://todobot.eu.ngrok.io/create-hook whenever a new message gets created on the WhatsApp channel you set up earlier.

Zo ziet een webhook payload eruit:

{
    "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"
 }

We willen die berichten beantwoorden. Laten we beginnen met ze te herhalen, wat zeg je ervan?

// define the structs where we'll parse the webhook payload intype 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 handvatr 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")
}

Nu het interessante gedeelte. Doe een POST verzoek naar:

“https://conversations.messagebird.com/v1/conversations/<conversationID>/messages” to answer the request.

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
}

Daar. Dit is alles wat je nodig hebt om een bot te maken die zich gedraagt als een 5-jarige mens.

Now, let’s make a push towards building the whole to-do list. First, modify the createHookHandler function a bit so it calls the new handleMessage function instead of respond.

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

handle will simplistically parse the messages, do some work, and pick the response. Let’s look aan de “add” command:

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)
}

Hier stellen we in:list := manager.fetch(whp.Conversation.ID). In principe is "manager" een concurrency safe map die conversatie-ID's koppelt aan to-do lijsten.

Een takenlijst is een concurrency veilige string slice. Alles in het geheugen!

Nog iets belangrijks! U kunt gesprekken archiveren. In sommige toepassingen, zoals CRM's, is het belangrijk om bepaalde interacties bij te houden - bijvoorbeeld om de effectiviteit van medewerkers van de klantenservice te volgen. Met de Conversaties API kunt u een gesprek archiveren om het onderwerp "af te sluiten". Als de gebruiker/klant een ander bericht stuurt, opent de Conversaties-API automatisch een nieuw onderwerp.

Also. Doing PATCH request to https://conversations.messagebird.com/v1/conversations/{id} with the right status on the body allows you to archive the conversation with that id. We do this with the “bye” command:

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

archiveConversation will do the PATCH request and manager.close(whp.Conversation.ID) will remove the to-do list conversation.

Maar hé, Programmable Conversations is een omnichannel oplossing. Wat als u de code van de bot wilt hergebruiken voor een ander platform, zoals WeChat? Hoe zou u dat aanpakken?

Just create a new webhook to target that channel! A webhook that sends requests to the same https://todobot.eu.ngrok.io/create-hook url we used for WhatsApp!

Dit zal werken omdat de handler code altijd de conversationID van de webhook payload gebruikt om de berichten te beantwoorden in plaats van een hardcoded channelID. MessageBird's Conversaties API zal automatisch het kanaal voor de conversatie bepalen om uw bericht over te sturen.

Do you want to build your own bot? Take a look aan de full code on Github: https://github.com/marcelcorso/wabot, request early access to WhatsApp via this link and start building directly. Happy botting!

Your new standard in Marketing, Pay & Sales. It's Bird

De right message -> to the right person -> at the right time.

Your new standard in Marketing, Pay & Sales. It's Bird

The right message -> to the right person -> at the right time.