Comparing version
var AGIServer = require('./../lib/index'); | ||
var handler = function (context) { | ||
context.onEvent('variables') | ||
context.onEvent('variables') | ||
.then(function (vars) { | ||
return context.streamFile('beep'); | ||
console.log('vars', vars); | ||
return context.streamFile(); | ||
}) | ||
@@ -13,6 +14,7 @@ .then(function (result) { | ||
return context.end(); | ||
}); | ||
}) | ||
.fail(console.log); | ||
}; | ||
var agi = new AGIServer(handler); | ||
agi.start(3000); | ||
var agi = new AGIServer(handler, {debug: true}); | ||
agi.start(3007); |
@@ -5,18 +5,26 @@ var Readable = require('readable-stream'); | ||
var Q = require('q'); | ||
var commands = require('./command'); | ||
//base context | ||
var Context = function (stream, debug) { | ||
EventEmitter.call(this); | ||
this.debug = debug; | ||
this.stream = new Readable(); | ||
this.stream.setEncoding('utf8'); | ||
this.stream.wrap(stream); | ||
this.state = state.init; | ||
this.msg = ""; | ||
this.variables = {}; | ||
this.pending = null; | ||
var self = this; | ||
this.stream.on('readable', function () { | ||
//always keep the 'leftover' part of the message | ||
//always keep the 'leftover' part of the message | ||
self.msg = self.read(); | ||
}); | ||
this.msg = this.read(); | ||
this.variables = {}; | ||
this.pending = null; | ||
this.stream.on('error', this.emit.bind(this, 'error')); | ||
@@ -28,28 +36,31 @@ this.stream.on('close', this.emit.bind(this, 'close')); | ||
Context.prototype.read = function() { | ||
var buffer = this.stream.read(); | ||
if(!buffer) return this.msg; | ||
this.msg += buffer.toString('utf8'); | ||
if(this.state === state.init) { | ||
if(this.msg.indexOf('\n\n') < 0) return this.msg; //we don't have whole message | ||
Context.prototype.read = function () { | ||
var buffer = this.stream.read(); | ||
if (!buffer) return this.msg; | ||
this.msg += buffer; | ||
if (this.state === state.init) { | ||
if (this.msg.indexOf('\n\n') < 0) return this.msg; //we don't have whole message | ||
this.readVariables(this.msg); | ||
} else if(this.state === state.waiting) { | ||
if(this.msg.indexOf('\n') < 0) return this.msg; //we don't have whole message | ||
} else if (this.state === state.waiting) { | ||
if (this.msg.indexOf('\n') < 0) return this.msg; //we don't have whole message | ||
this.readResponse(this.msg); | ||
} | ||
return ""; | ||
return ''; | ||
}; | ||
Context.prototype.readVariables = function (msg) { | ||
var lines = msg.split('\n'); | ||
for(var i = 0; i < lines.length; i++) { | ||
var line = lines[i]; | ||
var split = line.split(':') | ||
var lines = msg.split('\n'); | ||
lines.map(function (line) { | ||
var split = line.split(':'); | ||
var name = split[0]; | ||
var value = split[1]; | ||
this.variables[name] = (value||'').trim(); | ||
} | ||
this.variables[name] = (value || '').trim(); | ||
}, this); | ||
this.emit('variables', this.variables); | ||
this.setState(state.waiting); | ||
return ""; | ||
}; | ||
@@ -59,23 +70,26 @@ | ||
var lines = msg.split('\n'); | ||
for(var i = 0; i < lines.length; i++) { | ||
this.readResponseLine(lines[i]); | ||
} | ||
return ""; | ||
lines.map(function (line) { | ||
this.readResponseLine(line); | ||
}, this); | ||
}; | ||
Context.prototype.readResponseLine = function (line) { | ||
if(!line) return; | ||
if (!line) return; | ||
var parsed = /^(\d{3})(?: result=)(.*)/.exec(line); | ||
//var parsed = /^(\d{3})(?: result=)(.*)/.exec(line); | ||
var parsed = /^(\d{3})(?: result=)([^(]*)(?:\((.*)\))?/.exec(line); | ||
if(!parsed) { | ||
if (!parsed) { | ||
return this.emit('hangup'); | ||
} | ||
var response = { | ||
code: parseInt(parsed[1]), | ||
result: parsed[2] | ||
result: parsed[2].trim(), | ||
}; | ||
//our last command had a pending callback | ||
if(this.pending) { | ||
if (this.pending) { | ||
var pending = this.pending; | ||
@@ -88,7 +102,7 @@ this.pending = null; | ||
Context.prototype.setState = function(state) { | ||
Context.prototype.setState = function (state) { | ||
this.state = state; | ||
}; | ||
Context.prototype.send = function(msg, cb) { | ||
Context.prototype.send = function (msg, cb) { | ||
this.pending = cb; | ||
@@ -126,203 +140,48 @@ this.stream.write(msg); | ||
Context.prototype.exec = function() { | ||
var args = Array.prototype.slice.call(arguments, 0); | ||
return this.sendCommand('EXEC ' + args.join(' ')); | ||
}; | ||
//additional agi commands | ||
Context.prototype.dial = function (num, timeout, params) { | ||
return this.exec('Dial', num + ',' + timeout + ',' + params); | ||
}; | ||
commands.map(function (command) { | ||
var str = ''; | ||
Context.prototype[command.name] = function () { | ||
if (command.params > 0) { | ||
var args = [].slice.call(arguments, 0, command.params); | ||
str = command.command + " " + prepareArgs(args, command.paramRules, command.params).join(" "); | ||
} else { | ||
str = command.command; | ||
} | ||
return this.sendCommand(str); | ||
}; | ||
}); | ||
Context.prototype.databaseDel = function (family, key) { | ||
return this.sendCommand('DATABASE DEL ' + family + ' ' + key); | ||
}; | ||
var prepareArgs = function (args, argsRules, count) { | ||
var q, argsP = []; | ||
if (argsRules && count) { | ||
Context.prototype.databaseDelTree = function (family, keytree) { | ||
return this.sendCommand('DATABASE DELTREE ' + family + ' ' + keytree); | ||
}; | ||
args = args.map(function (arg){ | ||
return arg.toString(); | ||
}); | ||
Context.prototype.databaseGet = function (family, key) { | ||
return this.sendCommand('DATABASE GET ' + family + ' ' + key); | ||
}; | ||
for (var i = 0; i < count; i++) { | ||
argsP[i] = (args[i]) ? | ||
args[i] : | ||
((argsRules[i] && argsRules[i].default) ? | ||
argsRules[i].default : | ||
''); | ||
} | ||
Context.prototype.databasePut = function (family, key, value) { | ||
return this.sendCommand('DATABASE PUT ' + family + ' ' + key + ' ' + value); | ||
q = argsP.map(function (arg, i) { | ||
return (argsRules[i] && argsRules[i].prepare) ? argsRules[i].prepare(arg) : arg; | ||
}); | ||
} else { | ||
q = args; | ||
} | ||
return q; | ||
}; | ||
Context.prototype.speechCreate = function (engine) { | ||
return this.sendCommand('SPEECH CREATE ' + engine); | ||
}; | ||
//sugar commands | ||
Context.prototype.speechDestroy = function () { | ||
return this.sendCommand('SPEECH DESTROY'); | ||
Context.prototype.dial = function (target, timeout, params) { | ||
return this.exec('Dial', target + ',' + timeout + ',' + params); | ||
}; | ||
Context.prototype.speechActivateGrammar = function (name) { | ||
return this.sendCommand('SPEECH ACTIVATE GRAMMAR ' + name); | ||
}; | ||
Context.prototype.speechDeactivateGrammar = function (name) { | ||
return this.sendCommand('SPEECH DEACTIVATE GRAMMAR ' + name); | ||
}; | ||
Context.prototype.speechLoadGrammar = function (name, path) { | ||
return this.sendCommand('SPEECH LOAD GRAMMAR ' + name + ' ' + path); | ||
}; | ||
Context.prototype.speechUnloadGrammar = function (name) { | ||
return this.sendCommand('SPEECH UNLOAD GRAMMAR ' + name); | ||
}; | ||
Context.prototype.speechSet = function (name, value) { | ||
return this.sendCommand('SPEECH SET ' + name + ' ' + value); | ||
}; | ||
Context.prototype.speechRecognize = function (prompt, timeout, offset) { | ||
return this.sendCommand('SPEECH RECOGNIZE ' + prompt + ' ' + timeout + ' ' + offset); | ||
}; | ||
Context.prototype.getVariable = function (name) { | ||
return this.sendCommand('GET VARIABLE ' + name); | ||
}; | ||
Context.prototype.getFullVariable = function (variable, channel) { | ||
return this.sendCommand('GET FULL VARIABLE ' + variable + ' ' + channel); | ||
}; | ||
Context.prototype.getData = function (file, timeout, maxdigits) { | ||
return this.sendCommand('GET DATA ' + file + ' ' + timeout + ' ' + maxdigits); | ||
}; | ||
Context.prototype.getOption = function (file, escape_digits, timeout) { | ||
return this.sendCommand('GET OPTION ' + file + ' "' + escape_digits + '" ' + timeout); | ||
}; | ||
Context.prototype.receiveChar = function (timeout) { | ||
return this.sendCommand('RECEIVE CHAR ' + timeout); | ||
}; | ||
Context.prototype.receiveText = function (timeout) { | ||
return this.sendCommand('RECEIVE TEXT ' + timeout); | ||
}; | ||
Context.prototype.setAutoHangup = function (seconds) { | ||
return this.sendCommand('SET AUTOHANGUP ' + seconds); | ||
}; | ||
Context.prototype.setCallerID = function (number) { | ||
return this.sendCommand('SET CALLERID ' + number); | ||
}; | ||
Context.prototype.setContext = function (context) { | ||
return this.sendCommand('SET CONTEXT ' + context); | ||
}; | ||
Context.prototype.setExtension = function (extension) { | ||
return this.sendCommand('SET EXTENSION ' + extension); | ||
}; | ||
Context.prototype.setPriority = function (priority) { | ||
return this.sendCommand('SET PRIORITY ' + priority); | ||
}; | ||
Context.prototype.setMusic = function (musicclass) { | ||
return this.sendCommand('SET MUSIC ' + musicclass); | ||
}; | ||
Context.prototype.setVariable = function (name, value) { | ||
return this.sendCommand('SET VARIABLE ' + name + ' "' + value + '"'); | ||
}; | ||
Context.prototype.sendImage = function (image) { | ||
return this.sendCommand('SEND IMAGE ' + image); | ||
}; | ||
Context.prototype.sendText = function (text) { | ||
return this.sendCommand('SEND TEXT "' + text + '"'); | ||
}; | ||
Context.prototype.channelStatus = function (name) { | ||
return this.sendCommand('CHANNEL STATUS ' + name); | ||
}; | ||
Context.prototype.answer = function () { | ||
return this.sendCommand('ANSWER'); | ||
}; | ||
Context.prototype.verbose = function (message, level) { | ||
return this.sendCommand('VERBOSE "' + message + '" ' + level); | ||
}; | ||
Context.prototype.tddMode = function (value) { | ||
return this.sendCommand('TDD MODE ' + value); | ||
}; | ||
Context.prototype.noop = function (cb) { | ||
return this.sendCommand('NOOP'); | ||
}; | ||
Context.prototype.gosub = function (context, extension, priority, option) { | ||
var str = [context, extension, priority, option].join(' '); | ||
return this.sendCommand('GOSUB ' + str); | ||
}; | ||
Context.prototype.recordFile = function (filename, format, escape_digits, timeout, offset, beep, silence) { | ||
var str = [ | ||
'"' + filename + '"', | ||
format, | ||
escape_digits, | ||
parseInt(timeout)*1000, | ||
offset, | ||
beep, | ||
silence | ||
].join(' '); | ||
return this.sendCommand('RECORD FILE ' + str); | ||
}; | ||
Context.prototype.sayNumber = function (number, escape_digits) { | ||
return this.sendCommand('SAY NUMBER ' + number + ' "' + escape_digits + '"'); | ||
}; | ||
Context.prototype.sayAlpha = function (number, escape_digits) { | ||
return this.sendCommand('SAY ALPHA ' + number + ' "' + escape_digits + '"'); | ||
}; | ||
Context.prototype.sayDate = function (seconds, escape_digits) { //seconds since 1.01.1970 | ||
return this.sendCommand('SAY DATE ' + seconds + ' "' + escape_digits + '"'); | ||
}; | ||
Context.prototype.sayTime = function (seconds, escape_digits) { //seconds since 1.01.1970 | ||
return this.sendCommand('SAY TIME ' + seconds + ' "' + escape_digits + '"'); | ||
}; | ||
Context.prototype.sayDateTime = function (seconds, escape_digits, format, timezone) { //seconds since 1.01.1970 | ||
return this.sendCommand('SAY DATETIME ' + seconds + ' "' + escape_digits + '" ' + format + ' ' + timezone); | ||
}; | ||
Context.prototype.sayDigits = function (digits, escape_digits) { | ||
return this.sendCommand('SAY DIGITS ' + digits + ' "' + escape_digits + '"'); | ||
}; | ||
Context.prototype.sayPhonetic = function (string, escape_digits) { | ||
return this.sendCommand('SAY PHONETIC ' + string + ' "' + escape_digits + '"'); | ||
}; | ||
Context.prototype.streamFile = function (filename, digits) { | ||
var acceptDigits = digits ? digits : "1234567890#*"; | ||
return this.sendCommand('STREAM FILE "' + filename + '" "' + acceptDigits + '"'); | ||
}; | ||
Context.prototype.waitForDigit = function (timeoutIn) { | ||
var timeout = timeoutIn ? timeoutIn : 5000; | ||
return this.sendCommand('WAIT FOR DIGIT ' + timeout); | ||
}; | ||
Context.prototype.hangup = function () { | ||
return this.sendCommand('HANGUP'); | ||
}; | ||
Context.prototype.asyncAGIBreak = function () { | ||
return this.sendCommand('ASYNCAGI BREAK'); | ||
}; | ||
module.exports = Context; |
@@ -5,3 +5,3 @@ { | ||
"description": "Write AGI-server quickly! (AGI - Asterisk Gateway Interface)", | ||
"version": "0.1.1", | ||
"version": "0.1.2", | ||
"repository": { | ||
@@ -8,0 +8,0 @@ "type": "git", |
@@ -5,9 +5,15 @@ # ding-dong | ||
Create AGI server with ding-dong. Use with Asterisk for fast telephony apps. [Fork of node-agi](http://github.com/brianc/node-agi) | ||
Create AGI server with ding-dong. Use with Asterisk for fast telephony apps. | ||
[Fork of node-agi](http://github.com/brianc/node-agi) | ||
stable version 0.1.1 | ||
unstable version 0.1.2 | ||
Use ding-dong | ||
============= | ||
[voicer](http://github.com/antirek/voicer) - AGI yandex voice recognizer for Asterisk | ||
[voicer](http://github.com/antirek/voicer) - AGI voice recognizer for Asterisk (use Yandex and Google speech recognizers) | ||
@@ -60,41 +66,12 @@ [agi-number-archer](http://github.com/antirek/agi-number-archer) - AGI server for find region code of phone number (Russia) | ||
### And call to 1000 and view asterisk output. | ||
### And call to 1000 and view asterisk output. Profit! | ||
## API | ||
see [API.md](API.md) | ||
## API | ||
attention: using javascript promises | ||
### context.exec(command, [args]) | ||
Dispatches the `EXEC` AGI command to asterisk with supplied command name and arguments. _callback_ is called with the result of the dispatch. | ||
```js | ||
context.exec('Dial', opt1, opt2, .., optN) | ||
.then(function(result) | ||
//the channel call app Dial with options | ||
}); | ||
context.exec('RecieveFax', '/tmp/myfax.tif') | ||
.then(function(result) { | ||
//fax has been recieved by asterisk and written to /tmp/myfax.tif | ||
}); | ||
``` | ||
### context.hangup() | ||
Dispatches the 'HANGUP' AGI command to asterisk. Does __not__ close the sockets automatically. _callback_ is called with the result of the dispatch. | ||
```js | ||
context.hangup() | ||
.then(function(){ | ||
//do something | ||
}); | ||
``` | ||
## Links | ||
[Asterisk AGI](https://wiki.asterisk.org/wiki/display/AST/Asterisk+13+AGI+Commands) |
@@ -24,5 +24,7 @@ var MemoryStream = require('memorystream'); | ||
}; | ||
ctx.once('variables', function(vars) { | ||
cb(ctx); | ||
}); | ||
writeVars(stream); | ||
@@ -32,5 +34,5 @@ }; | ||
describe('Context', function() { | ||
beforeEach(function(done) { | ||
beforeEach(function (done) { | ||
var self = this; | ||
context(function(context) { | ||
context(function (context) { | ||
self.context = context; | ||
@@ -422,3 +424,3 @@ done(); | ||
it('sends with default timeout', function() { | ||
this.context.waitForDigit(); | ||
this.context.waitForDigit(5000); | ||
expect(this.context.sent.join('')).to.eql('WAIT FOR DIGIT 5000\n'); | ||
@@ -456,3 +458,3 @@ }); | ||
it('sends correct command', function() { | ||
this.context.verbose("good", 2); | ||
this.context.verbose('good', 2); | ||
expect(this.context.sent.join('')).to.eql('VERBOSE "good" 2\n'); | ||
@@ -459,0 +461,0 @@ }); |
33659
10.83%14
27.27%1006
33.25%75
-23.47%