librevox 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.md +159 -0
- data/Rakefile +6 -0
- data/TODO +24 -0
- data/lib/librevox.rb +35 -0
- data/lib/librevox/applications.rb +127 -0
- data/lib/librevox/command_socket.rb +50 -0
- data/lib/librevox/commands.rb +53 -0
- data/lib/librevox/listener/base.rb +80 -0
- data/lib/librevox/listener/inbound.rb +19 -0
- data/lib/librevox/listener/outbound.rb +83 -0
- data/lib/librevox/response.rb +48 -0
- data/librevox.gemspec +37 -0
- data/spec/helper.rb +6 -0
- data/spec/librevox/listener.rb +130 -0
- data/spec/librevox/listener/spec_inbound.rb +21 -0
- data/spec/librevox/listener/spec_outbound.rb +221 -0
- data/spec/librevox/spec_applications.rb +142 -0
- data/spec/librevox/spec_command_socket.rb +111 -0
- data/spec/librevox/spec_commands.rb +68 -0
- data/spec/librevox/spec_response.rb +56 -0
- metadata +76 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 rubyists.com (TJ Vanderpoel, Jayson Vaughn, Michael Fellinger, Kevin Berry)
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
# Librevox
|
2
|
+
|
3
|
+
> An EventMachine-based Ruby library for interacting with the open source
|
4
|
+
> telephony platform [FreeSWITCH](http://www.freeswitch.org).
|
5
|
+
|
6
|
+
Librevox eventually came to life during a major rewrite of
|
7
|
+
[Freeswitcher](http://code.rubyists.com/projects/fs/). Not everything would
|
8
|
+
fit into the existing architecture, and I felt that a blank slate was needed.
|
9
|
+
Librevox and Freeswitcher looks much alike on the outside, but Librevox tries
|
10
|
+
to take a simpler approach on the inside. Eventually this is still beta
|
11
|
+
software, and needs some real-life testing before seeing a regular release.
|
12
|
+
|
13
|
+
## Prerequisites
|
14
|
+
|
15
|
+
Librevox lets you interact with FreeSWITCH through mod_event_socket. You
|
16
|
+
should know how the event socket works, and the differences between inbound and
|
17
|
+
outbound event sockets before proceeding. The
|
18
|
+
[wiki page on mod_event_socket](http://wiki.freeswitch.org/wiki/Event_Socket) is
|
19
|
+
a good place to start.
|
20
|
+
|
21
|
+
## Inbound listener
|
22
|
+
|
23
|
+
To create an inbound listener, you should subclass `Librevox::Listener::Inbound`
|
24
|
+
and add custom behaviour to it. An inbound listener subscribes to all events
|
25
|
+
from FreeSWITCH, and lets you react on events in two different ways:
|
26
|
+
|
27
|
+
1. By overiding `on_event` which gets called every time an event arrives.
|
28
|
+
|
29
|
+
2. By adding an event hook with `event`, which will get called every time
|
30
|
+
an event with the specified name arrives.
|
31
|
+
|
32
|
+
The header and content of the event is accessible through `event`.
|
33
|
+
|
34
|
+
Below is an example of an inbound listener utilising all the aforementioned
|
35
|
+
techniques:
|
36
|
+
|
37
|
+
require 'librevox'
|
38
|
+
|
39
|
+
class MyInbound < Librevox::Listener::Inbound
|
40
|
+
def on_event
|
41
|
+
puts "Got event: #{event.content[:event_name]}"
|
42
|
+
end
|
43
|
+
|
44
|
+
# You can add a hook for a certain event:
|
45
|
+
event :channel_hangup do
|
46
|
+
# It is instance_eval'ed, so you can use your instance methods etc:
|
47
|
+
do_something
|
48
|
+
end
|
49
|
+
|
50
|
+
def do_something
|
51
|
+
...
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
## Outbound listener
|
56
|
+
|
57
|
+
You create an outbound listener by subclassing `Librevox::Listener::Outbound`.
|
58
|
+
|
59
|
+
### Events
|
60
|
+
|
61
|
+
An outbound listener has the same event functionality as the inbound listener,
|
62
|
+
but it only receives events related to that given session.
|
63
|
+
|
64
|
+
### Dialplan
|
65
|
+
|
66
|
+
When a call is made and Freeswitch connects to the outbound event listener,
|
67
|
+
the `session` callback is executed. This is where you set up your dialplan:
|
68
|
+
|
69
|
+
session do
|
70
|
+
answer
|
71
|
+
set "some_var", "some value"
|
72
|
+
playback "path/to/file"
|
73
|
+
hangup
|
74
|
+
end
|
75
|
+
|
76
|
+
When using applications that expect a reply, such as `play_and_get_digits`,
|
77
|
+
you have to use callbacks to read the value, as the function itself returns
|
78
|
+
immediately due to the async nature of EventMachine:
|
79
|
+
|
80
|
+
session do
|
81
|
+
answer
|
82
|
+
|
83
|
+
play_and_get_digits "enter-number.wav", "error.wav" do |digit|
|
84
|
+
puts "User pressed #{digit}"
|
85
|
+
playback "thanks-for-the-input.wav"
|
86
|
+
hangup
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
You can also use the commands defined in `Librevox::Command`, which, to avoid
|
91
|
+
namespace clashes, are wrapped in the `api` command:
|
92
|
+
|
93
|
+
session do
|
94
|
+
answer
|
95
|
+
api :status
|
96
|
+
end
|
97
|
+
|
98
|
+
They can be used in conjunction with applications, and do also take a block,
|
99
|
+
passing the response to an eventual block argument.
|
100
|
+
|
101
|
+
## Starting listeners
|
102
|
+
|
103
|
+
To start a single listener, connection/listening on localhost on the default
|
104
|
+
port is quite simple:
|
105
|
+
|
106
|
+
Librevox.start SomeListener
|
107
|
+
|
108
|
+
it takes an optional hash with arguments:
|
109
|
+
|
110
|
+
Librevox.start SomeListener, :host => "1.2.3.4", :port => "8087", :auth => "pwd"
|
111
|
+
|
112
|
+
Multiple listeners can be started at once by passing a block to `Librevox.start`:
|
113
|
+
|
114
|
+
Librevox.start do
|
115
|
+
run SomeListener
|
116
|
+
run OtherListener, :port => "8080"
|
117
|
+
end
|
118
|
+
|
119
|
+
## Using `Librevox::CommandSocket`
|
120
|
+
|
121
|
+
Librevox also ships with a CommandSocket class, which allows you to connect
|
122
|
+
to the FreeSWITCH management console, from which you can originate calls,
|
123
|
+
restart FreeSWITCH etc.
|
124
|
+
|
125
|
+
>> require `librevox`
|
126
|
+
=> true
|
127
|
+
|
128
|
+
>> socket = Librevox::CommandSocket.new
|
129
|
+
=> #<Librevox::CommandSocket:0xb7a89104 @server=“127.0.0.1”,
|
130
|
+
@socket=#<TCPSocket:0xb7a8908c>, @port=“8021”, @auth=“ClueCon”>
|
131
|
+
|
132
|
+
>> socket.originate('sofia/user/coltrane', :extension => "1234")
|
133
|
+
>> #<Librevox::Response:0x10179d388 @content="+OK de0ecbbe-e847...">
|
134
|
+
|
135
|
+
>> socket.status
|
136
|
+
>> > #<Librevox::Response:0x1016acac8 ...>
|
137
|
+
|
138
|
+
## Further documentation
|
139
|
+
|
140
|
+
All applications and commands are documented in the code. You can run
|
141
|
+
`yardoc` from the root of the source tree to generate YARD docs. Look under
|
142
|
+
the `Librevox::Commands` and `Librevox::Applications` modules.
|
143
|
+
|
144
|
+
## Extras
|
145
|
+
|
146
|
+
* Source: [http://github.com/ichverstehe/librevox](http://github.com/ichverstehe/librevox)
|
147
|
+
* API docs: [http://rdoc.info/projects/ichverstehe/librevox](http://rdoc.info/projects/ichverstehe/librevox)
|
148
|
+
* Mailing list: librevox@librelist.com
|
149
|
+
* IRC: #librevox @ irc.freenode.net
|
150
|
+
|
151
|
+
## License
|
152
|
+
|
153
|
+
(c) 2009 Harry Vangberg <harry@vangberg.name>
|
154
|
+
|
155
|
+
Librevox was inspired by and uses code from Freeswitcher, which is distributed
|
156
|
+
under the MIT license and (c) 2009 The Rubyists (Jayson Vaughn, Tj Vanderpoel,
|
157
|
+
Michael Fellinger, Kevin Berry), Harry Vangberg
|
158
|
+
|
159
|
+
Distributed under the terms of the MIT license.
|
data/Rakefile
ADDED
data/TODO
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
- the execute_app/run_app business might be done better, simpler. e.g.
|
2
|
+
having to pass the block for every command becomes a bit tedious.
|
3
|
+
|
4
|
+
- move test suite from bacon -> test/unit.
|
5
|
+
|
6
|
+
- map more commands/applications.
|
7
|
+
|
8
|
+
- test how this works in async mode - I imagine that it might mess with
|
9
|
+
@command_queue
|
10
|
+
|
11
|
+
- how can applications be used w/ command socket? would be nice if you could
|
12
|
+
do e.g. originate("user/coltrane", :endpoint => bridge("user/davis")) and
|
13
|
+
it would translate to "originate user/coltrane &bridge(user/davis)"
|
14
|
+
|
15
|
+
- add logger.
|
16
|
+
|
17
|
+
- check command/application input and raise ArgumentError.
|
18
|
+
|
19
|
+
- maybe let commands handle the response, so something useful could be returned?
|
20
|
+
|
21
|
+
- possibly catch all exceptions, so everything doesn't die? or should this be
|
22
|
+
be the users responsibilty?
|
23
|
+
|
24
|
+
- filter events
|
data/lib/librevox.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'librevox/listener/inbound'
|
3
|
+
require 'librevox/listener/outbound'
|
4
|
+
|
5
|
+
module Librevox
|
6
|
+
# When called without a block, it will start the listener that is passed as
|
7
|
+
# first argument:
|
8
|
+
#
|
9
|
+
# Librevox.start SomeListener
|
10
|
+
#
|
11
|
+
# To start multiple listeners, call with a block and use `run`:
|
12
|
+
#
|
13
|
+
# Librevox.start do
|
14
|
+
# run SomeListener
|
15
|
+
# run OtherListner
|
16
|
+
# end
|
17
|
+
def self.start(klass=nil, args={}, &block)
|
18
|
+
EM.run {
|
19
|
+
block_given? ? instance_eval(&block) : run(klass, args)
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.run(klass, args={})
|
24
|
+
host = args.delete(:host) || "localhost"
|
25
|
+
port = args.delete(:port)
|
26
|
+
|
27
|
+
if klass.ancestors.include? Librevox::Listener::Inbound
|
28
|
+
port ||= "8021"
|
29
|
+
EM.connect host, port, klass, args
|
30
|
+
elsif klass.ancestors.include? Librevox::Listener::Outbound
|
31
|
+
port ||= "8084"
|
32
|
+
EM.start_server host, port, klass, args
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module Librevox
|
2
|
+
# All applications should call `execute_app` with the following parameters:
|
3
|
+
#
|
4
|
+
# `name` - name of the application
|
5
|
+
# `args` - arguments as a string (optional)
|
6
|
+
# `params` - optional hash (optional)
|
7
|
+
#
|
8
|
+
# Applications *must* pass on any eventual block passed to them.
|
9
|
+
module Applications
|
10
|
+
# Answers an incoming call or session.
|
11
|
+
def answer(&b)
|
12
|
+
execute_app "answer", &b
|
13
|
+
end
|
14
|
+
|
15
|
+
# Binds an application to the specified call legs.
|
16
|
+
# @example
|
17
|
+
# bind_meta_app :key => 2,
|
18
|
+
# :listen_to => "a",
|
19
|
+
# :respond_on => "s",
|
20
|
+
# :application => "execute_extension",
|
21
|
+
# :parameters => "dx XML features"
|
22
|
+
# @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_bind_meta_app
|
23
|
+
def bind_meta_app(args={}, &b)
|
24
|
+
key = args[:key]
|
25
|
+
listen_to = args[:listen_to]
|
26
|
+
respond_on = args[:respond_on]
|
27
|
+
application = args[:application]
|
28
|
+
parameters = args[:parameters] ? "::#{args[:parameters]}" : ""
|
29
|
+
|
30
|
+
arg_string = "%s %s %s %s%s" % [key, listen_to, respond_on, application,
|
31
|
+
parameters]
|
32
|
+
|
33
|
+
execute_app "bind_meta_app", arg_string, &b
|
34
|
+
end
|
35
|
+
|
36
|
+
# Bridges an incoming call to an endpoint
|
37
|
+
# @example
|
38
|
+
# bridge "user/coltrane", "user/backup-office"
|
39
|
+
# @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_bridgecall
|
40
|
+
def bridge(*endpoints, &b)
|
41
|
+
execute_app "bridge", endpoints.join(","), &b
|
42
|
+
end
|
43
|
+
|
44
|
+
def hangup(cause="", &b)
|
45
|
+
execute_app "hangup", cause, &b
|
46
|
+
end
|
47
|
+
|
48
|
+
# Plays a sound file and reads DTMF presses.
|
49
|
+
# @example
|
50
|
+
# play_and_get_digits "please-enter.wav", "wrong-choice.wav",
|
51
|
+
# :min => 1,
|
52
|
+
# :max => 2,
|
53
|
+
# :tries => 3,
|
54
|
+
# :terminators => "#",
|
55
|
+
# :timeout => 5000,
|
56
|
+
# :regexp => '\d+'
|
57
|
+
# @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_play_and_get_digits
|
58
|
+
def play_and_get_digits(file, invalid_file, args={}, &b)
|
59
|
+
min = args[:min] || 1
|
60
|
+
max = args[:max] || 2
|
61
|
+
tries = args[:tries] || 3
|
62
|
+
terminators = args[:terminators] || "#"
|
63
|
+
timeout = args[:timeout] || 5000
|
64
|
+
read_var = args[:read_var] || "read_digits_var"
|
65
|
+
regexp = args[:regexp] || "\\d+"
|
66
|
+
|
67
|
+
args = [min, max, tries, timeout, terminators, file, invalid_file,
|
68
|
+
read_var, regexp].join " "
|
69
|
+
|
70
|
+
params = {:read_var => read_var}
|
71
|
+
|
72
|
+
execute_app "play_and_get_digits", args, params, &b
|
73
|
+
end
|
74
|
+
|
75
|
+
# Plays a sound file on the current channel.
|
76
|
+
# @example
|
77
|
+
# playback "/path/to/file.wav"
|
78
|
+
# @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_playback
|
79
|
+
def playback(file, &b)
|
80
|
+
execute_app "playback", file, &b
|
81
|
+
end
|
82
|
+
|
83
|
+
# @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_read
|
84
|
+
def read(file, args={}, &b)
|
85
|
+
min = args[:min] || 1
|
86
|
+
max = args[:max] || 2
|
87
|
+
terminators = args[:terminators] || "#"
|
88
|
+
timeout = args[:timeout] || 5000
|
89
|
+
read_var = args[:read_var] || "read_digits_var"
|
90
|
+
|
91
|
+
arg_string = "%s %s %s %s %s %s" % [min, max, file, read_var, timeout,
|
92
|
+
terminators]
|
93
|
+
|
94
|
+
params = {:read_var => read_var}
|
95
|
+
|
96
|
+
execute_app "read", arg_string, params, &b
|
97
|
+
end
|
98
|
+
|
99
|
+
# Records a message, with an optional limit on the maximum duration of the
|
100
|
+
# recording.
|
101
|
+
# @example Without limit
|
102
|
+
# record "/path/to/new/file.wac"
|
103
|
+
# @example With 20 second limit
|
104
|
+
# record "/path/to/new/file.wac", :limit => 20
|
105
|
+
# @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_record
|
106
|
+
def record(path, params={}, &b)
|
107
|
+
args = [path, params[:limit]].compact.join(" ")
|
108
|
+
execute_app "record", args, &b
|
109
|
+
end
|
110
|
+
|
111
|
+
# Sets a channel variable.
|
112
|
+
# @example
|
113
|
+
# set "some_var", "some value"
|
114
|
+
# @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_set
|
115
|
+
def set(variable, value, &b)
|
116
|
+
execute_app "set", "#{variable}=#{value}", &b
|
117
|
+
end
|
118
|
+
|
119
|
+
# Transfers the current channel to a new context.
|
120
|
+
# @example
|
121
|
+
# transfer "new_context"
|
122
|
+
# @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_transfer
|
123
|
+
def transfer(context, &b)
|
124
|
+
execute_app "transfer", context, &b
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'librevox/response'
|
3
|
+
require 'librevox/commands'
|
4
|
+
require 'librevox/applications'
|
5
|
+
|
6
|
+
module Librevox
|
7
|
+
class CommandSocket
|
8
|
+
include Librevox::Commands
|
9
|
+
|
10
|
+
def initialize(args={})
|
11
|
+
@server = args[:server] || "127.0.0.1"
|
12
|
+
@port = args[:port] || "8021"
|
13
|
+
@auth = args[:auth] || "ClueCon"
|
14
|
+
|
15
|
+
connect unless args[:connect] == false
|
16
|
+
end
|
17
|
+
|
18
|
+
def connect
|
19
|
+
@socket = TCPSocket.open(@server, @port)
|
20
|
+
run_cmd "auth #{@auth}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def run_cmd(cmd)
|
24
|
+
@socket.print "#{cmd}\n\n"
|
25
|
+
read_response
|
26
|
+
end
|
27
|
+
|
28
|
+
def read_response
|
29
|
+
response = Librevox::Response.new
|
30
|
+
until response.command_reply? or response.api_response?
|
31
|
+
response.headers = read_headers
|
32
|
+
end
|
33
|
+
|
34
|
+
length = response.headers[:content_length].to_i
|
35
|
+
response.content = @socket.read(length) if length > 0
|
36
|
+
|
37
|
+
response
|
38
|
+
end
|
39
|
+
|
40
|
+
def read_headers
|
41
|
+
headers = ""
|
42
|
+
|
43
|
+
while line = @socket.gets and !line.chomp.empty?
|
44
|
+
headers += line
|
45
|
+
end
|
46
|
+
|
47
|
+
headers
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Librevox
|
2
|
+
# All commands should call `execute_cmd` with the following parameters:
|
3
|
+
#
|
4
|
+
# `name` - name of the command
|
5
|
+
# `args` - arguments as a string (optional)
|
6
|
+
#
|
7
|
+
# Commands *must* pass on any eventual block passed to them.
|
8
|
+
module Commands
|
9
|
+
# Executes a generic API command, optionally taking arguments as string.
|
10
|
+
# @example
|
11
|
+
# socket.execute_cmd "fsctl", "hupall normal_clearing"
|
12
|
+
# @see http://wiki.freeswitch.org/wiki/Mod_commands
|
13
|
+
def execute_cmd(name, args="", &block)
|
14
|
+
msg = "api #{name}"
|
15
|
+
msg << " #{args}" unless args.empty?
|
16
|
+
|
17
|
+
run_cmd(msg, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def status(&b)
|
21
|
+
execute_cmd "status", &b
|
22
|
+
end
|
23
|
+
|
24
|
+
# Originate a new call.
|
25
|
+
# @example Minimum options
|
26
|
+
# socket.originate 'sofia/user/coltrane', :extension => "1234"
|
27
|
+
# @example With :dialplan and :context
|
28
|
+
# @see http://wiki.freeswitch.org/wiki/Mod_commands#originate
|
29
|
+
def originate(url, args={}, &b)
|
30
|
+
extension = args.delete(:extension)
|
31
|
+
dialplan = args.delete(:dialplan)
|
32
|
+
context = args.delete(:context)
|
33
|
+
|
34
|
+
vars = args.map {|k,v| "#{k}=#{v}"}.join(",")
|
35
|
+
|
36
|
+
arg_string = "{#{vars}}" +
|
37
|
+
[url, extension, dialplan, context].compact.join(" ")
|
38
|
+
execute_cmd "originate", arg_string, &b
|
39
|
+
end
|
40
|
+
|
41
|
+
# FreeSWITCH control messages.
|
42
|
+
# @example
|
43
|
+
# socket.fsctl :hupall, :normal_clearing
|
44
|
+
# @see http://wiki.freeswitch.org/wiki/Mod_commands#fsctl
|
45
|
+
def fsctl(*args, &b)
|
46
|
+
execute_cmd "fsctl", args.join(" "), &b
|
47
|
+
end
|
48
|
+
|
49
|
+
def hupall(cause=nil, &b)
|
50
|
+
execute_cmd "hupall", cause, &b
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|