librevox 0.1

Sign up to get free protection for your applications and to get access to all the features.
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