Skip to content

Commit

Permalink
fix: implement template message component (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
sarthakjdev authored Nov 16, 2024
1 parent 67e4295 commit e75734f
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The first step to build a Wapi.go based chat bot is to create a new project. You

```go
go mod init <project-name>
go get github.com/wapichat/wapi.go
go get github.com/sarthakjdev/wapi.go
```

### Other use cases
Expand Down
12 changes: 6 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ module github.com/sarthakjdev/wapi.go
go 1.21.3

require (
github.com/go-playground/validator/v10 v10.20.0
github.com/go-playground/validator/v10 v10.22.1
github.com/labstack/echo/v4 v4.12.0
)

require (
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gabriel-vasile/mimetype v1.4.6 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/labstack/gommon v0.4.2 // indirect
Expand All @@ -17,8 +17,8 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/crypto v0.29.0 // indirect
golang.org/x/net v0.31.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/text v0.20.0 // indirect
)
24 changes: 12 additions & 12 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
Expand All @@ -29,15 +29,15 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
222 changes: 222 additions & 0 deletions pkg/components/template_message.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,141 @@
package components

import (
"encoding/json"
"fmt"

"github.com/sarthakjdev/wapi.go/internal"
)

type TemplateMessageComponentType string

const (
TemplateMessageComponentTypeHeader TemplateMessageComponentType = "header"
TemplateMessageComponentTypeBody TemplateMessageComponentType = "body"
TemplateMessageComponentTypeButton TemplateMessageComponentType = "button"
)

type TemplateMessageButtonComponentType string

const (
TemplateMessageButtonComponentTypeQuickReply TemplateMessageButtonComponentType = "quick_reply"
TemplateMessageButtonComponentTypeUrl TemplateMessageButtonComponentType = "url"
TemplateMessageButtonComponentTypeCatalog TemplateMessageButtonComponentType = "catalog"
)

type TemplateMessageComponent interface {
GetComponentType() string
}

type TemplateMessageComponentButtonType struct {
Type TemplateMessageComponentType `json:"type" validate:"required"` // Type of the template message component.
SubType TemplateMessageButtonComponentType `json:"sub_type" validate:"required"` // Subtype of the template message component.
Index int `json:"index" validate:"required"` // Index of the template message component.
Parameters []TemplateMessageParameter `json:"parameters" validate:"required"` // Parameters of the template message component.
}

func (t TemplateMessageComponentButtonType) GetComponentType() string {
return string(t.Type)
}

type TemplateMessageComponentHeaderType struct {
Type TemplateMessageComponentType `json:"type" validate:"required"` // Type of the template message component.
Parameters []TemplateMessageParameter `json:"parameters" validate:"required"` // Parameters of the template message component.
}

func (t TemplateMessageComponentHeaderType) GetComponentType() string {
return string(t.Type)
}

type TemplateMessageComponentBodyType struct {
Type TemplateMessageComponentType `json:"type" validate:"required"` // Type of the template message component.
Parameters []TemplateMessageParameter `json:"parameters" validate:"required"` // Parameters of the template message component.
}

func (t TemplateMessageComponentBodyType) GetComponentType() string {
return string(t.Type)
}

type TemplateMessageParameterType string

const (
TemplateMessageParameterTypeCurrency TemplateMessageParameterType = "currency"
TemplateMessageParameterTypeDateTime TemplateMessageParameterType = "date_time"
TemplateMessageParameterTypeDocument TemplateMessageParameterType = "document"
TemplateMessageParameterTypeImage TemplateMessageParameterType = "image"
TemplateMessageParameterTypeText TemplateMessageParameterType = "text"
TemplateMessageParameterTypeVideo TemplateMessageParameterType = "video"
)

type TemplateMessageParameterCurrency struct {
FallbackValue string `json:"fallback_value" validate:"required"` // Fallback value of the currency parameter.
Code string `json:"code" validate:"required"` // Code of the currency parameter.
Amount1000 int `json:"amount_1000" validate:"required"` // Amount of the currency parameter.
}

type TemplateMessageParameterDateTime struct {
FallbackValue string `json:"fallback_value" validate:"required"` // Fallback value of the date time parameter.
}

type TemplateMessageParameterDocument struct {
Document interface{} `json:"document" validate:"required"` // Document of the document parameter.
}

type TemplateMessageParameterImage struct {
Image interface{} `json:"image" validate:"required"` // Image of the image parameter.
}

type TemplateMessageParameterText struct {
Text string `json:"text" validate:"required"` // Text of the text parameter.
}

type TemplateMessageParameterVideo struct {
Video interface{} `json:"video" validate:"required"` // Video of the video parameter.
}

type TemplateMessageParameter interface {
GetParameterType() string
}

type TemplateMessageBodyAndHeaderParameter struct {
Type TemplateMessageParameterType `json:"type" validate:"required"` // Type of the template message parameter.
Currency *TemplateMessageParameterCurrency `json:"currency,omitempty"` // Currency of the template message parameter.
DateTime *TemplateMessageParameterDateTime `json:"date_time,omitempty"` // Date time of the template message parameter.
Document *TemplateMessageParameterDocument `json:"document,omitempty"` // Document of the template message parameter.
Image *TemplateMessageParameterImage `json:"image,omitempty"` // Image of the template message parameter.
Text *TemplateMessageParameterText `json:"text,omitempty"` // Text of the template message parameter.
Video *TemplateMessageParameterVideo `json:"video,omitempty"` // Video of the template message parameter.
}

func (t TemplateMessageBodyAndHeaderParameter) GetParameterType() string {
return string(t.Type)
}

type TemplateMessageButtonParameterType string

const (
TemplateMessageButtonParameterTypePayload TemplateMessageButtonParameterType = "payload"
TemplateMessageButtonParameterTypeText TemplateMessageButtonParameterType = "text"
)

type TemplateMessageButtonParameter struct {
Type TemplateMessageButtonParameterType `json:"type" validate:"required"` // Type of the template message parameter.
Payload string `json:"payload,omitempty"` // Payload of the template message parameter, required for type of button sub_type -> quick_reply
Text string `json:"text,omitempty"` // Text of the template message parameter, required for type of button sub_type -> URL.
}

func (t TemplateMessageButtonParameter) GetParameterType() string {
return string(t.Type)
}

// TemplateMessage represents a template message.
type TemplateMessage struct {
Name string `json:"name" validate:"required"` // Name of the template message.
Language struct {
Code string `json:"code" validate:"required"`
Policy string `json:"policy" validate:"required"` // default is "deterministic"
} `json:"language" validate:"required"`
Components []TemplateMessageComponent `json:"components" validate:"required"` // Components of the template message.
}

// TemplateMessageApiPayload represents the API payload for a template message.
Expand All @@ -14,3 +148,91 @@ type TemplateMessageApiPayload struct {
func NewTemplateMessage() (*TemplateMessage, error) {
return &TemplateMessage{}, nil
}

// AddHeader adds a header to the template message.
// Template Messages in whatsapp can only have one header, so this function will override the last header added if any.
func (tm *TemplateMessage) AddHeader(params TemplateMessageComponentHeaderType) {
var existingHeaderIndex int
var found bool

for i, component := range tm.Components {
if TemplateMessageComponentType(component.GetComponentType()) == TemplateMessageComponentTypeHeader {
existingHeaderIndex = i
found = true
break
}
}

if found {
// Override the existing header.
tm.Components[existingHeaderIndex] = params
} else {
// Add the new header if no existing header is found.
tm.Components = append(tm.Components, params)
}
}

// AddBody adds a body to the template message.
// Template Messages in whatsapp can only have one body component, so this function will override the last body added if any.
func (tm *TemplateMessage) AddBody(params TemplateMessageComponentBodyType) {
var existingBodyIndex int
var found bool

for i, component := range tm.Components {
if TemplateMessageComponentType(component.GetComponentType()) == TemplateMessageComponentTypeBody {
existingBodyIndex = i
found = true
break
}
}

if found {
// Override the existing body.
tm.Components[existingBodyIndex] = params
} else {
// Add the new body if no existing body is found.
tm.Components = append(tm.Components, params)
}
}

func (tm *TemplateMessage) AddButton(params TemplateMessageComponentButtonType) error {
// at max 10 buttons can be added to a template message
numberOfButtons := 0
for _, component := range tm.Components {
if TemplateMessageComponentType(component.GetComponentType()) == TemplateMessageComponentTypeButton {
numberOfButtons++
}
}

if numberOfButtons >= 10 {
return fmt.Errorf("maximum number of buttons reached")
}
tm.Components = append(tm.Components, params)
return nil
}

// ToJson converts the template message to JSON.
func (m *TemplateMessage) ToJson(configs ApiCompatibleJsonConverterConfigs) ([]byte, error) {
if err := internal.GetValidator().Struct(configs); err != nil {
return nil, fmt.Errorf("error validating configs: %v", err)
}

jsonData := TemplateMessageApiPayload{
BaseMessagePayload: NewBaseMessagePayload(configs.SendToPhoneNumber, MessageTypeTemplate),
Template: *m,
}

if configs.ReplyToMessageId != "" {
jsonData.Context = &Context{
MessageId: configs.ReplyToMessageId,
}
}

jsonToReturn, err := json.Marshal(jsonData)

if err != nil {
return nil, fmt.Errorf("error marshalling json: %v", err)
}

return jsonToReturn, nil
}

0 comments on commit e75734f

Please sign in to comment.