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 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.
@@ -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.
@@ -0,0 +1,6 @@
1
+ desc "Run tests"
2
+ task :default => :test
3
+
4
+ task :test do
5
+ sh "bacon -Ilib -a -q"
6
+ end
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
@@ -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