Skip to content

Commit

Permalink
Merge pull request #39 from weni-ai/feat/sticker-wpp
Browse files Browse the repository at this point in the history
Add support for sending sticker to WA and WAC
  • Loading branch information
Robi9 authored Aug 30, 2023
2 parents a04465b + bb44d7f commit d34e698
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 16 deletions.
38 changes: 30 additions & 8 deletions handlers/facebookapp/facebookapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,14 @@ type wacMedia struct {
Mimetype string `json:"mime_type"`
SHA256 string `json:"sha256"`
}

type wacSticker struct {
Animated bool `json:"animated"`
ID string `json:"id"`
Mimetype string `json:"mime_type"`
SHA256 string `json:"sha256"`
}

type moPayload struct {
Object string `json:"object"`
Entry []struct {
Expand Down Expand Up @@ -174,11 +182,12 @@ type moPayload struct {
Text struct {
Body string `json:"body"`
} `json:"text"`
Image *wacMedia `json:"image"`
Audio *wacMedia `json:"audio"`
Video *wacMedia `json:"video"`
Document *wacMedia `json:"document"`
Voice *wacMedia `json:"voice"`
Image *wacMedia `json:"image"`
Audio *wacMedia `json:"audio"`
Video *wacMedia `json:"video"`
Document *wacMedia `json:"document"`
Voice *wacMedia `json:"voice"`
Sticker *wacSticker `json:"sticker"`
Location *struct {
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
Expand Down Expand Up @@ -513,6 +522,8 @@ func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel couri
} else if msg.Type == "image" && msg.Image != nil {
text = msg.Image.Caption
mediaURL, err = resolveMediaURL(channel, msg.Image.ID, token)
} else if msg.Type == "sticker" && msg.Sticker != nil {
mediaURL, err = resolveMediaURL(channel, msg.Sticker.ID, token)
} else if msg.Type == "video" && msg.Video != nil {
text = msg.Video.Caption
mediaURL, err = resolveMediaURL(channel, msg.Video.ID, token)
Expand Down Expand Up @@ -1293,6 +1304,7 @@ type wacMTPayload struct {
Image *wacMTMedia `json:"image,omitempty"`
Audio *wacMTMedia `json:"audio,omitempty"`
Video *wacMTMedia `json:"video,omitempty"`
Sticker *wacMTMedia `json:"sticker,omitempty"`

Interactive *wacInteractive `json:"interactive,omitempty"`

Expand Down Expand Up @@ -1494,6 +1506,11 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg)
} else if i < len(msg.Attachments()) && len(qrs) == 0 || len(qrs) > 3 && i < len(msg.Attachments()) {
attType, attURL := handlers.SplitAttachment(msg.Attachments()[i])
fileURL := attURL

splitedAttType := strings.Split(attType, "/")
attType = splitedAttType[0]
attFormat := splitedAttType[1]

mediaID, mediaLogs, err := h.fetchWACMediaID(msg, attType, attURL, accessToken)
for _, log := range mediaLogs {
status.AddLog(log)
Expand All @@ -1507,20 +1524,25 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg)
if err != nil {
return status, err
}
attType = strings.Split(attType, "/")[0]

if attType == "application" {
attType = "document"
}
payload.Type = attType
media := wacMTMedia{ID: mediaID, Link: parsedURL.String()}
if len(msgParts) == 1 && attType != "audio" && len(msg.Attachments()) == 1 && len(msg.QuickReplies()) == 0 {
if len(msgParts) == 1 && (attType != "audio" && attFormat != "webp") && len(msg.Attachments()) == 1 && len(msg.QuickReplies()) == 0 {
media.Caption = msgParts[i]
hasCaption = true
}

switch attType {
case "image":
payload.Image = &media
if attFormat == "webp" {
payload.Sticker = &media
payload.Type = "sticker"
} else {
payload.Image = &media
}
case "audio":
payload.Audio = &media
case "video":
Expand Down
31 changes: 31 additions & 0 deletions handlers/facebookapp/facebookapp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,9 @@ var testCasesWAC = []ChannelHandleTestCase{
{Label: "Receive Valid Image Message", URL: wacReceiveURL, Data: string(courier.ReadFile("./testdata/wac/imageWAC.json")), Status: 200, Response: "Handled", NoQueueErrorCheck: true, NoInvalidChannelCheck: true,
Text: Sp("Check out my new phone!"), URN: Sp("whatsapp:5678"), ExternalID: Sp("external_id"), Attachment: Sp("https://foo.bar/attachmentURL_Image"), Date: Tp(time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC)),
PrepRequest: addValidSignatureWAC},
{Label: "Receive Valid Sticker Message", URL: wacReceiveURL, Data: string(courier.ReadFile("./testdata/wac/stickerWAC.json")), Status: 200, Response: "Handled", NoQueueErrorCheck: true, NoInvalidChannelCheck: true,
Text: Sp(""), URN: Sp("whatsapp:5678"), ExternalID: Sp("external_id"), Attachment: Sp("https://foo.bar/attachmentURL_Sticker"), Date: Tp(time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC)),
PrepRequest: addValidSignatureWAC},
{Label: "Receive Valid Video Message", URL: wacReceiveURL, Data: string(courier.ReadFile("./testdata/wac/videoWAC.json")), Status: 200, Response: "Handled", NoQueueErrorCheck: true, NoInvalidChannelCheck: true,
Text: Sp("Check out my new phone!"), URN: Sp("whatsapp:5678"), ExternalID: Sp("external_id"), Attachment: Sp("https://foo.bar/attachmentURL_Video"), Date: Tp(time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC)),
PrepRequest: addValidSignatureWAC},
Expand Down Expand Up @@ -398,6 +401,10 @@ func TestHandler(t *testing.T) {
return
}

if strings.HasSuffix(r.URL.Path, "sticker") {
w.Write([]byte(`{"url": "https://foo.bar/attachmentURL_Sticker"}`))
}

// valid token
w.Write([]byte(`{"url": "https://foo.bar/attachmentURL"}`))

Expand Down Expand Up @@ -623,6 +630,30 @@ var SendTestCasesWAC = []ChannelSendTestCase{
},
},
SendPrep: setSendURL},
{Label: "Sticker Send",
Text: "sticker caption",
URN: "whatsapp:250788123123",
Status: "W", ExternalID: "157b5e14568e8",
Attachments: []string{"image/webp:https://foo.bar/sticker.webp"},
Responses: map[MockedRequest]MockedResponse{
MockedRequest{
Method: "POST",
Path: "/12345_ID/messages",
Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"sticker","sticker":{"link":"https://foo.bar/sticker.webp"}}`,
}: MockedResponse{
Status: 201,
Body: `{ "messages": [{"id": "157b5e14568e8"}] }`,
},
MockedRequest{
Method: "POST",
Path: "/12345_ID/messages",
Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"text","text":{"body":"sticker caption"}}`,
}: MockedResponse{
Status: 201,
Body: `{ "messages": [{"id": "157b5e14568e8"}] }`,
},
},
SendPrep: setSendURL},
{Label: "Document Send",
Text: "document caption",
URN: "whatsapp:250788123123",
Expand Down
42 changes: 42 additions & 0 deletions handlers/facebookapp/testdata/wac/stickerWAC.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"object": "whatsapp_business_account",
"entry": [
{
"id": "8856996819413533",
"changes": [
{
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "+250 788 123 200",
"phone_number_id": "12345"
},
"contacts": [
{
"profile": {
"name": "Kerry Fisher"
},
"wa_id": "5678"
}
],
"messages": [
{
"from": "5678",
"id": "external_id",
"sticker": {
"file": "/usr/local/wamedia/shared/b1cf38-8734-4ad3-b4a1-ef0c10d0d683",
"id": "id_sticker",
"mime_type": "image/webp",
"sha256": "29ed500fa64eb55fc19dc4124acb300e5dcc54a0f822a301ae99944db"
},
"timestamp": "1454119029",
"type": "sticker"
}
]
},
"field": "messages"
}
]
}
]
}
50 changes: 42 additions & 8 deletions handlers/whatsapp/whatsapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@ type eventPayload struct {
Sha256 string `json:"sha256" validate:"required"`
Caption string `json:"caption"`
} `json:"image"`
Sticker *struct {
Animated bool `json:"animated"`
ID string `json:"id"`
Mimetype string `json:"mime_type"`
SHA256 string `json:"sha256"`
}
Interactive *struct {
ButtonReply *struct {
ID string `json:"id"`
Expand Down Expand Up @@ -276,6 +282,8 @@ func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w h
} else if msg.Type == "image" && msg.Image != nil {
text = msg.Image.Caption
mediaURL, err = resolveMediaURL(channel, msg.Image.ID)
} else if msg.Type == "sticker" && msg.Sticker != nil {
mediaURL, err = resolveMediaURL(channel, msg.Sticker.ID)
} else if msg.Type == "interactive" {
if msg.Interactive.Type == "button_reply" {
text = msg.Interactive.ButtonReply.Title
Expand Down Expand Up @@ -579,6 +587,12 @@ type mtImagePayload struct {
Image *mediaObject `json:"image"`
}

type mtStickerPayload struct {
To string `json:"to" validate:"required"`
Type string `json:"type" validate:"required"`
Sticker *mediaObject `json:"sticker"`
}

type mtVideoPayload struct {
To string `json:"to" validate:"required"`
Type string `json:"type" validate:"required"`
Expand Down Expand Up @@ -673,6 +687,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 err != nil {
Expand Down Expand Up @@ -875,8 +890,10 @@ func buildPayloads(msg courier.Msg, h *handler) ([]interface{}, []*courier.Chann

if len(msg.Attachments()) > 0 {
for attachmentCount, attachment := range msg.Attachments() {

mimeType, mediaURL := handlers.SplitAttachment(attachment)
splitedAttType := strings.Split(mimeType, "/")
mimeType = splitedAttType[0]
attFormat := splitedAttType[1]
mediaID, mediaLogs, err := h.fetchMediaID(msg, mimeType, mediaURL)
if len(mediaLogs) > 0 {
logs = append(logs, mediaLogs...)
Expand Down Expand Up @@ -920,14 +937,31 @@ func buildPayloads(msg courier.Msg, h *handler) ([]interface{}, []*courier.Chann
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()
var payload interface{}
if attFormat == "webp" {
payload = mtStickerPayload{
To: msg.URN().Path(),
Type: "sticker",
Sticker: mediaPayload,
}
if attachmentCount == 0 {
payloadText := mtTextPayload{
To: msg.URN().Path(),
Type: "text",
}
payloadText.Text.Body = msg.Text()
payloads = append(payloads, payloadText)
}
} else {
if attachmentCount == 0 {
mediaPayload.Caption = msg.Text()
}
payload = mtImagePayload{
To: msg.URN().Path(),
Type: "image",
Image: mediaPayload,
}
}
payload.Image = mediaPayload
payloads = append(payloads, payload)
} else if strings.HasPrefix(mimeType, "video") {
payload := mtVideoPayload{
Expand Down
42 changes: 42 additions & 0 deletions handlers/whatsapp/whatsapp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,22 @@ var imageMsg = `{
}]
}`

var stickerMsg = `{
"messages": [{
"from": "250788123123",
"id": "41",
"timestamp": "1454119029",
"type": "sticker",
"sticker": {
"file": "/path/to/v1/media/41",
"id": "41",
"link": "https://example.org/v1/media/41",
"mime_type": "text/plain",
"sha256": "the-sha-signature"
}
}]
}`

var interactiveButtonMsg = `{
"messages": [{
"from": "250788123123",
Expand Down Expand Up @@ -373,6 +389,8 @@ var waTestCases = []ChannelHandleTestCase{
Text: Sp("the caption"), Attachment: Sp("https://foo.bar/v1/media/41"), URN: Sp("whatsapp:250788123123"), ExternalID: Sp("41"), Date: Tp(time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC))},
{Label: "Receive Valid Image Message", URL: waReceiveURL, Data: imageMsg, Status: 200, Response: `"type":"msg"`,
Text: Sp("the caption"), Attachment: Sp("https://foo.bar/v1/media/41"), URN: Sp("whatsapp:250788123123"), ExternalID: Sp("41"), Date: Tp(time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC))},
{Label: "Receive Valid Sticker Message", URL: waReceiveURL, Data: stickerMsg, Status: 200, Response: `"type":"msg"`,
Text: Sp(""), Attachment: Sp("https://foo.bar/v1/media/41"), URN: Sp("whatsapp:250788123123"), ExternalID: Sp("41"), Date: Tp(time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC))},
{Label: "Receive Valid Interactive Button Message", URL: waReceiveURL, Data: interactiveButtonMsg, Status: 200, Response: `"type":"msg"`,
Text: Sp("BUTTON1"), URN: Sp("whatsapp:250788123123"), ExternalID: Sp("41"), Date: Tp(time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC))},
{Label: "Receive Valid Interactive List Message", URL: waReceiveURL, Data: interactiveListMsg, Status: 200, Response: `"type":"msg"`,
Expand Down Expand Up @@ -560,6 +578,30 @@ var defaultSendTestCases = []ChannelSendTestCase{
},
SendPrep: setSendURL,
},
{Label: "Sticker Send",
Text: "sticker caption",
URN: "whatsapp:250788123123",
Status: "W", ExternalID: "157b5e14568e8",
Attachments: []string{"image/webp:https://foo.bar/sticker.webp"},
Responses: map[MockedRequest]MockedResponse{
MockedRequest{
Method: "POST",
Path: "/v1/messages",
Body: `{"to":"250788123123","type":"sticker","sticker":{"link":"https://foo.bar/sticker.webp"}}`,
}: MockedResponse{
Status: 201,
Body: `{ "messages": [{"id": "157b5e14568e8"}] }`,
}, MockedRequest{
Method: "POST",
Path: "/v1/messages",
Body: `{"to":"250788123123","type":"text","text":{"body":"sticker caption"}}`,
}: MockedResponse{
Status: 201,
Body: `{ "messages": [{"id": "157b5e14568e8"}] }`,
},
},
SendPrep: setSendURL,
},
{Label: "Video Send",
Text: "video caption",
URN: "whatsapp:250788123123",
Expand Down

0 comments on commit d34e698

Please sign in to comment.