YateGo packes enables communication with YATE core via external protocol http://docs.yate.ro/wiki/Programmer%27s_guide
go get github.com/rukavina/yatego
this simple IVR just answers and plays tone/congestion
In order to install it as regalar callflow/ivr in yate, compile, place binary in share/yate/scripts
.
Then you need to have a route to it, eg:
^920$=external/nodata/yourbinary
package main
import "github.com/rukavina/yatego/pkg/yatego"
func main() {
f := yatego.NewFactory()
c := f.Controller(nil)
c.Logger().Debug("Starting yatego IVR [inline]")
com := f.BaseComponent()
com.OnEnter(func(call *yatego.Call, message *yatego.Message) *yatego.CallbackResult {
com.(*yatego.Base).PlayTone("congestion", call, map[string]string{})
return yatego.NewCallbackResult(yatego.ResStay, "")
})
c.AddStaticComponent(com)
c.Run("")
}
The callflow definition allows more cleaner and structural setup of your IVRs. IVRs based on callflows are defined as the system of components.
There are different ways to load a callflow, simplest one is by using CallflowLoaderStatic
package main
import "github.com/rukavina/yatego/pkg/yatego"
func main() {
f := yatego.NewFactory()
c := f.Controller(nil)
c.Logger().Debug("Starting yatego IVR [inline]")
com := f.BaseComponent()
com.OnEnter(func(call *yatego.Call, message *yatego.Message) *yatego.CallbackResult {
com.(*yatego.Base).PlayTone("congestion", call, map[string]string{})
return yatego.NewCallbackResult(yatego.ResStay, "")
})
c.AddStaticComponent(com)
c.Run("")
}
loader.go:
package main
import (
"os"
"path/filepath"
"github.com/rukavina/dicgo"
"github.com/rukavina/yatego/pkg/yatego"
)
func loader(c dicgo.Container) yatego.CallflowLoader {
ex, err := os.Executable()
if err != nil {
panic(err)
}
dir := filepath.Dir(ex)
return yatego.NewCallflowLoaderStatic(&yatego.Callflow{
Components: []*yatego.CallflowComponent{
//player start
&yatego.CallflowComponent{
Name: "start",
ClassName: "player",
Config: map[string]interface{}{
"playlist": dir + "/assets/audio/welcome.sln",
"transfer": "menu",
},
Factory: yatego.PlayerComponentFactory(c),
},
//menu
&yatego.CallflowComponent{
Name: "menu",
ClassName: "menu",
Config: map[string]interface{}{
"keys": "1,2,3",
"transfer": "playlist1,playlist2,recorder",
},
Factory: yatego.MenuComponentFactory(c),
},
//playlist1
&yatego.CallflowComponent{
Name: "playlist1",
ClassName: "player",
Config: map[string]interface{}{
"playlist": dir + "/assets/audio/clicked_1.sln",
"transfer": "goodbye",
},
Factory: yatego.PlayerComponentFactory(c),
},
//playlist2
&yatego.CallflowComponent{
Name: "playlist2",
ClassName: "player",
Config: map[string]interface{}{
"playlist": dir + "/assets/audio/clicked_2.sln",
"transfer": "goodbye",
},
Factory: yatego.PlayerComponentFactory(c),
},
//recorder
&yatego.CallflowComponent{
Name: "recorder",
ClassName: "recorder",
Config: map[string]interface{}{
"file": dir + "/assets/voicemail/{called}_{caller}_{billingId}.sln",
"maxlen": 80000,
"transfer": "goodbye",
},
Factory: yatego.RecorderComponentFactory(c),
},
//player goodbye
&yatego.CallflowComponent{
Name: "goodbye",
ClassName: "player",
Config: map[string]interface{}{
"playlist": dir + "/assets/audio/goodbye.sln",
},
Factory: yatego.PlayerComponentFactory(c),
},
},
})
}
More callflow flexibility you gain by using CallflowLoaderJSON
which allows to define you callflow in an external json file
package main
import (
"os"
"path/filepath"
"github.com/rukavina/yatego/pkg/yatego"
)
func main() {
f := yatego.NewFactory()
//json loader
l := f.CallflowLoaderJSON()
//load json content from external file
exec, _ := os.Executable()
dir := filepath.Dir(exec)
l.SetJSONFile(dir + "/assets/configs/callflow_static.json")
//controller
c := f.Controller(l)
c.Logger().Debug("Starting yatego IVR [callflow-json]")
c.Run("")
}
The next level of flexibility is achieved by using json file as callflow template, which means that it can contain variables instead of hard-coded values for component configuration. At the runtime, it's possible to obtain values to be used to parse template variables
package main
import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"github.com/rukavina/yatego/pkg/yatego"
)
func main() {
//get current dir
exec, _ := os.Executable()
dir := filepath.Dir(exec)
f := yatego.NewFactory()
//json loader
l := f.CallflowLoaderJSON()
//custom onload, pull vars from json
l.OnLoad = func(loader *yatego.CallflowLoaderJSON, cf *yatego.Callflow, params map[string]string) error {
//load vars from json
data, err := ioutil.ReadFile(dir + "/assets/configs/callflow_vars.json")
if err != nil {
return err
}
var vars map[string]string
if err := json.Unmarshal(data, &vars); err != nil {
return err
}
loader.SetVars(vars)
return nil
}
//load json CF template from external file
l.SetJSONFile(dir + "/assets/configs/callflow_tpl.json")
//controller
c := f.Controller(l)
c.Logger().Debug("Starting yatego IVR [callflow-vars]")
c.Run("")
}
template json file:
{
"components":[
{
"name": "start",
"class": "player",
"config": {
"playlist": "/vagrant/assets/audio/{prompt_file}",
"transfer": "menu"
}
},
{
"name": "menu",
"class": "menu",
"config": {
"keys": "1,2,3",
"transfer": "playlist1,playlist2,recorder"
}
},
{
"name": "playlist1",
"class": "player",
"config": {
"playlist": "/vagrant/assets/audio/clicked_1.sln",
"transfer": "goodbye"
}
},
{
"name": "playlist2",
"class": "player",
"config": {
"playlist": "/vagrant/assets/audio/clicked_2.sln",
"transfer": "goodbye"
}
},
{
"name": "recorder",
"class": "recorder",
"config": {
"file": "/vagrant/assets/voicemail/{called}_{caller}_{billingId}.sln",
"maxlen": "{rec_maxlen}",
"transfer": "goodbye"
}
},
{
"name": "goodbye",
"class": "player",
"config": {
"playlist": "/vagrant/assets/audio/goodbye.sln"
}
}
]
}
values :
{
"prompt_file": "welcome.sln",
"rec_maxlen": "160000"
}
The most flexible callflow you can get by using component fetcher
. It has config url
. When fetcher enters execution it will make http POST request to defined url and expects json result. The json response should be new callflow. New components are generated and appended to the existing call components. The execution is trasfered to the very next component among new ones.
Your http server/handler receives the following as a form's post data:
ID=1522323299-11
called=924
caller=41587000201
Thus you can generate json callflow to return dynamically based on provided params.
package main
import (
"os"
"path/filepath"
"github.com/rukavina/yatego/pkg/yatego"
)
func main() {
f := yatego.NewFactory()
//json loader
l := f.CallflowLoaderJSON()
//load json content from external file
exec, _ := os.Executable()
dir := filepath.Dir(exec)
l.SetJSONFile(dir + "/assets/configs/callflow_dynamic.json")
//controller
c := f.Controller(l)
c.Logger().Debug("Starting yatego IVR [callflow-dynamic]")
c.Run("")
}
demo url handler:
package main
import (
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if err != nil {
log.Printf("error parsing form: %s", err)
} else {
log.Println("Provided values on request:")
for key := range r.PostForm {
log.Printf("\"%s\":\"%s\"", key, r.PostFormValue(key))
}
}
http.ServeFile(w, r, "assets/configs/callflow_static.json")
})
log.Println("HTTP server up and running on port 9000 and serving file [assets/configs/callflow_static.json]")
err := http.ListenAndServe(":9000", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
- First build all
cmd
executables withgo build
in each subfolder - From project's root folder run
./start_dev.sh
- connect sip softphone (eg. zoiper) to sip account
[email protected]
, pass:milan
- call example destinations:
920
,921
,922
,923
,924
,925
NOTE: due to vagrant issues sometimes mock http srv is not started in vagrant up
. To check and start do:
SSH to vagrant:
vagrant ssh
sudo -i
Log:
tail -f /var/log/yate/messages
Stop:
pkill yate
Start:
/opt/yate/startyate.sh
Callflows in json
formatten can be documented/visualized using https://mermaidjs.github.io/
There's a special tool provided which can build .mmd
mermaid syntax from .json callflow files.
cd tools/mermaid-gen
go build
./mermaid-gen --cf=../../assets/configs/callflow_static.json
this will output mmd content which can be pasted into https://mermaidjs.github.io/mermaid-live-editor to produce svg
output