From 2b18aa0d57a36703a3a80e40c20ccc23f858b1bd Mon Sep 17 00:00:00 2001 From: Roberta Date: Thu, 9 Sep 2021 10:40:10 -0300 Subject: [PATCH 1/3] Supporting media message template from whatsapp. --- handlers/whatsapp/whatsapp.go | 212 +++++++++++++++++++---------- handlers/whatsapp/whatsapp_test.go | 24 ++++ 2 files changed, 163 insertions(+), 73 deletions(-) diff --git a/handlers/whatsapp/whatsapp.go b/handlers/whatsapp/whatsapp.go index e1fd4116d..fab0a5676 100644 --- a/handlers/whatsapp/whatsapp.go +++ b/handlers/whatsapp/whatsapp.go @@ -172,6 +172,7 @@ type eventPayload struct { // receiveMessage is our HTTP handler function for incoming messages func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request) ([]courier.Event, error) { payload := &eventPayload{} + err := handlers.DecodeAndValidateJSON(payload, r) if err != nil { return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) @@ -419,9 +420,24 @@ type LocalizableParam struct { Default string `json:"default"` } +type mmtImage struct{ + Link string `json:"link,omitempty"` +} + +type mmtDocument struct{ + Link string `json:"link,omitempty"` +} + +type mmtVideo struct{ + Link string `json:"link,omitempty"` +} + type Param struct { - Type string `json:"type"` - Text string `json:"text"` + Type string `json:"type"` + Text string `json:"text,omitempty"` + Image *mmtImage `json:"image,omitempty"` + Document *mmtDocument `json:"document,omitempty"` + Video *mmtVideo `json:"video,omitempty"` } type Component struct { @@ -495,6 +511,7 @@ const maxMsgLength = 4096 // SendMsg sends the passed in message, returning any error func (h *handler) SendMsg(ctx context.Context, msg courier.Msg) (courier.MsgStatus, error) { start := time.Now() + // get our token token := msg.Channel().StringConfigForKey(courier.ConfigAuthToken, "") if token == "" { @@ -525,6 +542,7 @@ func (h *handler) SendMsg(ctx context.Context, msg courier.Msg) (courier.MsgStat for i, payload := range payloads { externalID := "" + wppID, externalID, logs, err = sendWhatsAppMsg(msg, sendPath, payload) // add logs to our status for _, log := range logs { @@ -564,76 +582,11 @@ func buildPayloads(msg courier.Msg, h *handler) ([]interface{}, []*courier.Chann var logs []*courier.ChannelLog var err error - if len(msg.Attachments()) > 0 { - for attachmentCount, attachment := range msg.Attachments() { - - mimeType, mediaURL := handlers.SplitAttachment(attachment) - mediaID, mediaLogs, err := h.fetchMediaID(msg, mimeType, mediaURL) - if len(mediaLogs) > 0 { - logs = append(logs, mediaLogs...) - } - if err != nil { - logrus.WithField("channel_uuid", msg.Channel().UUID().String()).WithError(err).Error("error while uploading media to whatsapp") - } - if err == nil && mediaID != "" { - mediaURL = "" - } - mediaPayload := &mediaObject{ID: mediaID, Link: mediaURL} - if strings.HasPrefix(mimeType, "audio") { - payload := mtAudioPayload{ - To: msg.URN().Path(), - Type: "audio", - } - payload.Audio = mediaPayload - payloads = append(payloads, payload) - } else if strings.HasPrefix(mimeType, "application") { - payload := mtDocumentPayload{ - To: msg.URN().Path(), - Type: "document", - } - if attachmentCount == 0 { - mediaPayload.Caption = msg.Text() - } - mediaPayload.Filename, err = utils.BasePathForURL(mediaURL) + // do we have a template? + var templating *MsgTemplating + templating,err = h.getTemplate(msg) + if templating != nil || len(msg.Attachments()) == 0{ - // Logging error - if err != nil { - logrus.WithField("channel_uuid", msg.Channel().UUID().String()).WithError(err).Error("Error while parsing the media URL") - } - payload.Document = mediaPayload - payloads = append(payloads, payload) - } else if strings.HasPrefix(mimeType, "image") { - payload := mtImagePayload{ - To: msg.URN().Path(), - Type: "image", - } - if attachmentCount == 0 { - mediaPayload.Caption = msg.Text() - } - payload.Image = mediaPayload - payloads = append(payloads, payload) - } else if strings.HasPrefix(mimeType, "video") { - payload := mtVideoPayload{ - To: msg.URN().Path(), - Type: "video", - } - if attachmentCount == 0 { - mediaPayload.Caption = msg.Text() - } - payload.Video = mediaPayload - payloads = append(payloads, payload) - } else { - duration := time.Since(start) - err = fmt.Errorf("unknown attachment mime type: %s", mimeType) - attachmentLogs := []*courier.ChannelLog{courier.NewChannelLogFromError("Error sending message", msg.Channel(), msg.ID(), duration, err)} - logs = append(logs, attachmentLogs...) - break - } - } - } else { - // do we have a template? - var templating *MsgTemplating - templating, err := h.getTemplate(msg) if err != nil { return nil, nil, errors.Wrapf(err, "unable to decode template: %s for channel: %s", string(msg.Metadata()), msg.Channel().UUID()) } @@ -660,7 +613,6 @@ func buildPayloads(msg courier.Msg, h *handler) ([]interface{}, []*courier.Chann } payloads = append(payloads, payload) } else { - payload := templatePayload{ To: msg.URN().Path(), Type: "template", @@ -677,6 +629,50 @@ func buildPayloads(msg courier.Msg, h *handler) ([]interface{}, []*courier.Chann } payload.Template.Components = append(payload.Template.Components, *component) + if(len(msg.Attachments()) > 0){ + + header := &Component{Type:"header"} + + for _, attachment := range msg.Attachments() { + + mimeType, mediaURL := handlers.SplitAttachment(attachment) + mediaID, mediaLogs, err := h.fetchMediaID(msg, mimeType, mediaURL) + if len(mediaLogs) > 0 { + logs = append(logs, mediaLogs...) + } + if err != nil { + logrus.WithField("channel_uuid", msg.Channel().UUID().String()).WithError(err).Error("error while uploading media to whatsapp") + } + if err != nil && mediaID != "" { + mediaURL = "" + } + if strings.HasPrefix(mimeType, "image"){ + image := &mmtImage{ + Link: mediaURL, + } + header.Parameters = append(header.Parameters, Param{Type: "image", Image: image}) + payload.Template.Components = append(payload.Template.Components, *header) + }else if strings.HasPrefix(mimeType, "application"){ + document := &mmtDocument{ + Link: mediaURL, + } + header.Parameters = append(header.Parameters, Param{Type: "document", Document: document}) + payload.Template.Components = append(payload.Template.Components, *header) + }else if strings.HasPrefix(mimeType, "video") { + video := &mmtVideo{ + Link: mediaURL, + } + header.Parameters = append(header.Parameters, Param{Type: "video", Video: video}) + payload.Template.Components = append(payload.Template.Components, *header) + }else { + duration := time.Since(start) + err = fmt.Errorf("unknown attachment mime type: %s", mimeType) + attachmentLogs := []*courier.ChannelLog{courier.NewChannelLogFromError("Error sending message", msg.Channel(), msg.ID(), duration, err)} + logs = append(logs, attachmentLogs...) + break + } + } + } payloads = append(payloads, payload) } } else { @@ -748,7 +744,76 @@ func buildPayloads(msg courier.Msg, h *handler) ([]interface{}, []*courier.Chann } } } - } + }else{ + + if len(msg.Attachments()) > 0 { + for attachmentCount, attachment := range msg.Attachments() { + + mimeType, mediaURL := handlers.SplitAttachment(attachment) + mediaID, mediaLogs, err := h.fetchMediaID(msg, mimeType, mediaURL) + if len(mediaLogs) > 0 { + logs = append(logs, mediaLogs...) + } + if err != nil { + logrus.WithField("channel_uuid", msg.Channel().UUID().String()).WithError(err).Error("error while uploading media to whatsapp") + } + if err == nil && mediaID != "" { + mediaURL = "" + } + mediaPayload := &mediaObject{ID: mediaID, Link: mediaURL} + if strings.HasPrefix(mimeType, "audio") { + payload := mtAudioPayload{ + To: msg.URN().Path(), + Type: "audio", + } + payload.Audio = mediaPayload + payloads = append(payloads, payload) + } else if strings.HasPrefix(mimeType, "application") { + payload := mtDocumentPayload{ + To: msg.URN().Path(), + Type: "document", + } + if attachmentCount == 0 { + mediaPayload.Caption = msg.Text() + } + mediaPayload.Filename, err = utils.BasePathForURL(mediaURL) + + // Logging error + if err != nil { + logrus.WithField("channel_uuid", msg.Channel().UUID().String()).WithError(err).Error("Error while parsing the media URL") + } + payload.Document = mediaPayload + payloads = append(payloads, payload) + } else if strings.HasPrefix(mimeType, "image") { + payload := mtImagePayload{ + To: msg.URN().Path(), + Type: "image", + } + if attachmentCount == 0 { + mediaPayload.Caption = msg.Text() + } + payload.Image = mediaPayload + payloads = append(payloads, payload) + } else if strings.HasPrefix(mimeType, "video") { + payload := mtVideoPayload{ + To: msg.URN().Path(), + Type: "video", + } + if attachmentCount == 0 { + mediaPayload.Caption = msg.Text() + } + payload.Video = mediaPayload + payloads = append(payloads, payload) + } else { + duration := time.Since(start) + err = fmt.Errorf("unknown attachment mime type: %s", mimeType) + attachmentLogs := []*courier.ChannelLog{courier.NewChannelLogFromError("Error sending message", msg.Channel(), msg.ID(), duration, err)} + logs = append(logs, attachmentLogs...) + break + } + } + } + } return payloads, logs, err } @@ -836,6 +901,7 @@ func sendWhatsAppMsg(msg courier.Msg, sendPath *url.URL, payload interface{}) (s log := courier.NewChannelLogFromError("unable to build JSON body", msg.Channel(), msg.ID(), elapsed, err) return "", "", []*courier.ChannelLog{log}, err } + req, _ := http.NewRequest(http.MethodPost, sendPath.String(), bytes.NewReader(jsonBody)) req.Header = buildWhatsAppHeaders(msg.Channel()) rr, err := utils.MakeHTTPRequest(req) diff --git a/handlers/whatsapp/whatsapp_test.go b/handlers/whatsapp/whatsapp_test.go index 40d940445..6cde2514b 100644 --- a/handlers/whatsapp/whatsapp_test.go +++ b/handlers/whatsapp/whatsapp_test.go @@ -599,6 +599,30 @@ var defaultSendTestCases = []ChannelSendTestCase{ ResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, ResponseStatus: 201, RequestBody: `{"to":"250788123123","type":"interactive","interactive":{"type":"list","body":{"text":"Interactive List Msg"},"action":{"button":"Menu","sections":[{"rows":[{"id":"0","title":"ROW1"},{"id":"1","title":"ROW2"},{"id":"2","title":"ROW3"},{"id":"3","title":"ROW4"}]}]}}}`, SendPrep: setSendURL}, + {Label: "Media Message Template Send - Image", + Text: "Media Message Msg", URN: "whatsapp:250788123123", + Status: "W", ExternalID: "157b5e14568e8", + Metadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "namespace": "wa_template_namespace", "language": "eng", "country": "US", "variables": ["Chef", "tomorrow"]}}`), + Attachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + ResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, ResponseStatus: 201, + RequestBody: `{"to":"250788123123","type":"template","template":{"namespace":"wa_template_namespace","name":"revive_issue","language":{"policy":"deterministic","code":"en_US"},"components":[{"type":"body","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]},{"type":"header","parameters":[{"type":"image","image":{"link":"https://foo.bar/image.jpg"}}]}]}}`, + SendPrep: setSendURL}, + {Label: "Media Message Template Send - Video", + Text: "Media Message Msg", URN: "whatsapp:250788123123", + Status: "W", ExternalID: "157b5e14568e8", + Metadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "namespace": "wa_template_namespace", "language": "eng", "country": "US", "variables": ["Chef", "tomorrow"]}}`), + Attachments: []string{"video/mp4:https://foo.bar/video.mp4"}, + ResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, ResponseStatus: 201, + RequestBody: `{"to":"250788123123","type":"template","template":{"namespace":"wa_template_namespace","name":"revive_issue","language":{"policy":"deterministic","code":"en_US"},"components":[{"type":"body","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]},{"type":"header","parameters":[{"type":"video","video":{"link":"https://foo.bar/video.mp4"}}]}]}}`, + SendPrep: setSendURL}, + {Label: "Media Message Template Send - Document", + Text: "Media Message Msg", URN: "whatsapp:250788123123", + Status: "W", ExternalID: "157b5e14568e8", + Metadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "namespace": "wa_template_namespace", "language": "eng", "country": "US", "variables": ["Chef", "tomorrow"]}}`), + Attachments: []string{"application/pdf:https://foo.bar/document.pdf"}, + ResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, ResponseStatus: 201, + RequestBody: `{"to":"250788123123","type":"template","template":{"namespace":"wa_template_namespace","name":"revive_issue","language":{"policy":"deterministic","code":"en_US"},"components":[{"type":"body","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]},{"type":"header","parameters":[{"type":"document","document":{"link":"https://foo.bar/document.pdf"}}]}]}}`, + SendPrep: setSendURL}, } var mediaCacheSendTestCases = []ChannelSendTestCase{ From 1304f0c3883e5eea37b6c672fcb4e7731d21a031 Mon Sep 17 00:00:00 2001 From: Roberta Moreira <30378788+Robi9@users.noreply.github.com> Date: Wed, 22 Sep 2021 10:40:44 -0300 Subject: [PATCH 2/3] Code formatting on whatsapp.go --- handlers/whatsapp/whatsapp.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/handlers/whatsapp/whatsapp.go b/handlers/whatsapp/whatsapp.go index fab0a5676..3283a2a17 100644 --- a/handlers/whatsapp/whatsapp.go +++ b/handlers/whatsapp/whatsapp.go @@ -583,8 +583,7 @@ func buildPayloads(msg courier.Msg, h *handler) ([]interface{}, []*courier.Chann var err error // do we have a template? - var templating *MsgTemplating - templating,err = h.getTemplate(msg) + templating, err := h.getTemplate(msg) if templating != nil || len(msg.Attachments()) == 0{ if err != nil { From 41af54834b70e0707cab3f5ea590cce281fa4d69 Mon Sep 17 00:00:00 2001 From: Robi9 Date: Wed, 22 Sep 2021 12:07:38 -0300 Subject: [PATCH 3/3] Code formatting in every whatsapp.go file --- handlers/whatsapp/whatsapp.go | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/handlers/whatsapp/whatsapp.go b/handlers/whatsapp/whatsapp.go index 3283a2a17..afb574be5 100644 --- a/handlers/whatsapp/whatsapp.go +++ b/handlers/whatsapp/whatsapp.go @@ -172,7 +172,7 @@ type eventPayload struct { // receiveMessage is our HTTP handler function for incoming messages func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request) ([]courier.Event, error) { payload := &eventPayload{} - + err := handlers.DecodeAndValidateJSON(payload, r) if err != nil { return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) @@ -420,21 +420,21 @@ type LocalizableParam struct { Default string `json:"default"` } -type mmtImage struct{ +type mmtImage struct { Link string `json:"link,omitempty"` } -type mmtDocument struct{ +type mmtDocument struct { Link string `json:"link,omitempty"` } -type mmtVideo struct{ +type mmtVideo struct { Link string `json:"link,omitempty"` } type Param struct { - Type string `json:"type"` - Text string `json:"text,omitempty"` + Type string `json:"type"` + Text string `json:"text,omitempty"` Image *mmtImage `json:"image,omitempty"` Document *mmtDocument `json:"document,omitempty"` Video *mmtVideo `json:"video,omitempty"` @@ -511,7 +511,7 @@ const maxMsgLength = 4096 // SendMsg sends the passed in message, returning any error func (h *handler) SendMsg(ctx context.Context, msg courier.Msg) (courier.MsgStatus, error) { start := time.Now() - + // get our token token := msg.Channel().StringConfigForKey(courier.ConfigAuthToken, "") if token == "" { @@ -584,7 +584,7 @@ func buildPayloads(msg courier.Msg, h *handler) ([]interface{}, []*courier.Chann // do we have a template? templating, err := h.getTemplate(msg) - if templating != nil || len(msg.Attachments()) == 0{ + if templating != nil || len(msg.Attachments()) == 0 { if err != nil { return nil, nil, errors.Wrapf(err, "unable to decode template: %s for channel: %s", string(msg.Metadata()), msg.Channel().UUID()) @@ -628,9 +628,9 @@ func buildPayloads(msg courier.Msg, h *handler) ([]interface{}, []*courier.Chann } payload.Template.Components = append(payload.Template.Components, *component) - if(len(msg.Attachments()) > 0){ + if len(msg.Attachments()) > 0 { - header := &Component{Type:"header"} + header := &Component{Type: "header"} for _, attachment := range msg.Attachments() { @@ -645,31 +645,31 @@ func buildPayloads(msg courier.Msg, h *handler) ([]interface{}, []*courier.Chann if err != nil && mediaID != "" { mediaURL = "" } - if strings.HasPrefix(mimeType, "image"){ + if strings.HasPrefix(mimeType, "image") { image := &mmtImage{ Link: mediaURL, } header.Parameters = append(header.Parameters, Param{Type: "image", Image: image}) payload.Template.Components = append(payload.Template.Components, *header) - }else if strings.HasPrefix(mimeType, "application"){ + } else if strings.HasPrefix(mimeType, "application") { document := &mmtDocument{ Link: mediaURL, } header.Parameters = append(header.Parameters, Param{Type: "document", Document: document}) payload.Template.Components = append(payload.Template.Components, *header) - }else if strings.HasPrefix(mimeType, "video") { + } else if strings.HasPrefix(mimeType, "video") { video := &mmtVideo{ Link: mediaURL, } header.Parameters = append(header.Parameters, Param{Type: "video", Video: video}) payload.Template.Components = append(payload.Template.Components, *header) - }else { + } else { duration := time.Since(start) err = fmt.Errorf("unknown attachment mime type: %s", mimeType) attachmentLogs := []*courier.ChannelLog{courier.NewChannelLogFromError("Error sending message", msg.Channel(), msg.ID(), duration, err)} logs = append(logs, attachmentLogs...) break - } + } } } payloads = append(payloads, payload) @@ -743,7 +743,7 @@ func buildPayloads(msg courier.Msg, h *handler) ([]interface{}, []*courier.Chann } } } - }else{ + } else { if len(msg.Attachments()) > 0 { for attachmentCount, attachment := range msg.Attachments() { @@ -812,7 +812,7 @@ func buildPayloads(msg courier.Msg, h *handler) ([]interface{}, []*courier.Chann } } } - } + } return payloads, logs, err }