Skip to content

Commit 2a3d363

Browse files
feat: add twiml support (#186)
Co-authored-by: cchua <[email protected]>
1 parent dfeee1f commit 2a3d363

File tree

6 files changed

+1956
-4
lines changed

6 files changed

+1956
-4
lines changed

README.md

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func main() {
6969
}
7070
```
7171

72-
If you don't want to use environment variables, you can also pass the credentials directly to the constructor as below.
72+
If you don't want to use environment variables, you can also pass the credentials directly to the constructor as below.
7373

7474
```go
7575
package main
@@ -427,6 +427,56 @@ func main() {
427427
For more descriptive exception types, please see
428428
the [Twilio documentation](https://www.twilio.com/docs/libraries/go/usage-guide#exceptions).
429429

430+
### Generating TwiML
431+
432+
To control phone calls, your application needs to output [TwiML](https://www.twilio.com/docs/voice/twiml).
433+
434+
Use the `twiml` package to easily create such responses.
435+
436+
```go
437+
package main
438+
439+
import (
440+
"fmt"
441+
"github.com/twilio/twilio-go/twiml"
442+
)
443+
444+
func main() {
445+
//Construct Verbs
446+
dial := &twiml.VoiceDial{}
447+
say := &twiml.VoiceSay{
448+
Message: "Welcome to Twilio!",
449+
Voice: "woman",
450+
Language: "en-gb",
451+
OptionalAttributes: map[string]string{"input": "test"},
452+
}
453+
pause := &twiml.VoicePause{
454+
Length: "10",
455+
}
456+
//Construct Noun
457+
queue := &twiml.VoiceQueue{
458+
Url: "www.twilio.com",
459+
}
460+
//Adding Queue to Dial
461+
dial.Nouns = []twiml.Element{queue}
462+
463+
//Adding all Verbs to twiml.Voice
464+
verbList := []twiml.Element{dial, say, pause}
465+
twimlResult := twiml.Voice(verbList)
466+
fmt.Println(twimlResult)
467+
}
468+
```
469+
This will print the following:
470+
```xml
471+
<?xml version="1.0" encoding="UTF-8"?>
472+
<Response>
473+
<Dial>
474+
<Queue url="www.twilio.com"/>
475+
</Dial>
476+
<Say voice="woman" language="en-gb" input="test">Welcome to Twilio!</Say>
477+
<Pause length="10"/>
478+
</Response>
479+
```
430480
## Advanced Usage
431481

432482
### Using Request Validator
@@ -555,7 +605,7 @@ func main() {
555605
```
556606

557607
## Building Access Tokens
558-
This library supports [access token](https://www.twilio.com/docs/iam/access-tokens) generation for use in the Twilio Client SDKs.
608+
This library supports [access token](https://www.twilio.com/docs/iam/access-tokens) generation for use in the Twilio Client SDKs.
559609

560610
Here's how you would generate a token for the Voice SDK:
561611
```go
@@ -616,7 +666,6 @@ capabilityToken := taskrouter.CreateCapabilityToken(Params)
616666
token, err := capabilityToken.ToJwt()
617667
```
618668

619-
620669
## Local Usage
621670

622671
### Building

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ module github.com/twilio/twilio-go
33
go 1.16
44

55
require (
6+
github.com/beevik/etree v1.1.0
67
github.com/davecgh/go-spew v1.1.1 // indirect
78
github.com/golang-jwt/jwt v3.2.2+incompatible
89
github.com/golang/mock v1.6.0
910
github.com/kr/text v0.2.0 // indirect
10-
github.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275 // indirect
11+
github.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275
1112
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
1213
github.com/pkg/errors v0.9.1
1314
github.com/stretchr/testify v1.7.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
2+
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
13
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
24
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
35
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=

twiml/messaging_response.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
//This code was generated by
2+
//\ / _ _ _| _ _
3+
// | (_)\/(_)(_|\/| |(/_ v1.0.0
4+
// / /
5+
6+
package twiml
7+
8+
func Messages(verbs []Element)(TwimlXml string){
9+
doc, response := CreateDocument()
10+
if verbs != nil {
11+
AddAllVerbs(response, verbs)
12+
}
13+
return ToXML(doc)
14+
}
15+
16+
//MessagingRedirect <Redirect> TwiML Verb
17+
type MessagingRedirect struct {
18+
// url: Redirect URL
19+
// method: Redirect URL method
20+
// OptionalAttributes: additional attributes
21+
Url string
22+
Method string
23+
Nouns []Element
24+
OptionalAttributes map[string]string
25+
}
26+
27+
func (m MessagingRedirect) GetName() string {
28+
return "Redirect"
29+
}
30+
31+
func (m MessagingRedirect) GetText() string {
32+
return m.Url
33+
}
34+
35+
func (m MessagingRedirect) GetAttr() (map[string]string, map[string]string) {
36+
paramsAttr := map[string]string{
37+
"Method": m.Method,
38+
}
39+
return m.OptionalAttributes, paramsAttr
40+
}
41+
42+
func (m MessagingRedirect) GetNouns() []Element {
43+
return m.Nouns
44+
}
45+
//MessagingMessage <Message> TwiML Verb
46+
type MessagingMessage struct {
47+
// body: Message Body
48+
// to: Phone Number to send Message to
49+
// from: Phone Number to send Message from
50+
// action: Action URL
51+
// method: Action URL Method
52+
// status_callback: Status callback URL. Deprecated in favor of action.
53+
// OptionalAttributes: additional attributes
54+
Body string
55+
To string
56+
From string
57+
Action string
58+
Method string
59+
StatusCallback string
60+
Nouns []Element
61+
OptionalAttributes map[string]string
62+
}
63+
64+
func (m MessagingMessage) GetName() string {
65+
return "Message"
66+
}
67+
68+
func (m MessagingMessage) GetText() string {
69+
return m.Body
70+
}
71+
72+
func (m MessagingMessage) GetAttr() (map[string]string, map[string]string) {
73+
paramsAttr := map[string]string{
74+
"To": m.To,
75+
"From": m.From,
76+
"Action": m.Action,
77+
"Method": m.Method,
78+
"StatusCallback": m.StatusCallback,
79+
}
80+
return m.OptionalAttributes, paramsAttr
81+
}
82+
83+
func (m MessagingMessage) GetNouns() []Element {
84+
return m.Nouns
85+
}
86+
//MessagingMedia <Media> TwiML Noun
87+
type MessagingMedia struct {
88+
// url: Media URL
89+
// OptionalAttributes: additional attributes
90+
Url string
91+
Nouns []Element
92+
OptionalAttributes map[string]string
93+
}
94+
95+
func (m MessagingMedia) GetName() string {
96+
return "Media"
97+
}
98+
99+
func (m MessagingMedia) GetText() string {
100+
return m.Url
101+
}
102+
103+
func (m MessagingMedia) GetAttr() (map[string]string, map[string]string) {
104+
return m.OptionalAttributes, nil
105+
}
106+
107+
func (m MessagingMedia) GetNouns() []Element {
108+
return m.Nouns
109+
}
110+
//MessagingBody <Body> TwiML Noun
111+
type MessagingBody struct {
112+
// message: Message Body
113+
// OptionalAttributes: additional attributes
114+
Message string
115+
Nouns []Element
116+
OptionalAttributes map[string]string
117+
}
118+
119+
func (m MessagingBody) GetName() string {
120+
return "Body"
121+
}
122+
123+
func (m MessagingBody) GetText() string {
124+
return m.Message
125+
}
126+
127+
func (m MessagingBody) GetAttr() (map[string]string, map[string]string) {
128+
return m.OptionalAttributes, nil
129+
}
130+
131+
func (m MessagingBody) GetNouns() []Element {
132+
return m.Nouns
133+
}

twiml/twiml.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package twiml
2+
3+
import (
4+
"github.com/beevik/etree"
5+
"strings"
6+
)
7+
8+
type Element interface {
9+
GetName() string
10+
GetText() string
11+
GetAttr() (map[string]string, map[string]string)
12+
GetNouns() []Element
13+
}
14+
15+
func AddAllVerbs(response *etree.Element, verbs []Element) {
16+
for _, verb := range verbs {
17+
verbEl := createElement(verb)
18+
response.AddChild(verbEl)
19+
}
20+
}
21+
22+
func createElement(tag Element) *etree.Element {
23+
el := etree.NewElement(tag.GetName())
24+
optAttr, paramAttr := tag.GetAttr()
25+
addPropertyToElement(el, tag.GetText(), optAttr, paramAttr)
26+
//Loop through all Nouns
27+
if len(tag.GetNouns()) != 0 {
28+
for _, noun := range tag.GetNouns() {
29+
child := createElement(noun)
30+
el.AddChild(child)
31+
}
32+
}
33+
return el
34+
}
35+
36+
func CreateDocument() (*etree.Document, *etree.Element) {
37+
doc := etree.NewDocument()
38+
doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
39+
element := doc.CreateElement("Response")
40+
return doc, element
41+
}
42+
43+
func ToXML(document *etree.Document) string {
44+
xml, err := document.WriteToString()
45+
if err == nil {
46+
return xml
47+
}
48+
return err.Error()
49+
}
50+
51+
func addPropertyToElement(treeElement *etree.Element, text string, optAttr map[string]string, paramAttr map[string]string) {
52+
if text != "" {
53+
treeElement.SetText(text)
54+
}
55+
56+
for _, attr := range []map[string]string{paramAttr, optAttr} {
57+
for k, v := range attr {
58+
if v != "" {
59+
treeElement.CreateAttr(formatAttrKey(k), v)
60+
}
61+
}
62+
}
63+
}
64+
65+
func formatAttrKey(s string) string {
66+
return strings.ToLower(string(s[0])) + s[1:]
67+
}

0 commit comments

Comments
 (0)