Skip to content

Commit

Permalink
Add media support config for WAC
Browse files Browse the repository at this point in the history
  • Loading branch information
norkans7 committed Sep 5, 2023
1 parent f42aadb commit fd6068d
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 42 deletions.
55 changes: 34 additions & 21 deletions handlers/facebookapp/facebookapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ var waIgnoreStatuses = map[string]bool{
"deleted": true,
}

// see https://developers.facebook.com/docs/whatsapp/cloud-api/reference/media#supported-media-types
var wacMediaSupport = map[handlers.MediaType]handlers.MediaTypeSupport{
handlers.MediaType("image/webp"): {Types: []string{"image/webp"}, MaxBytes: 100 * 1024, MaxWidth: 512, MaxHeight: 512},
handlers.MediaTypeImage: {Types: []string{"image/jpeg", "image/png"}, MaxBytes: 5 * 1024 * 1024},
handlers.MediaTypeAudio: {Types: []string{"audio/aac", "audio/mp4", "audio/mpeg", "audio/amr", "audio/ogg"}, MaxBytes: 16 * 1024 * 1024},
handlers.MediaTypeVideo: {Types: []string{"video/mp4", "video/3gp"}, MaxBytes: 16 * 1024 * 1024},
handlers.MediaTypeApplication: {MaxBytes: 100 * 1024 * 1024},
}

func newHandler(channelType courier.ChannelType, name string, useUUIDRoutes bool) courier.ChannelHandler {
return &handler{handlers.NewBaseHandlerWithParams(channelType, name, useUUIDRoutes, []string{courier.ConfigAuthToken})}
}
Expand Down Expand Up @@ -1129,12 +1138,17 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg,
qrs := msg.QuickReplies()
lang := getSupportedLanguage(msg.Locale())

attachments, err := handlers.ResolveAttachments(ctx, h.Backend(), msg.Attachments(), wacMediaSupport, false)
if err != nil {
return nil, errors.Wrap(err, "error resolving attachments")
}

var payloadAudio wacMTPayload

