diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go
index 6380da05d..7446f55e7 100644
--- a/backends/rapidpro/backend_test.go
+++ b/backends/rapidpro/backend_test.go
@@ -172,7 +172,7 @@ func (ts *BackendTestSuite) TestMsgUnmarshal() {
ts.Equal([]string{"https://foo.bar/image.jpg"}, msg.Attachments())
ts.Equal("5ApPVsFDcFt:RZdK9ne7LgfvBYdtCYg7tv99hC9P2", msg.URNAuth_)
ts.Equal("", msg.ExternalID())
- ts.Equal([]string{"Yes", "No"}, msg.QuickReplies())
+ ts.Equal([]courier.QuickReply{{Text: "Yes"}, {Text: "No"}}, msg.QuickReplies())
ts.Equal("event", msg.Topic())
ts.Equal("external-id", msg.ResponseToExternalID())
ts.True(msg.HighPriority())
@@ -1605,7 +1605,6 @@ SELECT
direction,
text,
attachments,
- quick_replies,
msg_count,
error_count,
failed_reason,
diff --git a/backends/rapidpro/msg.go b/backends/rapidpro/msg.go
index f1ca29b2f..8b5852c5a 100644
--- a/backends/rapidpro/msg.go
+++ b/backends/rapidpro/msg.go
@@ -45,20 +45,20 @@ const (
// Msg is our base struct to represent msgs both in our JSON and db representations
type Msg struct {
- OrgID_ OrgID `json:"org_id" db:"org_id"`
- ID_ courier.MsgID `json:"id" db:"id"`
- UUID_ courier.MsgUUID `json:"uuid" db:"uuid"`
- Direction_ MsgDirection ` db:"direction"`
- Status_ courier.MsgStatus ` db:"status"`
- Visibility_ MsgVisibility ` db:"visibility"`
- HighPriority_ bool `json:"high_priority" db:"high_priority"`
- Text_ string `json:"text" db:"text"`
- Attachments_ pq.StringArray `json:"attachments" db:"attachments"`
- QuickReplies_ pq.StringArray `json:"quick_replies" db:"quick_replies"`
- Locale_ null.String `json:"locale" db:"locale"`
- Templating_ *courier.Templating `json:"templating" db:"templating"`
- ExternalID_ null.String ` db:"external_id"`
- Metadata_ json.RawMessage `json:"metadata" db:"metadata"`
+ OrgID_ OrgID `json:"org_id" db:"org_id"`
+ ID_ courier.MsgID `json:"id" db:"id"`
+ UUID_ courier.MsgUUID `json:"uuid" db:"uuid"`
+ Direction_ MsgDirection ` db:"direction"`
+ Status_ courier.MsgStatus ` db:"status"`
+ Visibility_ MsgVisibility ` db:"visibility"`
+ HighPriority_ bool `json:"high_priority" db:"high_priority"`
+ Text_ string `json:"text" db:"text"`
+ Attachments_ pq.StringArray `json:"attachments" db:"attachments"`
+ QuickReplies_ []courier.QuickReply `json:"quick_replies"`
+ Locale_ null.String `json:"locale" db:"locale"`
+ Templating_ *courier.Templating `json:"templating" db:"templating"`
+ ExternalID_ null.String ` db:"external_id"`
+ Metadata_ json.RawMessage `json:"metadata" db:"metadata"`
ChannelID_ courier.ChannelID ` db:"channel_id"`
ContactID_ ContactID `json:"contact_id" db:"contact_id"`
@@ -135,12 +135,12 @@ func (m *Msg) URN() urns.URN { return m.URN_ }
func (m *Msg) Channel() courier.Channel { return m.channel }
// outgoing specific
-func (m *Msg) QuickReplies() []string { return m.QuickReplies_ }
-func (m *Msg) Locale() i18n.Locale { return i18n.Locale(string(m.Locale_)) }
-func (m *Msg) Templating() *courier.Templating { return m.Templating_ }
-func (m *Msg) URNAuth() string { return m.URNAuth_ }
-func (m *Msg) Origin() courier.MsgOrigin { return m.Origin_ }
-func (m *Msg) ContactLastSeenOn() *time.Time { return m.ContactLastSeenOn_ }
+func (m *Msg) QuickReplies() []courier.QuickReply { return m.QuickReplies_ }
+func (m *Msg) Locale() i18n.Locale { return i18n.Locale(string(m.Locale_)) }
+func (m *Msg) Templating() *courier.Templating { return m.Templating_ }
+func (m *Msg) URNAuth() string { return m.URNAuth_ }
+func (m *Msg) Origin() courier.MsgOrigin { return m.Origin_ }
+func (m *Msg) ContactLastSeenOn() *time.Time { return m.ContactLastSeenOn_ }
func (m *Msg) Topic() string {
if m.Metadata_ == nil {
return ""
diff --git a/handlers/dialog360/handler.go b/handlers/dialog360/handler.go
index 9205bb297..17d54608a 100644
--- a/handlers/dialog360/handler.go
+++ b/handlers/dialog360/handler.go
@@ -357,7 +357,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, res *courier.Sen
Type: "reply",
}
btns[i].Reply.ID = fmt.Sprint(i)
- btns[i].Reply.Title = qr
+ btns[i].Reply.Title = qr.Text
}
interactive.Action = &struct {
Button string "json:\"button,omitempty\""
@@ -376,7 +376,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, res *courier.Sen
for i, qr := range qrs {
section.Rows[i] = whatsapp.SectionRow{
ID: fmt.Sprint(i),
- Title: qr,
+ Title: qr.Text,
}
}
@@ -510,7 +510,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, res *courier.Sen
Type: "reply",
}
btns[i].Reply.ID = fmt.Sprint(i)
- btns[i].Reply.Title = qr
+ btns[i].Reply.Title = qr.Text
}
interactive.Action = &struct {
Button string "json:\"button,omitempty\""
@@ -530,7 +530,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, res *courier.Sen
for i, qr := range qrs {
section.Rows[i] = whatsapp.SectionRow{
ID: fmt.Sprint(i),
- Title: qr,
+ Title: qr.Text,
}
}
diff --git a/handlers/dialog360/handler_test.go b/handlers/dialog360/handler_test.go
index 6e8b0cf86..5e231a78e 100644
--- a/handlers/dialog360/handler_test.go
+++ b/handlers/dialog360/handler_test.go
@@ -487,7 +487,7 @@ var SendTestCasesD3C = []OutgoingTestCase{
Label: "Interactive Button Message Send",
MsgText: "Interactive Button Msg",
MsgURN: "whatsapp:250788123123",
- MsgQuickReplies: []string{"BUTTON1"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "BUTTON1"}},
MockResponses: map[string][]*httpx.MockResponse{
"https://waba-v2.360dialog.io/messages": {
httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)),
@@ -502,7 +502,7 @@ var SendTestCasesD3C = []OutgoingTestCase{
Label: "Interactive List Message Send",
MsgText: "Interactive List Msg",
MsgURN: "whatsapp:250788123123",
- MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3", "ROW4"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "ROW1"}, {Text: "ROW2"}, {Text: "ROW3"}, {Text: "ROW4"}},
MockResponses: map[string][]*httpx.MockResponse{
"https://waba-v2.360dialog.io/messages": {
httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)),
@@ -517,7 +517,7 @@ var SendTestCasesD3C = []OutgoingTestCase{
Label: "Interactive List Message Send more than 10 QRs",
MsgText: "Interactive List Msg",
MsgURN: "whatsapp:250788123123",
- MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3", "ROW4", "ROW5", "ROW6", "ROW7", "ROW8", "ROW9", "ROW10", "ROW11", "ROW12"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "ROW1"}, {Text: "ROW2"}, {Text: "ROW3"}, {Text: "ROW4"}, {Text: "ROW5"}, {Text: "ROW6"}, {Text: "ROW7"}, {Text: "ROW8"}, {Text: "ROW9"}, {Text: "ROW10"}, {Text: "ROW11"}, {Text: "ROW12"}},
MockResponses: map[string][]*httpx.MockResponse{
"https://waba-v2.360dialog.io/messages": {
httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)),
@@ -534,7 +534,7 @@ var SendTestCasesD3C = []OutgoingTestCase{
MsgText: "Hola",
MsgURN: "whatsapp:250788123123",
MsgLocale: "spa",
- MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3", "ROW4"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "ROW1"}, {Text: "ROW2"}, {Text: "ROW3"}, {Text: "ROW4"}},
MockResponses: map[string][]*httpx.MockResponse{
"https://waba-v2.360dialog.io/messages": {
httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)),
@@ -549,7 +549,7 @@ var SendTestCasesD3C = []OutgoingTestCase{
Label: "Interactive Button Message Send with image attachment",
MsgText: "Interactive Button Msg",
MsgURN: "whatsapp:250788123123",
- MsgQuickReplies: []string{"BUTTON1"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "BUTTON1"}},
MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"},
MockResponses: map[string][]*httpx.MockResponse{
"https://waba-v2.360dialog.io/messages": {
@@ -569,7 +569,7 @@ var SendTestCasesD3C = []OutgoingTestCase{
Label: "Interactive Button Message Send with video attachment",
MsgText: "Interactive Button Msg",
MsgURN: "whatsapp:250788123123",
- MsgQuickReplies: []string{"BUTTON1"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "BUTTON1"}},
MsgAttachments: []string{"video/mp4:https://foo.bar/video.mp4"},
MockResponses: map[string][]*httpx.MockResponse{
"https://waba-v2.360dialog.io/messages": {
@@ -589,7 +589,7 @@ var SendTestCasesD3C = []OutgoingTestCase{
Label: "Interactive Button Message Send with document attachment",
MsgText: "Interactive Button Msg",
MsgURN: "whatsapp:250788123123",
- MsgQuickReplies: []string{"BUTTON1"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "BUTTON1"}},
MsgAttachments: []string{"document/pdf:https://foo.bar/document.pdf"},
MockResponses: map[string][]*httpx.MockResponse{
"https://waba-v2.360dialog.io/messages": {
@@ -609,7 +609,7 @@ var SendTestCasesD3C = []OutgoingTestCase{
Label: "Interactive Button Message Send with audio attachment",
MsgText: "Interactive Button Msg",
MsgURN: "whatsapp:250788123123",
- MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "ROW1"}, {Text: "ROW2"}, {Text: "ROW3"}},
MsgAttachments: []string{"audio/mp3:https://foo.bar/audio.mp3"},
MockResponses: map[string][]*httpx.MockResponse{
"*/messages": {
@@ -627,7 +627,7 @@ var SendTestCasesD3C = []OutgoingTestCase{
Label: "Interactive List Message Send with attachment",
MsgText: "Interactive List Msg",
MsgURN: "whatsapp:250788123123",
- MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3", "ROW4"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "ROW1"}, {Text: "ROW2"}, {Text: "ROW3"}, {Text: "ROW4"}},
MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"},
MockResponses: map[string][]*httpx.MockResponse{
"*/messages": {
diff --git a/handlers/discord/handler.go b/handlers/discord/handler.go
index 270286dcb..ac27d8310 100644
--- a/handlers/discord/handler.go
+++ b/handlers/discord/handler.go
@@ -171,7 +171,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, res *courier.Sen
To: msg.URN().Path(),
Channel: string(msg.Channel().UUID()),
Attachments: attachmentURLs,
- QuickReplies: msg.QuickReplies(),
+ QuickReplies: handlers.TextOnlyQuickReplies(msg.QuickReplies()),
}
var body io.Reader
diff --git a/handlers/discord/handler_test.go b/handlers/discord/handler_test.go
index 75e5eeab5..a50e628cb 100644
--- a/handlers/discord/handler_test.go
+++ b/handlers/discord/handler_test.go
@@ -24,7 +24,7 @@ var testChannels = []courier.Channel{
var testCases = []IncomingTestCase{
{
- Label: "Recieve Message",
+ Label: "Receive Message",
URL: "/c/ds/bac782c2-7aeb-4389-92f5-97887744f573/receive",
Data: `from=694634743521607802&text=hello`,
ExpectedRespStatus: 200,
@@ -32,7 +32,7 @@ var testCases = []IncomingTestCase{
ExpectedURN: "discord:694634743521607802",
},
{
- Label: "Recieve Message with attachment",
+ Label: "Receive Message with attachment",
URL: "/c/ds/bac782c2-7aeb-4389-92f5-97887744f573/receive",
Data: `from=694634743521607802&text=hello&attachments=https://test.test/foo.png`,
ExpectedRespStatus: 200,
@@ -90,7 +90,7 @@ var sendTestCases = []OutgoingTestCase{
ExpectedRequests: []ExpectedRequest{
{
Path: "/discord/rp/send",
- Body: `{"id":"10","text":"Hello World","to":"694634743521607802","channel":"bac782c2-7aeb-4389-92f5-97887744f573","attachments":[],"quick_replies":null}`,
+ Body: `{"id":"10","text":"Hello World","to":"694634743521607802","channel":"bac782c2-7aeb-4389-92f5-97887744f573","attachments":[],"quick_replies":[]}`,
},
},
},
@@ -107,7 +107,7 @@ var sendTestCases = []OutgoingTestCase{
ExpectedRequests: []ExpectedRequest{
{
Path: "/discord/rp/send",
- Body: `{"id":"10","text":"Hello World","to":"694634743521607802","channel":"bac782c2-7aeb-4389-92f5-97887744f573","attachments":["https://foo.bar/image.jpg"],"quick_replies":null}`,
+ Body: `{"id":"10","text":"Hello World","to":"694634743521607802","channel":"bac782c2-7aeb-4389-92f5-97887744f573","attachments":["https://foo.bar/image.jpg"],"quick_replies":[]}`,
},
},
},
@@ -115,7 +115,7 @@ var sendTestCases = []OutgoingTestCase{
Label: "Attachement and quick replies",
MsgText: "Hello World",
MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"},
- MsgQuickReplies: []string{"hello", "world"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "hello"}, {Text: "world"}},
MsgURN: "discord:694634743521607802",
MockResponses: map[string][]*httpx.MockResponse{
"http://example.com/discord/rp/send": {
@@ -141,7 +141,7 @@ var sendTestCases = []OutgoingTestCase{
ExpectedRequests: []ExpectedRequest{
{
Path: "/discord/rp/send",
- Body: `{"id":"10","text":"Error Sending","to":"694634743521607802","channel":"bac782c2-7aeb-4389-92f5-97887744f573","attachments":[],"quick_replies":null}`,
+ Body: `{"id":"10","text":"Error Sending","to":"694634743521607802","channel":"bac782c2-7aeb-4389-92f5-97887744f573","attachments":[],"quick_replies":[]}`,
},
},
ExpectedError: courier.ErrResponseStatus,
@@ -158,7 +158,7 @@ var sendTestCases = []OutgoingTestCase{
ExpectedRequests: []ExpectedRequest{
{
Path: "/discord/rp/send",
- Body: `{"id":"10","text":"Error","to":"694634743521607802","channel":"bac782c2-7aeb-4389-92f5-97887744f573","attachments":[],"quick_replies":null}`,
+ Body: `{"id":"10","text":"Error","to":"694634743521607802","channel":"bac782c2-7aeb-4389-92f5-97887744f573","attachments":[],"quick_replies":[]}`,
},
},
ExpectedError: courier.ErrConnectionFailed,
diff --git a/handlers/external/handler.go b/handlers/external/handler.go
index bd1d18b39..f296f2028 100644
--- a/handlers/external/handler.go
+++ b/handlers/external/handler.go
@@ -16,6 +16,7 @@ import (
"github.com/nyaruka/courier"
"github.com/nyaruka/courier/handlers"
"github.com/nyaruka/gocommon/gsm7"
+ "github.com/nyaruka/gocommon/jsonx"
"github.com/nyaruka/gocommon/urns"
)
@@ -328,7 +329,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, res *courier.Sen
if i == len(parts)-1 {
formEncoded["quick_replies"] = buildQuickRepliesResponse(msg.QuickReplies(), sendMethod, contentURLEncoded)
} else {
- formEncoded["quick_replies"] = buildQuickRepliesResponse([]string{}, sendMethod, contentURLEncoded)
+ formEncoded["quick_replies"] = buildQuickRepliesResponse([]courier.QuickReply{}, sendMethod, contentURLEncoded)
}
url := replaceVariables(sendURL, formEncoded)
@@ -339,7 +340,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, res *courier.Sen
if i == len(parts)-1 {
formEncoded["quick_replies"] = buildQuickRepliesResponse(msg.QuickReplies(), sendMethod, contentType)
} else {
- formEncoded["quick_replies"] = buildQuickRepliesResponse([]string{}, sendMethod, contentType)
+ formEncoded["quick_replies"] = buildQuickRepliesResponse([]courier.QuickReply{}, sendMethod, contentType)
}
body = strings.NewReader(replaceVariables(sendBody, formEncoded))
}
@@ -381,18 +382,17 @@ type quickReplyXMLItem struct {
Value string `xml:",chardata"`
}
-func buildQuickRepliesResponse(quickReplies []string, sendMethod string, contentType string) string {
+func buildQuickRepliesResponse(quickReplies []courier.QuickReply, sendMethod string, contentType string) string {
if quickReplies == nil {
- quickReplies = []string{}
+ quickReplies = []courier.QuickReply{}
}
if (sendMethod == http.MethodPost || sendMethod == http.MethodPut) && contentType == contentJSON {
- marshalled, _ := json.Marshal(quickReplies)
- return string(marshalled)
+ return string(jsonx.MustMarshal(handlers.TextOnlyQuickReplies(quickReplies)))
} else if (sendMethod == http.MethodPost || sendMethod == http.MethodPut) && contentType == contentXML {
items := make([]quickReplyXMLItem, len(quickReplies))
for i, v := range quickReplies {
- items[i] = quickReplyXMLItem{Value: v}
+ items[i] = quickReplyXMLItem{Value: v.Text}
}
marshalled, _ := xml.Marshal(items)
return string(marshalled)
@@ -400,8 +400,8 @@ func buildQuickRepliesResponse(quickReplies []string, sendMethod string, content
response := bytes.Buffer{}
for _, reply := range quickReplies {
- reply = url.QueryEscape(reply)
- response.WriteString(fmt.Sprintf("&quick_reply=%s", reply))
+ reply.Text = url.QueryEscape(reply.Text)
+ response.WriteString(fmt.Sprintf("&quick_reply=%s", reply.Text))
}
return response.String()
}
diff --git a/handlers/external/handler_test.go b/handlers/external/handler_test.go
index cc57982b7..c702a6e82 100644
--- a/handlers/external/handler_test.go
+++ b/handlers/external/handler_test.go
@@ -313,7 +313,7 @@ var longSendTestCases = []OutgoingTestCase{
{
Label: "Long Send",
MsgText: "This is a long message that will be longer than 30....... characters", MsgURN: "tel:+250788383383",
- MsgQuickReplies: []string{"One"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "One"}},
MockResponses: map[string][]*httpx.MockResponse{
"http://example.com/send*": {
httpx.NewMockResponse(200, nil, []byte(`0: Accepted for delivery`)),
@@ -627,7 +627,7 @@ var jsonSendTestCases = []OutgoingTestCase{
Label: "Send Quick Replies",
MsgText: "Some message",
MsgURN: "tel:+250788383383",
- MsgQuickReplies: []string{"One", "Two", "Three"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "One"}, {Text: "Two"}, {Text: "Three"}},
MockResponses: map[string][]*httpx.MockResponse{
"http://example.com/send": {
httpx.NewMockResponse(200, nil, []byte(`0: Accepted for delivery`)),
@@ -645,7 +645,7 @@ var jsonLongSendTestCases = []OutgoingTestCase{
Label: "Send Long message JSON",
MsgText: "This is a long message that will be longer than 30....... characters",
MsgURN: "tel:+250788383383",
- MsgQuickReplies: []string{"One", "Two", "Three"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "One"}, {Text: "Two"}, {Text: "Three"}},
MockResponses: map[string][]*httpx.MockResponse{
"*": {
httpx.NewMockResponse(200, nil, []byte(`0: Accepted for delivery`)),
@@ -733,7 +733,7 @@ var xmlSendTestCases = []OutgoingTestCase{
Label: "Send Quick Replies",
MsgText: "Some message",
MsgURN: "tel:+250788383383",
- MsgQuickReplies: []string{"One", "Two", "Three"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "One"}, {Text: "Two"}, {Text: "Three"}},
MockResponses: map[string][]*httpx.MockResponse{
"http://example.com/send": {
httpx.NewMockResponse(200, nil, []byte(`0: Accepted for delivery`)),
@@ -751,7 +751,7 @@ var xmlLongSendTestCases = []OutgoingTestCase{
Label: "Send Long message XML",
MsgText: "This is a long message that will be longer than 30....... characters",
MsgURN: "tel:+250788383383",
- MsgQuickReplies: []string{"One", "Two", "Three"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "One"}, {Text: "Two"}, {Text: "Three"}},
MockResponses: map[string][]*httpx.MockResponse{
"*": {
httpx.NewMockResponse(200, nil, []byte(`0: Accepted for delivery`)),
@@ -855,7 +855,7 @@ var xmlSendWithResponseContentTestCases = []OutgoingTestCase{
Label: "Send Quick Replies",
MsgText: "Some message",
MsgURN: "tel:+250788383383",
- MsgQuickReplies: []string{"One", "Two", "Three"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "One"}, {Text: "Two"}, {Text: "Three"}},
MockResponses: map[string][]*httpx.MockResponse{
"http://example.com/send": {
httpx.NewMockResponse(200, nil, []byte(`0`)),
diff --git a/handlers/facebook_legacy/handler.go b/handlers/facebook_legacy/handler.go
index f929901e3..d624b02dd 100644
--- a/handlers/facebook_legacy/handler.go
+++ b/handlers/facebook_legacy/handler.go
@@ -530,7 +530,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, res *courier.Sen
// include any quick replies on the last piece we send
if i == (len(msgParts)+len(msg.Attachments()))-1 {
for _, qr := range msg.QuickReplies() {
- payload.Message.QuickReplies = append(payload.Message.QuickReplies, mtQuickReply{qr, qr, "text"})
+ payload.Message.QuickReplies = append(payload.Message.QuickReplies, mtQuickReply{qr.Text, qr.Text, "text"})
}
} else {
payload.Message.QuickReplies = nil
diff --git a/handlers/facebook_legacy/handler_test.go b/handlers/facebook_legacy/handler_test.go
index a6fd0b559..8c6633abd 100644
--- a/handlers/facebook_legacy/handler_test.go
+++ b/handlers/facebook_legacy/handler_test.go
@@ -858,7 +858,7 @@ var defaultSendTestCases = []OutgoingTestCase{
Label: "Quick Reply",
MsgText: "Are you happy?",
MsgURN: "facebook:12345",
- MsgQuickReplies: []string{"Yes", "No"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "Yes"}, {Text: "No"}},
MockResponses: map[string][]*httpx.MockResponse{
"https://graph.facebook.com/v3.3/me/messages*": {
httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)),
@@ -876,7 +876,7 @@ var defaultSendTestCases = []OutgoingTestCase{
Label: "Long Message",
MsgText: "This is a long message which spans more than one part, what will actually be sent in the end if we exceed the max length?",
MsgURN: "facebook:12345",
- MsgQuickReplies: []string{"Yes", "No"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "Yes"}, {Text: "No"}},
MsgTopic: "account",
MockResponses: map[string][]*httpx.MockResponse{
"https://graph.facebook.com/v3.3/me/messages*": {
@@ -922,7 +922,7 @@ var defaultSendTestCases = []OutgoingTestCase{
MsgText: "This is some text.",
MsgURN: "facebook:12345",
MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"},
- MsgQuickReplies: []string{"Yes", "No"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "Yes"}, {Text: "No"}},
MsgTopic: "event",
MockResponses: map[string][]*httpx.MockResponse{
"https://graph.facebook.com/v3.3/me/messages*": {
diff --git a/handlers/firebase/handler.go b/handlers/firebase/handler.go
index e8085a503..3d72ee54f 100644
--- a/handlers/firebase/handler.go
+++ b/handlers/firebase/handler.go
@@ -205,7 +205,7 @@ func (h *handler) sendWithAPIKey(msg courier.MsgOut, res *courier.SendResult, cl
// include any quick replies on the last piece we send
if i == len(msgParts)-1 {
- payload.Data.QuickReplies = msg.QuickReplies()
+ payload.Data.QuickReplies = handlers.TextOnlyQuickReplies(msg.QuickReplies())
}
payload.To = msg.URNAuth()
@@ -289,7 +289,7 @@ func (h *handler) sendWithCredsJSON(msg courier.MsgOut, res *courier.SendResult,
if i == len(msgParts)-1 {
if msg.QuickReplies() != nil {
- payload.Message.Data.QuickReplies = string(jsonx.MustMarshal(msg.QuickReplies()))
+ payload.Message.Data.QuickReplies = string(jsonx.MustMarshal(handlers.TextOnlyQuickReplies(msg.QuickReplies())))
}
}
diff --git a/handlers/firebase/handler_test.go b/handlers/firebase/handler_test.go
index 7bd373d4a..87ec62559 100644
--- a/handlers/firebase/handler_test.go
+++ b/handlers/firebase/handler_test.go
@@ -199,7 +199,7 @@ var sendAPIkeyTestCases = []OutgoingTestCase{
MsgText: "Simple Message",
MsgURN: "fcm:250788123123",
MsgURNAuth: "auth1",
- MsgQuickReplies: []string{"yes", "no"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "yes"}, {Text: "no"}},
MsgAttachments: []string{"image/jpeg:https://foo.bar"},
MockResponses: map[string][]*httpx.MockResponse{
"https://fcm.googleapis.com/fcm/send": {
@@ -326,7 +326,7 @@ var sendTestCases = []OutgoingTestCase{
MsgText: "Simple Message",
MsgURN: "fcm:250788123123",
MsgURNAuth: "auth1",
- MsgQuickReplies: []string{"yes", "no"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "yes"}, {Text: "no"}},
MsgAttachments: []string{"image/jpeg:https://foo.bar"},
MockResponses: map[string][]*httpx.MockResponse{
"https://fcm.googleapis.com/v1/projects/foo-project-id/messages:send": {
diff --git a/handlers/line/handler.go b/handlers/line/handler.go
index a684137aa..ce42ad78c 100644
--- a/handlers/line/handler.go
+++ b/handlers/line/handler.go
@@ -335,8 +335,8 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, res *courier.Sen
for j, qr := range qrs {
items[j] = QuickReplyItem{Type: "action"}
items[j].Action.Type = "message"
- items[j].Action.Label = qr
- items[j].Action.Text = qr
+ items[j].Action.Label = qr.Text
+ items[j].Action.Text = qr.Text
}
if len(items) > 0 {
mtTextMsg.QuickReply = &mtQuickReply{Items: items}
diff --git a/handlers/line/handler_test.go b/handlers/line/handler_test.go
index d357cfbba..8d719dc9d 100644
--- a/handlers/line/handler_test.go
+++ b/handlers/line/handler_test.go
@@ -490,7 +490,7 @@ var defaultSendTestCases = []OutgoingTestCase{
Label: "Quick Reply",
MsgText: "Are you happy?",
MsgURN: "line:uabcdefghij",
- MsgQuickReplies: []string{"Yes", "No"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "Yes"}, {Text: "No"}},
MockResponses: map[string][]*httpx.MockResponse{
"https://api.line.me/v2/bot/message/push": {httpx.NewMockResponse(200, nil, []byte(`{}`))},
},
@@ -505,7 +505,7 @@ var defaultSendTestCases = []OutgoingTestCase{
MsgText: "Are you happy?",
MsgURN: "line:uabcdefghij",
MsgAttachments: []string{"image/jpeg:http://mock.com/1234/test.jpg"},
- MsgQuickReplies: []string{"Yes", "No"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "Yes"}, {Text: "No"}},
MockResponses: map[string][]*httpx.MockResponse{
"https://api.line.me/v2/bot/message/push": {httpx.NewMockResponse(200, nil, []byte(`{}`))},
},
@@ -521,7 +521,7 @@ var defaultSendTestCases = []OutgoingTestCase{
MsgURN: "line:uabcdefghij",
MsgResponseToExternalID: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
MsgAttachments: []string{"image/jpeg:http://mock.com/1234/test.jpg"},
- MsgQuickReplies: []string{"Yes", "No"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "Yes"}, {Text: "No"}},
MockResponses: map[string][]*httpx.MockResponse{
"https://api.line.me/v2/bot/message/reply": {httpx.NewMockResponse(200, nil, []byte(`{}`))},
},
diff --git a/handlers/meta/facebook_test.go b/handlers/meta/facebook_test.go
index f5bbb5ec0..2ef3ca0d8 100644
--- a/handlers/meta/facebook_test.go
+++ b/handlers/meta/facebook_test.go
@@ -454,7 +454,7 @@ var facebookOutgoingTests = []OutgoingTestCase{
MsgText: "Are you happy?",
MsgURN: "facebook:12345",
MsgOrigin: courier.MsgOriginBroadcast,
- MsgQuickReplies: []string{"Yes", "No"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "Yes"}, {Text: "No"}},
MockResponses: map[string][]*httpx.MockResponse{
"https://graph.facebook.com/v18.0/me/messages*": {
httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)),
@@ -471,7 +471,7 @@ var facebookOutgoingTests = []OutgoingTestCase{
MsgText: "Are you happy?",
MsgURN: "facebook:12345",
MsgOrigin: courier.MsgOriginBroadcast,
- MsgQuickReplies: []string{"Yes", "No"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "Yes"}, {Text: "No"}},
MockResponses: map[string][]*httpx.MockResponse{
"https://graph.facebook.com/v18.0/me/messages*": {
httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)),
@@ -487,7 +487,7 @@ var facebookOutgoingTests = []OutgoingTestCase{
Label: "Message that exceeds max text length",
MsgText: "This is a long message which spans more than one part, what will actually be sent in the end if we exceed the max length?",
MsgURN: "facebook:12345",
- MsgQuickReplies: []string{"Yes", "No"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "Yes"}, {Text: "No"}},
MsgTopic: "account",
MockResponses: map[string][]*httpx.MockResponse{
"https://graph.facebook.com/v18.0/me/messages*": {
@@ -527,7 +527,7 @@ var facebookOutgoingTests = []OutgoingTestCase{
MsgText: "This is some text.",
MsgURN: "facebook:12345",
MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"},
- MsgQuickReplies: []string{"Yes", "No"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "Yes"}, {Text: "No"}},
MsgTopic: "event",
MockResponses: map[string][]*httpx.MockResponse{
"https://graph.facebook.com/v18.0/me/messages*": {
diff --git a/handlers/meta/handlers.go b/handlers/meta/handlers.go
index 9bc34e46c..11a929a3b 100644
--- a/handlers/meta/handlers.go
+++ b/handlers/meta/handlers.go
@@ -706,7 +706,7 @@ func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.MsgO
// include any quick replies on the last piece we send
if part.IsLast {
for _, qr := range msg.QuickReplies() {
- payload.Message.QuickReplies = append(payload.Message.QuickReplies, messenger.QuickReply{Title: qr, Payload: qr, ContentType: "text"})
+ payload.Message.QuickReplies = append(payload.Message.QuickReplies, messenger.QuickReply{Title: qr.Text, Payload: qr.Text, ContentType: "text"})
}
} else {
payload.Message.QuickReplies = nil
@@ -855,7 +855,7 @@ func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, res *
Type: "reply",
}
btns[i].Reply.ID = fmt.Sprint(i)
- btns[i].Reply.Title = qr
+ btns[i].Reply.Title = qr.Text
}
interactive.Action = &struct {
Button string "json:\"button,omitempty\""
@@ -874,7 +874,7 @@ func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, res *
for i, qr := range qrs {
section.Rows[i] = whatsapp.SectionRow{
ID: fmt.Sprint(i),
- Title: qr,
+ Title: qr.Text,
}
}
@@ -1011,7 +1011,7 @@ func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, res *
Type: "reply",
}
btns[i].Reply.ID = fmt.Sprint(i)
- btns[i].Reply.Title = qr
+ btns[i].Reply.Title = qr.Text
}
interactive.Action = &struct {
Button string "json:\"button,omitempty\""
@@ -1031,7 +1031,7 @@ func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, res *
for i, qr := range qrs {
section.Rows[i] = whatsapp.SectionRow{
ID: fmt.Sprint(i),
- Title: qr,
+ Title: qr.Text,
}
}
diff --git a/handlers/meta/instagram_test.go b/handlers/meta/instagram_test.go
index 0f31a6db3..02e989e52 100644
--- a/handlers/meta/instagram_test.go
+++ b/handlers/meta/instagram_test.go
@@ -238,7 +238,7 @@ var instagramOutgoingTests = []OutgoingTestCase{
MsgText: "Are you happy?",
MsgURN: "instagram:12345",
MsgOrigin: courier.MsgOriginBroadcast,
- MsgQuickReplies: []string{"Yes", "No"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "Yes"}, {Text: "No"}},
MockResponses: map[string][]*httpx.MockResponse{
"https://graph.facebook.com/v18.0/me/messages*": {
httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)),
@@ -254,7 +254,7 @@ var instagramOutgoingTests = []OutgoingTestCase{
Label: "Message that exceeds max text length",
MsgText: "This is a long message which spans more than one part, what will actually be sent in the end if we exceed the max length?",
MsgURN: "instagram:12345",
- MsgQuickReplies: []string{"Yes", "No"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "Yes"}, {Text: "No"}},
MsgTopic: "account",
MockResponses: map[string][]*httpx.MockResponse{
"https://graph.facebook.com/v18.0/me/messages*": {
@@ -294,7 +294,7 @@ var instagramOutgoingTests = []OutgoingTestCase{
MsgText: "This is some text.",
MsgURN: "instagram:12345",
MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"},
- MsgQuickReplies: []string{"Yes", "No"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "Yes"}, {Text: "No"}},
MsgTopic: "event",
MockResponses: map[string][]*httpx.MockResponse{
"https://graph.facebook.com/v18.0/me/messages*": {
diff --git a/handlers/meta/whataspp_test.go b/handlers/meta/whataspp_test.go
index 23a9a6646..20cf8231a 100644
--- a/handlers/meta/whataspp_test.go
+++ b/handlers/meta/whataspp_test.go
@@ -517,7 +517,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{
Label: "Interactive Button Message Send",
MsgText: "Interactive Button Msg",
MsgURN: "whatsapp:250788123123",
- MsgQuickReplies: []string{"BUTTON1"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "BUTTON1"}},
MockResponses: map[string][]*httpx.MockResponse{
"*/12345_ID/messages": {
httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)),
@@ -532,7 +532,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{
Label: "Interactive List Message Send",
MsgText: "Interactive List Msg",
MsgURN: "whatsapp:250788123123",
- MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3", "ROW4"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "ROW1"}, {Text: "ROW2"}, {Text: "ROW3"}, {Text: "ROW4"}},
MockResponses: map[string][]*httpx.MockResponse{
"*/12345_ID/messages": {
httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)),
@@ -544,10 +544,14 @@ var whatsappOutgoingTests = []OutgoingTestCase{
ExpectedExtIDs: []string{"157b5e14568e8"},
},
{
- Label: "Interactive List Message Send, more than 10 QRs",
- MsgText: "Interactive List Msg",
- MsgURN: "whatsapp:250788123123",
- MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3", "ROW4", "ROW5", "ROW6", "ROW7", "ROW8", "ROW9", "ROW10", "ROW11", "ROW12"},
+ Label: "Interactive List Message Send, more than 10 QRs",
+ MsgText: "Interactive List Msg",
+ MsgURN: "whatsapp:250788123123",
+ MsgQuickReplies: []courier.QuickReply{
+ {Text: "ROW1"}, {Text: "ROW2"}, {Text: "ROW3"}, {Text: "ROW4"},
+ {Text: "ROW5"}, {Text: "ROW6"}, {Text: "ROW7"}, {Text: "ROW8"},
+ {Text: "ROW9"}, {Text: "ROW10"}, {Text: "ROW11"}, {Text: "ROW12"},
+ },
MockResponses: map[string][]*httpx.MockResponse{
"*/12345_ID/messages": {
httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)),
@@ -564,7 +568,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{
MsgText: "Hola",
MsgURN: "whatsapp:250788123123",
MsgLocale: "spa",
- MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3", "ROW4"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "ROW1"}, {Text: "ROW2"}, {Text: "ROW3"}, {Text: "ROW4"}},
MockResponses: map[string][]*httpx.MockResponse{
"*/12345_ID/messages": {
httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)),
@@ -579,7 +583,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{
Label: "Interactive Button Message Send with image attachment",
MsgText: "Interactive Button Msg",
MsgURN: "whatsapp:250788123123",
- MsgQuickReplies: []string{"BUTTON1"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "BUTTON1"}},
MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"},
MockResponses: map[string][]*httpx.MockResponse{
"*/12345_ID/messages": {
@@ -598,7 +602,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{
Label: "Interactive Button Message Send with video attachment",
MsgText: "Interactive Button Msg",
MsgURN: "whatsapp:250788123123",
- MsgQuickReplies: []string{"BUTTON1"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "BUTTON1"}},
MsgAttachments: []string{"video/mp4:https://foo.bar/video.mp4"},
MockResponses: map[string][]*httpx.MockResponse{
"*/12345_ID/messages": {
@@ -617,7 +621,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{
Label: "Interactive Button Message Send with document attachment",
MsgText: "Interactive Button Msg",
MsgURN: "whatsapp:250788123123",
- MsgQuickReplies: []string{"BUTTON1"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "BUTTON1"}},
MsgAttachments: []string{"document/pdf:https://foo.bar/document.pdf"},
MockResponses: map[string][]*httpx.MockResponse{
"*/12345_ID/messages": {
@@ -636,7 +640,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{
Label: "Interactive Button Message Send with audio attachment",
MsgText: "Interactive Button Msg",
MsgURN: "whatsapp:250788123123",
- MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "ROW1"}, {Text: "ROW2"}, {Text: "ROW3"}},
MsgAttachments: []string{"audio/mp3:https://foo.bar/audio.mp3"},
MockResponses: map[string][]*httpx.MockResponse{
"*/12345_ID/messages": {
@@ -654,7 +658,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{
Label: "Interactive List Message Send with attachment",
MsgText: "Interactive List Msg",
MsgURN: "whatsapp:250788123123",
- MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3", "ROW4"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "ROW1"}, {Text: "ROW2"}, {Text: "ROW3"}, {Text: "ROW4"}},
MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"},
MockResponses: map[string][]*httpx.MockResponse{
"*/12345_ID/messages": {
diff --git a/handlers/telegram/handler_test.go b/handlers/telegram/handler_test.go
index df810bff9..e7e047430 100644
--- a/handlers/telegram/handler_test.go
+++ b/handlers/telegram/handler_test.go
@@ -798,7 +798,7 @@ var outgoingCases = []OutgoingTestCase{
Label: "Quick Reply",
MsgText: "Are you happy?",
MsgURN: "telegram:12345",
- MsgQuickReplies: []string{"Yes", "No"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "Yes"}, {Text: "No"}},
MockResponses: map[string][]*httpx.MockResponse{
"*/botauth_token/sendMessage": {
httpx.NewMockResponse(200, nil, []byte(`{ "ok": true, "result": { "message_id": 133 } }`)),
@@ -813,7 +813,7 @@ var outgoingCases = []OutgoingTestCase{
Label: "Quick Reply with multiple attachments",
MsgText: "Are you happy?",
MsgURN: "telegram:12345",
- MsgQuickReplies: []string{"Yes", "No"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "Yes"}, {Text: "No"}},
MsgAttachments: []string{"application/pdf:https://foo.bar/doc1.pdf", "application/pdf:https://foo.bar/document.pdf"},
MockResponses: map[string][]*httpx.MockResponse{
"*/botauth_token/sendMessage": {
diff --git a/handlers/telegram/keyboard.go b/handlers/telegram/keyboard.go
index 79356c5ea..eb5a7bdf4 100644
--- a/handlers/telegram/keyboard.go
+++ b/handlers/telegram/keyboard.go
@@ -1,6 +1,10 @@
package telegram
-import "github.com/nyaruka/courier/utils"
+import (
+ "github.com/nyaruka/courier"
+ "github.com/nyaruka/courier/handlers"
+ "github.com/nyaruka/courier/utils"
+)
// KeyboardButton is button on a keyboard, see https://core.telegram.org/bots/api/#keyboardbutton
type KeyboardButton struct {
@@ -17,8 +21,8 @@ type ReplyKeyboardMarkup struct {
}
// NewKeyboardFromReplies creates a keyboard from the given quick replies
-func NewKeyboardFromReplies(replies []string) *ReplyKeyboardMarkup {
- rows := utils.StringsToRows(replies, 5, 30, 2)
+func NewKeyboardFromReplies(replies []courier.QuickReply) *ReplyKeyboardMarkup {
+ rows := utils.StringsToRows(handlers.TextOnlyQuickReplies(replies), 5, 30, 2)
keyboard := make([][]KeyboardButton, len(rows))
for i := range rows {
diff --git a/handlers/telegram/keyboard_test.go b/handlers/telegram/keyboard_test.go
index 5c682abd1..f0a11b57e 100644
--- a/handlers/telegram/keyboard_test.go
+++ b/handlers/telegram/keyboard_test.go
@@ -3,18 +3,19 @@ package telegram_test
import (
"testing"
+ "github.com/nyaruka/courier"
"github.com/nyaruka/courier/handlers/telegram"
"github.com/stretchr/testify/assert"
)
func TestKeyboardFromReplies(t *testing.T) {
tcs := []struct {
- replies []string
+ replies []courier.QuickReply
expected *telegram.ReplyKeyboardMarkup
}{
{
- []string{"OK"},
+ []courier.QuickReply{{Text: "OK"}},
&telegram.ReplyKeyboardMarkup{
[][]telegram.KeyboardButton{
{{Text: "OK"}},
@@ -23,7 +24,7 @@ func TestKeyboardFromReplies(t *testing.T) {
},
},
{
- []string{"Yes", "No", "Maybe"},
+ []courier.QuickReply{{Text: "Yes"}, {Text: "No"}, {Text: "Maybe"}},
&telegram.ReplyKeyboardMarkup{
[][]telegram.KeyboardButton{
{{Text: "Yes"}, {Text: "No"}, {Text: "Maybe"}},
@@ -32,7 +33,7 @@ func TestKeyboardFromReplies(t *testing.T) {
},
},
{
- []string{"Vanilla", "Chocolate", "Mint", "Lemon Sorbet", "Papaya", "Strawberry"},
+ []courier.QuickReply{{Text: "Vanilla"}, {Text: "Chocolate"}, {Text: "Mint"}, {Text: "Lemon Sorbet"}, {Text: "Papaya"}, {Text: "Strawberry"}},
&telegram.ReplyKeyboardMarkup{
[][]telegram.KeyboardButton{
{{Text: "Vanilla"}, {Text: "Chocolate"}},
@@ -43,7 +44,7 @@ func TestKeyboardFromReplies(t *testing.T) {
},
},
{
- []string{"A", "B", "C", "D", "Chicken", "Fish", "Peanut Butter Pickle"},
+ []courier.QuickReply{{Text: "A"}, {Text: "B"}, {Text: "C"}, {Text: "D"}, {Text: "Chicken"}, {Text: "Fish"}, {Text: "Peanut Butter Pickle"}},
&telegram.ReplyKeyboardMarkup{
[][]telegram.KeyboardButton{
{{Text: "A"}, {Text: "B"}, {Text: "C"}, {Text: "D"}},
diff --git a/handlers/test.go b/handlers/test.go
index 9d3e28469..0008f82bd 100644
--- a/handlers/test.go
+++ b/handlers/test.go
@@ -299,7 +299,7 @@ type OutgoingTestCase struct {
MsgURN string
MsgURNAuth string
MsgAttachments []string
- MsgQuickReplies []string
+ MsgQuickReplies []courier.QuickReply
MsgLocale i18n.Locale
MsgTopic string
MsgTemplating string
diff --git a/handlers/utils.go b/handlers/utils.go
index 0155087d1..f16400db3 100644
--- a/handlers/utils.go
+++ b/handlers/utils.go
@@ -35,6 +35,15 @@ func SplitAttachment(attachment string) (string, string) {
return parts[0], parts[1]
}
+// TextOnlyQuickReplies returns the text of a list of quick replies
+func TextOnlyQuickReplies(qrs []courier.QuickReply) []string {
+ t := make([]string, len(qrs))
+ for i, qr := range qrs {
+ t[i] = qr.Text
+ }
+ return t
+}
+
// NameFromFirstLastUsername is a utility function to build a contact's name from the passed
// in values, all of which can be empty
func NameFromFirstLastUsername(first string, last string, username string) string {
diff --git a/handlers/viber/handler_test.go b/handlers/viber/handler_test.go
index 0379f4dd4..1dbdcd1e9 100644
--- a/handlers/viber/handler_test.go
+++ b/handlers/viber/handler_test.go
@@ -97,7 +97,7 @@ var defaultSendTestCases = []OutgoingTestCase{
Label: "Quick Reply",
MsgText: "Are you happy?",
MsgURN: "viber:xy5/5y6O81+/kbWHpLhBoA==",
- MsgQuickReplies: []string{"Yes", "No"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "Yes"}, {Text: "No"}},
MockResponses: map[string][]*httpx.MockResponse{
"https://chatapi.viber.com/pa/send_message": {
httpx.NewMockResponse(200, nil, []byte(`{"status":0,"status_message":"ok","message_token":4987381194038857789}`)),
@@ -279,7 +279,7 @@ var buttonLayoutSendTestCases = []OutgoingTestCase{
Label: "Quick Reply With Layout With Column, Row and BgColor definitions",
MsgText: "Select a, b, c or d.",
MsgURN: "viber:xy5/5y6O81+/kbWHpLhBoA==",
- MsgQuickReplies: []string{"a", "b", "c", "d"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "a"}, {Text: "b"}, {Text: "c"}, {Text: "d"}},
MockResponses: map[string][]*httpx.MockResponse{
"https://chatapi.viber.com/pa/send_message": {
httpx.NewMockResponse(200, nil, []byte(`{"status":0,"status_message":"ok","message_token":4987381194038857789}`)),
diff --git a/handlers/viber/keyboard.go b/handlers/viber/keyboard.go
index ed031dffc..99231b6ae 100644
--- a/handlers/viber/keyboard.go
+++ b/handlers/viber/keyboard.go
@@ -5,6 +5,9 @@ import (
"html"
"strings"
"unicode/utf8"
+
+ "github.com/nyaruka/courier"
+ "github.com/nyaruka/courier/handlers"
)
// KeyboardButton is button on a keyboard, see https://developers.viber.com/docs/tools/keyboards/#buttons-parameters
@@ -36,8 +39,8 @@ const (
var textSizes = map[string]bool{"small": true, "regular": true, "large": true}
// NewKeyboardFromReplies create a keyboard from the given quick replies
-func NewKeyboardFromReplies(replies []string, buttonConfig map[string]any) *Keyboard {
- rows := StringsToRows(replies, maxColumns, maxRowRunes, paddingRunes)
+func NewKeyboardFromReplies(replies []courier.QuickReply, buttonConfig map[string]any) *Keyboard {
+ rows := StringsToRows(handlers.TextOnlyQuickReplies(replies), maxColumns, maxRowRunes, paddingRunes)
buttons := []KeyboardButton{}
for i := range rows {
diff --git a/handlers/viber/keyboard_test.go b/handlers/viber/keyboard_test.go
index 37d4d30f7..92130f8dc 100644
--- a/handlers/viber/keyboard_test.go
+++ b/handlers/viber/keyboard_test.go
@@ -3,18 +3,19 @@ package viber_test
import (
"testing"
+ "github.com/nyaruka/courier"
"github.com/nyaruka/courier/handlers/viber"
"github.com/stretchr/testify/assert"
)
func TestKeyboardFromReplies(t *testing.T) {
tsc := []struct {
- replies []string
+ replies []courier.QuickReply
expected *viber.Keyboard
buttonConfig map[string]any
}{
{
- []string{"OK"},
+ []courier.QuickReply{{Text: "OK"}},
&viber.Keyboard{
"keyboard",
false,
@@ -25,7 +26,7 @@ func TestKeyboardFromReplies(t *testing.T) {
map[string]any{},
},
{
- []string{"Yes", "No", "Maybe"},
+ []courier.QuickReply{{Text: "Yes"}, {Text: "No"}, {Text: "Maybe"}},
&viber.Keyboard{
"keyboard",
false,
@@ -38,7 +39,7 @@ func TestKeyboardFromReplies(t *testing.T) {
map[string]any{},
},
{
- []string{"A", "B", "C", "D"},
+ []courier.QuickReply{{Text: "A"}, {Text: "B"}, {Text: "C"}, {Text: "D"}},
&viber.Keyboard{
"keyboard",
false,
@@ -52,7 +53,7 @@ func TestKeyboardFromReplies(t *testing.T) {
map[string]any{},
},
{
- []string{"\"A\"", ""},
+ []courier.QuickReply{{Text: "\"A\""}, {Text: ""}},
&viber.Keyboard{
"keyboard",
false,
@@ -64,7 +65,7 @@ func TestKeyboardFromReplies(t *testing.T) {
map[string]any{},
},
{
- []string{"Vanilla", "Chocolate", "Mint", "Lemon Sorbet", "Papaya", "Strawberry"},
+ []courier.QuickReply{{Text: "Vanilla"}, {Text: "Chocolate"}, {Text: "Mint"}, {Text: "Lemon Sorbet"}, {Text: "Papaya"}, {Text: "Strawberry"}},
&viber.Keyboard{
"keyboard",
false,
@@ -80,7 +81,7 @@ func TestKeyboardFromReplies(t *testing.T) {
map[string]any{},
},
{
- []string{"A", "B", "C", "D", "Chicken", "Fish", "Peanut Butter Pickle"},
+ []courier.QuickReply{{Text: "A"}, {Text: "B"}, {Text: "C"}, {Text: "D"}, {Text: "Chicken"}, {Text: "Fish"}, {Text: "Peanut Butter Pickle"}},
&viber.Keyboard{
"keyboard",
false,
@@ -97,7 +98,7 @@ func TestKeyboardFromReplies(t *testing.T) {
map[string]any{},
},
{
- []string{"Foo", "Bar", "Baz"},
+ []courier.QuickReply{{Text: "Foo"}, {Text: "Bar"}, {Text: "Baz"}},
&viber.Keyboard{
"keyboard",
false,
@@ -114,7 +115,7 @@ func TestKeyboardFromReplies(t *testing.T) {
},
},
{
- []string{"Yes", "No", "Maybe"},
+ []courier.QuickReply{{Text: "Yes"}, {Text: "No"}, {Text: "Maybe"}},
&viber.Keyboard{
"keyboard",
false,
diff --git a/handlers/vk/handler_test.go b/handlers/vk/handler_test.go
index 45e180431..a4ebb6efc 100644
--- a/handlers/vk/handler_test.go
+++ b/handlers/vk/handler_test.go
@@ -473,7 +473,7 @@ var outgoingCases = []OutgoingTestCase{
Label: "Send keyboard",
MsgText: "Send keyboard",
MsgURN: "vk:123456789",
- MsgQuickReplies: []string{"A", "B", "C", "D", "E"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "A"}, {Text: "B"}, {Text: "C"}, {Text: "D"}, {Text: "E"}},
MockResponses: map[string][]*httpx.MockResponse{
"https://api.vk.com/method/messages.send.json?*": {
httpx.NewMockResponse(200, nil, []byte(`{"response": 1}`)),
diff --git a/handlers/vk/keyboard.go b/handlers/vk/keyboard.go
index a8b435254..42175b818 100644
--- a/handlers/vk/keyboard.go
+++ b/handlers/vk/keyboard.go
@@ -1,6 +1,8 @@
package vk
import (
+ "github.com/nyaruka/courier"
+ "github.com/nyaruka/courier/handlers"
"github.com/nyaruka/courier/utils"
"github.com/nyaruka/gocommon/jsonx"
)
@@ -23,8 +25,8 @@ type ButtonAction struct {
}
// NewKeyboardFromReplies creates a keyboard from the given quick replies
-func NewKeyboardFromReplies(replies []string) *Keyboard {
- rows := utils.StringsToRows(replies, 10, 30, 2)
+func NewKeyboardFromReplies(replies []courier.QuickReply) *Keyboard {
+ rows := utils.StringsToRows(handlers.TextOnlyQuickReplies(replies), 10, 30, 2)
buttons := make([][]ButtonPayload, len(rows))
for i := range rows {
diff --git a/handlers/vk/keyboard_test.go b/handlers/vk/keyboard_test.go
index 6a2491b68..89190a5ba 100644
--- a/handlers/vk/keyboard_test.go
+++ b/handlers/vk/keyboard_test.go
@@ -3,18 +3,19 @@ package vk_test
import (
"testing"
+ "github.com/nyaruka/courier"
"github.com/nyaruka/courier/handlers/vk"
"github.com/stretchr/testify/assert"
)
func TestKeyboardFromReplies(t *testing.T) {
tcs := []struct {
- replies []string
+ replies []courier.QuickReply
expected *vk.Keyboard
}{
{
- []string{"OK"},
+ []courier.QuickReply{{Text: "OK"}},
&vk.Keyboard{
true,
[][]vk.ButtonPayload{
@@ -26,7 +27,7 @@ func TestKeyboardFromReplies(t *testing.T) {
},
},
{
- []string{"Yes", "No", "Maybe"},
+ []courier.QuickReply{{Text: "Yes"}, {Text: "No"}, {Text: "Maybe"}},
&vk.Keyboard{
true,
[][]vk.ButtonPayload{
@@ -40,7 +41,7 @@ func TestKeyboardFromReplies(t *testing.T) {
},
},
{
- []string{"Vanilla", "Chocolate", "Mint", "Lemon Sorbet", "Papaya", "Strawberry"},
+ []courier.QuickReply{{Text: "Vanilla"}, {Text: "Chocolate"}, {Text: "Mint"}, {Text: "Lemon Sorbet"}, {Text: "Papaya"}, {Text: "Strawberry"}},
&vk.Keyboard{
true,
[][]vk.ButtonPayload{
@@ -56,7 +57,7 @@ func TestKeyboardFromReplies(t *testing.T) {
},
},
{
- []string{"A", "B", "C", "D", "Chicken", "Fish", "Peanut Butter Pickle"},
+ []courier.QuickReply{{Text: "A"}, {Text: "B"}, {Text: "C"}, {Text: "D"}, {Text: "Chicken"}, {Text: "Fish"}, {Text: "Peanut Butter Pickle"}},
&vk.Keyboard{
true,
[][]vk.ButtonPayload{
@@ -73,7 +74,7 @@ func TestKeyboardFromReplies(t *testing.T) {
},
},
{
- []string{"A", "B", "C", "D", "E"},
+ []courier.QuickReply{{Text: "A"}, {Text: "B"}, {Text: "C"}, {Text: "D"}, {Text: "E"}},
&vk.Keyboard{
true,
[][]vk.ButtonPayload{
diff --git a/handlers/whatsapp_legacy/handler.go b/handlers/whatsapp_legacy/handler.go
index 5d1e734a7..d8de2bc0f 100644
--- a/handlers/whatsapp_legacy/handler.go
+++ b/handlers/whatsapp_legacy/handler.go
@@ -668,7 +668,7 @@ func buildPayloads(msg courier.MsgOut, h *handler, clog *courier.ChannelLog) ([]
Type: "reply",
}
btns[i].Reply.ID = fmt.Sprint(i)
- btns[i].Reply.Title = qr
+ btns[i].Reply.Title = qr.Text
}
payload.Interactive.Action.Buttons = btns
payloads = append(payloads, payload)
@@ -682,7 +682,7 @@ func buildPayloads(msg courier.MsgOut, h *handler, clog *courier.ChannelLog) ([]
for i, qr := range qrs {
section.Rows[i] = mtSectionRow{
ID: fmt.Sprint(i),
- Title: qr,
+ Title: qr.Text,
}
}
payload.Interactive.Action.Sections = []mtSection{
@@ -763,7 +763,7 @@ func buildPayloads(msg courier.MsgOut, h *handler, clog *courier.ChannelLog) ([]
Type: "reply",
}
btns[i].Reply.ID = fmt.Sprint(i)
- btns[i].Reply.Title = qr
+ btns[i].Reply.Title = qr.Text
}
payload.Interactive.Action.Buttons = btns
payloads = append(payloads, payload)
@@ -777,7 +777,7 @@ func buildPayloads(msg courier.MsgOut, h *handler, clog *courier.ChannelLog) ([]
for i, qr := range qrs {
section.Rows[i] = mtSectionRow{
ID: fmt.Sprint(i),
- Title: qr,
+ Title: qr.Text,
}
}
payload.Interactive.Action.Sections = []mtSection{
diff --git a/handlers/whatsapp_legacy/handler_test.go b/handlers/whatsapp_legacy/handler_test.go
index 334024572..9ad7d8f91 100644
--- a/handlers/whatsapp_legacy/handler_test.go
+++ b/handlers/whatsapp_legacy/handler_test.go
@@ -951,7 +951,7 @@ var defaultSendTestCases = []OutgoingTestCase{
Label: "Interactive Button Message Send",
MsgText: "Interactive Button Msg",
MsgURN: "whatsapp:250788123123",
- MsgQuickReplies: []string{"BUTTON1"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "BUTTON1"}},
MockResponses: map[string][]*httpx.MockResponse{
"*/v1/messages": {
httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)),
@@ -967,7 +967,7 @@ var defaultSendTestCases = []OutgoingTestCase{
Label: "Interactive List Message Send",
MsgText: "Interactive List Msg",
MsgURN: "whatsapp:250788123123",
- MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3", "ROW4"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "ROW1"}, {Text: "ROW2"}, {Text: "ROW3"}, {Text: "ROW4"}},
MockResponses: map[string][]*httpx.MockResponse{
"*/v1/messages": {
httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)),
@@ -984,7 +984,7 @@ var defaultSendTestCases = []OutgoingTestCase{
Label: "Interactive Button Message Send with attachment",
MsgText: "Interactive Button Msg",
MsgURN: "whatsapp:250788123123",
- MsgQuickReplies: []string{"BUTTON1"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "BUTTON1"}},
MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"},
MockResponses: map[string][]*httpx.MockResponse{
"*/v1/messages": {
@@ -1002,7 +1002,7 @@ var defaultSendTestCases = []OutgoingTestCase{
Label: "Interactive List Message Send with attachment",
MsgText: "Interactive List Msg",
MsgURN: "whatsapp:250788123123",
- MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3", "ROW4"},
+ MsgQuickReplies: []courier.QuickReply{{Text: "ROW1"}, {Text: "ROW2"}, {Text: "ROW3"}, {Text: "ROW4"}},
MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"},
MockResponses: map[string][]*httpx.MockResponse{
"*/v1/messages": {
diff --git a/msg.go b/msg.go
index 3e751b69f..8ebcd7a35 100644
--- a/msg.go
+++ b/msg.go
@@ -7,6 +7,7 @@ import (
"time"
"github.com/nyaruka/gocommon/i18n"
+ "github.com/nyaruka/gocommon/jsonx"
"github.com/nyaruka/gocommon/urns"
"github.com/nyaruka/gocommon/uuids"
"github.com/nyaruka/null/v3"
@@ -32,6 +33,22 @@ type MsgUUID uuids.UUID
// NilMsgUUID is a "zero value" message UUID
const NilMsgUUID = MsgUUID("")
+type QuickReply struct {
+ Text string `json:"text"`
+}
+
+func (q *QuickReply) UnmarshalJSON(d []byte) error {
+ // if we just have a string we unmarshal it into the text field
+ if len(d) > 2 && d[0] == '"' && d[len(d)-1] == '"' {
+ return jsonx.Unmarshal(d, &q.Text)
+ }
+
+ // alias our type so we don't end up here again
+ type alias QuickReply
+
+ return jsonx.Unmarshal(d, (*alias)(q))
+}
+
type FlowReference struct {
UUID string `json:"uuid" validate:"uuid4"`
Name string `json:"name"`
@@ -103,7 +120,7 @@ type MsgOut interface {
Msg
// outgoing specific
- QuickReplies() []string
+ QuickReplies() []QuickReply
Locale() i18n.Locale
Templating() *Templating
URNAuth() string
diff --git a/test/backend.go b/test/backend.go
index ad055d05a..15c9683bd 100644
--- a/test/backend.go
+++ b/test/backend.go
@@ -115,7 +115,7 @@ func (mb *MockBackend) NewIncomingMsg(channel courier.Channel, urn urns.URN, tex
}
// NewOutgoingMsg creates a new outgoing message from the given params
-func (mb *MockBackend) NewOutgoingMsg(channel courier.Channel, id courier.MsgID, urn urns.URN, text string, highPriority bool, quickReplies []string,
+func (mb *MockBackend) NewOutgoingMsg(channel courier.Channel, id courier.MsgID, urn urns.URN, text string, highPriority bool, quickReplies []courier.QuickReply,
topic string, responseToExternalID string, origin courier.MsgOrigin, contactLastSeenOn *time.Time) courier.MsgOut {
return &MockMsg{
diff --git a/test/msg.go b/test/msg.go
index 556fa6be3..59d75bf24 100644
--- a/test/msg.go
+++ b/test/msg.go
@@ -23,7 +23,7 @@ type MockMsg struct {
externalID string
contactName string
highPriority bool
- quickReplies []string
+ quickReplies []courier.QuickReply
origin courier.MsgOrigin
contactLastSeenOn *time.Time
topic string
@@ -62,22 +62,22 @@ func (m *MockMsg) URN() urns.URN { return m.urn }
func (m *MockMsg) Channel() courier.Channel { return m.channel }
// outgoing specific
-func (m *MockMsg) QuickReplies() []string { return m.quickReplies }
-func (m *MockMsg) Locale() i18n.Locale { return m.locale }
-func (m *MockMsg) Templating() *courier.Templating { return m.templating }
-func (m *MockMsg) URNAuth() string { return m.urnAuth }
-func (m *MockMsg) Origin() courier.MsgOrigin { return m.origin }
-func (m *MockMsg) ContactLastSeenOn() *time.Time { return m.contactLastSeenOn }
-func (m *MockMsg) Topic() string { return m.topic }
-func (m *MockMsg) Metadata() json.RawMessage { return m.metadata }
-func (m *MockMsg) ResponseToExternalID() string { return m.responseToExternalID }
-func (m *MockMsg) SentOn() *time.Time { return m.sentOn }
-func (m *MockMsg) IsResend() bool { return m.isResend }
-func (m *MockMsg) Flow() *courier.FlowReference { return m.flow }
-func (m *MockMsg) OptIn() *courier.OptInReference { return m.optIn }
-func (m *MockMsg) UserID() courier.UserID { return m.userID }
-func (m *MockMsg) Session() *courier.Session { return m.session }
-func (m *MockMsg) HighPriority() bool { return m.highPriority }
+func (m *MockMsg) QuickReplies() []courier.QuickReply { return m.quickReplies }
+func (m *MockMsg) Locale() i18n.Locale { return m.locale }
+func (m *MockMsg) Templating() *courier.Templating { return m.templating }
+func (m *MockMsg) URNAuth() string { return m.urnAuth }
+func (m *MockMsg) Origin() courier.MsgOrigin { return m.origin }
+func (m *MockMsg) ContactLastSeenOn() *time.Time { return m.contactLastSeenOn }
+func (m *MockMsg) Topic() string { return m.topic }
+func (m *MockMsg) Metadata() json.RawMessage { return m.metadata }
+func (m *MockMsg) ResponseToExternalID() string { return m.responseToExternalID }
+func (m *MockMsg) SentOn() *time.Time { return m.sentOn }
+func (m *MockMsg) IsResend() bool { return m.isResend }
+func (m *MockMsg) Flow() *courier.FlowReference { return m.flow }
+func (m *MockMsg) OptIn() *courier.OptInReference { return m.optIn }
+func (m *MockMsg) UserID() courier.UserID { return m.userID }
+func (m *MockMsg) Session() *courier.Session { return m.session }
+func (m *MockMsg) HighPriority() bool { return m.highPriority }
// incoming specific
func (m *MockMsg) ReceivedOn() *time.Time { return m.receivedOn }