Skip to content

Commit

Permalink
Merge pull request #2 from percipia/originate-foreground
Browse files Browse the repository at this point in the history
Allow the originate helpers to be called in the foreground.
  • Loading branch information
winsock authored Oct 29, 2020
2 parents 2af98db + b17fc66 commit cb8665b
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 87 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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, "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
Expand Down Expand Up @@ -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, "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)
Expand Down
8 changes: 5 additions & 3 deletions event.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
Expand All @@ -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()
}
6 changes: 3 additions & 3 deletions example/inbound/inbound.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, "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)
Expand Down
6 changes: 3 additions & 3 deletions example/outbound/outbound.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, "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)
}
65 changes: 0 additions & 65 deletions helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -49,69 +47,6 @@ 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) {
if vars == nil {
vars = make(map[string]string)
}
if _, ok := vars["origination_uuid"]; !ok {
vars["origination_uuid"] = uuid.New().String()
}
response, err := c.SendCommand(ctx, command.API{
Command: "originate",
Arguments: fmt.Sprintf("%s%s %s", BuildVars("{%s}", vars), aLeg, bLeg),
Background: true,
})
if err != nil {
return vars["origination_uuid"], response, err
}
return vars["origination_uuid"], response, nil
}

func (c *Conn) EnterpriseOriginateCall(ctx context.Context, 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: true,
})
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)
Expand Down
111 changes: 111 additions & 0 deletions helper_call.go
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
*/
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)
}
30 changes: 23 additions & 7 deletions response.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -58,28 +59,43 @@ 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. Calls GetReply internally
func (r RawResponse) IsOk() bool {
return strings.HasPrefix(r.GetHeader("Reply-Text"), "+OK")
return strings.HasPrefix(r.GetReply(), "+OK")
}

// Helper to get the channel UUID
// 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 r.GetHeader("Reply-Text")
}
return string(r.Body)
}

// 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 {
Expand All @@ -89,7 +105,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()
}
5 changes: 5 additions & 0 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit cb8665b

Please sign in to comment.