bougyman-freeswitcher 0.1.4 → 0.3.0
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/AUTHORS +0 -10
- data/CHANGELOG +123 -0
- data/MANIFEST +3 -9
- data/README +57 -60
- data/Rakefile +4 -4
- data/examples/outbound_event_socket.rb +2 -5
- data/freeswitcher.gemspec +62 -65
- data/lib/fsr/app.rb +1 -3
- data/lib/fsr/app/answer.rb +2 -0
- data/lib/fsr/app/play_and_get_digits.rb +11 -1
- data/lib/fsr/app/read.rb +12 -2
- data/lib/fsr/app/uuid_getvar.rb +13 -2
- data/lib/fsr/app/uuid_setvar.rb +11 -1
- data/lib/fsr/cmd.rb +22 -4
- data/lib/fsr/cmd/originate.rb +23 -6
- data/lib/fsr/command_socket.rb +1 -1
- data/lib/fsr/listener/inbound.rb +19 -10
- data/lib/fsr/listener/outbound.rb +75 -18
- data/lib/fsr/version.rb +1 -1
- data/spec/fsr/app/answer.rb +12 -0
- data/spec/fsr/app/fs_break.rb +12 -0
- data/spec/fsr/app/fs_sleep.rb +12 -0
- data/spec/fsr/cmd/originate.rb +32 -0
- metadata +62 -68
- data/examples/bin/cmd_demo.rb +0 -19
- data/examples/bin/dtmf_test.rb +0 -38
- data/examples/bin/ies_demo.rb +0 -19
- data/examples/bin/ies_demo_with_hook.rb +0 -25
- data/examples/bin/oes_demo.rb +0 -22
- data/examples/dtmf_test.rb +0 -35
- data/examples/ies_demo.rb +0 -19
- data/examples/ies_demo_with_hook.rb +0 -25
- data/lib/fsr/listener/outbound.rb.orig +0 -131
data/lib/fsr/app.rb
CHANGED
@@ -25,9 +25,7 @@ module FSR
|
|
25
25
|
|
26
26
|
def self.register(application, obj)
|
27
27
|
APPLICATIONS[application.to_sym] = obj
|
28
|
-
|
29
|
-
code = REGISTER_CODE % [application, application]
|
30
|
-
App.module_eval(code)
|
28
|
+
App.module_eval(REGISTER_CODE % [application, application])
|
31
29
|
end
|
32
30
|
|
33
31
|
def self.list
|
data/lib/fsr/app/answer.rb
CHANGED
@@ -3,6 +3,8 @@ module FSR
|
|
3
3
|
#http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_play_and_get_digits
|
4
4
|
module App
|
5
5
|
class PlayAndGetDigits < Application
|
6
|
+
attr_reader :chan_var
|
7
|
+
|
6
8
|
def initialize(sound_file, invalid_file, min = 0, max = 10, tries = 3, timeout = 7000, terminators = ["#"], chan_var = "fsr_read_dtmf", regexp = "\d")
|
7
9
|
@sound_file = sound_file
|
8
10
|
@invalid_file = invalid_file
|
@@ -22,7 +24,15 @@ module FSR
|
|
22
24
|
def sendmsg
|
23
25
|
"call-command: execute\nexecute-app-name: %s\nexecute-app-arg: %s\nevent-lock:true\n\n" % ["play_and_get_digits", arguments.join(" ")]
|
24
26
|
end
|
25
|
-
|
27
|
+
SENDMSG_METHOD = %q|
|
28
|
+
def play_and_get_digits(*args, &block)
|
29
|
+
me = super(*args)
|
30
|
+
@read_var = "variable_#{me.chan_var}"
|
31
|
+
sendmsg me
|
32
|
+
@queue.unshift Proc.new { update_session }
|
33
|
+
@queue.unshift block if block_given?
|
34
|
+
end
|
35
|
+
|
|
26
36
|
end
|
27
37
|
|
28
38
|
register(:play_and_get_digits, PlayAndGetDigits)
|
data/lib/fsr/app/read.rb
CHANGED
@@ -3,6 +3,7 @@ module FSR
|
|
3
3
|
# http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_read
|
4
4
|
module App
|
5
5
|
class Read < Application
|
6
|
+
attr_reader :chan_var
|
6
7
|
def initialize(sound_file, min = 0, max = 10, chan_var = "fsr_read_dtmf", timeout = 10000, terminators = ["#"])
|
7
8
|
@sound_file, @min, @max, @chan_var, @timeout, @terminators = sound_file, min, max, chan_var, timeout, terminators
|
8
9
|
end
|
@@ -14,9 +15,18 @@ module FSR
|
|
14
15
|
def sendmsg
|
15
16
|
"call-command: execute\nexecute-app-name: %s\nexecute-app-arg: %s\nevent-lock:true\n\n" % [app_name, arguments.join(" ")]
|
16
17
|
end
|
17
|
-
|
18
|
+
SENDMSG_METHOD = %q|
|
19
|
+
def read(*args, &block)
|
20
|
+
me = super(*args)
|
21
|
+
@read_var = "variable_#{me.chan_var}"
|
22
|
+
sendmsg me
|
23
|
+
@queue.unshift Proc.new { update_session }
|
24
|
+
@queue.unshift block if block_given?
|
25
|
+
end
|
26
|
+
|
|
18
27
|
end
|
19
|
-
|
20
28
|
register(:read, Read)
|
29
|
+
|
30
|
+
|
21
31
|
end
|
22
32
|
end
|
data/lib/fsr/app/uuid_getvar.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
require "fsr/app"
|
2
2
|
module FSR
|
3
|
-
#http://wiki.freeswitch.org/wiki/Mod_commands#
|
3
|
+
#http://wiki.freeswitch.org/wiki/Mod_commands#uuid_setvar
|
4
4
|
module App
|
5
5
|
class UuidGetVar < Application
|
6
|
+
attr_reader :var, :uuid
|
7
|
+
|
6
8
|
def initialize(uuid, var)
|
7
9
|
@uuid = uuid # Unique channel ID
|
8
|
-
@var = var # Channel variable you wish to '
|
10
|
+
@var = var # Channel variable you wish to 'set'
|
9
11
|
end
|
10
12
|
|
11
13
|
def arguments
|
@@ -16,6 +18,15 @@ module FSR
|
|
16
18
|
"call-command: execute\nexecute-app-name: %s\nexecute-app-arg: %s\nevent-lock:true\n\n" % [app_name, arguments.join(" ")]
|
17
19
|
end
|
18
20
|
|
21
|
+
SENDMSG_METHOD = %q|
|
22
|
+
def uuid_getvar(*args, &block)
|
23
|
+
me = super(*args)
|
24
|
+
@uuid_var = me.var
|
25
|
+
api_call = "api uuid_getvar #{me.uuid} #{me.var}\n\n"
|
26
|
+
send_data(api_call)
|
27
|
+
@queue.unshift block if block_given?
|
28
|
+
end
|
29
|
+
|
|
19
30
|
end
|
20
31
|
|
21
32
|
register(:uuid_getvar, UuidGetVar)
|
data/lib/fsr/app/uuid_setvar.rb
CHANGED
@@ -3,10 +3,12 @@ module FSR
|
|
3
3
|
#http://wiki.freeswitch.org/wiki/Mod_commands#uuid_setvar
|
4
4
|
module App
|
5
5
|
class UuidSetVar < Application
|
6
|
+
attr_reader :var, :uuid, :assignment
|
7
|
+
|
6
8
|
def initialize(uuid, var, assignment)
|
7
9
|
@uuid = uuid # Unique channel ID
|
8
10
|
@var = var # Channel variable you wish to 'set'
|
9
|
-
@assignment
|
11
|
+
@assignment = assignment
|
10
12
|
end
|
11
13
|
|
12
14
|
def arguments
|
@@ -17,6 +19,14 @@ module FSR
|
|
17
19
|
"call-command: execute\nexecute-app-name: %s\nexecute-app-arg: %s\nevent-lock:true\n\n" % [app_name, arguments.join(" ")]
|
18
20
|
end
|
19
21
|
|
22
|
+
SENDMSG_METHOD = %q|
|
23
|
+
def uuid_setvar(*args, &block)
|
24
|
+
me = super(*args)
|
25
|
+
api_call = "api uuid_setvar #{me.uuid} #{me.var} #{me.assignment}\n\n"
|
26
|
+
send_data(api_call)
|
27
|
+
@queue.unshift block if block_given?
|
28
|
+
end
|
29
|
+
|
|
20
30
|
end
|
21
31
|
|
22
32
|
register(:uuid_setvar, UuidSetVar)
|
data/lib/fsr/cmd.rb
CHANGED
@@ -1,10 +1,28 @@
|
|
1
1
|
module FSR
|
2
2
|
module Cmd
|
3
3
|
class Command
|
4
|
+
DEFAULT_OPTIONS = {
|
5
|
+
:origination_caller_id_name => FSR::DEFAULT_CALLER_ID_NAME,
|
6
|
+
:origination_caller_id_number => FSR::DEFAULT_CALLER_ID_NUMBER,
|
7
|
+
:originate_timeout => 30,
|
8
|
+
:ignore_early_media => true
|
9
|
+
}
|
10
|
+
|
11
|
+
protected
|
12
|
+
def default_options(args = {}, defaults = nil, &block)
|
13
|
+
opts = if defaults.nil?
|
14
|
+
DEFAULT_OPTIONS.merge(args)
|
15
|
+
else
|
16
|
+
raise(ArgumentError, "defaults argument must ba a hash") unless defaults.kind_of?(Hash)
|
17
|
+
defaults.merge(args)
|
18
|
+
end
|
19
|
+
yield opts if block_given?
|
20
|
+
end
|
21
|
+
|
4
22
|
end
|
5
23
|
|
6
24
|
COMMANDS = {}
|
7
|
-
LOAD_PATH = [
|
25
|
+
LOAD_PATH = [Pathname(FSR::ROOT).join( "fsr", "cmd")]
|
8
26
|
|
9
27
|
def self.register(command, obj)
|
10
28
|
COMMANDS[command.to_sym] = obj
|
@@ -28,8 +46,8 @@ module FSR
|
|
28
46
|
end
|
29
47
|
Log.debug "Trying load paths"
|
30
48
|
# If we find a file named the same as the command passed in LOAD_PATH, load it
|
31
|
-
if found_command_path = LOAD_PATH.detect { |command_path|
|
32
|
-
command_file =
|
49
|
+
if found_command_path = LOAD_PATH.detect { |command_path| command_path.join("#{command}.rb").file? }
|
50
|
+
command_file = found_command_path.join(command)
|
33
51
|
Log.debug "Trying to load #{command_file}"
|
34
52
|
if force_reload
|
35
53
|
load command_file.to_s + ".rb"
|
@@ -44,7 +62,7 @@ module FSR
|
|
44
62
|
# Load all of the commands we find in Cmd::LOAD_PATH
|
45
63
|
def self.load_all(force_reload = false)
|
46
64
|
LOAD_PATH.each do |load_path|
|
47
|
-
Dir[
|
65
|
+
Dir[load_path.join("*.rb")].each { |command_file| load_command(command_file, force_reload) }
|
48
66
|
end
|
49
67
|
list
|
50
68
|
end
|
data/lib/fsr/cmd/originate.rb
CHANGED
@@ -6,19 +6,35 @@ module FSR
|
|
6
6
|
attr_reader :fs_socket, :target_options
|
7
7
|
|
8
8
|
def initialize(fs_socket = nil, args = {})
|
9
|
+
# Using an argument hash may not be the the best way to go here, but as long as we're doing
|
10
|
+
# so we'll type check it
|
11
|
+
raise(ArgumentError, "args (Passed: <<<#{args}>>>) must be a hash") unless args.kind_of?(Hash)
|
12
|
+
|
9
13
|
# These are options that will precede the target address
|
10
|
-
|
11
|
-
|
14
|
+
if args[:target_options]
|
15
|
+
raise(ArgumentError, "args[:target_options] (Passed: <<<#{args[:target_options]}>>>) must be a hash") unless args[:target_options].kind_of?(Hash)
|
16
|
+
else
|
17
|
+
args[:target_options] = {}
|
18
|
+
end
|
19
|
+
@target_options = default_options(args[:target_options]) do |o|
|
20
|
+
o[:origination_caller_id_number] = args[:caller_id_number] if args[:caller_id_number]
|
21
|
+
o[:origination_caller_id_name] = args[:caller_id_name] if args[:caller_id_name]
|
22
|
+
if o[:timeout]
|
23
|
+
o[:originate_timeout] = o.delete(:timeout)
|
24
|
+
elsif args[:timeout]
|
25
|
+
o[:originate_timeout] = args[:timeout]
|
26
|
+
end
|
27
|
+
o
|
28
|
+
end
|
29
|
+
raise(ArgumentError, "Origination timeout (#{@target_options[:originate_timeout]}) must be a positive integer") unless @target_options[:originate_timeout].to_i > 0
|
12
30
|
|
13
31
|
@fs_socket = fs_socket # This socket must support say and <<
|
14
32
|
@target = args[:target] # The target address to call
|
33
|
+
raise(ArgumentError, "Cannot originate without a :target set") unless @target.to_s.size > 0
|
15
34
|
# The origination endpoint (can be an extension (use a string) or application)
|
16
35
|
@endpoint = args[:endpoint] || args[:application]
|
36
|
+
raise(ArgumentError, "Cannot originate without an :endpoint set") unless @endpoint.to_s.size > 0
|
17
37
|
|
18
|
-
@target_options[:origination_caller_id_number] ||= args[:caller_id_number] || FSR::DEFAULT_CALLER_ID_NUMBER
|
19
|
-
@target_options[:origination_caller_id_name] ||= args[:caller_id_name] || FSR::DEFAULT_CALLER_ID_NAME
|
20
|
-
@target_options[:originate_timeout] ||= args[:timeout] || @target_options[:timeout] || 30
|
21
|
-
@target_options[:ignore_early_media] ||= true
|
22
38
|
end
|
23
39
|
|
24
40
|
# Send the command to the event socket, using bgapi by default.
|
@@ -39,6 +55,7 @@ module FSR
|
|
39
55
|
raise "Invalid endpoint"
|
40
56
|
end
|
41
57
|
end
|
58
|
+
|
42
59
|
end
|
43
60
|
|
44
61
|
register(:originate, Originate)
|
data/lib/fsr/command_socket.rb
CHANGED
data/lib/fsr/listener/inbound.rb
CHANGED
@@ -13,11 +13,16 @@ module FSR
|
|
13
13
|
@auth = args[:auth] || "ClueCon"
|
14
14
|
end
|
15
15
|
|
16
|
+
# post_init is called upon each "new" socket connection
|
16
17
|
def post_init
|
17
18
|
say("auth #{@auth}")
|
18
19
|
say('event plain ALL')
|
19
20
|
end
|
20
21
|
|
22
|
+
# receive_request is the callback method when data is recieved from the socket
|
23
|
+
#
|
24
|
+
# param header headers from standard Header and Content protocol
|
25
|
+
# param content content from standard Header and Content protocol
|
21
26
|
def receive_request(header, content)
|
22
27
|
hash_header = headers_2_hash(header)
|
23
28
|
hash_content = headers_2_hash(content)
|
@@ -29,28 +34,32 @@ module FSR
|
|
29
34
|
on_event(event)
|
30
35
|
end
|
31
36
|
|
37
|
+
# say encapsulates #send_data for the user
|
38
|
+
#
|
39
|
+
# param line Line of text to send to the socket
|
32
40
|
def say(line)
|
33
41
|
send_data("#{line}\r\n\r\n")
|
34
42
|
end
|
35
|
-
|
43
|
+
|
44
|
+
# on_event is the callback method when an event is triggered
|
45
|
+
#
|
46
|
+
# param event The triggered event object
|
47
|
+
# return event The triggered event object
|
36
48
|
def on_event(event)
|
37
49
|
event
|
38
50
|
end
|
39
51
|
|
40
|
-
#
|
41
|
-
#
|
42
|
-
# <b>Parameters</b>
|
43
|
-
# - event : What event to trigger the block on. May be
|
44
|
-
# :CHANNEL_CREATE, :CHANNEL_DESTROY etc
|
45
|
-
# - block : Block to execute
|
52
|
+
# add_event_hook adds an Event to listen for. When that Event is triggered, it will call the defined block
|
46
53
|
#
|
47
|
-
#
|
48
|
-
#
|
54
|
+
# @param event The event to trigger the block on. Examples, :CHANNEL_CREATE, :CHANNEL_DESTROY, etc
|
55
|
+
# @param block The block to execute when the event is triggered
|
49
56
|
def self.add_event_hook(event, &block)
|
50
57
|
HOOKS[event] = block
|
51
58
|
end
|
52
59
|
|
53
|
-
#
|
60
|
+
# del_event_hook removes an Event.
|
61
|
+
#
|
62
|
+
# @param event The event to remove. Examples, :CHANNEL_CREATE, :CHANNEL_DESTROY, etc
|
54
63
|
def self.del_event_hook(event)
|
55
64
|
HOOKS.delete(event)
|
56
65
|
end
|
@@ -20,7 +20,13 @@ module FSR
|
|
20
20
|
"end"
|
21
21
|
].join("\n")
|
22
22
|
|
23
|
-
APPLICATIONS.each
|
23
|
+
APPLICATIONS.each do |app, obj|
|
24
|
+
if obj.const_defined?("SENDMSG_METHOD")
|
25
|
+
module_eval(obj::SENDMSG_METHOD)
|
26
|
+
else
|
27
|
+
module_eval(SENDMSG_METHOD_DEFINITION % app.to_s)
|
28
|
+
end
|
29
|
+
end
|
24
30
|
|
25
31
|
# session_initiated is called when a @session is first created.
|
26
32
|
# Overwrite this in your worker class with the call/channel
|
@@ -66,6 +72,7 @@ module FSR
|
|
66
72
|
|
67
73
|
protected
|
68
74
|
def post_init
|
75
|
+
@read_var = nil
|
69
76
|
@session = nil # holds the session object
|
70
77
|
@queue = [] # Keep track of queue for state machine
|
71
78
|
send_data("connect\n\n")
|
@@ -77,36 +84,86 @@ module FSR
|
|
77
84
|
# to be picked up by #session_initiated or #receive_reply.
|
78
85
|
# If your listener is listening for events, this will also renew your @session
|
79
86
|
# each time you receive a CHANNEL_DATA event.
|
87
|
+
#
|
80
88
|
# @param header The header of the request, as passed by HeaderAndContentProtocol
|
81
89
|
# @param content The content of the request, as passed by HeaderAndContentProtocol
|
82
90
|
#
|
83
91
|
# @return [HeaderAndContentResponse] An EventMachine HeaderAndContentResponse
|
84
92
|
def receive_request(header, content)
|
85
93
|
hash_header = headers_2_hash(header)
|
86
|
-
|
94
|
+
if content.to_s.match(/:/)
|
95
|
+
hash_content = headers_2_hash(content)
|
96
|
+
else
|
97
|
+
hash_content = content
|
98
|
+
end
|
87
99
|
session_header_and_content = HeaderAndContentResponse.new({:headers => hash_header, :content => hash_content})
|
88
100
|
# If we're a new session, call session initiate
|
89
|
-
if
|
90
|
-
|
91
|
-
|
92
|
-
@
|
93
|
-
|
94
|
-
@
|
101
|
+
if session.nil?
|
102
|
+
establish_new_session(session_header_and_content)
|
103
|
+
elsif @uuid_var and session_header_and_content.headers[:content_type] == "api/response"
|
104
|
+
FSR::Log.info("@uuid_var is set => #{session_header_and_content.inspect} : #{content}")
|
105
|
+
r, @uuid_var = session_header_and_content.content.strip, nil
|
106
|
+
@queue.pop.call(r) if @queue.size > 0
|
95
107
|
elsif session_header_and_content.content[:event_name] # If content includes an event_name, it must be a response from an api command
|
96
|
-
|
97
|
-
session_header_and_content = HeaderAndContentResponse.new({:headers => hash_header.merge(hash_content.strip_value_newlines), :content => {}})
|
98
|
-
@session = session_header_and_content
|
99
|
-
@step += 1 if @state.include?(:initiated)
|
100
|
-
@queue.pop.call unless @queue.empty?
|
101
|
-
receive_reply(hash_header)
|
102
|
-
end
|
108
|
+
check_for_updated_session(session_header_and_content, hash_content, hash_header)
|
103
109
|
else
|
104
|
-
|
105
|
-
|
106
|
-
|
110
|
+
update_state_machine(session_header_and_content)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
protected
|
115
|
+
def queue_pop
|
116
|
+
if @queue.size > 0
|
117
|
+
if @read_var and session.headers[@read_var.to_sym]
|
118
|
+
r, @read_var = @read_var.to_sym, nil
|
119
|
+
@queue.pop.call(session.headers[r])
|
120
|
+
else
|
121
|
+
@queue.pop.call
|
122
|
+
end
|
107
123
|
end
|
108
124
|
end
|
109
125
|
|
126
|
+
private
|
127
|
+
# establish_new_session is called once during a new session.
|
128
|
+
# This establishes and sets up the variables used to hold state.
|
129
|
+
# Called from #receive_request
|
130
|
+
#
|
131
|
+
# @param header_and_content_hash HeaderAndContentProtocol object representing the session
|
132
|
+
#
|
133
|
+
# @return header_and_content_hash HeaderAndContentProtocol object representing the session
|
134
|
+
def establish_new_session(header_and_content_hash)
|
135
|
+
@session = header_and_content_hash
|
136
|
+
@step = 0
|
137
|
+
@state = [:uninitiated]
|
138
|
+
session_initiated
|
139
|
+
@state << :initiated
|
140
|
+
header_and_content_hash
|
141
|
+
end
|
142
|
+
|
143
|
+
# check_for_updated_session is called from #receive_request if a session has already been established
|
144
|
+
# This will overwrite @session with the new variables representing state.
|
145
|
+
#
|
146
|
+
# @param header_and_content_hash HeaderAndContentProtocol object
|
147
|
+
# @param hash_content hash of content from standard Header and Content Protocol
|
148
|
+
# @param hash_header hash of headers from standard Header and Content Protocol
|
149
|
+
def check_for_updated_session(header_and_content_hash, hash_content, hash_header)
|
150
|
+
if header_and_content_hash.content[:event_name].to_s.match(/CHANNEL_DATA/i) # Anytime we see CHANNEL_DATA event, we want to update our @session
|
151
|
+
header_and_content_hash = HeaderAndContentResponse.new({:headers => hash_header.merge(hash_content.strip_value_newlines), :content => {}})
|
152
|
+
@session = header_and_content_hash
|
153
|
+
update_state_machine(header_and_content_hash.headers)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# update_state_machine when called will increment @step,
|
158
|
+
# call the next block from @queue and dispatch the callback method
|
159
|
+
# #receive_reply
|
160
|
+
# @param response_header Response sent to #recieve_reply callback method
|
161
|
+
def update_state_machine(response_header)
|
162
|
+
@step += 1 if @state.include?(:initiated)
|
163
|
+
queue_pop
|
164
|
+
receive_reply(response_header)
|
165
|
+
end
|
166
|
+
|
110
167
|
end
|
111
168
|
end
|
112
169
|
end
|
data/lib/fsr/version.rb
CHANGED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'spec/helper'
|
2
|
+
require "fsr/app"
|
3
|
+
FSR::App.load_application("answer")
|
4
|
+
|
5
|
+
describe "Testing FSR::App::Answer" do
|
6
|
+
|
7
|
+
it "answers the incoming call" do
|
8
|
+
ans = FSR::App::Answer.new
|
9
|
+
ans.sendmsg.should == "call-command: execute\nexecute-app-name: answer\n\n"
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|