Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 9 additions & 12 deletions message/interactive.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,15 @@ func NewInteractiveReplyButtons(to string, opts InteractiveReplyButtonsOpts) Env
}
}

return Envelope{
MessagingProduct: "whatsapp",
RecipientType: "individual",
To: to,
Type: "interactive",
Interactive: &Interactive{
Header: opts.Header,
Body: TextObject{Text: opts.Body},
Type: "button",
Action: Action{
Buttons: &buttons,
},
e := baseEnvelope(to)
e.Type = "interactive"
e.Interactive = &Interactive{
Header: opts.Header,
Body: TextObject{Text: opts.Body},
Type: "button",
Action: Action{
Buttons: &buttons,
},
}
return e
}
124 changes: 66 additions & 58 deletions message/message.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,34 @@
package message

import "regexp"

// bsuidPattern matches regular BSUIDs (e.g. US.13491208655302741918) and
// parent BSUIDs (e.g. US.ENT.11815799212886844830):
// 2-letter ISO country code + "." + optional "ENT." + 1–128 alphanumeric chars.
var bsuidPattern = regexp.MustCompile(`^[A-Za-z]{2}\.(ENT\.)?[A-Za-z0-9]{1,128}$`)

func isBSUID(s string) bool {
return bsuidPattern.MatchString(s)
}

func baseEnvelope(to string) Envelope {
e := Envelope{
MessagingProduct: "whatsapp",
RecipientType: "individual",
}
if isBSUID(to) {
e.Recipient = to
} else {
e.To = to
}
return e
}

type Envelope struct {
MessagingProduct string `json:"messaging_product"`
RecipientType string `json:"recipient_type,omitempty"`
To string `json:"to,omitempty"`
Recipient string `json:"recipient,omitempty"`
Type string `json:"type,omitempty"`
Text *Text `json:"text,omitempty"`
Image *Image `json:"image,omitempty"`
Expand Down Expand Up @@ -115,57 +140,48 @@ type NewFlowOpts struct {
}

func NewText(to string, opts NewTextOpts) Envelope {
return Envelope{
MessagingProduct: "whatsapp",
RecipientType: "individual",
To: to,
Type: "text",
Text: &Text{
Body: opts.Text,
PreviewURL: opts.PreviewURL,
},
e := baseEnvelope(to)
e.Type = "text"
e.Text = &Text{
Body: opts.Text,
PreviewURL: opts.PreviewURL,
}
return e
}

func NewImageLink(to string, opts NewImageLinkOpts) Envelope {
return Envelope{
MessagingProduct: "whatsapp",
RecipientType: "individual",
To: to,
Type: "image",
Image: &Image{
Link: opts.Link,
Caption: opts.Caption,
},
e := baseEnvelope(to)
e.Type = "image"
e.Image = &Image{
Link: opts.Link,
Caption: opts.Caption,
}
return e
}

func NewInteractiveFlow(to string, opts NewFlowOpts) Envelope {
flow := "flow"
return Envelope{
MessagingProduct: "whatsapp",
RecipientType: "individual",
To: to,
Type: "interactive",
Interactive: &Interactive{
Type: "flow",
Header: opts.Header,
Body: opts.Body,
Footer: opts.Footer,
Action: Action{
Name: &flow,
Parameters: &Parameters{
Mode: opts.FlowMode,
FlowMessageVersion: "3",
FlowToken: opts.FlowToken,
FlowID: opts.FlowId,
FlowCTA: opts.FlowCTA,
FlowAction: "navigate",
FlowActionPayload: FlowAction{Screen: opts.FirstScreen},
},
e := baseEnvelope(to)
e.Type = "interactive"
e.Interactive = &Interactive{
Type: "flow",
Header: opts.Header,
Body: opts.Body,
Footer: opts.Footer,
Action: Action{
Name: &flow,
Parameters: &Parameters{
Mode: opts.FlowMode,
FlowMessageVersion: "3",
FlowToken: opts.FlowToken,
FlowID: opts.FlowId,
FlowCTA: opts.FlowCTA,
FlowAction: "navigate",
FlowActionPayload: FlowAction{Screen: opts.FirstScreen},
},
},
}
return e
}

func NewMessageRead(messageId string, typingIndicator bool) Envelope {
Expand All @@ -185,27 +201,19 @@ func NewMessageRead(messageId string, typingIndicator bool) Envelope {
}

func NewDocument(to string, opts NewDocumentOpts) Envelope {
return Envelope{
MessagingProduct: "whatsapp",
RecipientType: "individual",
To: to,
Type: "document",
Document: &Document{
Link: opts.Link,
Caption: opts.Caption,
Filename: opts.Filename,
},
e := baseEnvelope(to)
e.Type = "document"
e.Document = &Document{
Link: opts.Link,
Caption: opts.Caption,
Filename: opts.Filename,
}
return e
}

func NewSticker(to, link string) Envelope {
return Envelope{
MessagingProduct: "whatsapp",
RecipientType: "individual",
To: to,
Type: "sticker",
Sticker: &Sticker{
Link: link,
},
}
e := baseEnvelope(to)
e.Type = "sticker"
e.Sticker = &Sticker{Link: link}
return e
}
25 changes: 25 additions & 0 deletions message/message_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,31 @@ var expectedMessageReadWithTypingJSON string
//go:embed testdata/message_read_without_typing.json
var expectedMessageReadWithoutTypingJSON string

func TestBaseEnvelopeRouting(t *testing.T) {
tests := []struct {
name string
to string
wantTo string
wantRecipient string
}{
{name: "phoneNumber", to: "16505551234", wantTo: "16505551234", wantRecipient: ""},
{name: "bsuid", to: "US.123", wantTo: "", wantRecipient: "US.123"},
{name: "parentBsuid", to: "US.ENT.123", wantTo: "", wantRecipient: "US.ENT.123"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
e := baseEnvelope(tt.to)
if e.To != tt.wantTo {
t.Errorf("To: got %q, want %q", e.To, tt.wantTo)
}
if e.Recipient != tt.wantRecipient {
t.Errorf("Recipient: got %q, want %q", e.Recipient, tt.wantRecipient)
}
})
}
}

func TestNewMessageRead(t *testing.T) {
tests := []struct {
messageID string
Expand Down
25 changes: 15 additions & 10 deletions webhook/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,27 @@ type Metadata struct {
}

type Contact struct {
WhatsappID string `json:"wa_id"`
Profile Profile `json:"profile"`
WhatsappID string `json:"wa_id"`
UserID string `json:"user_id"`
ParentUserID string `json:"parent_user_id"`
Profile Profile `json:"profile"`
}

type Profile struct {
Name string `json:"name"`
Name string `json:"name"`
Username string `json:"username"`
}

type Message struct {
From string `json:"from"`
ID string `json:"id"`
Timestamp string `json:"timestamp"`
Type string `json:"type"`
Context *Context `json:"context"`
Text Text `json:"text"`
Interactive Interactive `json:"interactive"`
From string `json:"from"`
FromUserID string `json:"from_user_id"`
FromParentUserID string `json:"from_parent_user_id"`
ID string `json:"id"`
Timestamp string `json:"timestamp"`
Type string `json:"type"`
Context *Context `json:"context"`
Text Text `json:"text"`
Interactive Interactive `json:"interactive"`
}

type Text struct {
Expand Down
Loading