adhearsion 2.0.0.rc5 → 2.0.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/.yardopts +1 -0
- data/CHANGELOG.md +28 -126
- data/LICENSE +20 -456
- data/README.markdown +20 -29
- data/adhearsion.gemspec +22 -31
- data/lib/adhearsion.rb +5 -2
- data/lib/adhearsion/call.rb +57 -20
- data/lib/adhearsion/call_controller.rb +83 -13
- data/lib/adhearsion/call_controller/dial.rb +35 -20
- data/lib/adhearsion/call_controller/input.rb +20 -15
- data/lib/adhearsion/call_controller/menu_dsl.rb +1 -0
- data/lib/adhearsion/call_controller/output.rb +53 -42
- data/lib/adhearsion/call_controller/record.rb +3 -3
- data/lib/adhearsion/call_controller/utility.rb +11 -5
- data/lib/adhearsion/cli_commands.rb +2 -1
- data/lib/adhearsion/generators.rb +1 -1
- data/lib/adhearsion/generators/app/templates/README.md +4 -2
- data/lib/adhearsion/generators/plugin/templates/lib/plugin-template.rb.tt +4 -5
- data/lib/adhearsion/linux_proc_name.rb +2 -2
- data/lib/adhearsion/version.rb +2 -2
- data/spec/adhearsion/call_controller/output_spec.rb +10 -12
- metadata +112 -141
@@ -4,38 +4,44 @@ module Adhearsion
|
|
4
4
|
class CallController
|
5
5
|
module Dial
|
6
6
|
#
|
7
|
-
# Dial
|
7
|
+
# Dial one or more third parties and join one to this call
|
8
8
|
#
|
9
|
-
# @
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
# The first call answered is joined, the others are hung up.
|
15
|
-
# A hash argument has the dial target as each key, and an hash of options as the value, in the form:
|
16
|
-
# dial({'SIP/100' => {:timeout => 3000}, 'SIP/200' => {:timeout => 4000} })
|
17
|
-
# The option hash for each target is merged into the global options, overriding them for the single dial.
|
18
|
-
# Destinations are dialed simultaneously as with an array.
|
9
|
+
# @overload dial(to[String], options = {})
|
10
|
+
# @param [String] to The target URI to dial.
|
11
|
+
# You must specify a properly formatted string that your VoIP platform understands.
|
12
|
+
# eg. sip:foo@bar.com, tel:+14044754840, or SIP/foo/1234
|
13
|
+
# @param [Hash] options see below
|
19
14
|
#
|
20
|
-
# @
|
15
|
+
# @overload dial(to[Array], options = {})
|
16
|
+
# @param [Array<String>] to Target URIs to dial.
|
17
|
+
# Each will be called with the same options simultaneously.
|
18
|
+
# The first call answered is joined, the others are hung up.
|
19
|
+
# @param [Hash] options see below
|
21
20
|
#
|
22
|
-
#
|
23
|
-
#
|
21
|
+
# @overload dial(to[Hash], options = {})
|
22
|
+
# @param [Hash<String => Hash>] to Target URIs to dial, mapped to their per-target options overrides.
|
23
|
+
# Each will be called with the same options simultaneously.
|
24
|
+
# The first call answered is joined, the others are hung up.
|
25
|
+
# Each calls options are deep-merged with the global options hash.
|
26
|
+
# @param [Hash] options see below
|
24
27
|
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
+
# @option options [String] :from the caller id to be used when the call is placed. It is advised you properly adhere to the
|
29
|
+
# policy of VoIP termination providers with respect to caller id values.
|
30
|
+
#
|
31
|
+
# @option options [Numeric] :for this option can be thought of best as a timeout.
|
32
|
+
# i.e. timeout after :for if no one answers the call
|
28
33
|
#
|
29
34
|
# @example Make a call to the PSTN using my SIP provider for VoIP termination
|
30
35
|
# dial "SIP/19095551001@my.sip.voip.terminator.us"
|
31
36
|
#
|
32
|
-
# @example Make 3
|
33
|
-
# for this call specified by the variable my_callerid
|
37
|
+
# @example Make 3 simulataneous calls to the SIP extensions, try for 15 seconds and use the callerid for this call specified by the variable my_callerid
|
34
38
|
# dial %w{SIP/jay-desk-650 SIP/jay-desk-601 SIP/jay-desk-601-2}, :for => 15.seconds, :from => my_callerid
|
35
39
|
#
|
36
40
|
# @example Make a call using the IAX provider to the PSTN
|
37
41
|
# dial "IAX2/my.id@voipjet/19095551234", :from => "John Doe <9095551234>"
|
38
42
|
#
|
43
|
+
# @return [DialStatus] the status of the dial operation
|
44
|
+
#
|
39
45
|
def dial(to, options = {}, latch = nil)
|
40
46
|
targets = to.respond_to?(:has_key?) ? to : Array(to)
|
41
47
|
|
@@ -111,29 +117,38 @@ module Adhearsion
|
|
111
117
|
end
|
112
118
|
|
113
119
|
class DialStatus
|
120
|
+
# The collection of calls created during the dial operation
|
114
121
|
attr_accessor :calls
|
115
122
|
|
123
|
+
# @private
|
116
124
|
def initialize
|
117
125
|
@result = nil
|
118
126
|
end
|
119
127
|
|
128
|
+
#
|
129
|
+
# The result of the dial operation.
|
130
|
+
#
|
131
|
+
# @return [Symbol] :no_answer, :answer, :timeout, :error
|
120
132
|
def result
|
121
133
|
@result || :no_answer
|
122
134
|
end
|
123
135
|
|
136
|
+
# @private
|
124
137
|
def answer!
|
125
138
|
@result = :answer
|
126
139
|
end
|
127
140
|
|
141
|
+
# @private
|
128
142
|
def timeout!
|
129
143
|
@result ||= :timeout
|
130
144
|
end
|
131
145
|
|
146
|
+
# @private
|
132
147
|
def error!
|
133
148
|
@result ||= :error
|
134
149
|
end
|
135
150
|
end
|
136
151
|
|
137
|
-
end
|
152
|
+
end
|
138
153
|
end
|
139
154
|
end
|
@@ -10,6 +10,7 @@ module Adhearsion
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
+
#
|
13
14
|
# Prompts for input via DTMF, handling playback of prompts,
|
14
15
|
# timeouts, digit limits and terminator digits.
|
15
16
|
#
|
@@ -23,7 +24,7 @@ module Adhearsion
|
|
23
24
|
# :timeout, :terminator and :limit options may then be specified.
|
24
25
|
# A block may be passed which is invoked on each digit being collected. If it returns true, the collection is terminated.
|
25
26
|
#
|
26
|
-
# @param [Object] A list of outputs to play, as accepted by #play
|
27
|
+
# @param [Object, Array<Object>] args A list of outputs to play, as accepted by #play
|
27
28
|
# @param [Hash] options Options to use for the menu
|
28
29
|
# @option options [Boolean] :interruptible If the prompt should be interruptible or not. Defaults to true
|
29
30
|
# @option options [Integer] :limit Digit limit (causes collection to cease after a specified number of digits have been collected)
|
@@ -32,8 +33,8 @@ module Adhearsion
|
|
32
33
|
#
|
33
34
|
# @return [Result] a result object from which the #response and #status may be established
|
34
35
|
#
|
35
|
-
# @see play
|
36
|
-
# @see pass
|
36
|
+
# @see Output#play
|
37
|
+
# @see CallController#pass
|
37
38
|
#
|
38
39
|
def ask(*args, &block)
|
39
40
|
options = args.last.kind_of?(Hash) ? args.pop : {}
|
@@ -51,8 +52,8 @@ module Adhearsion
|
|
51
52
|
if result_of_menu.is_a?(MenuDSL::Menu::MenuGetAnotherDigit)
|
52
53
|
next_digit = play_sound_files_for_menu menu_instance, sound_files
|
53
54
|
menu_instance << next_digit if next_digit
|
54
|
-
end
|
55
|
-
end
|
55
|
+
end
|
56
|
+
end
|
56
57
|
|
57
58
|
Result.new.tap do |result|
|
58
59
|
result.response = menu_instance.result
|
@@ -103,7 +104,7 @@ module Adhearsion
|
|
103
104
|
# Execution of the current context resumes after #ask finishes. If you wish to jump to an entirely different controller, use #pass.
|
104
105
|
# Menu will return :failed if failure was reached, or :done if a match was executed.
|
105
106
|
#
|
106
|
-
# @param [Object] A list of outputs to play, as accepted by #play
|
107
|
+
# @param [Object] args A list of outputs to play, as accepted by #play
|
107
108
|
# @param [Hash] options Options to use for the menu
|
108
109
|
# @option options [Integer] :tries Number of tries allowed before failure
|
109
110
|
# @option options [Integer] :timeout Timeout in seconds before the first and between each input digit
|
@@ -111,8 +112,8 @@ module Adhearsion
|
|
111
112
|
#
|
112
113
|
# @return [Result] a result object from which the #response and #status may be established
|
113
114
|
#
|
114
|
-
# @see play
|
115
|
-
# @see pass
|
115
|
+
# @see Output#play
|
116
|
+
# @see CallController#pass
|
116
117
|
#
|
117
118
|
def menu(*args, &block)
|
118
119
|
options = args.last.kind_of?(Hash) ? args.pop : {}
|
@@ -158,8 +159,8 @@ module Adhearsion
|
|
158
159
|
logger.debug "Menu received valid input (#{result_of_menu.new_extension}). Calling the matching hook."
|
159
160
|
jump_to result_of_menu.match_object, :extension => result_of_menu.new_extension
|
160
161
|
throw :finish
|
161
|
-
end
|
162
|
-
end
|
162
|
+
end
|
163
|
+
end
|
163
164
|
end
|
164
165
|
|
165
166
|
Result.new.tap do |result|
|
@@ -169,7 +170,8 @@ module Adhearsion
|
|
169
170
|
end
|
170
171
|
end
|
171
172
|
|
172
|
-
|
173
|
+
# @private
|
174
|
+
def play_sound_files_for_menu(menu_instance, sound_files)
|
173
175
|
digit = nil
|
174
176
|
if sound_files.any? && menu_instance.digit_buffer_empty?
|
175
177
|
if menu_instance.interruptible
|
@@ -185,9 +187,11 @@ module Adhearsion
|
|
185
187
|
# Waits for a single digit and returns it, or returns nil if nothing was pressed
|
186
188
|
#
|
187
189
|
# @param [Integer] the timeout to wait before returning, in seconds. nil or -1 mean no timeout.
|
188
|
-
# @return [String
|
190
|
+
# @return [String, nil] the pressed key, or nil if timeout was reached.
|
191
|
+
#
|
192
|
+
# @private
|
189
193
|
#
|
190
|
-
def wait_for_digit(timeout = 1)
|
194
|
+
def wait_for_digit(timeout = 1)
|
191
195
|
timeout = nil if timeout == -1
|
192
196
|
timeout *= 1_000 if timeout
|
193
197
|
input_component = execute_component_and_await_completion ::Punchblock::Component::Input.new :mode => :dtmf,
|
@@ -202,7 +206,8 @@ module Adhearsion
|
|
202
206
|
parse_single_dtmf result
|
203
207
|
end
|
204
208
|
|
205
|
-
|
209
|
+
# @private
|
210
|
+
def jump_to(match_object, overrides = nil)
|
206
211
|
if match_object.block
|
207
212
|
instance_exec overrides[:extension], &match_object.block
|
208
213
|
else
|
@@ -210,6 +215,6 @@ module Adhearsion
|
|
210
215
|
end
|
211
216
|
end
|
212
217
|
|
213
|
-
end
|
218
|
+
end
|
214
219
|
end
|
215
220
|
end
|
@@ -5,8 +5,14 @@ module Adhearsion
|
|
5
5
|
module Output
|
6
6
|
PlaybackError = Class.new Adhearsion::Error # Represents failure to play audio, such as when the sound file cannot be found
|
7
7
|
|
8
|
+
#
|
9
|
+
# Speak output using text-to-speech (TTS)
|
10
|
+
#
|
11
|
+
# @param [String, #to_s] text The text to be rendered
|
12
|
+
# @param [Hash] options A set of options for output
|
13
|
+
#
|
8
14
|
def say(text, options = {})
|
9
|
-
play_ssml(text, options) || output(
|
15
|
+
play_ssml(text, options) || output(ssml_for_text(text.to_s), options)
|
10
16
|
end
|
11
17
|
alias :speak :say
|
12
18
|
|
@@ -54,6 +60,8 @@ module Adhearsion
|
|
54
60
|
# Plays the specified input arguments, raising an exception if any can't be played.
|
55
61
|
# @see play
|
56
62
|
#
|
63
|
+
# @private
|
64
|
+
#
|
57
65
|
def play!(*arguments)
|
58
66
|
play(*arguments) or raise PlaybackError, "One of the passed outputs is invalid"
|
59
67
|
end
|
@@ -62,25 +70,22 @@ module Adhearsion
|
|
62
70
|
# Plays the given Date, Time, or Integer (seconds since epoch)
|
63
71
|
# using the given timezone and format.
|
64
72
|
#
|
65
|
-
# @param [Date
|
66
|
-
# @param [Hash] Additional options to specify how exactly to say time specified.
|
67
|
-
#
|
68
|
-
# +:format+ - This format is used only to disambiguate times that could be interpreted in different ways.
|
73
|
+
# @param [Date, Time, DateTime] time Time to be said.
|
74
|
+
# @param [Hash] options Additional options to specify how exactly to say time specified.
|
75
|
+
# @option options [String] :format This format is used only to disambiguate times that could be interpreted in different ways.
|
69
76
|
# For example, 01/06/2011 could mean either the 1st of June or the 6th of January.
|
70
77
|
# Please refer to the SSML specification.
|
71
78
|
# @see http://www.w3.org/TR/ssml-sayas/#S3.1
|
72
|
-
#
|
79
|
+
# @option options [String] :strftime This format is what defines the string that is sent to the Speech Synthesis Engine.
|
73
80
|
# It uses Time::strftime symbols.
|
74
81
|
#
|
75
82
|
# @return [Boolean] true if successful, false if the given argument could not be played.
|
76
83
|
#
|
77
|
-
def play_time(
|
78
|
-
|
79
|
-
return false unless [Date, Time, DateTime].include? argument.class
|
84
|
+
def play_time(time, options = {})
|
85
|
+
return false unless [Date, Time, DateTime].include? time.class
|
80
86
|
|
81
|
-
options ||= {}
|
82
87
|
return false unless options.is_a? Hash
|
83
|
-
play_ssml ssml_for_time(
|
88
|
+
play_ssml ssml_for_time(time, options)
|
84
89
|
end
|
85
90
|
|
86
91
|
#
|
@@ -88,14 +93,13 @@ module Adhearsion
|
|
88
93
|
# When playing numbers, Adhearsion assumes you're saying the number, not the digits. For example, play("100")
|
89
94
|
# is pronounced as "one hundred" instead of "one zero zero".
|
90
95
|
#
|
91
|
-
# @param [Numeric
|
96
|
+
# @param [Numeric, String] Numeric or String containing a valid Numeric, like "321".
|
92
97
|
#
|
93
98
|
# @return [Boolean] true if successful, false if the given argument could not be played.
|
94
99
|
#
|
95
|
-
def play_numeric(
|
96
|
-
|
97
|
-
|
98
|
-
play_ssml ssml_for_numeric(argument, options)
|
100
|
+
def play_numeric(number, options = nil)
|
101
|
+
if number.kind_of?(Numeric) || number =~ /^\d+$/
|
102
|
+
play_ssml ssml_for_numeric(number, options)
|
99
103
|
end
|
100
104
|
end
|
101
105
|
|
@@ -104,30 +108,26 @@ module Adhearsion
|
|
104
108
|
# SSML supports http:// paths and full disk paths.
|
105
109
|
# The Punchblock backend will have to handle cases like Asterisk where there is a fixed sounds directory.
|
106
110
|
#
|
107
|
-
# @param [String] http:// URL or full disk path to the sound file
|
108
|
-
# @param [Hash] Additional options to specify how exactly to say time specified.
|
109
|
-
#
|
111
|
+
# @param [String] file http:// URL or full disk path to the sound file
|
112
|
+
# @param [Hash] options Additional options to specify how exactly to say time specified.
|
113
|
+
# @option options [String] :fallback The text to play if the file is not available
|
110
114
|
#
|
111
115
|
# @return [Boolean] true on correct play of the file, false on file missing or not playable
|
112
116
|
#
|
113
|
-
def play_audio(
|
114
|
-
|
115
|
-
play_ssml ssml_for_audio(argument, options)
|
117
|
+
def play_audio(file, options = nil)
|
118
|
+
play_ssml ssml_for_audio(file, options)
|
116
119
|
end
|
117
120
|
|
118
|
-
|
121
|
+
# @private
|
122
|
+
def play_ssml(ssml, options = {})
|
119
123
|
if [RubySpeech::SSML::Speak, Nokogiri::XML::Document].include? ssml.class
|
120
|
-
output
|
124
|
+
output ssml.to_s, options
|
121
125
|
end
|
122
126
|
end
|
123
127
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
end
|
128
|
-
|
129
|
-
def output!(type, content, options = {}) # :nodoc:
|
130
|
-
options.merge! type => content
|
128
|
+
# @private
|
129
|
+
def output(content, options = {})
|
130
|
+
options.merge! :ssml => content
|
131
131
|
execute_component_and_await_completion ::Punchblock::Component::Output.new(options)
|
132
132
|
end
|
133
133
|
|
@@ -135,6 +135,8 @@ module Adhearsion
|
|
135
135
|
# Same as interruptible_play, but throws an error if unable to play the output
|
136
136
|
# @see interruptible_play
|
137
137
|
#
|
138
|
+
# @private
|
139
|
+
#
|
138
140
|
def interruptible_play!(*outputs)
|
139
141
|
result = nil
|
140
142
|
outputs.each do |output|
|
@@ -155,10 +157,10 @@ module Adhearsion
|
|
155
157
|
# input = interruptible_play ssml
|
156
158
|
# play input unless input.nil?
|
157
159
|
#
|
158
|
-
# @param [String
|
160
|
+
# @param [String, Numeric, Date, Time, RubySpeech::SSML::Speak, Array, Hash] The argument to play to the user, or an array of arguments.
|
159
161
|
# @param [Hash] Additional options.
|
160
162
|
#
|
161
|
-
# @return [String
|
163
|
+
# @return [String, nil] The single DTMF character entered by the user, or nil if nothing was entered
|
162
164
|
#
|
163
165
|
def interruptible_play(*outputs)
|
164
166
|
result = nil
|
@@ -175,7 +177,8 @@ module Adhearsion
|
|
175
177
|
result
|
176
178
|
end
|
177
179
|
|
178
|
-
|
180
|
+
# @private
|
181
|
+
def detect_type(output)
|
179
182
|
result = nil
|
180
183
|
result = :time if [Date, Time, DateTime].include? output.class
|
181
184
|
result = :numeric if output.kind_of?(Numeric) || output =~ /^\d+$/
|
@@ -183,7 +186,8 @@ module Adhearsion
|
|
183
186
|
result ||= :text
|
184
187
|
end
|
185
188
|
|
186
|
-
|
189
|
+
# @private
|
190
|
+
def play_ssml_for(*args)
|
187
191
|
play_ssml ssml_for(args)
|
188
192
|
end
|
189
193
|
|
@@ -191,10 +195,12 @@ module Adhearsion
|
|
191
195
|
# Generates SSML for the argument and options passed, using automatic detection
|
192
196
|
# Directly returns the argument if it is already an SSML document
|
193
197
|
#
|
194
|
-
# @param [String
|
198
|
+
# @param [String, Hash, RubySpeech::SSML::Speak] the argument with options as accepted by the play_ methods, or an SSML document
|
195
199
|
# @return [RubySpeech::SSML::Speak] an SSML document
|
196
200
|
#
|
197
|
-
|
201
|
+
# @private
|
202
|
+
#
|
203
|
+
def ssml_for(*args)
|
198
204
|
return args[0] if args.size == 1 && args[0].is_a?(RubySpeech::SSML::Speak)
|
199
205
|
argument, options = args.flatten
|
200
206
|
options ||= {}
|
@@ -202,11 +208,13 @@ module Adhearsion
|
|
202
208
|
send "ssml_for_#{type}", argument, options
|
203
209
|
end
|
204
210
|
|
205
|
-
|
211
|
+
# @private
|
212
|
+
def ssml_for_text(argument, options = {})
|
206
213
|
RubySpeech::SSML.draw { argument }
|
207
214
|
end
|
208
215
|
|
209
|
-
|
216
|
+
# @private
|
217
|
+
def ssml_for_time(argument, options = {})
|
210
218
|
interpretation = case argument
|
211
219
|
when Date then 'date'
|
212
220
|
when Time then 'time'
|
@@ -222,13 +230,15 @@ module Adhearsion
|
|
222
230
|
end
|
223
231
|
end
|
224
232
|
|
225
|
-
|
233
|
+
# @private
|
234
|
+
def ssml_for_numeric(argument, options = {})
|
226
235
|
RubySpeech::SSML.draw do
|
227
236
|
say_as(:interpret_as => 'cardinal') { argument.to_s }
|
228
237
|
end
|
229
238
|
end
|
230
239
|
|
231
|
-
|
240
|
+
# @private
|
241
|
+
def ssml_for_audio(argument, options = {})
|
232
242
|
fallback = (options || {}).delete :fallback
|
233
243
|
RubySpeech::SSML.draw do
|
234
244
|
audio(:src => argument) { fallback }
|
@@ -241,7 +251,8 @@ module Adhearsion
|
|
241
251
|
#
|
242
252
|
# @param [Object] String or Hash specifying output and options
|
243
253
|
# @param [String] String with the digits that are allowed to interrupt output
|
244
|
-
#
|
254
|
+
#
|
255
|
+
# @return [String, nil] The pressed digit, or nil if nothing was pressed
|
245
256
|
#
|
246
257
|
def stream_file(argument, digits = '0123456789#*')
|
247
258
|
result = nil
|
@@ -9,7 +9,7 @@ module Adhearsion
|
|
9
9
|
# Start a recording
|
10
10
|
#
|
11
11
|
# @param [Hash] options
|
12
|
-
# @param [Block]
|
12
|
+
# @param [Block] block to process result of the record method
|
13
13
|
# @option options [Boolean, Optional] :async Execute asynchronously. Defaults to false
|
14
14
|
# @option options [Boolean, Optional] :start_beep Indicates whether subsequent record will be preceded with a beep. Default is true.
|
15
15
|
# @option options [Boolean, Optional] :start_paused Whether subsequent record will start in PAUSE mode. Default is false.
|
@@ -19,8 +19,8 @@ module Adhearsion
|
|
19
19
|
# @option options [String, Optional] :initial_timeout Controls how long (milliseconds) the recognizer should wait after the end of the prompt for the caller to speak before sending a Recorder event.
|
20
20
|
# @option options [String, Optional] :final_timeout Controls the length (milliseconds) of a period of silence after callers have spoken to conclude they finished.
|
21
21
|
#
|
22
|
-
# @return
|
23
|
-
|
22
|
+
# @return Punchblock::Component::Record::Recording
|
23
|
+
#
|
24
24
|
def record(options = {})
|
25
25
|
async = options.delete :async
|
26
26
|
|