From a2481df2bba50a708a684503d61ea2513ddc86a4 Mon Sep 17 00:00:00 2001 From: Andrew Querol Date: Wed, 28 Oct 2020 12:14:18 -0500 Subject: [PATCH 1/5] Allow the originate helpers to be called in the foreground. This helps if we need to wait on the a-Leg answer without having to explicitly listen to the background events. Also improve documentation for the helpers and the RawResponse, Event structs. --- event.go | 8 +++++--- helper.go | 18 ++++++++++++++---- response.go | 25 ++++++++++++++++++------- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/event.go b/event.go index 11c2031..7cd901b 100644 --- a/event.go +++ b/event.go @@ -73,22 +73,24 @@ func readJSONEvent(body []byte) (*Event, error) { }, nil } +// GetName Helper function that returns the event name header func (e Event) GetName() string { return e.GetHeader("Event-Name") } +// HasHeader Helper to check if the Event has a header func (e Event) HasHeader(header string) bool { _, ok := e.Headers[textproto.CanonicalMIMEHeaderKey(header)] return ok } -// Helper function that calls e.Header.Get +// GetHeader Helper function that calls e.Header.Get func (e Event) GetHeader(header string) string { value, _ := url.PathUnescape(e.Headers.Get(header)) return value } -// Implement the Stringer interface for pretty printing (%v) +// String Implement the Stringer interface for pretty printing (%v) func (e Event) String() string { var builder strings.Builder builder.WriteString(fmt.Sprintf("%s\n", e.GetName())) @@ -99,7 +101,7 @@ func (e Event) String() string { return builder.String() } -// Implement the GoStringer interface for pretty printing (%#v) +// GoString Implement the GoStringer interface for pretty printing (%#v) func (e Event) GoString() string { return e.String() } diff --git a/helper.go b/helper.go index 7d77a1e..210b0f0 100644 --- a/helper.go +++ b/helper.go @@ -49,7 +49,11 @@ func (c *Conn) DebugOff(id string) { c.RemoveEventListener(EventListenAll, id) } -func (c *Conn) OriginateCall(ctx context.Context, aLeg, bLeg string, vars map[string]string) (string, *RawResponse, error) { +// OriginateCall - Calls the originate function in FreeSWITCH. If you want variables for each leg independently set them in the aLeg and bLeg strings +// Arguments: ctx context.Context for supporting context cancellation, background bool should we wait for the origination to complete +// aLeg, bLeg string The aLeg and bLeg of the call respectively +// vars map[string]string, channel variables to be passed to originate for both legs, contained in {} +func (c *Conn) OriginateCall(ctx context.Context, background bool, aLeg, bLeg string, vars map[string]string) (string, *RawResponse, error) { if vars == nil { vars = make(map[string]string) } @@ -59,7 +63,7 @@ func (c *Conn) OriginateCall(ctx context.Context, aLeg, bLeg string, vars map[st response, err := c.SendCommand(ctx, command.API{ Command: "originate", Arguments: fmt.Sprintf("%s%s %s", BuildVars("{%s}", vars), aLeg, bLeg), - Background: true, + Background: background, }) if err != nil { return vars["origination_uuid"], response, err @@ -67,7 +71,13 @@ func (c *Conn) OriginateCall(ctx context.Context, aLeg, bLeg string, vars map[st return vars["origination_uuid"], response, nil } -func (c *Conn) EnterpriseOriginateCall(ctx context.Context, vars map[string]string, bLeg string, aLegs ...string) (*RawResponse, error) { +// EnterpriseOriginateCall - Calls the originate function in FreeSWITCH using the enterprise method for calling multiple legs ":_:" +// If you want variables for each leg independently set them in the aLeg and bLeg strings +// Arguments: ctx context.Context for supporting context cancellation, background bool should we wait for the origination to complete +// vars map[string]string, channel variables to be passed to originate for both legs, contained in <> +// bLeg string The bLeg of the call +// aLegs ...string variadic argument for each aLeg to call +func (c *Conn) EnterpriseOriginateCall(ctx context.Context, background bool, vars map[string]string, bLeg string, aLegs ...string) (*RawResponse, error) { if len(aLegs) == 0 { return nil, errors.New("no aLeg specified") } @@ -86,7 +96,7 @@ func (c *Conn) EnterpriseOriginateCall(ctx context.Context, vars map[string]stri response, err := c.SendCommand(ctx, command.API{ Command: "originate", Arguments: fmt.Sprintf("%s%s %s", BuildVars("<%s>", vars), aLeg, bLeg), - Background: true, + Background: background, }) if err != nil { return response, err diff --git a/response.go b/response.go index ef3760e..391b468 100644 --- a/response.go +++ b/response.go @@ -29,6 +29,7 @@ const ( TypeDisconnect = `text/disconnect-notice` ) +// RawResponse This struct contains all response data from FreeSWITCH type RawResponse struct { Headers textproto.MIMEHeader Body []byte @@ -58,28 +59,38 @@ func (c *Conn) readResponse() (*RawResponse, error) { return response, nil } -// Helper to check response status, only used for Auth checking at the moment +// IsOk Helper to check response status, uses the Reply-Text header primarily. +// Also will use the body if the Reply-Text header does not exist, this can be the case for TypeAPIResponse func (r RawResponse) IsOk() bool { - return strings.HasPrefix(r.GetHeader("Reply-Text"), "+OK") + if r.HasHeader("Reply-Text") { + strings.HasPrefix(r.GetHeader("Reply-Text"), "+OK") + } + return strings.HasPrefix(string(r.Body), "+OK") } -// Helper to get the channel UUID +// ChannelUUID Helper to get the channel UUID. Calls GetHeader internally func (r RawResponse) ChannelUUID() string { return r.GetHeader("Unique-ID") } -// Helper function to get "Variable_" headers +// HasHeader Helper to check if the RawResponse has a header +func (r RawResponse) HasHeader(header string) bool { + _, ok := r.Headers[textproto.CanonicalMIMEHeaderKey(header)] + return ok +} + +// GetVariable Helper function to get "Variable_" headers. Calls GetHeader internally func (r RawResponse) GetVariable(variable string) string { return r.GetHeader(fmt.Sprintf("Variable_%s", variable)) } -// Helper function that calls r.Header.Get +// GetHeader Helper function that calls RawResponse.Headers.Get. Result gets passed through url.PathUnescape func (r RawResponse) GetHeader(header string) string { value, _ := url.PathUnescape(r.Headers.Get(header)) return value } -// Implement the Stringer interface for pretty printing +// String Implement the Stringer interface for pretty printing func (r RawResponse) String() string { var builder strings.Builder for key, values := range r.Headers { @@ -89,7 +100,7 @@ func (r RawResponse) String() string { return builder.String() } -// Implement the GoStringer interface for pretty printing (%#v) +// GoString Implement the GoStringer interface for pretty printing (%#v) func (r RawResponse) GoString() string { return r.String() } From e52eb465c82e53eebf510c6e476bc5597f985dc6 Mon Sep 17 00:00:00 2001 From: Andrew Querol Date: Wed, 28 Oct 2020 12:19:41 -0500 Subject: [PATCH 2/5] Update the examples to reflect the new parameter --- README.md | 4 ++-- example/inbound/inbound.go | 2 +- example/outbound/outbound.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index db24ab2..f18891b 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ func handleConnection(ctx context.Context, conn *eslgo.Conn, response *eslgo.Raw fmt.Printf("Got connection! %#v\n", response) // Place the call to user 100 and playback an audio file as the bLeg and no channel variables - originationUUID, response, err := conn.OriginateCall(ctx, "user/100", "&playback(misc/ivr-to_hear_screaming_monkeys.wav)", map[string]string{}) + originationUUID, response, err := conn.OriginateCall(ctx, false, "user/100", "&playback(misc/ivr-to_hear_screaming_monkeys.wav)", map[string]string{}) fmt.Println("Call Originated: ", originationUUID, response, err) } ``` @@ -85,7 +85,7 @@ func main() { defer cancel() // Place the call to user 100 and playback an audio file as the bLeg - originationUUID, response, err := conn.OriginateCall(ctx, "user/100", "&playback(misc/ivr-to_hear_screaming_monkeys.wav)", map[string]string{}) + originationUUID, response, err := conn.OriginateCall(ctx, true, "user/100", "&playback(misc/ivr-to_hear_screaming_monkeys.wav)", map[string]string{}) fmt.Println("Call Originated: ", originationUUID, response, err) // Close the connection after sleeping for a bit diff --git a/example/inbound/inbound.go b/example/inbound/inbound.go index d826bb0..bfbf871 100644 --- a/example/inbound/inbound.go +++ b/example/inbound/inbound.go @@ -32,7 +32,7 @@ func main() { defer cancel() // Place the call to user 100 and playback an audio file as the bLeg - originationUUID, response, err := conn.OriginateCall(ctx, "user/100", "&playback(misc/ivr-to_hear_screaming_monkeys.wav)", map[string]string{}) + originationUUID, response, err := conn.OriginateCall(ctx, true, "user/100", "&playback(misc/ivr-to_hear_screaming_monkeys.wav)", map[string]string{}) fmt.Println("Call Originated: ", originationUUID, response, err) // Close the connection after sleeping for a bit diff --git a/example/outbound/outbound.go b/example/outbound/outbound.go index a38831c..328a787 100644 --- a/example/outbound/outbound.go +++ b/example/outbound/outbound.go @@ -26,6 +26,6 @@ func handleConnection(ctx context.Context, conn *eslgo.Conn, response *eslgo.Raw fmt.Printf("Got connection! %#v\n", response) // Place the call to user 100 and playback an audio file as the bLeg and no channel variables - originationUUID, response, err := conn.OriginateCall(ctx, "user/100", "&playback(misc/ivr-to_hear_screaming_monkeys.wav)", map[string]string{}) + originationUUID, response, err := conn.OriginateCall(ctx, false, "user/100", "&playback(misc/ivr-to_hear_screaming_monkeys.wav)", map[string]string{}) fmt.Println("Call Originated: ", originationUUID, response, err) } From fbd0b4e5b7a6550dc6fcd21bc1efc3459ad8b1bb Mon Sep 17 00:00:00 2001 From: Andrew Querol Date: Wed, 28 Oct 2020 12:22:07 -0500 Subject: [PATCH 3/5] Fix an issue with a missing return statement --- response.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/response.go b/response.go index 391b468..58facd7 100644 --- a/response.go +++ b/response.go @@ -63,7 +63,7 @@ func (c *Conn) readResponse() (*RawResponse, error) { // Also will use the body if the Reply-Text header does not exist, this can be the case for TypeAPIResponse func (r RawResponse) IsOk() bool { if r.HasHeader("Reply-Text") { - strings.HasPrefix(r.GetHeader("Reply-Text"), "+OK") + return strings.HasPrefix(r.GetHeader("Reply-Text"), "+OK") } return strings.HasPrefix(string(r.Body), "+OK") } From b77ce53d925c08d80ba5b3d577336a663d6c7115 Mon Sep 17 00:00:00 2001 From: Andrew Querol Date: Thu, 29 Oct 2020 10:49:45 -0500 Subject: [PATCH 4/5] Do not allow setting the origination UUID for foreground calls Also add a GetReply helper to RawResponse --- helper.go | 13 +++++++++++-- response.go | 13 +++++++++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/helper.go b/helper.go index 210b0f0..4271fdd 100644 --- a/helper.go +++ b/helper.go @@ -57,9 +57,18 @@ func (c *Conn) OriginateCall(ctx context.Context, background bool, aLeg, bLeg st if vars == nil { vars = make(map[string]string) } - if _, ok := vars["origination_uuid"]; !ok { - vars["origination_uuid"] = uuid.New().String() + + if background { + if _, ok := vars["origination_uuid"]; !ok { + vars["origination_uuid"] = uuid.New().String() + } + } else { + if _, ok := vars["origination_uuid"]; ok { + // We cannot set origination uuid globally foreground calls + delete(vars, "origination_uuid") + } } + response, err := c.SendCommand(ctx, command.API{ Command: "originate", Arguments: fmt.Sprintf("%s%s %s", BuildVars("{%s}", vars), aLeg, bLeg), diff --git a/response.go b/response.go index 58facd7..05577f2 100644 --- a/response.go +++ b/response.go @@ -59,13 +59,18 @@ func (c *Conn) readResponse() (*RawResponse, error) { return response, nil } -// IsOk Helper to check response status, uses the Reply-Text header primarily. -// Also will use the body if the Reply-Text header does not exist, this can be the case for TypeAPIResponse +// IsOk Helper to check response status, uses the Reply-Text header primarily. Calls GetReply internally func (r RawResponse) IsOk() bool { + return strings.HasPrefix(r.GetReply(), "+OK") +} + +// GetReply Helper to get the Reply text from FreeSWITCH, uses the Reply-Text header primarily. +// Also will use the body if the Reply-Text header does not exist, this can be the case for TypeAPIResponse +func (r RawResponse) GetReply() string { if r.HasHeader("Reply-Text") { - return strings.HasPrefix(r.GetHeader("Reply-Text"), "+OK") + return r.GetHeader("Reply-Text") } - return strings.HasPrefix(string(r.Body), "+OK") + return string(r.Body) } // ChannelUUID Helper to get the channel UUID. Calls GetHeader internally From b17fc661acd5df1f6a413574a48458f93fc7df2e Mon Sep 17 00:00:00 2001 From: Andrew Querol Date: Thu, 29 Oct 2020 12:07:39 -0500 Subject: [PATCH 5/5] Cleanup work on the originate functions. Remove all notion of returning the call UUID from the helpers and leave that up to the users. In replacement of that there is a new struct for building each leg of the call allowing more flexibility. --- README.md | 12 ++-- example/inbound/inbound.go | 6 +- example/outbound/outbound.go | 6 +- helper.go | 84 -------------------------- helper_call.go | 111 +++++++++++++++++++++++++++++++++++ utils.go | 5 ++ 6 files changed, 128 insertions(+), 96 deletions(-) create mode 100644 helper_call.go diff --git a/README.md b/README.md index f18891b..14be15e 100644 --- a/README.md +++ b/README.md @@ -54,9 +54,9 @@ func main() { func handleConnection(ctx context.Context, conn *eslgo.Conn, response *eslgo.RawResponse) { fmt.Printf("Got connection! %#v\n", response) - // Place the call to user 100 and playback an audio file as the bLeg and no channel variables - originationUUID, response, err := conn.OriginateCall(ctx, false, "user/100", "&playback(misc/ivr-to_hear_screaming_monkeys.wav)", map[string]string{}) - fmt.Println("Call Originated: ", originationUUID, response, err) + // Place the call in the foreground(api) to user 100 and playback an audio file as the bLeg and no exported variables + response, err := conn.OriginateCall(ctx, false, eslgo.Leg{CallURL: "user/100"}, eslgo.Leg{CallURL: "&playback(misc/ivr-to_hear_screaming_monkeys.wav)"}, map[string]string{}) + fmt.Println("Call Originated: ", response, err) } ``` ## Inbound ESL Client @@ -84,9 +84,9 @@ func main() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() - // Place the call to user 100 and playback an audio file as the bLeg - originationUUID, response, err := conn.OriginateCall(ctx, true, "user/100", "&playback(misc/ivr-to_hear_screaming_monkeys.wav)", map[string]string{}) - fmt.Println("Call Originated: ", originationUUID, response, err) + // Place the call in the background(bgapi) to user 100 and playback an audio file as the bLeg and no exported variables + response, err := conn.OriginateCall(ctx, true, eslgo.Leg{CallURL: "user/100"}, eslgo.Leg{CallURL: "&playback(misc/ivr-to_hear_screaming_monkeys.wav)"}, map[string]string{}) + fmt.Println("Call Originated: ", response, err) // Close the connection after sleeping for a bit time.Sleep(60 * time.Second) diff --git a/example/inbound/inbound.go b/example/inbound/inbound.go index bfbf871..190b217 100644 --- a/example/inbound/inbound.go +++ b/example/inbound/inbound.go @@ -31,9 +31,9 @@ func main() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() - // Place the call to user 100 and playback an audio file as the bLeg - originationUUID, response, err := conn.OriginateCall(ctx, true, "user/100", "&playback(misc/ivr-to_hear_screaming_monkeys.wav)", map[string]string{}) - fmt.Println("Call Originated: ", originationUUID, response, err) + // Place the call in the background(bgapi) to user 100 and playback an audio file as the bLeg and no exported variables + response, err := conn.OriginateCall(ctx, true, eslgo.Leg{CallURL: "user/100"}, eslgo.Leg{CallURL: "&playback(misc/ivr-to_hear_screaming_monkeys.wav)"}, map[string]string{}) + fmt.Println("Call Originated: ", response, err) // Close the connection after sleeping for a bit time.Sleep(60 * time.Second) diff --git a/example/outbound/outbound.go b/example/outbound/outbound.go index 328a787..881173a 100644 --- a/example/outbound/outbound.go +++ b/example/outbound/outbound.go @@ -25,7 +25,7 @@ func main() { func handleConnection(ctx context.Context, conn *eslgo.Conn, response *eslgo.RawResponse) { fmt.Printf("Got connection! %#v\n", response) - // Place the call to user 100 and playback an audio file as the bLeg and no channel variables - originationUUID, response, err := conn.OriginateCall(ctx, false, "user/100", "&playback(misc/ivr-to_hear_screaming_monkeys.wav)", map[string]string{}) - fmt.Println("Call Originated: ", originationUUID, response, err) + // Place the call in the foreground(api) to user 100 and playback an audio file as the bLeg and no exported variables + response, err := conn.OriginateCall(ctx, false, eslgo.Leg{CallURL: "user/100"}, eslgo.Leg{CallURL: "&playback(misc/ivr-to_hear_screaming_monkeys.wav)"}, map[string]string{}) + fmt.Println("Call Originated: ", response, err) } diff --git a/helper.go b/helper.go index 4271fdd..6058a43 100644 --- a/helper.go +++ b/helper.go @@ -14,12 +14,10 @@ import ( "context" "errors" "fmt" - "github.com/google/uuid" "github.com/percipia/eslgo/command" "github.com/percipia/eslgo/command/call" "io" "log" - "strings" ) func (c *Conn) EnableEvents(ctx context.Context) error { @@ -49,88 +47,6 @@ func (c *Conn) DebugOff(id string) { c.RemoveEventListener(EventListenAll, id) } -// OriginateCall - Calls the originate function in FreeSWITCH. If you want variables for each leg independently set them in the aLeg and bLeg strings -// Arguments: ctx context.Context for supporting context cancellation, background bool should we wait for the origination to complete -// aLeg, bLeg string The aLeg and bLeg of the call respectively -// vars map[string]string, channel variables to be passed to originate for both legs, contained in {} -func (c *Conn) OriginateCall(ctx context.Context, background bool, aLeg, bLeg string, vars map[string]string) (string, *RawResponse, error) { - if vars == nil { - vars = make(map[string]string) - } - - if background { - if _, ok := vars["origination_uuid"]; !ok { - vars["origination_uuid"] = uuid.New().String() - } - } else { - if _, ok := vars["origination_uuid"]; ok { - // We cannot set origination uuid globally foreground calls - delete(vars, "origination_uuid") - } - } - - response, err := c.SendCommand(ctx, command.API{ - Command: "originate", - Arguments: fmt.Sprintf("%s%s %s", BuildVars("{%s}", vars), aLeg, bLeg), - Background: background, - }) - if err != nil { - return vars["origination_uuid"], response, err - } - return vars["origination_uuid"], response, nil -} - -// EnterpriseOriginateCall - Calls the originate function in FreeSWITCH using the enterprise method for calling multiple legs ":_:" -// If you want variables for each leg independently set them in the aLeg and bLeg strings -// Arguments: ctx context.Context for supporting context cancellation, background bool should we wait for the origination to complete -// vars map[string]string, channel variables to be passed to originate for both legs, contained in <> -// bLeg string The bLeg of the call -// aLegs ...string variadic argument for each aLeg to call -func (c *Conn) EnterpriseOriginateCall(ctx context.Context, background bool, vars map[string]string, bLeg string, aLegs ...string) (*RawResponse, error) { - if len(aLegs) == 0 { - return nil, errors.New("no aLeg specified") - } - - if vars == nil { - vars = make(map[string]string) - } - - if _, ok := vars["origination_uuid"]; ok { - // We cannot set origination uuid globally for all A-legs - delete(vars, "origination_uuid") - } - - aLeg := strings.Join(aLegs, ":_:") - - response, err := c.SendCommand(ctx, command.API{ - Command: "originate", - Arguments: fmt.Sprintf("%s%s %s", BuildVars("<%s>", vars), aLeg, bLeg), - Background: background, - }) - if err != nil { - return response, err - } - return response, nil -} - -func (c *Conn) HangupCall(ctx context.Context, uuid, cause string) error { - _, err := c.SendCommand(ctx, call.Hangup{ - UUID: uuid, - Cause: cause, - Sync: false, - }) - return err -} - -func (c *Conn) AnswerCall(ctx context.Context, uuid string) error { - _, err := c.SendCommand(ctx, &call.Execute{ - UUID: uuid, - AppName: "answer", - Sync: true, - }) - return err -} - // Phrase - Executes the mod_dptools phrase app func (c *Conn) Phrase(ctx context.Context, uuid, macro string, times int, wait bool) (*RawResponse, error) { return c.audioCommand(ctx, "phrase", uuid, macro, times, wait) diff --git a/helper_call.go b/helper_call.go new file mode 100644 index 0000000..05379cc --- /dev/null +++ b/helper_call.go @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2020 Percipia + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * Contributor(s): + * Andrew Querol + */ +package eslgo + +import ( + "context" + "errors" + "fmt" + "github.com/percipia/eslgo/command" + "github.com/percipia/eslgo/command/call" + "strings" +) + +// Leg This struct is used to specify the individual legs of a call for the originate helpers +type Leg struct { + CallURL string + LegVariables map[string]string +} + +// OriginateCall - Calls the originate function in FreeSWITCH. If you want variables for each leg independently set them in the aLeg and bLeg +// Arguments: ctx context.Context for supporting context cancellation, background bool should we wait for the origination to complete +// aLeg, bLeg Leg The aLeg and bLeg of the call respectively +// vars map[string]string, channel variables to be passed to originate for both legs, contained in {} +func (c *Conn) OriginateCall(ctx context.Context, background bool, aLeg, bLeg Leg, vars map[string]string) (*RawResponse, error) { + if vars == nil { + vars = make(map[string]string) + } + + if _, ok := vars["origination_uuid"]; ok { + // We cannot set origination uuid globally + delete(vars, "origination_uuid") + } + + response, err := c.SendCommand(ctx, command.API{ + Command: "originate", + Arguments: fmt.Sprintf("%s%s %s", BuildVars("{%s}", vars), aLeg.String(), bLeg.String()), + Background: background, + }) + + return response, err +} + +// EnterpriseOriginateCall - Calls the originate function in FreeSWITCH using the enterprise method for calling multiple legs ":_:" +// If you want variables for each leg independently set them in the aLeg and bLeg strings +// Arguments: ctx context.Context for supporting context cancellation, background bool should we wait for the origination to complete +// vars map[string]string, channel variables to be passed to originate for both legs, contained in <> +// bLeg string The bLeg of the call +// aLegs ...string variadic argument for each aLeg to call +func (c *Conn) EnterpriseOriginateCall(ctx context.Context, background bool, vars map[string]string, bLeg Leg, aLegs ...Leg) (*RawResponse, error) { + if len(aLegs) == 0 { + return nil, errors.New("no aLeg specified") + } + + if vars == nil { + vars = make(map[string]string) + } + + if _, ok := vars["origination_uuid"]; ok { + // We cannot set origination uuid globally + delete(vars, "origination_uuid") + } + + var aLeg strings.Builder + for i, leg := range aLegs { + if i > 0 { + aLeg.WriteString(":_:") + } + aLeg.WriteString(leg.String()) + } + + response, err := c.SendCommand(ctx, command.API{ + Command: "originate", + Arguments: fmt.Sprintf("%s%s %s", BuildVars("<%s>", vars), aLeg.String(), bLeg.String()), + Background: background, + }) + + return response, err +} + +// HangupCall - A helper to hangup a call asynchronously +func (c *Conn) HangupCall(ctx context.Context, uuid, cause string) error { + _, err := c.SendCommand(ctx, call.Hangup{ + UUID: uuid, + Cause: cause, + Sync: false, + }) + return err +} + +// HangupCall - A helper to answer a call synchronously +func (c *Conn) AnswerCall(ctx context.Context, uuid string) error { + _, err := c.SendCommand(ctx, &call.Execute{ + UUID: uuid, + AppName: "answer", + Sync: true, + }) + return err +} + +// String - Build the Leg string for passing to Bridge/Originate functions +func (l Leg) String() string { + return fmt.Sprintf("%s%s", BuildVars("[%s]", l.LegVariables), l.CallURL) +} diff --git a/utils.go b/utils.go index 1e7ca36..c94998e 100644 --- a/utils.go +++ b/utils.go @@ -16,6 +16,11 @@ import ( ) func BuildVars(format string, vars map[string]string) string { + // No vars do not format + if vars == nil || len(vars) == 0 { + return "" + } + var builder strings.Builder for key, value := range vars { if builder.Len() > 0 {