rami 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (7) hide show
  1. data/LICENSE +26 -0
  2. data/README +3 -0
  3. data/bin/server.rb +27 -0
  4. data/bin/test.rb +144 -0
  5. data/lib/rami.rb +748 -0
  6. data/manager.txt +43 -0
  7. metadata +44 -0
data/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ # Copyright (c) 2005, Chris Ochs
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 are met:
6
+ #
7
+ # * Redistributions of source code must retain the above copyright notice,
8
+ # this list of conditions and the following disclaimer.
9
+ # * Redistributions in binary form must reproduce the above copyright notice,
10
+ # this list of conditions and the following disclaimer in the
11
+ # documentation and/or other materials provided with the distribution.
12
+ # * Neither the name of Chris Ochs nor the names of its contributors
13
+ # may be used to endorse or promote products derived from this software
14
+ # without specific prior written permission.
15
+ #
16
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
20
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
+
data/README ADDED
@@ -0,0 +1,3 @@
1
+
2
+
3
+ Documentation is in rdoc. For a quick start look at bin/server.rb and bin/test.rb.
data/bin/server.rb ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby -w
2
+
3
+ require 'rubygems'
4
+ require 'rami'
5
+ include Rami
6
+
7
+ server = RamiServer.new
8
+
9
+ ## if you want all events printed to the console set this to 1
10
+ server.console =1
11
+
12
+ ## The username and secret used to login to the Asterisk manager interface
13
+ server.username = 'asterisk'
14
+ server.secret = 'secret'
15
+
16
+ ## The host and port that the manager interface is running on.
17
+ server.host = 'localhost'
18
+ server.port = 5038
19
+
20
+ ## DRb URI
21
+ drburi = "druby://localhost:9000"
22
+
23
+ server.run
24
+
25
+ DRb.start_service(drburi, server)
26
+ DRb.thread.join
27
+
data/bin/test.rb ADDED
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env ruby -w
2
+
3
+ require 'rubygems'
4
+ require 'rami'
5
+ include Rami
6
+
7
+ def print_results(t)
8
+ if t.size > 0
9
+ t.each do |array|
10
+ array.each {|key,value| puts "#{key}: #{value}"}
11
+ end
12
+ end
13
+ puts "\n\n"
14
+ end
15
+
16
+ client = RamiClient.new
17
+
18
+ ## Redirect
19
+ client.timeout = 10
20
+ t = client.redirect({'Action' => 'Redirect', 'Channel' => 'test','ExtraChannel' => 'test', 'Context' => 'mainmenu', 'Exten' => '201', 'Priority' => 1})
21
+ print_results(t)
22
+
23
+ ## Queues
24
+ client.timeout = 10
25
+ t = client.queues
26
+ print_results(t)
27
+
28
+ ## IAXpeers
29
+ client.timeout = 10
30
+ t = client.iax_peers
31
+ print_results(t)
32
+
33
+ ## SIPpeers
34
+ client.timeout = 10
35
+ t = client.sip_peers
36
+ print_results(t)
37
+
38
+ ## SIPshowpeer
39
+ client.timeout = 10
40
+ t = client.sip_show_peer('test_peer')
41
+ print_results(t)
42
+
43
+ ## ParkedCalls
44
+ client.timeout = 10
45
+ t = client.parked_calls
46
+ print_results(t)
47
+
48
+ ## Hangup
49
+ client.timeout = 10
50
+ t = client.hangup('SIP/test')
51
+ print_results(t)
52
+
53
+ ## ExtensionState
54
+ client.timeout = 10
55
+ t = client.extension_state('menu','201')
56
+ print_results(t)
57
+
58
+ ## SetVar
59
+ client.timeout = 10
60
+ t = client.setvar('IAX2/teliax-6','testmon',1)
61
+ print_results(t)
62
+
63
+ ## GetVar
64
+ client.timeout = 10
65
+ t = client.getvar('IAX2/teliax-6','testmon')
66
+ print_results(t)
67
+
68
+ ## Monitor
69
+ client.timeout = 10
70
+ t = client.monitor('SIP/test','testmon',1)
71
+ print_results(t)
72
+
73
+ ## StopMonitor
74
+ t = client.stop_monitor('SIP/test')
75
+ print_results(t)
76
+
77
+ ## ChangeMonitor
78
+ t = client.change_monitor('SIP/test','testmon')
79
+ print_results(t)
80
+
81
+ ## Agents
82
+ client.timeout = 10
83
+ t = client.agents
84
+ print_results(t)
85
+
86
+ ## MailboxCount
87
+ client.timeout = 10
88
+ t = client.mailbox_count(1002)
89
+ print_results(t)
90
+
91
+ ## MailboxStatus
92
+ t = client.mailbox_status(1001)
93
+ print_results(t)
94
+
95
+
96
+ ## Ping
97
+ client.timeout = 10
98
+ t = client.ping
99
+ print_results(t)
100
+
101
+ ## DBPut
102
+ client.timeout = 10
103
+ t = client.dbput('testfamily','testkey','testval')
104
+ print_results(t)
105
+
106
+ ## DBGet
107
+ client.timeout = 10
108
+ t = client.dbget('testfamily','testkey')
109
+ print_results(t)
110
+
111
+ ## Originate
112
+ # Waits for Hangup or OriginateFailed. Returns all associated events
113
+ client.timeout = 90
114
+ t = client.originate({'Channel' => 'IAX2/nbts', 'Context' =>'default','Exten' =>'1000','Priority' =>1})
115
+ print_results(t)
116
+
117
+ # Does not wait, returns immediately. Associated events available via get_events or find_events.
118
+ client.timeout = 10
119
+ t = client.originate({'Async' => 1,'Channel' => 'IAX2/nbts', 'Context' =>'default','Exten' =>'1000','Priority' =>1})
120
+ print_results(t)
121
+
122
+ ## Status
123
+ t = client.status()
124
+ print_results(t)
125
+
126
+ ## AbsoluteTimeout
127
+ t = client.absolute_timeout('IAX2/nbts',20)
128
+ print_results(t)
129
+
130
+ ## Command
131
+ t = client.command('show manager commands')
132
+ print_results(t)
133
+
134
+ ## Find events in the state queue
135
+ t = client.find_events('Event','NewCallerID')
136
+ print_results(t)
137
+ # OR
138
+ t = client.find_events('any','SIP/test_extension')
139
+ print_results(t)
140
+
141
+ ## Get all events in the state queue
142
+ t = client.get_events
143
+ print_results(t)
144
+
data/lib/rami.rb ADDED
@@ -0,0 +1,748 @@
1
+ #
2
+ # RAMI - Ruby classes for implementing a proxy server/client api for the Asterisk Manager Interface
3
+ #
4
+ #
5
+ # Copyright (c) 2005, Chris Ochs
6
+ # All rights reserved.
7
+
8
+ # Redistribution and use in source and binary forms, with or without
9
+ # modification, are permitted provided that the following conditions are met:
10
+ #
11
+ # * Redistributions of source code must retain the above copyright notice,
12
+ # this list of conditions and the following disclaimer.
13
+ # * Redistributions in binary form must reproduce the above copyright notice,
14
+ # this list of conditions and the following disclaimer in the
15
+ # documentation and/or other materials provided with the distribution.
16
+ # * Neither the name of Chris Ochs nor the names of its contributors
17
+ # may be used to endorse or promote products derived from this software
18
+ # without specific prior written permission.
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
24
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+
31
+ require 'drb'
32
+ require 'monitor'
33
+ require 'socket'
34
+ require 'timeout'
35
+
36
+ # A proxy server/client api for the Asterisk Manager Interface
37
+ module Rami
38
+
39
+ # The Rami client library. To use the library make sure the server has been started and then call RamiClient.new and set the
40
+ # host, port, and timeout attributes. All methods return an array of hashes, or an empty array if no data is available.
41
+ #
42
+ # Each hash will contain an asterisk response packet. Note that not all response packets are always returned. If a response
43
+ # packet is necessary for the actual communication with asterisk, but does not in itself have any meaningful content, then
44
+ # the packet is droppped. For example some actions might generate an initial response packet of something like "Response Follows",
45
+ # followed by one or more response packets with the actual data, followed by a final response packet which contains "Response Complete".
46
+ # In this case the first and last response will not be included in the array of hashes passed to the caller.
47
+ #
48
+ # I tried to document the things that need it the most. Some things should be fairly evident, such as methods for simple
49
+ # commands like Monitor or Ping.
50
+ #
51
+ # For examples, see test.rb in the bin directory. It contains sample code for calling every available method.
52
+ #
53
+ # Not all manager commands are currently supported. This is only because I have not yet had the time to add them.
54
+ # I tried to add the most complicated commands first, so adding the remaining commands is fairly simple.
55
+ # If there is a command you need let me know and I will make sure it gets included.
56
+ # If you want to add your own action command it's fairly simple. Add a method in RamiClient for
57
+ # the new command, and then add a section in the RamiClient::send_action loop to harvest the response.
58
+ class RamiClient
59
+
60
+ # Server hostname. Default localhost
61
+ attr_writer :host
62
+ # Server port. Default 9000
63
+ attr_writer :port
64
+ # Number of seconds before a method call will timeout. Default 10.
65
+ attr_writer :timeout
66
+
67
+ def initialize
68
+ @host = 'localhost'
69
+ @port = 9000
70
+ @timeout = 10
71
+ @action_id = Time.now().to_f
72
+ DRb.start_service()
73
+ @client = DRbObject.new(nil,"druby://#{@host}:#{@port}")
74
+ end
75
+
76
+ def absolute_timeout(channel=nil,tout=nil)
77
+ increment_action_id
78
+ return @client.send_action({'ActionID' => @action_id, 'Action' => 'AbsoluteTimeout', 'Channel' => channel, 'Timeout' => tout},@timeout)
79
+ end
80
+
81
+ def agents
82
+ increment_action_id
83
+ return @client.send_action({'ActionID' => @action_id, 'Action' => 'Agents'},@timeout)
84
+ end
85
+
86
+ def change_monitor(channel=nil,file=nil)
87
+ increment_action_id
88
+ return @client.send_action({'ActionID' => @action_id, 'Action' => 'ChangeMonitor', 'Channel' =>channel, 'File' => file},@timeout)
89
+ end
90
+
91
+ def command(command)
92
+ increment_action_id
93
+ return @client.send_action({'ActionID' => @action_id, 'Action' => 'Command', 'Command' => command},@timeout)
94
+ end
95
+
96
+ def dbput(family,key,val)
97
+ increment_action_id
98
+ return @client.send_action({'ActionID' => @action_id, 'Action' => 'DBPut', 'Family' => family, 'Key' => key, 'Val' => val},@timeout)
99
+ end
100
+
101
+ def dbget(family,key)
102
+ increment_action_id
103
+ return @client.send_action({'ActionID' => @action_id, 'Action' => 'DBGet', 'Family' => family, 'Key' => key},@timeout)
104
+ end
105
+
106
+ def extension_state(context,exten)
107
+ increment_action_id
108
+ return @client.send_action({'ActionID' => @action_id, 'Action' => 'ExtensionState', 'Context' => context, 'Exten' => exten},@timeout)
109
+ end
110
+
111
+ # If called with key and value, searches the state queue for events matching the key and value given.
112
+ # The key is an exact match, the value is a regex. You can also call find_events with key=any, which will match
113
+ # any entry with the given value
114
+ #
115
+ # The returned results are deleted from the queue. See RamiServer for more information on the queue structure.
116
+ def find_events(key=nil,value=nil)
117
+ return @client.find_events(key,value)
118
+ end
119
+
120
+ # Get all events from the state queue. The returned results are deleted from the queue.
121
+ # See RamiServer for more information on the queue structure.
122
+ def get_events
123
+ return @client.get_events
124
+ end
125
+
126
+ def getvar(channel,variable)
127
+ increment_action_id
128
+ return @client.send_action({'ActionID' => @action_id, 'Action' => 'GetVar', 'Channel' => channel, 'Variable' => variable},@timeout)
129
+ end
130
+
131
+ def hangup(channel=nil)
132
+ increment_action_id
133
+ return @client.send_action({'ActionID' => @action_id, 'Action' => 'Hangup', 'Channel' => channel},@timeout)
134
+ end
135
+
136
+ # IAXpeers is bugged. The response does not contain an action id, nor does it contain any key/value pairs in the response.
137
+ # For this reason it gets put into the state queue where it can be retrieved using find_events('any','iax2 peers'). iax_peers
138
+ # will always return {'Response' => 'Success'}
139
+ def iax_peers
140
+ increment_action_id
141
+ @client.send_action({'ActionID' => @action_id, 'Action' => 'IAXpeers'},1)
142
+ return [{'Response' => 'Success'}]
143
+ end
144
+
145
+ def monitor(channel=nil,file=nil,mix=nil)
146
+ increment_action_id
147
+ return @client.send_action({'ActionID' => @action_id, 'Action' => 'Monitor', 'Channel' =>channel, 'File' => file, 'Mix' => mix},@timeout)
148
+ end
149
+
150
+ def mailbox_status(mailbox=nil)
151
+ increment_action_id
152
+ return @client.send_action({'ActionID' => @action_id, 'Action' => 'MailboxStatus', 'Mailbox' => mailbox},@timeout)
153
+ end
154
+
155
+ def mailbox_count(mailbox=nil)
156
+ increment_action_id
157
+ return @client.send_action({'ActionID' => @action_id, 'Action' => 'MailboxCount', 'Mailbox' => mailbox},@timeout)
158
+ end
159
+
160
+ # h is a hash with the following keys. keys that are nil will not be passed to asterisk.
161
+ # * Channel
162
+ # * Context
163
+ # * Exten
164
+ # * Priority
165
+ # * Timeout
166
+ # * CallerID
167
+ # * Variable
168
+ # * Account
169
+ # * Application
170
+ # * Data
171
+ # * Async
172
+ # If Async has a value, the method will wait until the call is hungup or fails. On hangup,
173
+ # Asterisk will response with Hangup event, and on failure it will respond with an OriginateFailed event.
174
+ # If Async is nil, the method will return immediately and the associated events can be obtained by calling
175
+ # find_events() or get_events().
176
+ def originate(h={})
177
+ increment_action_id
178
+ return @client.send_action({'ActionID' => @action_id,
179
+ 'Action' => 'Originate',
180
+ 'Channel' => h['Channel'],
181
+ 'Context' => h['Context'],
182
+ 'Exten' =>h['Exten'],
183
+ 'Priority' =>h['Priority'],
184
+ 'Timeout' =>h['Timeout'],
185
+ 'CallerID' =>h['CallerID'],
186
+ 'Variable' =>h['Variable'],
187
+ 'Account' =>h['Account'],
188
+ 'Application' =>h['Application'],
189
+ 'Data' =>h['Data'],
190
+ 'Async' => h['Async']}.delete_if {|key, value| value.nil? },@timeout)
191
+ end
192
+
193
+ def parked_calls
194
+ increment_action_id
195
+ return @client.send_action({'ActionID' => @action_id, 'Action' => 'ParkedCalls'},@timeout)
196
+ end
197
+
198
+ def ping
199
+ increment_action_id
200
+ return @client.send_action({'ActionID' => @action_id, 'Action' => 'Ping'},@timeout)
201
+ end
202
+
203
+ # Queues is just like IAXpeers. You can use find_events('any','default') to get the response from the state queue.
204
+ def queues
205
+ increment_action_id
206
+ @client.send_action({'ActionID' => @action_id, 'Action' => 'Queues'},@timeout)
207
+ return [{'Response' => 'Success'}]
208
+ end
209
+
210
+ # h is a hash with the following keys. keys that are nil will not be passed to asterisk.
211
+ # * Channel
212
+ # * ExtraChannel
213
+ # * Context
214
+ # * Exten
215
+ # * Priority
216
+ def redirect(h={})
217
+ increment_action_id
218
+ return @client.send_action({'ActionID' => @action_id,
219
+ 'Action' => 'Redirect',
220
+ 'Channel' => h['Channel'],
221
+ 'ExtraChannel' => h['ExtraChannel'],
222
+ 'Context' => h['Context'],
223
+ 'Exten' => h['Exten'],
224
+ 'Priority' => h['Priority']}.delete_if {|key,value| value.nil?},@timeout)
225
+ end
226
+
227
+ def setvar(channel,variable,value)
228
+ increment_action_id
229
+ return @client.send_action({'ActionID' => @action_id, 'Action' => 'SetVar', 'Channel' => channel, 'Variable' => variable, 'Value' => value},@timeout)
230
+ end
231
+
232
+ # Unlike IAXpeers, SIPpeers returns an event for each peer that is easily parsed and usable.
233
+ def sip_peers
234
+ increment_action_id
235
+ return @client.send_action({'ActionID' => @action_id, 'Action' => 'SIPpeers'},@timeout)
236
+ end
237
+
238
+ # Detailed information about a particular peer.
239
+ def sip_show_peer(peer)
240
+ increment_action_id
241
+ return @client.send_action({'ActionID' => @action_id, 'Action' => 'SIPshowpeer', 'Peer' => peer},@timeout)
242
+ end
243
+
244
+ def status(channel=nil)
245
+ increment_action_id
246
+ if channel.nil?
247
+ return @client.send_action({'ActionID' => @action_id, 'Action' => 'Status'},@timeout)
248
+ else
249
+ return @client.send_action({'ActionID' => @action_id, 'Action' => 'Status', 'Channel' => channel},@timeout)
250
+ end
251
+ end
252
+
253
+ def stop_monitor(channel=nil)
254
+ increment_action_id
255
+ return @client.send_action({'ActionID' => @action_id, 'Action' => 'StopMonitor', 'Channel' =>channel},@timeout)
256
+ end
257
+
258
+ private
259
+ def increment_action_id
260
+ @action_id = Time.now().to_f
261
+ end
262
+
263
+ def send_action(action=nil,tout=nil)
264
+ increment_action_id
265
+ action['ActionID'] = @action_id
266
+ return @client.send_action(action,tout)
267
+ end
268
+
269
+ end
270
+
271
+
272
+ # RamiServer maintains one open connection to asterisk. It uses one thread to constantly read responses and stick them into
273
+ # the appropriate queue. Clients connect via DRb and are each serviced in their own thread.
274
+ #
275
+ # The server uses two queues to hold responses from asterisk. The action queue holds all responses that contain an ActionID.
276
+ # The state queue holds all responses that do not have an ActionID. The action queue is only used internally, while the state
277
+ # queue can be queried via RamiClient.get_events and RamiClient.find_events.
278
+ #
279
+ # To run the server, call RamiServer.new, set the attributes, then call the run method.
280
+ # For an example of how to run the server, see bin/server.rb.
281
+ class RamiServer
282
+ # If set to 1, console logging is turned on. Default 0
283
+ attr_writer :console
284
+ # Asterisk manager username. Default asterisk.
285
+ attr_writer :username
286
+ # Asterisk manager secret. Default secret.
287
+ attr_writer :secret
288
+ # Asterisk manager hostname. Default localhost
289
+ attr_writer :host
290
+ # Asterisk manager port. Default 5038
291
+ attr_writer :port
292
+ # The number of responses to hold in the state queue. The state queue is a FIFO list. Default is 100.
293
+ attr_writer :event_cache
294
+
295
+ Thread.current.abort_on_exception=true
296
+
297
+ include DRbUndumped
298
+
299
+ def initialize
300
+ @console = 0
301
+ @username = 'asterisk'
302
+ @secret = 'secret'
303
+ @host = 'localhost'
304
+ @port = 5038
305
+ @eventcount = 1
306
+ @event_cache = 100
307
+
308
+ @sock = nil
309
+ @socklock = nil
310
+ @socklock.extend(MonitorMixin)
311
+
312
+ @action_events = []
313
+ @action_events.extend(MonitorMixin)
314
+ @action_events_pending = @action_events.new_cond
315
+
316
+ @state_events = []
317
+ @state_events.extend(MonitorMixin)
318
+ @state_events_pending = @state_events.new_cond
319
+
320
+ end
321
+
322
+
323
+
324
+
325
+ private
326
+
327
+ def connect
328
+ @sock = TCPSocket.new(@host,@port)
329
+ login = {'Action' => 'login', 'Username' => @username, 'Secret' => @secret, 'Events' => 'On'}
330
+ writesock(login)
331
+ accum = {}
332
+ login = 0
333
+ status = Timeout.timeout(10) do
334
+ while login == 0
335
+ @sock.each("\r\n") do |line|
336
+ if line.include?(':')
337
+ key,value = parseline(line) if line.include?(':')
338
+ accum[key] = value
339
+ end
340
+ if @console == 1
341
+ print "#{Time.now} RECV #{@eventcount}: #{line}"
342
+ end
343
+ if line == "\r\n" and accum['Message'] == 'Authentication accepted' and accum['Response'] == 'Success'
344
+ login =1
345
+ @eventcount += 1
346
+ break
347
+ end
348
+ end
349
+ end
350
+ return true
351
+ end
352
+ rescue Exception => e
353
+ puts "LOGIN TIMEOUT"
354
+ return false
355
+ end
356
+
357
+
358
+ def mainloop
359
+
360
+ ast_reader = Thread.new do
361
+ Thread.current.abort_on_exception=true
362
+ linecount = 0
363
+ loop do
364
+ event = {}
365
+ @sock.each("\r\n") do |line|
366
+ linecount += 1
367
+ type = 'state'
368
+ if @console == 1
369
+ print "#{Time.now} RECV #{@eventcount}: #{line}"
370
+ end
371
+ if line == "\r\n"
372
+ if event.size == 0
373
+ if @console == 1
374
+ print "#{Time.now} MSG: #{@eventcount} RECEIVED EXTRA CR/LF #{line}"
375
+ end
376
+ next
377
+ end
378
+ if event['ActionID']
379
+ type = 'action'
380
+ end
381
+
382
+ if @console == 1
383
+ print "#{Time.now} MSG: #{@eventcount} finished (type=#{type}) #{line}"
384
+ end
385
+
386
+ if type == 'action'
387
+ @action_events.synchronize do
388
+ @action_events << event.clone
389
+ event.clear
390
+ @action_events_pending.signal
391
+ end
392
+ elsif type == 'state'
393
+ @state_events.synchronize do
394
+ @state_events << event.clone
395
+ if @state_events.size >= @event_cache
396
+ @state_events.shift
397
+ end
398
+ event.clear
399
+ @state_events_pending.signal
400
+ end
401
+ end
402
+ @eventcount += 1
403
+ elsif line =~/^[\w\s\/-]*:[\s]*.*\r\n$/
404
+ key,value = parseline(line)
405
+ if key == 'ActionID'
406
+ value = value.gsub(' ','')
407
+ end
408
+ event[key] = value
409
+ else
410
+ event[linecount] = line
411
+ end
412
+ end
413
+ end
414
+ end
415
+ end
416
+
417
+ def parseline(line)
418
+ if line =~/(^[\w\s\/-]*:[\s]*)(.*\r\n$)/
419
+ key = $1
420
+ value = $2
421
+ key = key.gsub(/[\s:]*/,'')
422
+ value = value.gsub(/\r\n/,'')
423
+ return [key,value]
424
+ else
425
+ return ["UNKNOWN","UNKNOWN"]
426
+ end
427
+
428
+ end
429
+
430
+ def writelog(msg)
431
+ print "#{Time.now} #{msg}\r\n"
432
+ end
433
+
434
+ def writesock(action)
435
+ @socklock.synchronize do
436
+ action.each do |key,value|
437
+ @sock.write("#{key}: #{value}\r\n")
438
+ print "#{Time.now} SEND #{@eventcount}: #{key}: #{value}\r\n"
439
+ end
440
+ @sock.write("\r\n")
441
+ print "#{Time.now} SEND #{@eventcount}: \r\n"
442
+ end
443
+ end
444
+
445
+
446
+ public
447
+
448
+ # Starts the server and connects to asterisk.
449
+ def run
450
+ if connect
451
+ puts "#{Time.now} MSG: LOGGED IN"
452
+ else
453
+ puts "#{Time.now} MSG: LOGIN FAILED"
454
+ exit
455
+ end
456
+ mainloop
457
+ end
458
+
459
+ # Should only be called via Drb by RamiClient
460
+ def find_events(key=nil,value=nil)
461
+ print "#{Time.now} find_events: #{key}: #{value}\r\n"
462
+ found = []
463
+ @state_events.synchronize do
464
+ if @state_events.empty?
465
+ return found
466
+ else
467
+ @state_events_pending.wait_while {@state_events.empty?}
468
+ @state_events.clone.each do |e|
469
+ if key == 'any' and e.to_s =~/#{value}/
470
+ found.push(e)
471
+ @state_events.delete(e)
472
+ elsif key != 'any' and e[key] =~/#{value}/
473
+ found.push(e)
474
+ @state_events.delete(e)
475
+ end
476
+ end
477
+ return found
478
+ end
479
+ end
480
+ end
481
+
482
+ # Should only be called via Drb by RamiClient
483
+ def get_events
484
+ found = []
485
+ @state_events.synchronize do
486
+ if @state_events.empty?
487
+ return found
488
+ else
489
+ @state_events_pending.wait_while {@state_events.empty?}
490
+ @state_events.clone.each do |e|
491
+ found.push(e)
492
+ end
493
+ @state_events.clear
494
+ return found
495
+ end
496
+ end
497
+ end
498
+
499
+ # Should only be called via Drb by RamiClient
500
+ def send_action(action=nil,t=10)
501
+ sent_id = action['ActionID'].to_s
502
+ result = []
503
+ finished = 0
504
+ status = Timeout.timeout(t) do
505
+ writesock(action)
506
+
507
+ ## Some action responses have no action id or specific formatting, so we just return immediately and the caller can call
508
+ ## get_events or find_events to get the response.
509
+
510
+ ## IAXpeer - Just return immediately
511
+ if action['Action'] == 'IAXpeers'
512
+ finished =1
513
+ return
514
+ end
515
+
516
+ ## Queues - Just return immediately
517
+ if action['Action'] == 'Queues'
518
+ finished =1
519
+ return
520
+ end
521
+
522
+
523
+ while finished == 0
524
+ @action_events.synchronize do
525
+ @action_events_pending.wait_while {@action_events.empty?}
526
+ @action_events.clone.each do |e|
527
+
528
+ ## Action responses that contain an ActionID
529
+ if e['ActionID'].to_s == sent_id
530
+
531
+ ## Ping - Single response has ActionID
532
+ if action['Action'] == 'Ping' and e['Response'].gsub(/\s/,'') == 'Pong'
533
+ @action_events.delete(e)
534
+ result << e
535
+ finished = 1
536
+ end
537
+
538
+ ## Command - Single response has ActionID
539
+ if action['Action'] == 'Command' and e['Response'].gsub(/\s/,'') == 'Follows'
540
+ @action_events.delete(e)
541
+ result << e
542
+ finished = 1
543
+ end
544
+
545
+ ## Hangup - Single response has ActionID
546
+ if action['Action'] == 'Hangup'
547
+ @action_events.delete(e)
548
+ result << e
549
+ finished = 1
550
+ end
551
+
552
+ ## ExtensionState - Single response has ActionID
553
+ if action['Action'] == 'ExtensionState'
554
+ @action_events.delete(e)
555
+ result << e
556
+ finished = 1
557
+ end
558
+
559
+ ## SetVar - Single response has ActionID
560
+ if action['Action'] == 'SetVar'
561
+ @action_events.delete(e)
562
+ result << e
563
+ finished = 1
564
+ end
565
+
566
+ ## GetVar - Single response has ActionID
567
+ if action['Action'] == 'GetVar'
568
+ @action_events.delete(e)
569
+ result << e
570
+ finished = 1
571
+ end
572
+
573
+ ## Redirect - Single response has ActionID
574
+ if action['Action'] == 'Redirect'
575
+ @action_events.delete(e)
576
+ result << e
577
+ finished = 1
578
+ end
579
+
580
+ ## DBPut - Single response has ActionID
581
+ if action['Action'] == 'DBPut'
582
+ @action_events.delete(e)
583
+ result << e
584
+ finished = 1
585
+ end
586
+
587
+ ## DBGet - Single response has ActionID
588
+ if action['Action'] == 'DBGet' and (e['Response'] == 'Error' or e['Event'] == 'DBGetResponse')
589
+ @action_events.delete(e)
590
+ result << e
591
+ finished = 1
592
+ end
593
+
594
+ ## Monitor - Single response has ActionID
595
+ if action['Action'] == 'Monitor'
596
+ @action_events.delete(e)
597
+ result << e
598
+ finished = 1
599
+ end
600
+
601
+ ## Stop Monitor - Single response has ActionID
602
+ if action['Action'] == 'StopMonitor'
603
+ @action_events.delete(e)
604
+ result << e
605
+ finished = 1
606
+ end
607
+
608
+ ## ChangeMonitor - Single response has ActionID
609
+ if action['Action'] == 'ChangeMonitor'
610
+ @action_events.delete(e)
611
+ result << e
612
+ finished = 1
613
+ end
614
+
615
+ ## MailboxStatus - Single response has ActionID
616
+ if action['Action'] == 'MailboxStatus'
617
+ @action_events.delete(e)
618
+ result << e
619
+ finished = 1
620
+ end
621
+
622
+ ## MailboxCount - Single response has ActionID
623
+ if action['Action'] == 'MailboxCount'
624
+ @action_events.delete(e)
625
+ result << e
626
+ finished = 1
627
+ end
628
+
629
+ ## AbsoluteTimeout - Single response has ActionID
630
+ if action['Action'] == 'AbsoluteTimeout'
631
+ @action_events.delete(e)
632
+ result << e
633
+ finished = 1
634
+ end
635
+
636
+ ## SIPshowpeer - Single response has ActionID
637
+ if action['Action'] == 'SIPshowpeer'
638
+ @action_events.delete(e)
639
+ result << e
640
+ finished = 1
641
+ end
642
+
643
+ ## Logoff - Single response has ActionID
644
+ if action['Action'] == 'Logoff'
645
+ @action_events.delete(e)
646
+ result << e
647
+ finished = 1
648
+ end
649
+
650
+
651
+ ## Originate - Single response has ActionID, multiple events generated
652
+ ## end event is Hangup or OriginateFailed.
653
+ if action['Action'] == 'Originate'
654
+ if action['Async']
655
+ if action['Action'] == 'Originate' and e['Message'] == 'Originate successfully queued'
656
+ @action_events.delete(e)
657
+ result << e
658
+ finished = 1
659
+ end
660
+ else
661
+ eventfinished =0
662
+ while eventfinished == 0
663
+ @state_events.synchronize do
664
+ @state_events_pending.wait_while {@state_events.empty?}
665
+ @state_events.clone.each do |s|
666
+ if s['Channel'] =~/#{action['Channel']}/ and (s['Event'] == 'Hangup' or s['Event'] == 'OriginateFailed')
667
+ @state_events.delete(s)
668
+ result << s
669
+ eventfinished =1
670
+ finished =1
671
+ elsif s['Channel'] =~/#{action['Channel']}/
672
+ @state_events.delete(s)
673
+ result << s
674
+ end
675
+ end
676
+ end
677
+ end
678
+ end
679
+ end
680
+
681
+ ## ParkedCalls - multiple responses has ActionID
682
+ if action['Action'] == 'ParkedCalls'
683
+ if e['Message'] == 'Parked calls will follow'
684
+ @action_events.delete(e)
685
+ elsif e['Event'] == 'ParkedCallsComplete'
686
+ @action_events.delete(e)
687
+ finished =1
688
+ else
689
+ @action_events.delete(e)
690
+ result << e
691
+ end
692
+ end
693
+
694
+ ## SIPpeers - multiple responses has ActionID
695
+ if action['Action'] == 'SIPpeers'
696
+ if e['Message'] == 'Peer status list will follow'
697
+ @action_events.delete(e)
698
+ elsif e['Event'] == 'PeerlistComplete'
699
+ @action_events.delete(e)
700
+ finished =1
701
+ elsif e['Event'] == 'PeerEntry'
702
+ @action_events.delete(e)
703
+ result << e
704
+ end
705
+ end
706
+
707
+ ## Agents - multiple responses has ActionID
708
+ if action['Action'] == 'Agents'
709
+ if e['Message'] == 'Agents will follow'
710
+ @action_events.delete(e)
711
+ elsif e['Event'] == 'AgentsComplete'
712
+ @action_events.delete(e)
713
+ finished =1
714
+ elsif e['Event'] == 'Agents'
715
+ @action_events.delete(e)
716
+ result << e
717
+ end
718
+ end
719
+
720
+ ## Status - multiple responses has ActionID
721
+ if action['Action'] == 'Status'
722
+ if e['Message'] == 'Channel status will follow'
723
+ @action_events.delete(e)
724
+ elsif e['Event'] == 'StatusComplete'
725
+ @action_events.delete(e)
726
+ finished =1
727
+ elsif e['Event'] == 'Status'
728
+ @action_events.delete(e)
729
+ result << e
730
+ end
731
+ end
732
+
733
+ end
734
+ end
735
+ end
736
+ sleep 0.10
737
+ end
738
+ end
739
+ return result
740
+ rescue Exception => e
741
+ puts "#{e}: TIMEOUT #{t} #{sent_id}"
742
+ return result
743
+ end
744
+
745
+ end
746
+ rescue Exception => e
747
+ puts e
748
+ end
data/manager.txt ADDED
@@ -0,0 +1,43 @@
1
+ # Action Privilege Synopsis
2
+ # ------ --------- --------
3
+ # AbsoluteTimeout call,all Set Absolute Timeout
4
+ # AgentCallbackLo agent,all Sets an agent as logged in by callback
5
+ # AgentLogoff agent,all Sets an agent as no longer logged in
6
+ # Agents agent,all Lists agents and their status
7
+ # ChangeMonitor call,all Change monitoring filename of a channel
8
+ # Command command,all Execute Asterisk CLI Command
9
+ # DBGet system,all Get DB Entry
10
+ # DBPut system,all Put DB Entry
11
+ # Events <none> Control Event Flow
12
+ # ExtensionState call,all Check Extension Status
13
+ # Getvar call,all Gets a Channel Variable
14
+ # Hangup call,all Hangup Channel
15
+ # IAXnetstats <none> Show IAX Netstats
16
+ # IAXpeers <none> List IAX Peers
17
+ # ListCommands <none> List available manager commands
18
+ # Logoff <none> Logoff Manager
19
+ # MailboxCount call,all Check Mailbox Message Count
20
+ # MailboxStatus call,all Check Mailbox
21
+ # Monitor call,all Monitor a channel
22
+ # Originate call,all Originate Call
23
+ # ParkedCalls <none> List parked calls
24
+ # Ping <none> Keepalive command
25
+ # QueueAdd agent,all Add interface to queue.
26
+ # QueuePause agent,all Makes a queue member temporarily unavailable
27
+ # QueueRemove agent,all Remove interface from queue.
28
+ # Queues <none> Queues
29
+ # QueueStatus <none> Queue Status
30
+ # Redirect call,all Redirect (transfer) a call
31
+ # SetCDRUserField call,all Set the CDR UserField
32
+ # Setvar call,all Set Channel Variable
33
+ # SIPpeers system,all List SIP peers (text format)
34
+ # SIPshowpeer system,all Show SIP peer (text format)
35
+ # Status call,all Lists channel status
36
+ # StopMonitor call,all Stop monitoring a channel
37
+ # ZapDialOffhook <none> Dial over Zap channel while offhook
38
+ # ZapDNDoff <none> Toggle Zap channel Do Not Disturb status OFF
39
+ # ZapDNDon <none> Toggle Zap channel Do Not Disturb status ON
40
+ # ZapHangup <none> Hangup Zap Channel
41
+ # ZapShowChannels <none> Show status zapata channels
42
+ # ZapTransfer <none> Transfer Zap Channel
43
+ #
metadata ADDED
@@ -0,0 +1,44 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: rami
5
+ version: !ruby/object:Gem::Version
6
+ version: "0.1"
7
+ date: 2005-10-17 00:00:00 -07:00
8
+ summary: A proxy server/client api for the Asterisk Manager Interface
9
+ require_paths:
10
+ - lib
11
+ email: chris@paymentonline.com
12
+ homepage:
13
+ rubyforge_project:
14
+ description:
15
+ autorequire: rami
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ -
22
+ - ">"
23
+ - !ruby/object:Gem::Version
24
+ version: 0.0.0
25
+ version:
26
+ platform: ruby
27
+ signing_key:
28
+ cert_chain:
29
+ authors:
30
+ - Chris Ochs
31
+ files:
32
+ - README
33
+ - LICENSE
34
+ - manager.txt
35
+ - lib/rami.rb
36
+ - bin/server.rb
37
+ - bin/test.rb
38
+ test_files: []
39
+ rdoc_options: []
40
+ extra_rdoc_files: []
41
+ executables: []
42
+ extensions: []
43
+ requirements: []
44
+ dependencies: []