forked from njern/gonexmo
-
Notifications
You must be signed in to change notification settings - Fork 1
/
sms.go
329 lines (286 loc) · 10.5 KB
/
sms.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
package nexmo
import (
"bytes"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
)
// SMS represents the SMS API functions for sending text messages.
type SMS struct {
client *Client
}
// SMS message types.
const (
Text = "text"
Binary = "binary"
WAPPush = "wappush"
Unicode = "unicode"
VCal = "vcal"
VCard = "vcard"
)
// MessageClass will be one of the following:
// - Flash
// - Standard
// - SIMData
// - Forward
type MessageClass int
// SMS message classes.
const (
// This type of SMS message is displayed on the mobile screen without being
// saved in the message store or on the SIM card; unless explicitly saved
// by the mobile user.
Flash MessageClass = iota
// This message is to be stored in the device memory or the SIM card
// (depending on memory availability).
Standard
// This message class carries SIM card data. The SIM card data must be
// successfully transferred prior to sending acknowledgment to the service
// center. An error message is sent to the service center if this
// transmission is not possible.
SIMData
// This message is forwarded from the receiving entity to an external
// device. The delivery acknowledgment is sent to the service center
// regardless of whether or not the message was forwarded to the external
// device.
Forward
)
var messageClassMap = map[MessageClass]string{
Flash: "flash",
Standard: "standard",
SIMData: "SIM data",
Forward: "forward",
}
func (m MessageClass) String() string {
return messageClassMap[m]
}
// MarshalJSON implements the json.Marshaller interface
func (m *SMSMessage) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
APIKey string `json:"api_key"`
APISecret string `json:"api_secret"`
SMSMessage
}{
APIKey: m.apiKey,
APISecret: m.apiSecret,
SMSMessage: *m,
})
}
// SMSMessage defines a single SMS message.
type SMSMessage struct {
apiKey string
apiSecret string
From string `json:"from"`
To string `json:"to"`
Type string `json:"type"`
Text string `json:"text,omitempty"` // Optional.
StatusReportRequired int `json:"status-report-req,omitempty"` // Optional.
ClientReference string `json:"client-ref,omitempty"` // Optional.
NetworkCode string `json:"network-code,omitempty"` // Optional.
VCard string `json:"vcrad,omitempty"` // Optional.
VCal string `json:"vcal,omitempty"` // Optional.
TTL int `json:"ttl,omitempty"` // Optional.
Class MessageClass `json:"message-class,omitempty"` // Optional.
Callback string `json:"callback,omitempty"` // Optional.
Body []byte `json:"body,omitempty"` // Required for Binary message.
UDH []byte `json:"udh,omitempty"` // Required for Binary message.
// The following is only for type=wappush
Title string `json:"title,omitempty"` // Title shown to recipient
URL string `json:"url,omitempty"` // WAP Push URL
Validity int `json:"validity,omitempty"` // Duration WAP Push is available in milliseconds
}
// MessageStatusResponse defines information about a single message that was sent using SMS API.
// See https://developer.nexmo.com/api/developer/messages for details.
type MessageStatusResponse struct {
MessageId string `json:"message-id,omitempty"`
AccountId string `json:"account-id,omitempty"`
Network string `json:"network,omitempty"`
From string `json:"from,omitempty"`
To string `json:"to,omitempty"`
Body string `json:"body,omitempty"`
Price string `json:"price,omitempty"`
DateReceived string `json:"date-received,omitempty"`
Status string `json:"status,omitempty"`
FinalStatus string `json:"final-status,omitempty"`
DateClosed string `json:"date-closed,omitempty"`
Latency int `json:"latency,omitempty"`
Type string `json:"type,omitempty"`
ClientRef string `json:"client-ref,omitempty"`
ErrorCode string `json:"error-code,omitempty"`
ErrorCodeLabel string `json:"error-code-label,omitempty"`
RawBody []byte
HttpStatusCode int
}
// MessageStatusRequest request for search message status
// See https://developer.nexmo.com/api/developer/messages for details.
type MessageStatusRequest struct {
apiKey string
apiSecret string
ID string `json:"id"`
}
// A ResponseCode will be returned
// whenever an SMSMessage is sent.
type ResponseCode int
// String implements the fmt.Stringer interface
func (c ResponseCode) String() string {
return responseCodeMap[c]
}
// Possible response codes
// https://developer.nexmo.com/api-errors/sms updated at 2019-08-01
const (
ResponseSuccess ResponseCode = 0
ResponseThrottled ResponseCode = 1
ResponseMissingParams ResponseCode = 2
ResponseInvalidParams ResponseCode = 3
ResponseInvalidCredentials ResponseCode = 4
ResponseInternalError ResponseCode = 5
ResponseInvalidMessage ResponseCode = 6
ResponseNumberBarred ResponseCode = 7
ResponsePartnerAcctBarred ResponseCode = 8
ResponsePartnerQuotaViolation ResponseCode = 9
ResponseTooManyExistBinds ResponseCode = 10
ResponseRESTNotEnabled ResponseCode = 11
ResponseMessageTooLong ResponseCode = 12
ResponseInvalidSignature ResponseCode = 14
ResponseInvalidSenderAddress ResponseCode = 15
ResponseInvalidNetworkCode ResponseCode = 22
ResponseInvalidCallbackUrl ResponseCode = 23
ResponseNonWhitelisted ResponseCode = 29
ResponseSignatureAPIDisallowed ResponseCode = 32
ResponseNumberDeactivated ResponseCode = 33
)
var responseCodeMap = map[ResponseCode]string{
ResponseSuccess: "Success",
ResponseThrottled: "Throttled",
ResponseMissingParams: "Missing params",
ResponseInvalidParams: "Invalid params",
ResponseInvalidCredentials: "Invalid credentials",
ResponseInternalError: "Internal error",
ResponseInvalidMessage: "Invalid message",
ResponseNumberBarred: "Number barred",
ResponsePartnerAcctBarred: "Partner account barred",
ResponsePartnerQuotaViolation: "Partner quota violation",
ResponseTooManyExistBinds: "Too many exist binds",
ResponseRESTNotEnabled: "Account not enabled for REST",
ResponseMessageTooLong: "Message too long",
ResponseInvalidSignature: "Invalid signature",
ResponseInvalidSenderAddress: "Invalid sender address",
ResponseInvalidNetworkCode: "Invalid network code",
ResponseInvalidCallbackUrl: "Invalid callback url",
ResponseNonWhitelisted: "Non-whitelisted destination",
ResponseSignatureAPIDisallowed: "Signature and api secret disallowed",
ResponseNumberDeactivated: "Number De-activated",
}
// MessageReport is the "status report" for a single SMS sent via the Nexmo API
type MessageReport struct {
Status ResponseCode `json:"status,string"`
MessageID string `json:"message-id"`
To string `json:"to"`
ClientReference string `json:"client-ref"`
RemainingBalance string `json:"remaining-balance"`
MessagePrice string `json:"message-price"`
Network string `json:"network"`
ErrorText string `json:"error-text"`
}
// MessageResponse contains the response from Nexmo's API after we attempt to
// send any kind of message.
// It will contain one MessageReport for every 160 chars sent.
type MessageResponse struct {
MessageCount int `json:"message-count,string"`
Messages []MessageReport `json:"messages"`
}
// Send the message using the specified SMS client.
func (c *SMS) Send(msg *SMSMessage) (*MessageResponse, error) {
if len(msg.From) <= 0 {
return nil, errors.New("Invalid From field specified")
}
if len(msg.To) <= 0 {
return nil, errors.New("Invalid To field specified")
}
if len(msg.ClientReference) > 40 {
return nil, errors.New("Client reference too long")
}
var messageResponse *MessageResponse
switch msg.Type {
case Text:
case Unicode:
if len(msg.Text) <= 0 {
return nil, errors.New("Invalid message text")
}
case Binary:
if len(msg.UDH) == 0 || len(msg.Body) == 0 {
return nil, errors.New("Invalid binary message")
}
case WAPPush:
if len(msg.URL) == 0 || len(msg.Title) == 0 {
return nil, errors.New("Invalid WAP Push parameters")
}
}
if !c.client.useOauth {
msg.apiKey = c.client.apiKey
msg.apiSecret = c.client.apiSecret
}
var r *http.Request
buf, err := json.Marshal(msg)
if err != nil {
return nil, errors.New("invalid message struct - unable to convert to JSON")
}
b := bytes.NewBuffer(buf)
r, _ = http.NewRequest("POST", apiRoot+"/sms/json", b)
r.Header.Add("Accept", "application/json")
r.Header.Add("Content-Type", "application/json")
resp, err := c.client.HTTPClient.Do(r)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
err = json.Unmarshal(body, &messageResponse)
if err != nil {
return nil, err
}
return messageResponse, nil
}
// MessageStatus retrieves information about a single message that was sent using SMS API or that was received on your number.
// See https://developer.nexmo.com/api/developer/messages for details.
func (c *SMS) MessageStatus(msg *MessageStatusRequest) (*MessageStatusResponse, error) {
if len(msg.ID) == 0 {
return nil, errors.New("ID should not be empty")
}
if !c.client.useOauth {
msg.apiKey = c.client.apiKey
msg.apiSecret = c.client.apiSecret
}
var (
r *http.Request
err error
)
r, err = http.NewRequest("GET", apiRoot+"/search/message", nil)
if err != nil {
return nil, err
}
q := r.URL.Query()
q.Add("api_key", msg.apiKey)
q.Add("api_secret", msg.apiSecret)
q.Add("id", msg.ID)
r.URL.RawQuery = q.Encode()
r.Header.Add("Accept", "application/json")
var resp *http.Response
if resp, err = c.client.HTTPClient.Do(r); err != nil {
return nil, err
}
defer resp.Body.Close()
var body []byte
if body, err = ioutil.ReadAll(resp.Body); err != nil {
return nil, err
}
var messageStatusResponse = &MessageStatusResponse{
RawBody: body,
HttpStatusCode: resp.StatusCode,
}
err = json.Unmarshal(body, &messageStatusResponse)
if err != nil {
return messageStatusResponse, err
}
return messageStatusResponse, nil
}