next-yate
v0.2.1
Published
Next-Yate is Nodejs External module for YATE (Yet Another Telephony Engine)
Downloads
4
Readme
Next-Yate
Next-Yate is Nodejs interface library to the Yate external module (Yet Another Telephony Engine).
Features
- Simple to use.
- Channel half-stuff abstraction. (Now creation of IVR or dialplan is as easy as a piece of cake).
- Auto-restore connections and message handlers on network collisions.
- Cache of all requests in offline mode.
- Auto-acknowledge of the incoming messages by acknowledge_timeout. (This can be critical under high load, as it prevents Yate from crashing).
- Independence of other Nodejs modules.
(* Compatibility with javascript.yate has been moved to Next-Yate-Compat)
Overiew
- Yate class provides connection to Yate's external module.
- YateMessage class is object Yate's external module can interact with.
- YateChannel class is an abstraction over incoming call leg messages flow.
Quick start
Before starting
(If you still don't know what Yate is https://docs.yate.ro/wiki/Main_Page)
Make sure the Yate's module extmodule.yate is successfully loaded (https://docs.yate.ro/wiki/External_Module)
yate.conf:
[modules]
extmodule.yate=true
extmodule.conf:
; For network connection
[listener sample]
type=tcp
addr=127.0.0.1
port=5040
role=global
;
; Local stdin/stdout connected scripts
[scripts]
myscript.sh= ; Custom shell wrapper around Nodejs script
node.sh=my_script.js ; Run my_script.js with example wrapper: examples/node.sh
Install
npm install next-yate
Network connected script
example_core_api.js:
// Core API
const { Yate, YateMessage } = require("next-yate");
let yate = new Yate({ host: "127.0.0.1" });
yate.init(() => console.log("Connected")); // Initialize connection before use
yate.output("Hello World!");
Local connected script
When launching your script, be sure that Nodejs will find the necessary libraries.
extmodule.conf:
[scripts]
node.sh=my_scrypt.js
Example of shell wrapper around Nodejs
node.sh:
#!/bin/sh
SCRIPTS=/path_to/share/scripts
export NODE_PATH=$SCRIPTS
NODE=`which node`
$NODE $SCRIPTS/$1
Direct script execution (Channel mode)
regexroute.conf:
^NNN=extmodule/nodata/node.sh example.js
example.js
const { Yate } = require("next-yate");
const yate = new Yate();
const Channel = yate.toChannel();
Channel.init(main, {autoring: true});
async function main(message) {
await Channel.callTo("wave/play/./share/sounds/welcome.au");
await Channel.answered();
Channel.callJust("conf/333", {"lonely": true});
}
Featured IVR example (using YateChannel)
IVR Description:
Answer on the service number = 1234567890
|
Welcome menu (entry point)
Setup the DTMF handler:
1 = Jump to the Callerid menu
2 = Jump to the Echotest menu
3 = Transfer to support
0 = Repeat Welcome menu prompt
Play the menu prompt
Pause 5 seconds
Repeat the prompt up to 3 times or end the call
|
- Callerid Menu
Setup the DTMF handler:
1 = Repeat Callerid
0 = Return to the Welcome menu
Play the caller id by numbers
Replay slower
Wait 10 seconds and redirect to Welcome
|
- Echotest Menu
Setup the DTMF handler:
1 = Repeat Echotest
0 = Return to the Welcome menu
Play the Echo prompt
Give the signal to start recording
Record a sample lasting 3 seconds
Give the signal of the end of the recording
Play the recorded sample
Wait 10 seconds and redirect to Welcome
Code:
const {Yate, YateChannel} = require("next-yate");
const yate = new Yate({host: "127.0.0.1"});
yate.init(); // Connect to Yate
yate.install(onRoute, "call.route", 100, "called", "1234567890"); // Install the service number 1234567890
function onRoute(message) {
message.retValue("dumb/");
message.autoanswer = true;
let chan = new YateChannel(message);
chan.caller = message.caller; // Remember the callerid
chan.counter = 0; // Welcome prompt counter
chan.init(() => welcome(chan)).catch(console.log));
return true;
}
// Welcome menu item
async function welcome(chan) {
if (!chan.ready) return;
chan.counter++;
// Setup DTMF handler
chan.watch(dtmf => {
switch(dtmf.text) {
case "1":
chan.reset(); // Drop the active chain of prompts
callerid(chan); // Jump to CallerId menu item
break;
case "2":
chan.reset();
echo(chan); // Jump to Echo menu item
break;
case "3":
chan.reset();
chan.callJust("sip/sip:[email protected]"); // Transfer the call to support
break;
case "0":
chan.reset();
welcome(chan); // Jump to Welcome menu item
break;
}
}, "chan.dtmf");
// Play the chain of prompts
try {
await chan.callTo("wave/play/./share/sounds/words/hello.wav");
await chan.callTo("wave/play/./share/sounds/words/you-have-reached-a-test-number.wav");
await chan.callTo("wave/play/./share/sounds/words/press-1.wav");
await chan.callTo("wave/play/./share/sounds/words/to-hear-callerid.wav");
await chan.callTo("wave/play/./share/sounds/words/press-2.wav");
await chan.callTo("wave/play/./share/sounds/words/to-echotest.wav");
await chan.callTo("wave/play/./share/sounds/words/press-3.wav");
await chan.callTo("wave/play/./share/sounds/words/to-talk-with-operator.wav");
await chan.callTo("wave/play/./share/sounds/words/press-0.wav");
await chan.callTo("wave/play/./share/sounds/words/to-hear-menu-again.wav");
// Wait 5 sec and repeat the prompt
await new Promise(res => {setTimeout(res, 5000)});
// Repeat the prompt or hangup
if (chan.counter < 2) {
welcome(chan);
} else {
chan.hangup();
}
} catch(err) {
console.log(err);
return;
}
}
// CallerId menu item
async function callerid(chan) {
if (!chan.ready) return;
// Change DTMF handler:
chan.watch(dtmf => {
if (dtmf.text === "1") {
chan.reset();
callerid(chan);
} else if (dtmf.text === "0") {
chan.reset();
welcome(chan);
}
}, "chan.dtmf");
// Play the sequence
try {
// Say the callerid by numbers
await chan.callTo("wave/play/./share/sounds/words/your-callerid-is.wav");
for (let i = 0; i < chan.caller.length; i++) {
await chan.callTo("wave/play/./share/sounds/digits/" + chan.caller.charAt(i) + ".wav");
}
await chan.callTo("wave/record/-", {timeout: 2000});
// Repeat the callerid slowly
await chan.callTo("wave/play/./share/sounds/words/your-callerid-is.wav");
for (let i = 0; i < chan.caller.length; i++) {
await chan.callTo("wave/play/./share/sounds/digits/" + chan.caller.charAt(i) + ".wav");
await chan.callTo("wave/record/-", {timeout: 500}); // here is pause 0.5 sec between words
}
// Wait 2 seconds
await chan.callTo("wave/record/-", {timeout: 2000});
// Play menu prompt
await chan.callTo("wave/play/./share/sounds/words/press-1.wav");
await chan.callTo("wave/play/./share/sounds/words/to-hear-callerid.wav");
await chan.callTo("wave/play/./share/sounds/words/again.wav");
// Wait 10 seconds
await chan.callTo("wave/record/-", {timeout: 10000});
// If nothing happened back to Welcome
welcome(chan);
} catch(err) {
console.log(err);
return;
}
}
// Echo menu item
async function echo(chan) {
if (!chan.ready) return;
chan.watch(dtmf => {
if (dtmf.text === "1") {
chan.reset();
echo(chan);
} else if (dtmf.text === "0") {
chan.reset();
welcome(chan);
}
}, "chan.dtmf");
// Rec and play the Echo
try {
let rec = "/tmp/rec-" + Date.now() + ".au";
// Play prompt
await chan.callTo("wave/play/./share/sounds/echotest.au");
// tink
await chan.callTo("tone/dtmf/0");
// Rec the voice 3 seconds
await chan.callTo("wave/record/" + rec, {timeout: 3000});
// tink
await chan.callTo("tone/dtmf/0");
// Play recorded voice
await chan.callTo("wave/play/" + rec);
// tink
await chan.callTo("tone/dtmf/0");
// Wait 10 sec
await chan.callTo("wave/record/-", {timeout: 10000});
// Go to welcome
welcome(chan);
} catch(err) {
console.log(err);
return;
}
}