Skip to content

Commit

Permalink
Add support for Zenvia channels
Browse files Browse the repository at this point in the history
  • Loading branch information
norkans7 committed Aug 3, 2017
1 parent 21c6026 commit 3c0f77c
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 1 deletion.
6 changes: 6 additions & 0 deletions channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ const (
// ConfigAuthToken is a constant key for channel configs
ConfigAuthToken = "auth_token"

// ConfigAccount is a constant key for channel configs
ConfigAccount = "account"

// ConfigCode is a constant key for channel configs
ConfigCode = "code"

// ConfigUsername is a constant key for channel configs
ConfigUsername = "username"

Expand Down
163 changes: 162 additions & 1 deletion handlers/zenvia/zenvia.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,164 @@
package zenvia

/* no logs */
import (
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"

"github.com/nyaruka/courier"
"github.com/nyaruka/courier/handlers"
"github.com/nyaruka/courier/utils"
"github.com/pkg/errors"
)

var sendURL = "http://www.zenvia360.com.br/GatewayIntegration/msgSms.do"

func init() {
courier.RegisterHandler(NewHandler())
}

type handler struct {
handlers.BaseHandler
}

// NewHandler returns a new Zenvia handler
func NewHandler() courier.ChannelHandler {
return &handler{handlers.NewBaseHandler(courier.ChannelType("ZV"), "Zenvia")}
}

type messageRequest struct {
ID string `validate:"required" name:"id"`
Text string `validate:"required" name:"msg"`
From string `validate:"required" name:"from"`
To string `validate:"required" name:"to"`
Date string `validate:"required" name:"date"`
}

type statusRequest struct {
ID string `validate:"required" name:"id"`
Status int32 `validate:"required" name:"status"`
}

var statusMapping = map[int32]courier.MsgStatusValue{
120: courier.MsgDelivered,
111: courier.MsgSent,
}

// Initialize is called by the engine once everything is loaded
func (h *handler) Initialize(s courier.Server) error {
h.SetServer(s)
err := s.AddReceiveMsgRoute(h, "POST", "receive", h.ReceiveMessage)
if err != nil {
return err
}
return s.AddUpdateStatusRoute(h, "POST", "status", h.StatusMessage)
}

// ReceiveMessage is our HTTP handler function for incoming messages
func (h *handler) ReceiveMessage(channel courier.Channel, w http.ResponseWriter, r *http.Request) ([]courier.Msg, error) {
// get our params
zvMsg := &messageRequest{}
err := handlers.DecodeAndValidateForm(zvMsg, r)
if err != nil {
return nil, err
}

// create our date from the timestamp
// 03/05/2017 06:04:45
date, err := time.Parse("02/01/2006 15:04:05", zvMsg.Date)
if err != nil {
return nil, fmt.Errorf("invalid date format: %s", zvMsg.Date)
}

// create our URN
urn := courier.NewTelURNForChannel(zvMsg.From, channel)

// build our msg
msg := h.Backend().NewIncomingMsg(channel, urn, zvMsg.Text).WithReceivedOn(date)

// and finally queue our message
err = h.Backend().WriteMsg(msg)
if err != nil {
return nil, err
}

return []courier.Msg{msg}, courier.WriteReceiveSuccess(w, r, msg)
}

// StatusMessage is our HTTP handler function for status updates
func (h *handler) StatusMessage(channel courier.Channel, w http.ResponseWriter, r *http.Request) ([]courier.MsgStatus, error) {
// get our params
zvStatus := &statusRequest{}
err := handlers.DecodeAndValidateForm(zvStatus, r)
if err != nil {
return nil, err
}

msgStatus, found := statusMapping[zvStatus.Status]
if !found {
msgStatus = courier.MsgFailed
}

// write our status
status := h.Backend().NewMsgStatusForExternalID(channel, zvStatus.ID, msgStatus)
err = h.Backend().WriteMsgStatus(status)
if err != nil {
return nil, err
}

return []courier.MsgStatus{status}, courier.WriteStatusSuccess(w, r, status)

}

// SendMsg sends the passed in message, returning any error
func (h *handler) SendMsg(msg courier.Msg) (courier.MsgStatus, error) {
account := msg.Channel().StringConfigForKey(courier.ConfigAccount, "")
if account == "" {
return nil, fmt.Errorf("no account set for Zenvia channel")
}

code := msg.Channel().StringConfigForKey(courier.ConfigCode, "")
if code == "" {
return nil, fmt.Errorf("no code set for Zenvia channel")
}

// build our request
form := url.Values{
"dispatch": []string{"send"},
"account": []string{account},
"code": []string{code},
"to": []string{msg.URN().Path()},
"msg": []string{courier.GetTextAndAttachments(msg)},
"id": []string{msg.ID().String()},
"callbackOptions": []string{strconv.Itoa(1)},
}

req, err := http.NewRequest(http.MethodGet, sendURL, strings.NewReader(form.Encode()))
req.Header.Set("Content-Type", "text/html")
req.Header.Set("Accept-Charset", "ISO-8859-1")
rr, err := utils.MakeHTTPRequest(req)

// record our status and log
status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored)
status.AddLog(courier.NewChannelLogFromRR(msg.Channel(), msg.ID(), rr))
if err != nil {
return status, err
}

// was this request successful?
msgStatus, err := strconv.ParseInt(rr.Response, 10, 64)
if err != nil {
return status, err
}

if msgStatus != 0 {
msgStatusText := strconv.Itoa(123)
return status, errors.Errorf("received non-zero response from Zenvia '%s'", msgStatusText)
}

return status, nil

}
36 changes: 36 additions & 0 deletions handlers/zenvia/zenvia_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package zenvia

import (
"testing"
"time"

"github.com/nyaruka/courier"
. "github.com/nyaruka/courier/handlers"
)

var testChannels = []courier.Channel{
courier.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "ZV", "2020", "BR", map[string]interface{}{"account": "zv_account", "code": "zv-code"}),
}

var (
receiveURL = "/c/zv/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive/"
statusURL = "/c/zv/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status/"

emptyReceive = "empty"
validReceive = "msg=Msg&to=21512&id=ec9adc86-51d5-4bc8-8eb0-d8ab0bb53dc3&date=03%2F05%2F2017%2006%3A04%3A45&from=%2B254791541111"
)

var testCases = []ChannelHandleTestCase{
{Label: "Receive Valid", URL: receiveURL, Data: validReceive, Status: 200, Response: "Message Accepted",
Text: Sp("Msg"), URN: Sp("tel:+254791541111"), Date: Tp(time.Date(2017, 5, 3, 06, 04, 45, 0, time.UTC))},

{Label: "Receive Empty", URL: receiveURL, Data: emptyReceive, Status: 400, Response: "field 'id' required"},
}

func TestHandler(t *testing.T) {
RunChannelTestCases(t, testChannels, NewHandler(), testCases)
}

func BenchmarkHandler(b *testing.B) {
RunChannelBenchmarks(b, testChannels, NewHandler(), testCases)
}

0 comments on commit 3c0f77c

Please sign in to comment.