librevox 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.
- 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
|