for i := 0; i < len(msgParts)+len(msg.Attachments()); i++ {
for i := 0; i < len(msgParts)+len(attachments); i++ {
payload := wacMTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path()}

if len(msg.Attachments()) == 0 {
if len(attachments) == 0 {
// do we have a template?
templating, err := h.getTemplating(msg)
if err != nil {
Expand All @@ -1155,14 +1169,14 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg,
template.Components = append(payload.Template.Components, component)

} else {
if i < (len(msgParts) + len(msg.Attachments()) - 1) {
if i < (len(msgParts) + len(attachments) - 1) {
// this is still a msg part
text := &wacText{PreviewURL: false}
payload.Type = "text"
if strings.Contains(msgParts[i-len(msg.Attachments())], "https://") || strings.Contains(msgParts[i-len(msg.Attachments())], "http://") {
if strings.Contains(msgParts[i-len(attachments)], "https://") || strings.Contains(msgParts[i-len(attachments)], "http://") {
text.PreviewURL = true
}
text.Body = msgParts[i-len(msg.Attachments())]
text.Body = msgParts[i-len(attachments)]
payload.Text = text
} else {
if len(qrs) > 0 {
Expand All @@ -1171,7 +1185,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg,
if len(qrs) <= 3 {
interactive := wacInteractive{Type: "button", Body: struct {
Text string "json:\"text\""
}{Text: msgParts[i-len(msg.Attachments())]}}
}{Text: msgParts[i-len(attachments)]}}

btns := make([]wacMTButton, len(qrs))
for i, qr := range qrs {
Expand All @@ -1190,7 +1204,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg,
} else if len(qrs) <= 10 {
interactive := wacInteractive{Type: "list", Body: struct {
Text string "json:\"text\""
}{Text: msgParts[i-len(msg.Attachments())]}}
}{Text: msgParts[i-len(attachments)]}}

section := wacMTSection{
Rows: make([]wacMTSectionRow, len(qrs)),
Expand Down Expand Up @@ -1218,34 +1232,33 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg,
// this is still a msg part
text := &wacText{PreviewURL: false}
payload.Type = "text"
if strings.Contains(msgParts[i-len(msg.Attachments())], "https://") || strings.Contains(msgParts[i-len(msg.Attachments())], "http://") {
if strings.Contains(msgParts[i-len(attachments)], "https://") || strings.Contains(msgParts[i-len(attachments)], "http://") {
text.PreviewURL = true
}
text.Body = msgParts[i-len(msg.Attachments())]
text.Body = msgParts[i-len(attachments)]
payload.Text = text
}
}
}

} else if i < len(msg.Attachments()) && (len(qrs) == 0 || len(qrs) > 3) {
attType, attURL := handlers.SplitAttachment(msg.Attachments()[i])
splitedAttType := strings.Split(attType, "/")
attType = splitedAttType[0]
attFormat := splitedAttType[1]
} else if i < len(attachments) && (len(qrs) == 0 || len(qrs) > 3) {
attURL := attachments[i].Media.URL()
attType := string(attachments[i].Type)
attContentType := attachments[i].Media.ContentType()

if attType == "application" {
attType = "document"
}
payload.Type = attType
media := wacMTMedia{Link: attURL}

if len(msgParts) == 1 && attType != "audio" && len(msg.Attachments()) == 1 && len(msg.QuickReplies()) == 0 {
if len(msgParts) == 1 && attType != "audio" && len(attachments) == 1 && len(msg.QuickReplies()) == 0 {
media.Caption = msgParts[i]
hasCaption = true
}

if attType == "image" {
if attFormat == "webp" {
if attContentType == "image/webp" {
payload.Type = "sticker"
payload.Sticker = &media
} else {
Expand Down Expand Up @@ -1276,8 +1289,8 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg,

if len(msg.Attachments()) > 0 {
hasCaption = true
attType, attURL := handlers.SplitAttachment(msg.Attachments()[i])
attType = strings.Split(attType, "/")[0]
attURL := attachments[i].Media.URL()
attType := string(attachments[i].Type)
if attType == "application" {
attType = "document"
}
Expand Down Expand Up @@ -1353,7 +1366,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg,
} else if len(qrs) <= 10 {
interactive := wacInteractive{Type: "list", Body: struct {
Text string "json:\"text\""
}{Text: msgParts[i-len(msg.Attachments())]}}
}{Text: msgParts[i-len(attachments)]}}

section := wacMTSection{
Rows: make([]wacMTSectionRow, len(qrs)),
Expand Down Expand Up @@ -1381,10 +1394,10 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg,
// this is still a msg part
text := &wacText{PreviewURL: false}
payload.Type = "text"
if strings.Contains(msgParts[i-len(msg.Attachments())], "https://") || strings.Contains(msgParts[i-len(msg.Attachments())], "http://") {
if strings.Contains(msgParts[i-len(attachments)], "https://") || strings.Contains(msgParts[i-len(attachments)], "http://") {
text.PreviewURL = true
}
text.Body = msgParts[i-len(msg.Attachments())]
text.Body = msgParts[i-len(attachments)]
payload.Text = text
}
}
Expand Down
66 changes: 45 additions & 21 deletions handlers/facebookapp/facebookapp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1288,12 +1288,12 @@ var SendTestCasesWAC = []ChannelSendTestCase{
Label: "Audio Send",
MsgText: "audio caption",
MsgURN: "whatsapp:250788123123",
MsgAttachments: []string{"audio/mpeg:https://foo.bar/audio.mp3"},
MsgAttachments: []string{"audio/mpeg:http://mock.com/3456/test.mp3"},
MockResponses: map[MockedRequest]*httpx.MockResponse{
{
Method: "POST",
Path: "/12345_ID/messages",
Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"audio","audio":{"link":"https://foo.bar/audio.mp3"}}`,
Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"audio","audio":{"link":"http://mock.com/3456/test.mp3"}}`,
}: httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)),
{
Method: "POST",
Expand All @@ -1309,10 +1309,10 @@ var SendTestCasesWAC = []ChannelSendTestCase{
Label: "Document Send",
MsgText: "document caption",
MsgURN: "whatsapp:250788123123",
MsgAttachments: []string{"application/pdf:https://foo.bar/document.pdf"},
MsgAttachments: []string{"application/pdf:http://mock.com/7890/test.pdf"},
MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`,
MockResponseStatus: 201,
ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"document","document":{"link":"https://foo.bar/document.pdf","caption":"document caption","filename":"document.pdf"}}`,
ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"document","document":{"link":"http://mock.com/7890/test.pdf","caption":"document caption","filename":"test.pdf"}}`,
ExpectedRequestPath: "/12345_ID/messages",
ExpectedMsgStatus: "W",
ExpectedExternalID: "157b5e14568e8",
Expand All @@ -1322,10 +1322,10 @@ var SendTestCasesWAC = []ChannelSendTestCase{
Label: "Image Send",
MsgText: "image caption",
MsgURN: "whatsapp:250788123123",
MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"},
MsgAttachments: []string{"image/jpeg:http://mock.com/1234/test.jpg"},
MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`,
MockResponseStatus: 201,
ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"image","image":{"link":"https://foo.bar/image.jpg","caption":"image caption"}}`,
ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"image","image":{"link":"http://mock.com/1234/test.jpg","caption":"image caption"}}`,
ExpectedRequestPath: "/12345_ID/messages",
ExpectedMsgStatus: "W",
ExpectedExternalID: "157b5e14568e8",
Expand All @@ -1335,10 +1335,10 @@ var SendTestCasesWAC = []ChannelSendTestCase{
Label: "Sticker Send",
MsgText: "sticker caption",
MsgURN: "whatsapp:250788123123",
MsgAttachments: []string{"image/webp:https://foo.bar/sticker.webp"},
MsgAttachments: []string{"image/webp:http://mock.com/8901/test.webp"},
MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`,
MockResponseStatus: 201,
ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"sticker","sticker":{"link":"https://foo.bar/sticker.webp","caption":"sticker caption"}}`,
ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"sticker","sticker":{"link":"http://mock.com/8901/test.webp","caption":"sticker caption"}}`,
ExpectedRequestPath: "/12345_ID/messages",
ExpectedMsgStatus: "W",
ExpectedExternalID: "157b5e14568e8",
Expand All @@ -1348,10 +1348,10 @@ var SendTestCasesWAC = []ChannelSendTestCase{
Label: "Video Send",
MsgText: "video caption",
MsgURN: "whatsapp:250788123123",
MsgAttachments: []string{"video/mp4:https://foo.bar/video.mp4"},
MsgAttachments: []string{"video/mp4:http://mock.com/5678/test.mp4"},
MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`,
MockResponseStatus: 201,
ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"video","video":{"link":"https://foo.bar/video.mp4","caption":"video caption"}}`,
ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"video","video":{"link":"http://mock.com/5678/test.mp4","caption":"video caption"}}`,
ExpectedRequestPath: "/12345_ID/messages",
ExpectedMsgStatus: "W",
ExpectedExternalID: "157b5e14568e8",
Expand Down Expand Up @@ -1438,10 +1438,10 @@ var SendTestCasesWAC = []ChannelSendTestCase{
MsgText: "Interactive Button Msg",
MsgURN: "whatsapp:250788123123",
MsgQuickReplies: []string{"BUTTON1"},
MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"},
MsgAttachments: []string{"image/jpeg:http://mock.com/1234/test.jpg"},
MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`,
MockResponseStatus: 201,
ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"button","header":{"type":"image","image":{"link":"https://foo.bar/image.jpg"}},"body":{"text":"Interactive Button Msg"},"action":{"buttons":[{"type":"reply","reply":{"id":"0","title":"BUTTON1"}}]}}}`,
ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"button","header":{"type":"image","image":{"link":"http://mock.com/1234/test.jpg"}},"body":{"text":"Interactive Button Msg"},"action":{"buttons":[{"type":"reply","reply":{"id":"0","title":"BUTTON1"}}]}}}`,
ExpectedRequestPath: "/12345_ID/messages",
ExpectedMsgStatus: "W",
ExpectedExternalID: "157b5e14568e8",
Expand All @@ -1452,10 +1452,10 @@ var SendTestCasesWAC = []ChannelSendTestCase{
MsgText: "Interactive Button Msg",
MsgURN: "whatsapp:250788123123",
MsgQuickReplies: []string{"BUTTON1"},
MsgAttachments: []string{"video/mp4:https://foo.bar/video.mp4"},
MsgAttachments: []string{"video/mp4:http://mock.com/5678/test.mp4"},
MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`,
MockResponseStatus: 201,
ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"button","header":{"type":"video","video":{"link":"https://foo.bar/video.mp4"}},"body":{"text":"Interactive Button Msg"},"action":{"buttons":[{"type":"reply","reply":{"id":"0","title":"BUTTON1"}}]}}}`,
ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"button","header":{"type":"video","video":{"link":"http://mock.com/5678/test.mp4"}},"body":{"text":"Interactive Button Msg"},"action":{"buttons":[{"type":"reply","reply":{"id":"0","title":"BUTTON1"}}]}}}`,
ExpectedRequestPath: "/12345_ID/messages",
ExpectedMsgStatus: "W",
ExpectedExternalID: "157b5e14568e8",
Expand All @@ -1466,10 +1466,10 @@ var SendTestCasesWAC = []ChannelSendTestCase{
MsgText: "Interactive Button Msg",
MsgURN: "whatsapp:250788123123",
MsgQuickReplies: []string{"BUTTON1"},
MsgAttachments: []string{"document/pdf:https://foo.bar/document.pdf"},
MsgAttachments: []string{"document/pdf:http://mock.com/7890/test.pdf"},
MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`,
MockResponseStatus: 201,
ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"button","header":{"type":"document","document":{"link":"https://foo.bar/document.pdf","filename":"document.pdf"}},"body":{"text":"Interactive Button Msg"},"action":{"buttons":[{"type":"reply","reply":{"id":"0","title":"BUTTON1"}}]}}}`,
ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"button","header":{"type":"document","document":{"link":"http://mock.com/7890/test.pdf","filename":"test.pdf"}},"body":{"text":"Interactive Button Msg"},"action":{"buttons":[{"type":"reply","reply":{"id":"0","title":"BUTTON1"}}]}}}`,
ExpectedRequestPath: "/12345_ID/messages",
ExpectedMsgStatus: "W",
ExpectedExternalID: "157b5e14568e8",
Expand All @@ -1480,12 +1480,12 @@ var SendTestCasesWAC = []ChannelSendTestCase{
MsgText: "Interactive Button Msg",
MsgURN: "whatsapp:250788123123",
MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3"},
MsgAttachments: []string{"audio/mp3:https://foo.bar/audio.mp3"},
MsgAttachments: []string{"audio/mp3:http://mock.com/3456/test.mp3"},
MockResponses: map[MockedRequest]*httpx.MockResponse{
{
Method: "POST",
Path: "/12345_ID/messages",
Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"audio","audio":{"link":"https://foo.bar/audio.mp3"}}`,
Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"audio","audio":{"link":"http://mock.com/3456/test.mp3"}}`,
}: httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)),
{
Method: "POST",
Expand All @@ -1502,12 +1502,12 @@ var SendTestCasesWAC = []ChannelSendTestCase{
MsgText: "Interactive List Msg",
MsgURN: "whatsapp:250788123123",
MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3", "ROW4"},
MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"},
MsgAttachments: []string{"image/jpeg:http://mock.com/1234/test.jpg"},
MockResponses: map[MockedRequest]*httpx.MockResponse{
{
Method: "POST",
Path: "/12345_ID/messages",
Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"image","image":{"link":"https://foo.bar/image.jpg"}}`,
Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"image","image":{"link":"http://mock.com/1234/test.jpg"}}`,
}: httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)),
{
Method: "POST",
Expand Down Expand Up @@ -1553,6 +1553,30 @@ var SendTestCasesWAC = []ChannelSendTestCase{
},
}

// setupMedia takes care of having the media files needed to our test server host
func setupMedia(mb *test.MockBackend) {
imageJPG := test.NewMockMedia("test.jpg", "image/jpeg", "http://mock.com/1234/test.jpg", 1024*1024, 640, 480, 0, nil)

audioM4A := test.NewMockMedia("test.m4a", "audio/mp4", "http://mock.com/2345/test.m4a", 1024*1024, 0, 0, 200, nil)
audioMP3 := test.NewMockMedia("test.mp3", "audio/mpeg", "http://mock.com/3456/test.mp3", 1024*1024, 0, 0, 200, []courier.Media{audioM4A})

thumbJPG := test.NewMockMedia("test.jpg", "image/jpeg", "http://mock.com/4567/test.jpg", 1024*1024, 640, 480, 0, nil)
videoMP4 := test.NewMockMedia("test.mp4", "video/mp4", "http://mock.com/5678/test.mp4", 1024*1024, 0, 0, 1000, []courier.Media{thumbJPG})

videoMOV := test.NewMockMedia("test.mov", "video/quicktime", "http://mock.com/6789/test.mov", 100*1024*1024, 0, 0, 2000, nil)

filePDF := test.NewMockMedia("test.pdf", "application/pdf", "http://mock.com/7890/test.pdf", 100*1024*1024, 0, 0, 0, nil)

stickerWEBP := test.NewMockMedia("test.webp", "image/webp", "http://mock.com/8901/test.webp", 50*1024, 480, 480, 0, nil)

mb.MockMedia(imageJPG)
mb.MockMedia(audioMP3)
mb.MockMedia(videoMP4)
mb.MockMedia(videoMOV)
mb.MockMedia(filePDF)
mb.MockMedia(stickerWEBP)
}

func TestSending(t *testing.T) {
// shorter max msg length for testing
maxMsgLength = 100
Expand All @@ -1565,7 +1589,7 @@ func TestSending(t *testing.T) {

RunChannelSendTestCases(t, ChannelFBA, newHandler("FBA", "Facebook", false), SendTestCasesFBA, checkRedacted, nil)
RunChannelSendTestCases(t, ChannelIG, newHandler("IG", "Instagram", false), SendTestCasesIG, checkRedacted, nil)
RunChannelSendTestCases(t, ChannelWAC, newHandler("WAC", "Cloud API WhatsApp", false), SendTestCasesWAC, checkRedacted, nil)
RunChannelSendTestCases(t, ChannelWAC, newHandler("WAC", "Cloud API WhatsApp", false), SendTestCasesWAC, checkRedacted, setupMedia)
}

func TestSigning(t *testing.T) {
Expand Down

0 comments on commit fd6068d

Please sign in to comment.