├── .gitignore ├── LICENSE.txt ├── README.md ├── bridge-dial ├── example.js ├── example.py └── extensions.conf ├── bridge-hold ├── example.js ├── example.py └── extensions.conf ├── bridge-infinite-wait ├── example.js ├── example.py └── extensions.conf ├── bridge-move ├── example.js ├── example.py └── extensions.conf ├── channel-aa ├── channel-aa.js ├── channel-aa.py └── extensions.conf ├── channel-dump ├── example.js ├── example.py └── extensions.conf ├── channel-playback-monkeys ├── example.js ├── example.py └── extensions.conf ├── channel-state ├── example.cs ├── example.js ├── example.py └── extensions.conf └── channel-tones ├── example.js ├── example.py └── extensions.conf /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.swp 3 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Digium, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials 14 | provided with the distribution. 15 | 16 | The name of Digium, Inc., or the name of any Contributor, 17 | may not be used to endorse or promote products derived from this 18 | software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS NOT FAULT TOLERANT AND SHOULD NOT BE USED IN ANY 21 | SITUATION ENDANGERING HUMAN LIFE OR PROPERTY. 22 | 23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 28 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | OF THE POSSIBILITY OF SUCH DAMAGE. 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Asterisk ARI examples 2 | 3 | This repository contains a collection of ARI examples, written primarily in 4 | Python, JavaScript (Node.js) and C#. These ARI examples coincide with ARI 5 | documentation on the Asterisk wiki: 6 | 7 | https://wiki.asterisk.org/wiki/display/AST/Getting+Started+with+ARI 8 | 9 | The Python examples use the ari-py library: 10 | 11 | https://github.com/asterisk/ari-py 12 | 13 | The JavaScript examples use the node-ari-client library: 14 | 15 | https://github.com/asterisk/node-ari-client 16 | 17 | The .NET examples use the AsterNET.ARI client library: 18 | 19 | https://github.com/skrusty/AsterNET.ARI 20 | 21 | # Example Directory 22 | 23 | ## Channels 24 | 25 | ### channel-dump 26 | 27 | Dump basic information about the channels in an Asterisk system. 28 | 29 | ### channel-state 30 | 31 | Observe changes in channel state and Answer a channel. 32 | 33 | ### channel-playback-monkeys 34 | 35 | Play howler monkeys (with great anger) on a channel. 36 | 37 | ### channel-tones 38 | 39 | Manipulate locale specific indication tones on a channel. 40 | 41 | ### channel-aa 42 | 43 | Build a simple IVR/automated attendant by handling DTMF keypresses. 44 | 45 | ## Bridges 46 | 47 | ### bridge-hold 48 | 49 | Place all channels that enter into an application into a single holding bridge. 50 | 51 | ### bridge-infinite-area 52 | 53 | Place all channels that enter into an application into a holding bridge. Once all channels have left the bridge, destroy it. 54 | 55 | ### bridge-dial 56 | 57 | Dial an endpoint and put the resulting channel in a mixing bridge with the original Stasis channel. Gracefully handle hangups from either end. 58 | 59 | ### bridge-move 60 | 61 | Put channel that enters Stasis into a holding bridge with music on hold while dialing another endpoint. Once endpoint answers, remove original channel from holding bridge and put both channels in a mixing bridge. Gracefully handle hangups from either end. 62 | 63 | # License 64 | 65 | Copyright (c) 2014, Digium, Inc. All rights reserved. 66 | 67 | See the LICENSE.txt file for more information. 68 | 69 | -------------------------------------------------------------------------------- /bridge-dial/example.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 'use strict'; 3 | 4 | var ari = require('ari-client'); 5 | var util = require('util'); 6 | 7 | // ensure endpoint was passed in to script 8 | if (!process.argv[2]) { 9 | console.error('usage: node bridge-dial.js endpoint'); 10 | process.exit(1); 11 | } 12 | 13 | ari.connect('http://localhost:8088', 'asterisk', 'asterisk', clientLoaded); 14 | 15 | // handler for client being loaded 16 | function clientLoaded (err, client) { 17 | if (err) { 18 | throw err; 19 | } 20 | 21 | // handler for StasisStart event 22 | function stasisStart(event, channel) { 23 | // ensure the channel is not a dialed channel 24 | var dialed = event.args[0] === 'dialed'; 25 | 26 | if (!dialed) { 27 | channel.answer(function(err) { 28 | if (err) { 29 | throw err; 30 | } 31 | 32 | console.log('Channel %s has entered our application', channel.name); 33 | 34 | var playback = client.Playback(); 35 | channel.play({media: 'sound:pls-wait-connect-call'}, 36 | playback, function(err, playback) { 37 | if (err) { 38 | throw err; 39 | } 40 | }); 41 | 42 | originate(channel); 43 | }); 44 | } 45 | } 46 | 47 | function originate(channel) { 48 | var dialed = client.Channel(); 49 | 50 | channel.on('StasisEnd', function(event, channel) { 51 | hangupDialed(channel, dialed); 52 | }); 53 | 54 | dialed.on('ChannelDestroyed', function(event, dialed) { 55 | hangupOriginal(channel, dialed); 56 | }); 57 | 58 | dialed.on('StasisStart', function(event, dialed) { 59 | joinMixingBridge(channel, dialed); 60 | }); 61 | 62 | dialed.originate( 63 | {endpoint: process.argv[2], app: 'bridge-dial', appArgs: 'dialed'}, 64 | function(err, dialed) { 65 | if (err) { 66 | throw err; 67 | } 68 | }); 69 | } 70 | 71 | // handler for original channel hanging up so we can gracefully hangup the 72 | // other end 73 | function hangupDialed(channel, dialed) { 74 | console.log( 75 | 'Channel %s left our application, hanging up dialed channel %s', 76 | channel.name, dialed.name); 77 | 78 | // hangup the other end 79 | dialed.hangup(function(err) { 80 | // ignore error since dialed channel could have hung up, causing the 81 | // original channel to exit Stasis 82 | }); 83 | } 84 | 85 | // handler for the dialed channel hanging up so we can gracefully hangup the 86 | // other end 87 | function hangupOriginal(channel, dialed) { 88 | console.log('Dialed channel %s has been hung up, hanging up channel %s', 89 | dialed.name, channel.name); 90 | 91 | // hangup the other end 92 | channel.hangup(function(err) { 93 | // ignore error since original channel could have hung up, causing the 94 | // dialed channel to exit Stasis 95 | }); 96 | } 97 | 98 | // handler for dialed channel entering Stasis 99 | function joinMixingBridge(channel, dialed) { 100 | var bridge = client.Bridge(); 101 | 102 | dialed.on('StasisEnd', function(event, dialed) { 103 | dialedExit(dialed, bridge); 104 | }); 105 | 106 | dialed.answer(function(err) { 107 | if (err) { 108 | throw err; 109 | } 110 | }); 111 | 112 | bridge.create({type: 'mixing'}, function(err, bridge) { 113 | if (err) { 114 | throw err; 115 | } 116 | 117 | console.log('Created bridge %s', bridge.id); 118 | 119 | addChannelsToBridge(channel, dialed, bridge); 120 | }); 121 | } 122 | 123 | // handler for the dialed channel leaving Stasis 124 | function dialedExit(dialed, bridge) { 125 | console.log( 126 | 'Dialed channel %s has left our application, destroying bridge %s', 127 | dialed.name, bridge.id); 128 | 129 | bridge.destroy(function(err) { 130 | if (err) { 131 | throw err; 132 | } 133 | }); 134 | } 135 | 136 | // handler for new mixing bridge ready for channels to be added to it 137 | function addChannelsToBridge(channel, dialed, bridge) { 138 | console.log('Adding channel %s and dialed channel %s to bridge %s', 139 | channel.name, dialed.name, bridge.id); 140 | 141 | bridge.addChannel({channel: [channel.id, dialed.id]}, function(err) { 142 | if (err) { 143 | throw err; 144 | } 145 | }); 146 | } 147 | 148 | client.on('StasisStart', stasisStart); 149 | 150 | client.start('bridge-dial'); 151 | } 152 | -------------------------------------------------------------------------------- /bridge-dial/example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import logging 4 | import requests 5 | import ari 6 | 7 | logging.basicConfig(level=logging.ERROR) 8 | 9 | client = ari.connect('http://localhost:8088', 'asterisk', 'asterisk') 10 | 11 | 12 | def safe_hangup(channel): 13 | """Safely hang up the specified channel""" 14 | try: 15 | channel.hangup() 16 | print "Hung up {}".format(channel.json.get('name')) 17 | except requests.HTTPError as e: 18 | if e.response.status_code != requests.codes.not_found: 19 | raise e 20 | 21 | 22 | def safe_bridge_destroy(bridge): 23 | """Safely destroy the specified bridge""" 24 | try: 25 | bridge.destroy() 26 | except requests.HTTPError as e: 27 | if e.response.status_code != requests.codes.not_found: 28 | raise e 29 | 30 | 31 | def stasis_start_cb(channel_obj, ev): 32 | """Handler for StasisStart""" 33 | 34 | channel = channel_obj.get('channel') 35 | channel_name = channel.json.get('name') 36 | args = ev.get('args') 37 | 38 | if not args: 39 | print "Error: {} didn't provide any arguments!".format(channel_name) 40 | return 41 | 42 | if args and args[0] != 'inbound': 43 | # Only handle inbound channels here 44 | return 45 | 46 | if len(args) != 2: 47 | print "Error: {} didn't tell us who to dial".format(channel_name) 48 | channel.hangup() 49 | return 50 | 51 | print "{} entered our application".format(channel_name) 52 | channel.ring() 53 | 54 | try: 55 | print "Dialing {}".format(args[1]) 56 | outgoing = client.channels.originate(endpoint=args[1], 57 | app='bridge-dial', 58 | appArgs='dialed') 59 | except requests.HTTPError: 60 | print "Whoops, pretty sure %s wasn't valid" % args[1] 61 | channel.hangup() 62 | return 63 | 64 | channel.on_event('StasisEnd', lambda *args: safe_hangup(outgoing)) 65 | outgoing.on_event('StasisEnd', lambda *args: safe_hangup(channel)) 66 | 67 | def outgoing_start_cb(channel_obj, ev): 68 | """StasisStart handler for our dialed channel""" 69 | 70 | print "{} answered; bridging with {}".format(outgoing.json.get('name'), 71 | channel.json.get('name')) 72 | channel.answer() 73 | 74 | bridge = client.bridges.create(type='mixing') 75 | bridge.addChannel(channel=[channel.id, outgoing.id]) 76 | 77 | # Clean up the bridge when done 78 | channel.on_event('StasisEnd', lambda *args: 79 | safe_bridge_destroy(bridge)) 80 | outgoing.on_event('StasisEnd', lambda *args: 81 | safe_bridge_destroy(bridge)) 82 | 83 | outgoing.on_event('StasisStart', outgoing_start_cb) 84 | 85 | 86 | client.on_channel_event('StasisStart', stasis_start_cb) 87 | 88 | client.run(apps='bridge-dial') 89 | -------------------------------------------------------------------------------- /bridge-dial/extensions.conf: -------------------------------------------------------------------------------- 1 | exten => 1000,1,NoOp() 2 | same => n,Stasis(bridge-dial) 3 | same => n,Hangup() 4 | -------------------------------------------------------------------------------- /bridge-hold/example.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 'use strict'; 3 | 4 | var ari = require('ari-client'); 5 | var util = require('util'); 6 | 7 | ari.connect('http://localhost:8088', 'asterisk', 'asterisk', clientLoaded); 8 | 9 | // handler for client being loaded 10 | function clientLoaded (err, client) { 11 | if (err) { 12 | throw err; 13 | } 14 | 15 | // find or create a holding bridge 16 | var bridge = null; 17 | client.bridges.list(function(err, bridges) { 18 | if (err) { 19 | throw err; 20 | } 21 | 22 | bridge = bridges.filter(function(candidate) { 23 | return candidate.bridge_type === 'holding'; 24 | })[0]; 25 | 26 | if (bridge) { 27 | console.log(util.format('Using bridge %s', bridge.id)); 28 | } else { 29 | client.bridges.create({type: 'holding'}, function(err, newBridge) { 30 | if (err) { 31 | throw err; 32 | } 33 | 34 | bridge = newBridge; 35 | console.log(util.format('Created bridge %s', bridge.id)); 36 | }); 37 | } 38 | }); 39 | 40 | // handler for StasisStart event 41 | function stasisStart(event, channel) { 42 | console.log(util.format( 43 | 'Channel %s just entered our application, adding it to bridge %s', 44 | channel.name, 45 | bridge.id)); 46 | 47 | channel.answer(function(err) { 48 | if (err) { 49 | throw err; 50 | } 51 | 52 | bridge.addChannel({channel: channel.id}, function(err) { 53 | if (err) { 54 | throw err; 55 | } 56 | 57 | bridge.startMoh(function(err) { 58 | if (err) { 59 | throw err; 60 | } 61 | }); 62 | }); 63 | }); 64 | } 65 | 66 | // handler for StasisEnd event 67 | function stasisEnd(event, channel) { 68 | console.log(util.format( 69 | 'Channel %s just left our application', channel.name)); 70 | } 71 | 72 | client.on('StasisStart', stasisStart); 73 | client.on('StasisEnd', stasisEnd); 74 | 75 | client.start('bridge-hold'); 76 | } 77 | -------------------------------------------------------------------------------- /bridge-hold/example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import ari 4 | import logging 5 | 6 | logging.basicConfig(level=logging.ERROR) 7 | 8 | client = ari.connect('http://localhost:8088', 'asterisk', 'asterisk') 9 | 10 | # find or create a holding bridge 11 | bridges = [candidate for candidate in client.bridges.list() if 12 | candidate.json['bridge_type'] == 'holding'] 13 | if bridges: 14 | bridge = bridges[0] 15 | print "Using bridge %s" % bridge.id 16 | else: 17 | bridge = client.bridges.create(type='holding') 18 | print "Created bridge %s" % bridge.id 19 | 20 | def stasis_start_cb(channel_obj, ev): 21 | """Handler for StasisStart event""" 22 | 23 | channel = channel_obj.get('channel') 24 | print "Channel %s just entered our application, adding it to bridge %s" % ( 25 | channel.json.get('name'), bridge.id) 26 | 27 | channel.answer() 28 | bridge.addChannel(channel=channel.id) 29 | bridge.startMoh() 30 | 31 | def stasis_end_cb(channel, ev): 32 | """Handler for StasisEnd event""" 33 | 34 | print "Channel %s just left our application" % channel.json.get('name') 35 | 36 | client.on_channel_event('StasisStart', stasis_start_cb) 37 | client.on_channel_event('StasisEnd', stasis_end_cb) 38 | 39 | client.run(apps='bridge-hold') 40 | -------------------------------------------------------------------------------- /bridge-hold/extensions.conf: -------------------------------------------------------------------------------- 1 | [default] 2 | 3 | exten => 1000,1,NoOp() 4 | same => n,Answer() 5 | same => n,Stasis(bridge-hold) 6 | same => n,Hangup() 7 | -------------------------------------------------------------------------------- /bridge-infinite-wait/example.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 'use strict'; 3 | 4 | var ari = require('ari-client'); 5 | var util = require('util'); 6 | 7 | var timer = null; 8 | ari.connect('http://localhost:8088', 'asterisk', 'asterisk', clientLoaded); 9 | 10 | // handler for client being loaded 11 | function clientLoaded (err, client) { 12 | if (err) { 13 | throw err; 14 | } 15 | 16 | // handler for StasisStart event 17 | function stasisStart(event, channel) { 18 | console.log('Channel %s just entered our application', channel.name); 19 | 20 | // find or create a holding bridge 21 | client.bridges.list(function(err, bridges) { 22 | if (err) { 23 | throw err; 24 | } 25 | 26 | var bridge = bridges.filter(function(candidate) { 27 | return candidate.bridge_type === 'holding'; 28 | })[0]; 29 | 30 | if (bridge) { 31 | console.log('Using bridge %s', bridge.id); 32 | joinBridge(bridge); 33 | } else { 34 | client.bridges.create({type: 'holding'}, function(err, newBridge) { 35 | if (err) { 36 | throw err; 37 | } 38 | console.log('Created bridge %s', newBridge.id); 39 | newBridge.startMoh(function(err) { 40 | if (err) { 41 | throw err; 42 | } 43 | }); 44 | joinBridge(newBridge); 45 | 46 | timer = setTimeout(play_announcement, 30000); 47 | 48 | // callback that will let our users know how much we care 49 | function play_announcement() { 50 | console.log('Letting everyone know we care...'); 51 | newBridge.stopMoh(function(err) { 52 | if (err) { 53 | throw err; 54 | } 55 | 56 | var playback = client.Playback(); 57 | newBridge.play({media: 'sound:thnk-u-for-patience'}, 58 | playback, function(err, playback) { 59 | if (err) { 60 | throw err; 61 | } 62 | }); 63 | playback.once('PlaybackFinished', function(event, playback) { 64 | newBridge.startMoh(function(err) { 65 | if (err) { 66 | throw err; 67 | } 68 | }); 69 | timer = setTimeout(play_announcement, 30000); 70 | }); 71 | }); 72 | } 73 | }); 74 | } 75 | }); 76 | 77 | function joinBridge(bridge) { 78 | channel.once('ChannelLeftBridge', function(event, instances) { 79 | channelLeftBridge(event, instances, bridge); 80 | }); 81 | 82 | bridge.addChannel({channel: channel.id}, function(err) { 83 | if (err) { 84 | throw err; 85 | } 86 | }); 87 | channel.answer(function(err) { 88 | if (err) { 89 | throw err; 90 | } 91 | }); 92 | } 93 | 94 | // Handler for ChannelLeftBridge event 95 | function channelLeftBridge(event, instances, bridge) { 96 | var holdingBridge = instances.bridge; 97 | var channel = instances.channel; 98 | 99 | console.log('Channel %s left bridge %s', channel.name, bridge.id); 100 | 101 | if (holdingBridge.id === bridge.id && 102 | holdingBridge.channels.length === 0) { 103 | 104 | if (timer) { 105 | clearTimeout(timer); 106 | } 107 | 108 | bridge.destroy(function(err) { 109 | if (err) { 110 | throw err; 111 | } 112 | }); 113 | } 114 | } 115 | } 116 | 117 | // handler for StasisEnd event 118 | function stasisEnd(event, channel) { 119 | console.log('Channel %s just left our application', channel.name); 120 | } 121 | 122 | client.on('StasisStart', stasisStart); 123 | client.on('StasisEnd', stasisEnd); 124 | 125 | console.log('starting'); 126 | client.start('bridge-infinite-wait'); 127 | } 128 | -------------------------------------------------------------------------------- /bridge-infinite-wait/example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import ari 4 | import logging 5 | import threading 6 | 7 | logging.basicConfig(level=logging.ERROR) 8 | 9 | client = ari.connect('http://localhost:8088', 'asterisk', 'asterisk') 10 | 11 | # find or create a holding bridge 12 | holding_bridge = None 13 | 14 | # Announcer timer 15 | announcer_timer = None 16 | 17 | def find_or_create_bridge(): 18 | """Find our infinite wait bridge, or create a new one 19 | 20 | Returns: 21 | The one and only holding bridge 22 | """ 23 | 24 | global holding_bridge 25 | global announcer_timer 26 | 27 | if holding_bridge: 28 | return holding_bridge 29 | 30 | bridges = [candidate for candidate in client.bridges.list() if 31 | candidate.json['bridge_type'] == 'holding'] 32 | if bridges: 33 | bridge = bridges[0] 34 | print "Using bridge %s" % bridge.id 35 | else: 36 | bridge = client.bridges.create(type='holding') 37 | bridge.startMoh() 38 | print "Created bridge %s" % bridge.id 39 | 40 | def play_announcement(bridge): 41 | """Play an announcement to the bridge""" 42 | 43 | def on_playback_finished(playback, ev): 44 | """Handler for the announcement's PlaybackFinished event""" 45 | global announcer_timer 46 | global holding_bridge 47 | 48 | holding_bridge.startMoh() 49 | 50 | announcer_timer = threading.Timer(30, play_announcement, 51 | [holding_bridge]) 52 | announcer_timer.start() 53 | 54 | bridge.stopMoh() 55 | print "Letting everyone know we care..." 56 | thanks_playback = bridge.play(media='sound:thnk-u-for-patience') 57 | thanks_playback.on_event('PlaybackFinished', on_playback_finished) 58 | 59 | def on_channel_left_bridge(bridge, ev): 60 | """Handler for ChannelLeftBridge event""" 61 | global holding_bridge 62 | global announcer_timer 63 | 64 | channel = ev.get('channel') 65 | channel_count = len(bridge.json.get('channels')) 66 | 67 | print "Channel %s left bridge %s" % (channel.get('name'), bridge.id) 68 | if holding_bridge.id == bridge.id and channel_count == 0: 69 | if announcer_timer: 70 | announcer_timer.cancel() 71 | announcer_timer = None 72 | 73 | print "Destroying bridge %s" % bridge.id 74 | holding_bridge.destroy() 75 | holding_bridge = None 76 | 77 | holding_bridge = bridge 78 | holding_bridge.on_event('ChannelLeftBridge', on_channel_left_bridge) 79 | 80 | # After 30 seconds, let everyone in the bridge know that we care 81 | announcer_timer = threading.Timer(30, play_announcement, [holding_bridge]) 82 | announcer_timer.start() 83 | 84 | return bridge 85 | 86 | 87 | def stasis_start_cb(channel_obj, ev): 88 | """Handler for StasisStart event""" 89 | 90 | bridge = find_or_create_bridge() 91 | 92 | channel = channel_obj.get('channel') 93 | print "Channel %s just entered our application, adding it to bridge %s" % ( 94 | channel.json.get('name'), holding_bridge.id) 95 | 96 | channel.answer() 97 | bridge.addChannel(channel=channel.id) 98 | 99 | def stasis_end_cb(channel, ev): 100 | """Handler for StasisEnd event""" 101 | 102 | print "Channel %s just left our application" % channel.json.get('name') 103 | 104 | client.on_channel_event('StasisStart', stasis_start_cb) 105 | client.on_channel_event('StasisEnd', stasis_end_cb) 106 | 107 | client.run(apps='bridge-infinite-wait') 108 | 109 | -------------------------------------------------------------------------------- /bridge-infinite-wait/extensions.conf: -------------------------------------------------------------------------------- 1 | exten => 1000,1,NoOp() 2 | same => n,Stasis(bridge-infinite-wait) 3 | same => n,Hangup() 4 | -------------------------------------------------------------------------------- /bridge-move/example.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 'use strict'; 3 | 4 | var ari = require('ari-client'); 5 | var util = require('util'); 6 | 7 | // ensure endpoint was passed in to script 8 | if (!process.argv[2]) { 9 | console.error('usage: node bridge-move.js endpoint'); 10 | process.exit(1); 11 | } 12 | 13 | ari.connect('http://localhost:8088', 'asterisk', 'asterisk', clientLoaded); 14 | 15 | // handler for client being loaded 16 | function clientLoaded (err, client) { 17 | if (err) { 18 | throw err; 19 | } 20 | 21 | // handler for StasisStart event 22 | function stasisStart(event, channel) { 23 | // ensure the channel is not a dialed channel 24 | var dialed = event.args[0] === 'dialed'; 25 | 26 | if (!dialed) { 27 | channel.answer(function(err) { 28 | if (err) { 29 | throw err; 30 | } 31 | 32 | console.log('Channel %s has entered our application', channel.name); 33 | 34 | findOrCreateHoldingBridge(channel); 35 | }); 36 | } 37 | } 38 | 39 | function findOrCreateHoldingBridge(channel) { 40 | client.bridges.list(function(err, bridges) { 41 | var holdingBridge = bridges.filter(function(candidate) { 42 | return candidate.bridge_type === 'holding'; 43 | })[0]; 44 | 45 | if (holdingBridge) { 46 | console.log('Using existing holding bridge %s', holdingBridge.id); 47 | 48 | originate(channel, holdingBridge); 49 | } else { 50 | client.bridges.create({type: 'holding'}, function(err, holdingBridge) { 51 | if (err) { 52 | throw err; 53 | } 54 | 55 | console.log('Created new holding bridge %s', holdingBridge.id); 56 | 57 | originate(channel, holdingBridge); 58 | }); 59 | } 60 | }); 61 | } 62 | 63 | function originate(channel, holdingBridge) { 64 | holdingBridge.addChannel({channel: channel.id}, function(err) { 65 | if (err) { 66 | throw err; 67 | } 68 | 69 | holdingBridge.startMoh(function(err) { 70 | // ignore error 71 | }); 72 | }); 73 | 74 | var dialed = client.Channel(); 75 | 76 | channel.on('StasisEnd', function(event, channel) { 77 | safeHangup(dialed); 78 | }); 79 | 80 | dialed.on('ChannelDestroyed', function(event, dialed) { 81 | safeHangup(channel); 82 | }); 83 | 84 | dialed.on('StasisStart', function(event, dialed) { 85 | joinMixingBridge(channel, dialed, holdingBridge); 86 | }); 87 | 88 | dialed.originate( 89 | {endpoint: process.argv[2], app: 'bridge-move', appArgs: 'dialed'}, 90 | function(err, dialed) { 91 | if (err) { 92 | throw err; 93 | } 94 | }); 95 | } 96 | 97 | // safely hangs the given channel 98 | function safeHangup(channel) { 99 | console.log('Hanging up channel %s', channel.name); 100 | 101 | channel.hangup(function(err) { 102 | // ignore error 103 | }); 104 | } 105 | 106 | // handler for dialed channel entering Stasis 107 | function joinMixingBridge(channel, dialed, holdingBridge) { 108 | var mixingBridge = client.Bridge(); 109 | 110 | dialed.on('StasisEnd', function(event, dialed) { 111 | dialedExit(dialed, mixingBridge); 112 | }); 113 | 114 | dialed.answer(function(err) { 115 | if (err) { 116 | throw err; 117 | } 118 | }); 119 | 120 | mixingBridge.create({type: 'mixing'}, function(err, mixingBridge) { 121 | if (err) { 122 | throw err; 123 | } 124 | 125 | console.log('Created mixing bridge %s', mixingBridge.id); 126 | 127 | moveToMixingBridge(channel, dialed, mixingBridge, holdingBridge); 128 | }); 129 | } 130 | 131 | // handler for the dialed channel leaving Stasis 132 | function dialedExit(dialed, mixingBridge) { 133 | console.log( 134 | 'Dialed channel %s has left our application, destroying mixing bridge %s', 135 | dialed.name, mixingBridge.id); 136 | 137 | mixingBridge.destroy(function(err) { 138 | if (err) { 139 | throw err; 140 | } 141 | }); 142 | } 143 | 144 | // handler for new mixing bridge ready for channels to be added to it 145 | function moveToMixingBridge(channel, dialed, mixingBridge, holdingBridge) { 146 | console.log('Adding channel %s and dialed channel %s to bridge %s', 147 | channel.name, dialed.name, mixingBridge.id); 148 | 149 | holdingBridge.removeChannel({channel: channel.id}, function(err) { 150 | if (err) { 151 | throw err; 152 | } 153 | 154 | mixingBridge.addChannel( 155 | {channel: [channel.id, dialed.id]}, function(err) { 156 | if (err) { 157 | throw err; 158 | } 159 | }); 160 | }); 161 | } 162 | 163 | client.on('StasisStart', stasisStart); 164 | 165 | client.start('bridge-move'); 166 | } 167 | -------------------------------------------------------------------------------- /bridge-move/example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import logging 4 | import requests 5 | import ari 6 | 7 | logging.basicConfig(level=logging.ERROR) 8 | 9 | client = ari.connect('http://localhost:8088', 'asterisk', 'asterisk') 10 | 11 | # Our one and only holding bridge 12 | holding_bridge = None 13 | 14 | 15 | def find_or_create_holding_bridge(): 16 | """Find our infinite wait bridge, or create a new one 17 | 18 | Returns: 19 | The one and only holding bridge 20 | """ 21 | global holding_bridge 22 | 23 | if holding_bridge: 24 | return holding_bridge 25 | 26 | bridges = [candidate for candidate in client.bridges.list() if 27 | candidate.json['bridge_type'] == 'holding'] 28 | if bridges: 29 | bridge = bridges[0] 30 | print "Using bridge {}".format(bridge.id) 31 | else: 32 | bridge = client.bridges.create(type='holding') 33 | bridge.startMoh() 34 | print "Created bridge {}".format(bridge.id) 35 | 36 | holding_bridge = bridge 37 | return holding_bridge 38 | 39 | 40 | def safe_hangup(channel): 41 | """Safely hang up the specified channel""" 42 | try: 43 | channel.hangup() 44 | print "Hung up {}".format(channel.json.get('name')) 45 | except requests.HTTPError as e: 46 | if e.response.status_code != requests.codes.not_found: 47 | raise e 48 | 49 | 50 | def safe_bridge_destroy(bridge): 51 | """Safely destroy the specified bridge""" 52 | try: 53 | bridge.destroy() 54 | except requests.HTTPError as e: 55 | if e.response.status_code != requests.codes.not_found: 56 | raise e 57 | 58 | 59 | def stasis_start_cb(channel_obj, ev): 60 | """Handler for StasisStart""" 61 | 62 | channel = channel_obj.get('channel') 63 | channel_name = channel.json.get('name') 64 | args = ev.get('args') 65 | 66 | if not args: 67 | print "Error: {} didn't provide any arguments!".format(channel_name) 68 | return 69 | 70 | if args and args[0] != 'inbound': 71 | # Only handle inbound channels here 72 | return 73 | 74 | if len(args) != 2: 75 | print "Error: {} didn't tell us who to dial".format(channel_name) 76 | channel.hangup() 77 | return 78 | 79 | wait_bridge = find_or_create_holding_bridge() 80 | wait_bridge.addChannel(channel=channel.id) 81 | 82 | try: 83 | outgoing = client.channels.originate(endpoint=args[1], 84 | app='bridge-move', 85 | appArgs='dialed') 86 | except requests.HTTPError: 87 | print "Whoops, pretty sure %s wasn't valid" % args[1] 88 | channel.hangup() 89 | return 90 | 91 | channel.on_event('StasisEnd', lambda *args: safe_hangup(outgoing)) 92 | outgoing.on_event('StasisEnd', lambda *args: safe_hangup(channel)) 93 | 94 | def outgoing_start_cb(channel_obj, ev): 95 | """StasisStart handler for our dialed channel""" 96 | 97 | print "{} answered; bridging with {}".format(outgoing.json.get('name'), 98 | channel.json.get('name')) 99 | 100 | wait_bridge = find_or_create_holding_bridge() 101 | wait_bridge.removeChannel(channel=channel.id) 102 | 103 | bridge = client.bridges.create(type='mixing') 104 | bridge.addChannel(channel=[channel.id, outgoing.id]) 105 | 106 | # Clean up the bridge when done 107 | channel.on_event('StasisEnd', lambda *args: 108 | safe_bridge_destroy(bridge)) 109 | outgoing.on_event('StasisEnd', lambda *args: 110 | safe_bridge_destroy(bridge)) 111 | 112 | outgoing.on_event('StasisStart', outgoing_start_cb) 113 | 114 | 115 | client.on_channel_event('StasisStart', stasis_start_cb) 116 | 117 | client.run(apps='bridge-move') 118 | -------------------------------------------------------------------------------- /bridge-move/extensions.conf: -------------------------------------------------------------------------------- 1 | exten => 1000,1,NoOp() 2 | same => n,Stasis(bridge-move) 3 | same => n,Hangup() 4 | -------------------------------------------------------------------------------- /channel-aa/channel-aa.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 'use strict'; 3 | 4 | var ari = require('ari-client'); 5 | var util = require('util'); 6 | 7 | ari.connect('http://localhost:8088', 'asterisk', 'asterisk', clientLoaded); 8 | 9 | var menu = { 10 | // valid menu options 11 | options: [1, 2], 12 | // note: this uses the 'extra' sounds package 13 | sounds: ['sound:press-1', 'sound:or', 'sound:press-2'] 14 | }; 15 | 16 | var timers = {}; 17 | 18 | // Handler for client being loaded 19 | function clientLoaded (err, client) { 20 | if (err) { 21 | throw err; 22 | } 23 | 24 | client.on('StasisStart', stasisStart); 25 | client.on('StasisEnd', stasisEnd); 26 | 27 | // Handler for StasisStart event 28 | function stasisStart(event, channel) { 29 | console.log('Channel %s has entered the application', channel.name); 30 | 31 | channel.on('ChannelDtmfReceived', dtmfReceived); 32 | 33 | channel.answer(function(err) { 34 | if (err) { 35 | throw err; 36 | } 37 | 38 | playIntroMenu(channel); 39 | }); 40 | } 41 | 42 | // Handler for StasisEnd event 43 | function stasisEnd(event, channel) { 44 | console.log('Channel %s has left the application', channel.name); 45 | 46 | // clean up listeners 47 | channel.removeListener('ChannelDtmfReceived', dtmfReceived); 48 | cancelTimeout(channel); 49 | } 50 | 51 | // Main DTMF handler 52 | function dtmfReceived(event, channel) { 53 | cancelTimeout(channel); 54 | var digit = parseInt(event.digit); 55 | 56 | console.log('Channel %s entered %d', channel.name, digit); 57 | 58 | // will be non-zero if valid 59 | var valid = ~menu.options.indexOf(digit); 60 | if (valid) { 61 | handleDtmf(channel, digit); 62 | } else { 63 | console.log('Channel %s entered an invalid option!', channel.name); 64 | 65 | channel.play({media: 'sound:option-is-invalid'}, function(err, playback) { 66 | if (err) { 67 | throw err; 68 | } 69 | 70 | playIntroMenu(channel); 71 | }); 72 | } 73 | } 74 | 75 | /** 76 | * Play our intro menu to the specified channel 77 | * 78 | * Since we want to interrupt the playback of the menu when the user presses 79 | * a DTMF key, we maintain the state of the menu via the MenuState object. 80 | * A menu completes in one of two ways: 81 | * (1) The user hits a key 82 | * (2) The menu finishes to completion 83 | * 84 | * In the case of (2), a timer is started for the channel. If the timer pops, 85 | * a prompt is played back and the menu restarted. 86 | **/ 87 | function playIntroMenu(channel) { 88 | var state = { 89 | currentSound: menu.sounds[0], 90 | currentPlayback: undefined, 91 | done: false 92 | }; 93 | 94 | channel.on('ChannelDtmfReceived', cancelMenu); 95 | channel.on('StasisEnd', cancelMenu); 96 | queueUpSound(); 97 | 98 | // Cancel the menu, as the user did something 99 | function cancelMenu() { 100 | state.done = true; 101 | if (state.currentPlayback) { 102 | state.currentPlayback.stop(function(err) { 103 | // ignore errors 104 | }); 105 | } 106 | 107 | // remove listeners as future calls to playIntroMenu will create new ones 108 | channel.removeListener('ChannelDtmfReceived', cancelMenu); 109 | channel.removeListener('StasisEnd', cancelMenu); 110 | } 111 | 112 | // Start up the next sound and handle whatever happens 113 | function queueUpSound() { 114 | if (!state.done) { 115 | // have we played all sounds in the menu? 116 | if (!state.currentSound) { 117 | var timer = setTimeout(stillThere, 10 * 1000); 118 | timers[channel.id] = timer; 119 | } else { 120 | var playback = client.Playback(); 121 | state.currentPlayback = playback; 122 | 123 | channel.play({media: state.currentSound}, playback, function(err) { 124 | // ignore errors 125 | }); 126 | playback.once('PlaybackFinished', function(event, playback) { 127 | queueUpSound(); 128 | }); 129 | 130 | var nextSoundIndex = menu.sounds.indexOf(state.currentSound) + 1; 131 | state.currentSound = menu.sounds[nextSoundIndex]; 132 | } 133 | } 134 | } 135 | 136 | // plays are-you-still-there and restarts the menu 137 | function stillThere() { 138 | console.log('Channel %s stopped paying attention...', channel.name); 139 | 140 | channel.play({media: 'sound:are-you-still-there'}, function(err) { 141 | if (err) { 142 | throw err; 143 | } 144 | 145 | playIntroMenu(channel); 146 | }); 147 | } 148 | } 149 | 150 | // Cancel the timeout for the channel 151 | function cancelTimeout(channel) { 152 | var timer = timers[channel.id]; 153 | 154 | if (timer) { 155 | clearTimeout(timer); 156 | delete timers[channel.id]; 157 | } 158 | } 159 | 160 | // Handler for channel pressing valid option 161 | function handleDtmf(channel, digit) { 162 | var parts = ['sound:you-entered', util.format('digits:%s', digit)]; 163 | var done = 0; 164 | 165 | var playback = client.Playback(); 166 | channel.play({media: 'sound:you-entered'}, playback, function(err) { 167 | // ignore errors 168 | channel.play({media: util.format('digits:%s', digit)}, function(err) { 169 | // ignore errors 170 | playIntroMenu(channel); 171 | }); 172 | }); 173 | } 174 | 175 | client.start('channel-aa'); 176 | } 177 | -------------------------------------------------------------------------------- /channel-aa/channel-aa.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import ari 4 | import logging 5 | import threading 6 | 7 | logging.basicConfig(level=logging.ERROR) 8 | 9 | client = ari.connect('http://localhost:8088', 'asterisk', 'asterisk') 10 | 11 | # Note: this uses the 'extra' sounds package 12 | sounds = ['press-1', 'or', 'press-2'] 13 | 14 | channel_timers = {} 15 | 16 | class MenuState(object): 17 | """A small tracking object for the channel in the menu""" 18 | 19 | def __init__(self, current_sound, complete): 20 | self.current_sound = current_sound 21 | self.complete = complete 22 | 23 | 24 | def play_intro_menu(channel): 25 | """Play our intro menu to the specified channel 26 | 27 | Since we want to interrupt the playback of the menu when the user presses 28 | a DTMF key, we maintain the state of the menu via the MenuState object. 29 | A menu completes in one of two ways: 30 | (1) The user hits a key 31 | (2) The menu finishes to completion 32 | 33 | In the case of (2), a timer is started for the channel. If the timer pops, 34 | a prompt is played back and the menu restarted. 35 | 36 | Keyword Arguments: 37 | channel The channel in the IVR 38 | """ 39 | 40 | menu_state = MenuState(0, False) 41 | 42 | def play_next_sound(menu_state): 43 | """Play the next sound, if we should 44 | 45 | Keyword Arguments: 46 | menu_state The current state of the IVR 47 | 48 | Returns: 49 | None if no playback should occur 50 | A playback object if a playback was started 51 | """ 52 | if menu_state.current_sound == len(sounds) or menu_state.complete: 53 | return None 54 | try: 55 | current_playback = channel.play(media='sound:%s' % 56 | sounds[menu_state.current_sound]) 57 | except: 58 | current_playback = None 59 | return current_playback 60 | 61 | def queue_up_sound(channel, menu_state): 62 | """Start up the next sound and handle whatever happens 63 | 64 | Keywords Arguments: 65 | channel The channel in the IVR 66 | menu_state The current state of the menu 67 | """ 68 | 69 | def on_playback_finished(playback, ev, menu_state): 70 | """Callback handler for when a playback is finished 71 | 72 | Keyword Arguments: 73 | playback The playback object that finished 74 | ev The PlaybackFinished event 75 | menu_state The current state of the menu 76 | """ 77 | unsubscribe_playback_event() 78 | queue_up_sound(channel, menu_state) 79 | 80 | def menu_timeout(channel, menu_state): 81 | """Callback called by a timer when the menu times out""" 82 | print 'Channel %s stopped paying attention...' % \ 83 | channel.json.get('name') 84 | channel.play(media='sound:are-you-still-there') 85 | play_intro_menu(channel) 86 | 87 | def cancel_menu(channel, ev, current_playback, menu_state): 88 | """Cancel the menu, as the user did something""" 89 | menu_state.complete = True 90 | try: 91 | current_playback.stop() 92 | except: 93 | pass 94 | unsubscribe_cancel_menu_events() 95 | return 96 | 97 | current_playback = play_next_sound(menu_state) 98 | if not current_playback: 99 | # only start timer if menu is not complete 100 | if menu_state.current_sound == len(sounds) and \ 101 | menu_state.complete == False: 102 | # Menu played, start a timer! 103 | timer = threading.Timer(10, menu_timeout, [channel, menu_state]) 104 | channel_timers[channel.id] = timer 105 | timer.start() 106 | return 107 | 108 | menu_state.current_sound += 1 109 | playback_event = current_playback.on_event('PlaybackFinished', 110 | on_playback_finished, 111 | menu_state) 112 | 113 | # If the user hits a key or hangs up, cancel the menu operations 114 | dtmf_event = channel.on_event('ChannelDtmfReceived', cancel_menu, 115 | current_playback, menu_state) 116 | stasis_end_event = channel.on_event('StasisEnd', cancel_menu, 117 | current_playback, menu_state) 118 | 119 | def unsubscribe_cancel_menu_events(): 120 | """Unsubscribe to the ChannelDtmfReceived and StasisEnd events""" 121 | dtmf_event.close() 122 | stasis_end_event.close() 123 | 124 | def unsubscribe_playback_event(): 125 | """Unsubscribe to the PlaybackFinished event""" 126 | playback_event.close() 127 | 128 | queue_up_sound(channel, menu_state) 129 | 130 | 131 | def handle_extension_one(channel): 132 | """Handler for a channel pressing '1' 133 | 134 | Keyword Arguments: 135 | channel The channel in the IVR 136 | """ 137 | channel.play(media='sound:you-entered') 138 | channel.play(media='digits:1') 139 | play_intro_menu(channel) 140 | 141 | 142 | def handle_extension_two(channel): 143 | """Handler for a channel pressing '2' 144 | 145 | Keyword Arguments: 146 | channel The channel in the IVR 147 | """ 148 | channel.play(media='sound:you-entered') 149 | channel.play(media='digits:2') 150 | play_intro_menu(channel) 151 | 152 | 153 | def cancel_timeout(channel): 154 | """Cancel the timeout timer for the channel 155 | 156 | Keyword Arguments: 157 | channel The channel in the IVR 158 | """ 159 | timer = channel_timers.get(channel.id) 160 | if timer: 161 | timer.cancel() 162 | del channel_timers[channel.id] 163 | 164 | 165 | def on_dtmf_received(channel, ev): 166 | """Our main DTMF handler for a channel in the IVR 167 | 168 | Keyword Arguments: 169 | channel The channel in the IVR 170 | digit The DTMF digit that was pressed 171 | """ 172 | 173 | # Since they pressed something, cancel the timeout timer 174 | cancel_timeout(channel) 175 | digit = int(ev.get('digit')) 176 | 177 | print 'Channel %s entered %d' % (channel.json.get('name'), digit) 178 | if digit == 1: 179 | handle_extension_one(channel) 180 | elif digit == 2: 181 | handle_extension_two(channel) 182 | else: 183 | print 'Channel %s entered an invalid option!' % channel.json.get('name') 184 | channel.play(media='sound:option-is-invalid') 185 | play_intro_menu(channel) 186 | 187 | 188 | def stasis_start_cb(channel_obj, ev): 189 | """Handler for StasisStart event""" 190 | 191 | channel = channel_obj.get('channel') 192 | print "Channel %s has entered the application" % channel.json.get('name') 193 | 194 | channel.answer() 195 | channel.on_event('ChannelDtmfReceived', on_dtmf_received) 196 | play_intro_menu(channel) 197 | 198 | 199 | def stasis_end_cb(channel, ev): 200 | """Handler for StasisEnd event""" 201 | 202 | print "%s has left the application" % channel.json.get('name') 203 | cancel_timeout(channel) 204 | 205 | 206 | client.on_channel_event('StasisStart', stasis_start_cb) 207 | client.on_channel_event('StasisEnd', stasis_end_cb) 208 | 209 | client.run(apps='channel-aa') 210 | 211 | 212 | -------------------------------------------------------------------------------- /channel-aa/extensions.conf: -------------------------------------------------------------------------------- 1 | [default] 2 | 3 | exten => 1000,1,NoOp() 4 | same => n,Answer() 5 | same => n,Stasis(channel-aa) 6 | same => n,Hangup() 7 | -------------------------------------------------------------------------------- /channel-dump/example.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 'use strict'; 3 | 4 | var ari = require('ari-client'); 5 | var util = require('util'); 6 | 7 | ari.connect('http://localhost:8088', 'asterisk', 'asterisk', clientLoaded); 8 | 9 | // handler for client being loaded 10 | function clientLoaded (err, client) { 11 | if (err) { 12 | throw err; 13 | } 14 | 15 | client.channels.list(function(err, channels) { 16 | if (!channels.length) { 17 | console.log('No channels currently :-('); 18 | } else { 19 | console.log('Current channels:'); 20 | channels.forEach(function(channel) { 21 | console.log(channel.name); 22 | }); 23 | } 24 | }); 25 | 26 | // handler for StasisStart event 27 | function stasisStart(event, channel) { 28 | console.log(util.format( 29 | 'Channel %s has entered the application', channel.name)); 30 | 31 | // use keys on event since channel will also contain channel operations 32 | Object.keys(event.channel).forEach(function(key) { 33 | console.log(util.format('%s: %s', key, JSON.stringify(channel[key]))); 34 | }); 35 | } 36 | 37 | // handler for StasisEnd event 38 | function stasisEnd(event, channel) { 39 | console.log(util.format( 40 | 'Channel %s has left the application', channel.name)); 41 | } 42 | 43 | client.on('StasisStart', stasisStart); 44 | client.on('StasisEnd', stasisEnd); 45 | 46 | client.start('channel-dump'); 47 | } 48 | -------------------------------------------------------------------------------- /channel-dump/example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import ari 4 | import logging 5 | 6 | logging.basicConfig(level=logging.ERROR) 7 | 8 | client = ari.connect('http://localhost:8088', 'asterisk', 'asterisk') 9 | 10 | current_channels = client.channels.list() 11 | if len(current_channels) == 0: 12 | print "No channels currently :-(" 13 | else: 14 | print "Current channels:" 15 | for channel in current_channels: 16 | print channel.json.get('name') 17 | 18 | def stasis_start_cb(channel_obj, ev): 19 | """Handler for StasisStart event""" 20 | 21 | channel = channel_obj.get('channel') 22 | print "Channel %s has entered the application" % channel.json.get('name') 23 | 24 | for key, value in channel.json.items(): 25 | print "%s: %s" % (key, value) 26 | 27 | def stasis_end_cb(channel, ev): 28 | """Handler for StasisEnd event""" 29 | 30 | print "%s has left the application" % channel.json.get('name') 31 | 32 | client.on_channel_event('StasisStart', stasis_start_cb) 33 | client.on_channel_event('StasisEnd', stasis_end_cb) 34 | 35 | client.run(apps='channel-dump') 36 | -------------------------------------------------------------------------------- /channel-dump/extensions.conf: -------------------------------------------------------------------------------- 1 | [default] 2 | 3 | exten => 1000,1,NoOp() 4 | same => n,Answer() 5 | same => n,Stasis(channel-dump) 6 | same => n,Hangup() 7 | -------------------------------------------------------------------------------- /channel-playback-monkeys/example.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true*/ 2 | 'use strict'; 3 | 4 | var ari = require('ari-client'); 5 | var util = require('util'); 6 | 7 | ari.connect('http://localhost:8088', 'asterisk', 'asterisk', clientLoaded); 8 | 9 | // handler for client being loaded 10 | function clientLoaded (err, client) { 11 | if (err) { 12 | throw err; 13 | } 14 | 15 | // handler for StasisStart event 16 | function stasisStart(event, channel) { 17 | console.log(util.format( 18 | 'Monkeys! Attack %s!', channel.name)); 19 | 20 | var playback = client.Playback(); 21 | channel.play({media: 'sound:tt-monkeys'}, 22 | playback, function(err, newPlayback) { 23 | if (err) { 24 | throw err; 25 | } 26 | }); 27 | playback.on('PlaybackFinished', playbackFinished); 28 | 29 | function playbackFinished(event, completedPlayback) { 30 | console.log(util.format( 31 | 'Monkeys successfully vanquished %s; hanging them up', 32 | channel.name)); 33 | channel.hangup(function(err) { 34 | if (err) { 35 | throw err; 36 | } 37 | }); 38 | } 39 | } 40 | 41 | // handler for StasisEnd event 42 | function stasisEnd(event, channel) { 43 | console.log(util.format( 44 | 'Channel %s just left our application', channel.name)); 45 | } 46 | 47 | client.on('StasisStart', stasisStart); 48 | client.on('StasisEnd', stasisEnd); 49 | 50 | client.start('channel-playback-monkeys'); 51 | } 52 | -------------------------------------------------------------------------------- /channel-playback-monkeys/example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import ari 4 | import logging 5 | import uuid 6 | 7 | logging.basicConfig(level=logging.ERROR) 8 | 9 | client = ari.connect('http://localhost:8088', 'asterisk', 'asterisk') 10 | 11 | def stasis_end_cb(channel, ev): 12 | """Handler for StasisEnd event""" 13 | 14 | print "Channel %s just left our application" % channel.json.get('name') 15 | 16 | def stasis_start_cb(channel_obj, ev): 17 | """Handler for StasisStart event""" 18 | 19 | def playback_finished(playback, ev): 20 | """Callback when the monkeys have finished howling""" 21 | 22 | target_uri = playback.json.get('target_uri') 23 | channel_id = target_uri.replace('channel:', '') 24 | channel = client.channels.get(channelId=channel_id) 25 | 26 | print "Monkeys successfully vanquished %s; hanging them up" % channel.json.get('name') 27 | channel.hangup() 28 | 29 | channel = channel_obj.get('channel') 30 | print "Monkeys! Attack %s!" % channel.json.get('name') 31 | 32 | playback_id = str(uuid.uuid4()) 33 | playback = channel.playWithId(playbackId=playback_id, 34 | media='sound:tt-monkeys') 35 | playback.on_event('PlaybackFinished', playback_finished) 36 | 37 | client.on_channel_event('StasisStart', stasis_start_cb) 38 | client.on_channel_event('StasisEnd', stasis_end_cb) 39 | 40 | client.run(apps='channel-playback-monkeys') 41 | -------------------------------------------------------------------------------- /channel-playback-monkeys/extensions.conf: -------------------------------------------------------------------------------- 1 | exten => 1000,1,NoOp() 2 | same => n,Stasis(channel-playback-monkeys) 3 | same => n,Hangup() 4 | -------------------------------------------------------------------------------- /channel-state/example.cs: -------------------------------------------------------------------------------- 1 | using AsterNET.ARI; 2 | using System; 3 | using System.Threading; 4 | 5 | namespace channel_state 6 | { 7 | class Program 8 | { 9 | private static ARIClient _ari; 10 | static void Main(string[] args) 11 | { 12 | _ari = new ARIClient(new StasisEndpoint("localhost", 8088, "asterisk", "asterisk"), "channel-state"); 13 | _ari.OnStasisStartEvent += ari_OnStasisStartEvent; 14 | _ari.OnStasisEndEvent += ari_OnStasisEndEvent; 15 | _ari.OnChannelStateChangeEvent += ari_OnChannelStateChangeEvent; 16 | 17 | Console.WriteLine("Press any key to exit"); 18 | Console.ReadKey(); 19 | 20 | _ari.Disconnect(); 21 | } 22 | 23 | static void ari_OnChannelStateChangeEvent(object sender, AsterNET.ARI.Models.ChannelStateChangeEvent e) 24 | { 25 | Console.WriteLine("Channel {0} is now {1}", e.Channel.Id, e.Channel.State); 26 | } 27 | 28 | static void ari_OnStasisEndEvent(object sender, AsterNET.ARI.Models.StasisEndEvent e) 29 | { 30 | Console.WriteLine("Channel {0} just left our application", e.Channel.Id); 31 | } 32 | 33 | static void ari_OnStasisStartEvent(object sender, AsterNET.ARI.Models.StasisStartEvent e) 34 | { 35 | Console.WriteLine("Channel {0} has entered the application", e.Channel.Id); 36 | _ari.Channels.Ring(e.Channel.Id); 37 | Thread.Sleep(2000); // wait 2 seconds 38 | Console.WriteLine("Answering channel {0}", e.Channel.Id); 39 | 40 | _ari.Channels.Answer(e.Channel.Id); 41 | _ari.Channels.StartSilence(e.Channel.Id); 42 | Thread.Sleep(4000); // wait 4 seconds 43 | 44 | Console.WriteLine("hanging up channel {0}", e.Channel.Id); 45 | _ari.Channels.Hangup(e.Channel.Id); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /channel-state/example.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true*/ 2 | 'use strict'; 3 | 4 | var ari = require('ari-client'); 5 | var util = require('util'); 6 | 7 | var timers = {}; 8 | ari.connect('http://localhost:8088', 'asterisk', 'asterisk', clientLoaded); 9 | 10 | // handler for client being loaded 11 | function clientLoaded (err, client) { 12 | if (err) { 13 | throw err; 14 | } 15 | 16 | // handler for StasisStart event 17 | function stasisStart(event, channel) { 18 | console.log(util.format( 19 | 'Channel %s has entered the application', channel.name)); 20 | 21 | channel.ring(function(err) { 22 | if (err) { 23 | throw err; 24 | } 25 | }); 26 | // answer the channel after 2 seconds 27 | var timer = setTimeout(answer, 2000); 28 | timers[channel.id] = timer; 29 | 30 | // callback that will answer the channel 31 | function answer() { 32 | console.log(util.format('Answering channel %s', channel.name)); 33 | channel.answer(function(err) { 34 | if (err) { 35 | throw err; 36 | } 37 | }); 38 | channel.startSilence(function(err) { 39 | if (err) { 40 | throw err; 41 | } 42 | }); 43 | // hang up the channel in 4 seconds 44 | var timer = setTimeout(hangup, 4000); 45 | timers[channel.id] = timer; 46 | } 47 | 48 | // callback that will hangup the channel 49 | function hangup() { 50 | console.log(util.format('Hanging up channel %s', channel.name)); 51 | channel.hangup(function(err) { 52 | if (err) { 53 | throw err; 54 | } 55 | }); 56 | } 57 | } 58 | 59 | // handler for StasisEnd event 60 | function stasisEnd(event, channel) { 61 | console.log(util.format( 62 | 'Channel %s just left our application', channel.name)); 63 | var timer = timers[channel.id]; 64 | if (timer) { 65 | clearTimeout(timer); 66 | delete timers[channel.id]; 67 | } 68 | } 69 | 70 | // handler for ChannelStateChange event 71 | function channelStateChange(event, channel) { 72 | console.log(util.format( 73 | 'Channel %s is now: %s', channel.name, channel.state)); 74 | } 75 | 76 | client.on('StasisStart', stasisStart); 77 | client.on('StasisEnd', stasisEnd); 78 | client.on('ChannelStateChange', channelStateChange); 79 | 80 | client.start('channel-state'); 81 | } 82 | -------------------------------------------------------------------------------- /channel-state/example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import ari 4 | import logging 5 | import threading 6 | 7 | logging.basicConfig(level=logging.ERROR) 8 | 9 | client = ari.connect('http://localhost:8088', 'asterisk', 'asterisk') 10 | 11 | channel_timers = {} 12 | 13 | def stasis_end_cb(channel, ev): 14 | """Handler for StasisEnd event""" 15 | 16 | print "Channel %s just left our application" % channel.json.get('name') 17 | 18 | # Cancel any pending timers 19 | timer = channel_timers.get(channel.id) 20 | if timer: 21 | timer.cancel() 22 | del channel_timers[channel.id] 23 | 24 | def stasis_start_cb(channel_obj, ev): 25 | """Handler for StasisStart event""" 26 | 27 | def answer_channel(channel): 28 | """Callback that will actually answer the channel""" 29 | print "Answering channel %s" % channel.json.get('name') 30 | channel.answer() 31 | channel.startSilence() 32 | 33 | # Hang up the channel in 4 seconds 34 | timer = threading.Timer(4, hangup_channel, [channel]) 35 | channel_timers[channel.id] = timer 36 | timer.start() 37 | 38 | def hangup_channel(channel): 39 | """Callback that will actually hangup the channel""" 40 | 41 | print "Hanging up channel %s" % channel.json.get('name') 42 | channel.hangup() 43 | 44 | channel = channel_obj.get('channel') 45 | print "Channel %s has entered the application" % channel.json.get('name') 46 | 47 | channel.ring() 48 | # Answer the channel after 2 seconds 49 | timer = threading.Timer(2, answer_channel, [channel]) 50 | channel_timers[channel.id] = timer 51 | timer.start() 52 | 53 | def channel_state_change_cb(channel, ev): 54 | """Handler for changes in a channel's state""" 55 | print "Channel %s is now: %s" % (channel.json.get('name'), 56 | channel.json.get('state')) 57 | 58 | client.on_channel_event('StasisStart', stasis_start_cb) 59 | client.on_channel_event('ChannelStateChange', channel_state_change_cb) 60 | client.on_channel_event('StasisEnd', stasis_end_cb) 61 | 62 | client.run(apps='channel-state') 63 | -------------------------------------------------------------------------------- /channel-state/extensions.conf: -------------------------------------------------------------------------------- 1 | exten => 1000,1,NoOp() 2 | same => n,Stasis(channel-state) 3 | same => n,Hangup() 4 | -------------------------------------------------------------------------------- /channel-tones/example.js: -------------------------------------------------------------------------------- 1 | /*jshint node: true*/ 2 | 'use strict'; 3 | 4 | var ari = require('ari-client'); 5 | var util = require('util'); 6 | 7 | var timers = {}; 8 | ari.connect('http://localhost:8088', 'asterisk', 'asterisk', clientLoaded); 9 | 10 | // handler for client being loaded 11 | function clientLoaded (err, client) { 12 | if (err) { 13 | throw err; 14 | } 15 | 16 | // handler for StasisStart event 17 | function stasisStart(event, channel) { 18 | console.log(util.format( 19 | 'Channel %s has entered the application', channel.name)); 20 | 21 | var playback = client.Playback(); 22 | channel.play({media: 'tone:ring;tonezone=fr'}, 23 | playback, function(err, newPlayback) { 24 | if (err) { 25 | throw err; 26 | } 27 | }); 28 | // answer the channel after 8 seconds 29 | var timer = setTimeout(answer, 8000); 30 | timers[channel.id] = timer; 31 | 32 | // callback that will answer the channel 33 | function answer() { 34 | console.log(util.format('Answering channel %s', channel.name)); 35 | playback.stop(function(err) { 36 | if (err) { 37 | throw err; 38 | } 39 | }); 40 | channel.answer(function(err) { 41 | if (err) { 42 | throw err; 43 | } 44 | }); 45 | // hang up the channel in 1 seconds 46 | var timer = setTimeout(hangup, 1000); 47 | timers[channel.id] = timer; 48 | } 49 | 50 | // callback that will hangup the channel 51 | function hangup() { 52 | console.log(util.format('Hanging up channel %s', channel.name)); 53 | channel.hangup(function(err) { 54 | if (err) { 55 | throw err; 56 | } 57 | }); 58 | } 59 | } 60 | 61 | // handler for StasisEnd event 62 | function stasisEnd(event, channel) { 63 | console.log(util.format( 64 | 'Channel %s just left our application', channel.name)); 65 | var timer = timers[channel.id]; 66 | if (timer) { 67 | clearTimeout(timer); 68 | delete timers[channel.id]; 69 | } 70 | } 71 | 72 | client.on('StasisStart', stasisStart); 73 | client.on('StasisEnd', stasisEnd); 74 | 75 | client.start('channel-tones'); 76 | } 77 | -------------------------------------------------------------------------------- /channel-tones/example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import ari 4 | import logging 5 | import threading 6 | import uuid 7 | 8 | logging.basicConfig(level=logging.ERROR) 9 | 10 | client = ari.connect('http://localhost:8088', 'asterisk', 'asterisk') 11 | 12 | channel_timers = {} 13 | 14 | def stasis_end_cb(channel, ev): 15 | """Handler for StasisEnd event""" 16 | 17 | print "Channel %s just left our application" % channel.json.get('name') 18 | timer = channel_timers.get(channel.id) 19 | if timer: 20 | timer.cancel() 21 | del channel_timers[channel.id] 22 | 23 | def stasis_start_cb(channel_obj, ev): 24 | """Handler for StasisStart event""" 25 | 26 | def answer_channel(channel, playback): 27 | """Callback that will actually answer the channel""" 28 | 29 | print "Answering channel %s" % channel.json.get('name') 30 | playback.stop() 31 | channel.answer() 32 | 33 | timer = threading.Timer(1, hangup_channel, [channel]) 34 | channel_timers[channel.id] = timer 35 | timer.start() 36 | 37 | def hangup_channel(channel): 38 | """Callback that will actually hangup the channel""" 39 | 40 | print "Hanging up channel %s" % channel.json.get('name') 41 | channel.hangup() 42 | 43 | channel = channel_obj.get('channel') 44 | print "Channel %s has entered the application" % channel.json.get('name') 45 | 46 | playback_id = str(uuid.uuid4()) 47 | playback = channel.playWithId(playbackId=playback_id, 48 | media='tone:ring;tonezone=fr') 49 | timer = threading.Timer(8, answer_channel, [channel, playback]) 50 | channel_timers[channel.id] = timer 51 | timer.start() 52 | 53 | client.on_channel_event('StasisStart', stasis_start_cb) 54 | client.on_channel_event('StasisEnd', stasis_end_cb) 55 | 56 | client.run(apps='channel-tones') 57 | -------------------------------------------------------------------------------- /channel-tones/extensions.conf: -------------------------------------------------------------------------------- 1 | exten => 1000,1,NoOp() 2 | same => n,Stasis(channel-tones) 3 | same => n,Hangup() 4 | --------------------------------------------------------------------------------