Bird (formerly MessageBird) Bloggggggg | How to build a WhatsApp bot for to-do lists using Bird’s Programmable Conversations API

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

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

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

Feb 5, 2020

Publicerad av

Publicerad av

Bird

Bird

Kategori:

Kategori:

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 Bird’s Programmable Conversations API

Bird recently launched Programmable Conversations. It lets companies blend communications platforms like WhatsApp, Messenger and SMS into their systems — using a single API.

Jag ville testa det, så jag byggde en WhatsApp-bot med en att göra-lista, för vem behöver inte en automatiserad att göra-lista för att organisera sin dag? Det kanske låter komplicerat, men det var faktiskt enkelt, och jag vill gärna berätta allt om det.

Now, I work at MessageBird, so I could just dive in and start building. If you try this, you’ll need to begär tidig tillgång. But once you’re set up with a WhatsApp channel, you can log on till Dashboard on the MessageBird website and get started.

Den 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 -region eu -subdomän todobot

Next, I needed to do a call till 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))
       }
}

Jättebra. Nu kommer Conversations API att göra en POST-begäran till:

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

Så här ser en webhooks nyttolast ut:

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

Vi vill besvara dessa meddelanden. Låt oss börja med att upprepa dem, vad säger ni?

// 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", skapaHookHandler)
      log.Fatal(http.ListenAndServe(*httpListenAddress, nil))
}// createHookHandler is an http handtagr 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.konversation.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 till den intressanta delen. Gör en POST-förfrågan till:

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

Så där ja. Detta är allt du behöver för att skapa en bot som beter sig som en 5-årig människa.

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 hanteraMeddelande 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 vid “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)
}

Här ställer vi in:list := manager.fetch(whp.Conversation.ID). I grund och botten är "manager" en concurrency safe map som mappar konversations-ID:n till att göra-listor.

En att göra-lista är en samtidighetssäker string slice. Allt i minnet!

En annan viktig sak! Du kan arkivera konversationer. I vissa applikationer, som CRM-system, är det viktigt att hålla reda på vissa interaktioner - till exempel för att spåra effektiviteten hos anställda på kundsupporten. Med Conversations API kan du arkivera en konversation för att "stänga" ämnet. Om användaren/kunden skickar ett nytt meddelande öppnar Conversations API automatiskt ett nytt ämne.

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":
               arkivKonversation(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.

Men programmerbara konversationer är ju en omnikanalslösning. Vad händer om du vill återanvända botens kod för en annan plattform, som WeChat? Hur skulle du gå tillväga?

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!

Detta kommer att fungera eftersom hanterarkoden alltid använder konversationsID från webhook-nyttolasten för att svara på meddelandena istället för ett hårdkodat kanalID. MessageBirds Conversations API kommer automatiskt att bestämma vilken kanal för konversationen som meddelandet ska skickas över.

Do you want to build your own bot? Take a look vid 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, Betalningar & Sales. It's Bird

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

By clicking "See Bird" you agree to Bird's Meddelande om integritet.

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

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

By clicking "See Bird" you agree to Bird's Meddelande om integritet.