Skip to content

Commit 957b332

Browse files
committed
Add support for Conversations API
1 parent b6c60a2 commit 957b332

21 files changed

+983
-0
lines changed

conversation/api.go

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
package conversation
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"time"
7+
8+
messagebird "github.com/messagebird/go-rest-api"
9+
)
10+
11+
const (
12+
apiRoot = "https://conversations.messagebird.com/v1"
13+
path = "conversations"
14+
messagesPath = "messages"
15+
webhooksPath = "webhooks"
16+
)
17+
18+
type ConversationList struct {
19+
Offset int
20+
Limit int
21+
Count int
22+
TotalCount int
23+
Links struct {
24+
First string
25+
Previous string
26+
Next string
27+
Last string
28+
}
29+
Items []*Conversation
30+
}
31+
32+
type Conversation struct {
33+
ID string
34+
ContactID string
35+
Contact *Contact
36+
LastUsedChannelID string
37+
Channels []*Channel
38+
Messages *MessagesCount
39+
Status ConversationStatus
40+
CreatedDatetime time.Time
41+
UpdatedDatetime time.Time
42+
LastReceivedDatetime time.Time
43+
}
44+
45+
type Contact struct {
46+
ID string
47+
Href string
48+
MSISDN string
49+
FirstName string
50+
LastName string
51+
CustomDetails map[string]interface{}
52+
CreatedAt time.Time
53+
UpdatedAt time.Time
54+
}
55+
56+
type Channel struct {
57+
ID string
58+
Name string
59+
PlatformID string
60+
Status string
61+
CreatedDatetime time.Time
62+
UpdatedDatetime time.Time
63+
}
64+
65+
type MessagesCount struct {
66+
HRef string
67+
TotalCount int
68+
}
69+
70+
// ConversationStatus indicates what state a Conversation is in.
71+
type ConversationStatus string
72+
73+
const (
74+
ConversationStatusActive ConversationStatus = "active"
75+
ConversationStatusArchived ConversationStatus = "archived"
76+
)
77+
78+
type MessageList struct {
79+
Offset int
80+
Limit int
81+
Count int
82+
TotalCount int
83+
Links struct {
84+
First string
85+
Previous string
86+
Next string
87+
Last string
88+
}
89+
Items []*Message
90+
}
91+
92+
type Message struct {
93+
ID string
94+
ConversationID string
95+
ChannelID string
96+
Direction MessageDirection
97+
Status MessageStatus
98+
Type MessageType
99+
Content MessageContent
100+
CreatedDatetime time.Time
101+
UpdatedDatetime time.Time
102+
}
103+
104+
type WebhookList struct {
105+
Offset int
106+
Limit int
107+
Count int
108+
TotalCount int
109+
Links struct {
110+
First string
111+
Previous string
112+
Next string
113+
Last string
114+
}
115+
Items []*Webhook
116+
}
117+
118+
type Webhook struct {
119+
ID string
120+
ChannelID string
121+
Events []WebhookEvent
122+
URL string
123+
CreatedDatetime time.Time
124+
UpdatedDatetime time.Time
125+
}
126+
127+
type MessageContent struct {
128+
Audio *Audio `json:"audio,omitempty"`
129+
HSM *HSM `json:"hsm,omitempty"`
130+
File *File `json:"file,omitempty"`
131+
Image *Image `json:"image,omitempty"`
132+
Location *Location `json:"location,omitempty"`
133+
Video *Video `json:"video,omitempty"`
134+
Text string `json:"text,omitempty"`
135+
}
136+
137+
type Media struct {
138+
URL string `json:"url"`
139+
}
140+
141+
type Audio Media
142+
type File Media
143+
type Image Media
144+
type Video Media
145+
146+
type HSM struct {
147+
Namespace string `json:"namespace"`
148+
TemplateName string `json:"templateName"`
149+
Language *HSMLanguage `json:"language"`
150+
LocalizableParameters []*HSMLocalizableParameter `json:"params"`
151+
}
152+
153+
type HSMLanguage struct {
154+
Policy HSMLanguagePolicy `json:"policy"`
155+
Code string `json:"code"`
156+
}
157+
158+
type HSMLanguagePolicy string
159+
160+
type HSMLocalizableParameter struct {
161+
Default string `json:"default"`
162+
Currency *HSMLocalizableParameterCurrency `json:"currency,omitempty"`
163+
DateTime *time.Time `json:"dateTime,omitempty"`
164+
}
165+
166+
type HSMLocalizableParameterCurrency struct {
167+
Code string `json:"currencyCode"`
168+
Amount int64 `json:"amount"`
169+
}
170+
171+
const (
172+
HSMLanguagePolicyFallback HSMLanguagePolicy = "fallback"
173+
HSMLanguagePolicyDeterministic HSMLanguagePolicy = "deterministic"
174+
)
175+
176+
type Location struct {
177+
Latitude float32 `json:"latitude"`
178+
Longitude float32 `json:"longitude"`
179+
}
180+
181+
// MessageType indicates what kind of content a Message has, e.g. audio or
182+
// text.
183+
type MessageType string
184+
185+
const (
186+
MessageTypeAudio MessageType = "audio"
187+
MessageTypeFile MessageType = "file"
188+
MessageTypeHSM MessageType = "hsm"
189+
MessageTypeImage MessageType = "image"
190+
MessageTypeLocation MessageType = "location"
191+
MessageTypeText MessageType = "text"
192+
MessageTypeVideo MessageType = "video"
193+
)
194+
195+
type MessageDirection string
196+
197+
const (
198+
// MessageDirectionReceived indicates an inbound message received from the customer.
199+
MessageDirectionReceived MessageDirection = "received"
200+
201+
// MessageDirectionSent indicates an outbound message sent from the API.
202+
MessageDirectionSent MessageDirection = "sent"
203+
)
204+
205+
type MessageStatus string
206+
207+
const (
208+
MessageStatusDeleted MessageStatus = "deleted"
209+
MessageStatusDelivered MessageStatus = "delivered"
210+
MessageStatusFailed MessageStatus = "failed"
211+
MessageStatusPending MessageStatus = "pending"
212+
MessageStatusRead MessageStatus = "read"
213+
MessageStatusReceived MessageStatus = "received"
214+
MessageStatusSent MessageStatus = "sent"
215+
MessageStatusUnsupported MessageStatus = "unsupported"
216+
)
217+
218+
type WebhookEvent string
219+
220+
const (
221+
WebhookEventConversationCreated WebhookEvent = "conversation.created"
222+
WebhookEventConversationUpdated WebhookEvent = "conversation.updated"
223+
WebhookEventMessageCreated WebhookEvent = "message.created"
224+
WebhookEventMessageUpdated WebhookEvent = "message.updated"
225+
)
226+
227+
// request does the exact same thign as Client.Request. It does, however,
228+
// prefix the path with the Conversation API's root. This ensures the client
229+
// doesn't "handle" this for us: by default, it uses the REST API.
230+
func request(c *messagebird.Client, v interface{}, method, path string, data interface{}) error {
231+
return c.Request(v, method, fmt.Sprintf("%s/%s", apiRoot, path), data)
232+
}
233+
234+
// UnmarshalJSON is used to unmarshal the MSISDN to a string rather than an
235+
// int64. The API returns integers, but this client always uses strings.
236+
// Exposing a json.Number doesn't seem nice.
237+
func (c *Contact) UnmarshalJSON(data []byte) error {
238+
target := struct {
239+
ID string
240+
Href string
241+
MSISDN json.Number
242+
FirstName string
243+
LastName string
244+
CustomDetails map[string]interface{}
245+
CreatedAt time.Time
246+
UpdatedAt time.Time
247+
}{}
248+
249+
if err := json.Unmarshal(data, &target); err != nil {
250+
return err
251+
}
252+
253+
// In many cases, the CustomDetails will contain the user ID. As
254+
// CustomDetails has interface{} values, these are unmarshalled as floats.
255+
// Convert them to int64.
256+
// Map key is not a typo: API returns userId and not userID.
257+
if val, ok := target.CustomDetails["userId"]; ok {
258+
var userID float64
259+
if userID, ok = val.(float64); ok {
260+
target.CustomDetails["userId"] = int64(userID)
261+
}
262+
}
263+
264+
*c = Contact{
265+
target.ID,
266+
target.Href,
267+
target.MSISDN.String(),
268+
target.FirstName,
269+
target.LastName,
270+
target.CustomDetails,
271+
target.CreatedAt,
272+
target.UpdatedAt,
273+
}
274+
275+
return nil
276+
}

