How to make a WhatsApp Bot in Go [Part 3 of N]

WhatsApp Logo

Processing incoming messages, either text or media, is one of the fundamental features a Bot must implement. In this post we will explore how to do this.


Connection Handlers

go-whatsapp has a connection struct which exposes functions to perform login, handle session, and send messages. The reception of those messages is done by add a (message) handler.

The Handler interface contains the only function that we must implement, the ErrorHandler function.

type Handler interface {
	HandleError(err error)
}

Every other handler, like the TextMessageHandler inherits from Handler and adds its own functions. For example:

type TextMessageHandler interface {
	Handler
	HandleTextMessage(message TextMessage)
}

The most important part of this design, is that we can add as many handlers as you want. The connection struct contains a (private) list of handlers, and it checks on runtime to which handler it must dispatch each message.

Moreover, due to the implementation of this list of handlers, we can add two or more handlers of the same type. For example, one TextMessageHandler can log any incoming message into a database, and another one can perform text analysis and send responses based on that.

This implementation relies heavily on the very famous Go’s duck type (or structural typing).

If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.

Incoming messages

A TextMessage is represented by a struct with three public fields. Info, Text and ContextInfo.

type TextMessage struct {
	Info        MessageInfo
	Text        string
	ContextInfo ContextInfo
}

Text contains the message we received (or sent), Info (MessageInfo) gives us information about who and when the message was sent, and it’s status (pending, sent, delivered, read or played). And ContextInfo gives us additional information like if the message is a quote of another message (we also have access to this original message), or if it was forwarded.

Receiving messages is a simple as declaring a struct and implementing the TextMessageHandler interface. In this example I declared a struct called whatsAppMessageHandler which implements all of the functions requiered.

type whatsAppMessageHandler struct{}
func (whatsAppMessageHandler) HandleError(err error) {
	fmt.Fprintf(os.Stderr, "%+v", err)
}
func (whatsAppMessageHandler) HandleTextMessage(message whatsapp.TextMessage) {
	fmt.Printf("HandleTextMessage: %+v\n", message)
}

If we send a message, for example, «Hello WhatsApp Bot!» the following line will be printed in our terminal.

HandleTextMessage: {Info:{Id:F75C24CE7098AB206B22DF6B275533B8 RemoteJid:54911**12****@s.whatsapp.net SenderJid: FromMe:true Timestamp:1581616175 PushName: Status:4 Source:key:<remoteJid:"54911**12****@s.whatsapp.net" fromMe:true id:"F75C24CE7098AB206B22DF6B275533B8" > message:<conversation:"Hello, WhatsApp Bot!" > messageTimestamp:1581616175 status:READ } Text:Hello, WhatsApp Bot! ContextInfo:{QuotedMessageID: QuotedMessage:<nil> Participant: IsForwarded:false}}

The TextMessage struct in a more (JSON) pretty printed version looks like this

{
   "Info":{
      "Id":"F75C24CE7098AB206B22DF6B275533B8",
      "RemoteJid":"54911**12****@s.whatsapp.net",
      "SenderJid":"",
      "FromMe":true,
      "Timestamp":1581616175,
      "PushName":"",
      "Status":4,
      "Source":{
         "key":{
            "remoteJid":"54911**12****@s.whatsapp.net",
            "fromMe":true,
            "id":"F75C24CE7098AB206B22DF6B275533B8"
         },
         "message":{
            "conversation":"Hello, WhatsApp Bot!"
         },
         "messageTimestamp":1581616175,
         "status":4
      }
   },
   "Text":"Hello, WhatsApp Bot!",
   "ContextInfo":{
      "QuotedMessageID":"",
      "QuotedMessage":null,
      "Participant":"",
      "IsForwarded":false
   }
}

A reply quoting this message will look like

{
   "Info":{
      "Id":"31D97076ED9DFC5C95D82A8B0B9FE00E",
      "RemoteJid":"54911**12****@s.whatsapp.net",
      "SenderJid":"",
      "FromMe":true,
      "Timestamp":1581616344,
      "PushName":"",
      "Status":4,
      "Source":{
         "key":{
            "remoteJid":"54911**12****@s.whatsapp.net",
            "fromMe":true,
            "id":"31D97076ED9DFC5C95D82A8B0B9FE00E"
         },
         "message":{
            "extendedTextMessage":{
               "text":"Reply on a message",
               "previewType":0,
               "contextInfo":{
                  "stanzaId":"F75C24CE7098AB206B22DF6B275533B8",
                  "participant":"54911**12****@s.whatsapp.net",
                  "quotedMessage":{
                     "conversation":"Hello, WhatsApp Bot!"
                  }
               }
            }
         },
         "messageTimestamp":1581616344,
         "status":4
      }
   },
   "Text":"Reply on a message",
   "ContextInfo":{
      "QuotedMessageID":"F75C24CE7098AB206B22DF6B275533B8",
      "QuotedMessage":{
         "conversation":"Hello, WhatsApp Bot!"
      },
      "Participant":"54911**12****@s.whatsapp.net",
      "IsForwarded":false
   }
}

As this reply was done on a specific message, the same information appears on the top-level ContextInfo and inside Info struct.

Sé el primero en comentar

Dejar una contestacion