Skip to content

Commit

Permalink
Merge pull request #394 from Ilhasoft/feature/quick-replies-vk
Browse files Browse the repository at this point in the history
Quick Replies for VK
  • Loading branch information
rowanseymour authored Jan 5, 2022
2 parents 718aaa1 + 220295b commit eb0c1fd
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 6 deletions.
41 changes: 41 additions & 0 deletions handlers/vk/keyboard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package vk

import (
"github.com/nyaruka/courier/utils"
"github.com/nyaruka/gocommon/jsonx"
)

type Keyboard struct {
One_Time bool `json:"one_time"`
Buttons [][]ButtonPayload `json:"buttons"`
Inline bool `json:"inline"`
}

type ButtonPayload struct {
Action ButtonAction `json:"action"`
Color string `json:"color"`
}

type ButtonAction struct {
Type string `json:"type"`
Label string `json:"label"`
Payload string `json:"payload"`
}

// NewKeyboardFromReplies creates a keyboard from the given quick replies
func NewKeyboardFromReplies(replies []string) *Keyboard {
rows := utils.StringsToRows(replies, 10, 30, 2)
buttons := make([][]ButtonPayload, len(rows))

for i := range rows {
buttons[i] = make([]ButtonPayload, len(rows[i]))
for j := range rows[i] {
buttons[i][j].Action.Label = rows[i][j]
buttons[i][j].Action.Type = "text"
buttons[i][j].Action.Payload = string(jsonx.MustMarshal(rows[i][j]))
buttons[i][j].Color = "primary"
}
}

return &Keyboard{One_Time: true, Buttons: buttons, Inline: false}
}
98 changes: 98 additions & 0 deletions handlers/vk/keyboard_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package vk_test

import (
"testing"

"github.com/nyaruka/courier/handlers/vk"
"github.com/stretchr/testify/assert"
)

func TestKeyboardFromReplies(t *testing.T) {
tcs := []struct {
replies []string
expected *vk.Keyboard
}{
{

[]string{"OK"},
&vk.Keyboard{
true,
[][]vk.ButtonPayload{
{
{vk.ButtonAction{Type: "text", Label: "OK", Payload: "\"OK\""}, "primary"},
},
},
false,
},
},
{
[]string{"Yes", "No", "Maybe"},
&vk.Keyboard{
true,
[][]vk.ButtonPayload{
{
{vk.ButtonAction{Type: "text", Label: "Yes", Payload: "\"Yes\""}, "primary"},
{vk.ButtonAction{Type: "text", Label: "No", Payload: "\"No\""}, "primary"},
{vk.ButtonAction{Type: "text", Label: "Maybe", Payload: "\"Maybe\""}, "primary"},
},
},
false,
},
},
{
[]string{"Vanilla", "Chocolate", "Mint", "Lemon Sorbet", "Papaya", "Strawberry"},
&vk.Keyboard{
true,
[][]vk.ButtonPayload{

{{vk.ButtonAction{Type: "text", Label: "Vanilla", Payload: "\"Vanilla\""}, "primary"}},
{{vk.ButtonAction{Type: "text", Label: "Chocolate", Payload: "\"Chocolate\""}, "primary"}},
{{vk.ButtonAction{Type: "text", Label: "Mint", Payload: "\"Mint\""}, "primary"}},
{{vk.ButtonAction{Type: "text", Label: "Lemon Sorbet", Payload: "\"Lemon Sorbet\""}, "primary"}},
{{vk.ButtonAction{Type: "text", Label: "Papaya", Payload: "\"Papaya\""}, "primary"}},
{{vk.ButtonAction{Type: "text", Label: "Strawberry", Payload: "\"Strawberry\""}, "primary"}},
},
false,
},
},
{
[]string{"A", "B", "C", "D", "Chicken", "Fish", "Peanut Butter Pickle"},
&vk.Keyboard{
true,
[][]vk.ButtonPayload{

{{vk.ButtonAction{Type: "text", Label: "A", Payload: "\"A\""}, "primary"}},
{{vk.ButtonAction{Type: "text", Label: "B", Payload: "\"B\""}, "primary"}},
{{vk.ButtonAction{Type: "text", Label: "C", Payload: "\"C\""}, "primary"}},
{{vk.ButtonAction{Type: "text", Label: "D", Payload: "\"D\""}, "primary"}},
{{vk.ButtonAction{Type: "text", Label: "Chicken", Payload: "\"Chicken\""}, "primary"}},
{{vk.ButtonAction{Type: "text", Label: "Fish", Payload: "\"Fish\""}, "primary"}},
{{vk.ButtonAction{Type: "text", Label: "Peanut Butter Pickle", Payload: "\"Peanut Butter Pickle\""}, "primary"}},
},
false,
},
},
{
[]string{"A", "B", "C", "D", "E"},
&vk.Keyboard{
true,
[][]vk.ButtonPayload{

{
{vk.ButtonAction{Type: "text", Label: "A", Payload: "\"A\""}, "primary"},
{vk.ButtonAction{Type: "text", Label: "B", Payload: "\"B\""}, "primary"},
{vk.ButtonAction{Type: "text", Label: "C", Payload: "\"C\""}, "primary"},
{vk.ButtonAction{Type: "text", Label: "D", Payload: "\"D\""}, "primary"},
{vk.ButtonAction{Type: "text", Label: "E", Payload: "\"E\""}, "primary"},
},
},
false,
},
},
}

for _, tc := range tcs {
kb := vk.NewKeyboardFromReplies(tc.replies)
assert.Equal(t, tc.expected, kb, "keyboard mismatch for replies %v", tc.replies)
}
}
20 changes: 16 additions & 4 deletions handlers/vk/vk.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/nyaruka/courier"
"github.com/nyaruka/courier/handlers"
"github.com/nyaruka/courier/utils"
"github.com/nyaruka/gocommon/jsonx"
"github.com/nyaruka/gocommon/urns"
)