conversation/conversation.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package conversation
2+
3+
import (
4+
"net/http"
5+
6+
messagebird "github.com/messagebird/go-rest-api"
7+
)
8+
9+
// ListOptions can be used to set pagination options in List().
10+
type ListOptions struct {
11+
Limit, Offset int
12+
}
13+
14+
// StartRequest contains the request data for the Start endpoint.
15+
type StartRequest struct {
16+
ChannelID string `json:"channelId"`
17+
Content *MessageContent `json:"content"`
18+
To string `json:"to"`
19+
Type MessageType `json:"type"`
20+
}
21+
22+
// UpdateRequest contains the request data for the Update endpoint.
23+
type UpdateRequest struct {
24+
Status ConversationStatus `json:"status"`
25+
}
26+
27+
// DefaultListOptions provide a reasonable default for paginated endpoints.
28+
var DefaultListOptions = &ListOptions{10, 0}
29+
30+
// List gets a collection of Conversations. Pagination can be set in options.
31+
func List(c *messagebird.Client, options *ListOptions) (*ConversationList, error) {
32+
convList := &ConversationList{}
33+
if err := request(c, convList, http.MethodGet, path, options); err != nil {
34+
return nil, err
35+
}
36+
37+
return convList, nil
38+
}
39+
40+
// Read fetches a single Conversation based on its ID.
41+
func Read(c *messagebird.Client, id string) (*Conversation, error) {
42+
conv := &Conversation{}
43+
if err := request(c, conv, http.MethodGet, path+"/"+id, nil); err != nil {
44+
return nil, err
45+
}
46+
47+
return conv, nil
48+
}
49+
50+
// Start creates a conversation by sending an initial message. If an active
51+
// conversation exists for the recipient, it is resumed.
52+
func Start(c *messagebird.Client, req *StartRequest) (*Conversation, error) {
53+
conv := &Conversation{}
54+
if err := request(c, conv, http.MethodPost, path+"/start", req); err != nil {
55+
return nil, err
56+
}
57+
58+
return conv, nil
59+
}
60+
61+
// Update changes the conversation's status, so this can be used to (un)archive
62+
// conversations.
63+
func Update(c *messagebird.Client, id string, req *UpdateRequest) (*Conversation, error) {
64+
conv := &Conversation{}
65+
if err := request(c, conv, http.MethodPatch, path+"/"+id, req); err != nil {
66+
return nil, err
67+
}
68+
69+
return conv, nil
70+
}

0 commit comments

Comments
 (0)