Expand Down Expand Up @@ -56,6 +57,7 @@ var (
paramMessage = "message"
paramAttachments = "attachment"
paramRandomId = "random_id"
paramKeyboard = "keyboard"

// base upload media values
paramServerId = "server"
Expand Down Expand Up @@ -113,6 +115,7 @@ type moNewMessagePayload struct {
Lng float64 `json:"longitude"`
} `json:"coordinates"`
} `json:"geo"`
Payload string `json:"payload"`
} `json:"message" validate:"required"`
} `json:"object" validate:"required"`
}
Expand Down Expand Up @@ -384,11 +387,7 @@ func takeFirstAttachmentUrl(payload moNewMessagePayload) string {

func (h *handler) SendMsg(ctx context.Context, msg courier.Msg) (courier.MsgStatus, error) {
status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored)
req, err := http.NewRequest(http.MethodPost, apiBaseURL+actionSendMessage, nil)

if err != nil {
return status, errors.New("Cannot create send message request")
}
params := buildApiBaseParams(msg.Channel())
params.Set(paramUserId, msg.URN().Path())
params.Set(paramRandomId, msg.ID().String())
Expand All @@ -397,6 +396,19 @@ func (h *handler) SendMsg(ctx context.Context, msg courier.Msg) (courier.MsgStat
params.Set(paramMessage, text)
params.Set(paramAttachments, attachments)

if len(msg.QuickReplies()) != 0 {
qrs := msg.QuickReplies()
keyboard := NewKeyboardFromReplies(qrs)

params.Set(paramKeyboard, string(jsonx.MustMarshal(keyboard)))
}

req, err := http.NewRequest(http.MethodPost, apiBaseURL+actionSendMessage, nil)

if err != nil {
return status, errors.New("Cannot create send message request")
}

req.URL.RawQuery = params.Encode()
res, err := utils.MakeHTTPRequest(req)

Expand Down
50 changes: 48 additions & 2 deletions handlers/vk/vk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ package vk

import (
"context"
"github.com/nyaruka/gocommon/urns"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"time"

"github.com/nyaruka/gocommon/urns"
"github.com/stretchr/testify/assert"

"github.com/nyaruka/courier"
. "github.com/nyaruka/courier/handlers"
)
Expand Down Expand Up @@ -210,6 +211,22 @@ const eventServerVerification = `{
"secret": "abc123xyz"
}`

const msgKeyboard = `{
"type": "message_new",
"object": {
"message": {
"id": 1,
"date": 1580125800,
"from_id": 123456,
"text": "Yes",
"payload": "\"Yes\""
}
},
"secret": "abc123xyz"
}`

const keyboardJson = `{"one_time":true,"buttons":[[{"action":{"type":"text","label":"A","payload":"\"A\""},"color":"primary"},{"action":{"type":"text","label":"B","payload":"\"B\""},"color":"primary"},{"action":{"type":"text","label":"C","payload":"\"C\""},"color":"primary"},{"action":{"type":"text","label":"D","payload":"\"D\""},"color":"primary"},{"action":{"type":"text","label":"E","payload":"\"E\""},"color":"primary"}]],"inline":false}`

var testCases = []ChannelHandleTestCase{
{
Label: "Receive Message",
Expand Down Expand Up @@ -281,6 +298,16 @@ var testCases = []ChannelHandleTestCase{
ExternalID: Sp("1"),
Date: Tp(time.Date(2020, 1, 27, 11, 50, 0, 0, time.UTC)), Attachments: []string{"https://foo.bar/doc.pdf"},
},
{
Label: "Receive Message Keyboard",
URL: receiveURL,
Data: msgKeyboard,
Status: 200,
Response: "ok",
URN: Sp("vk:123456"),
ExternalID: Sp("1"),
Date: Tp(time.Date(2020, 1, 27, 11, 50, 0, 0, time.UTC)),
},
{
Label: "Receive Geolocation Attachment",
URL: receiveURL,
Expand Down Expand Up @@ -445,6 +472,25 @@ var sendTestCases = []ChannelSendTestCase{
},
},
},
{
Label: "Send keyboard",
Text: "Send keyboard",
URN: "vk:123456789",
QuickReplies: []string{"A", "B", "C", "D", "E"},
Status: "S",
SendPrep: setSendURL,
ExternalID: "1",
Responses: map[MockedRequest]MockedResponse{
MockedRequest{
Method: "POST",
Path: actionSendMessage,
RawQuery: "access_token=token123xyz&attachment=&keyboard=" + url.QueryEscape(keyboardJson) + "&message=Send+keyboard&random_id=10&user_id=123456789&v=5.103",
}: {
Status: 200,
Body: `{"response": 1}`,
},
},
},
}

func mockAttachmentURLs(mediaServer *httptest.Server, testCases []ChannelSendTestCase) []ChannelSendTestCase {
Expand Down

0 comments on commit eb0c1fd

Please sign in to comment.