adhearsion 1.0.0 → 1.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.
@@ -49,6 +49,7 @@ class AhnGenerator < RubiGen::Base
49
49
  m.file *["events.rb"]*2
50
50
  m.file *["README"]*2
51
51
  m.file *["Rakefile"]*2
52
+ m.file *["Gemfile"]*2
52
53
 
53
54
  # m.dependency "install_rubigen_scripts", [destination_root, 'ahn', 'adhearsion', 'test_spec'],
54
55
  # :shebang => options[:shebang], :collision => :force
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "rake"
4
+ gem "rubigen", ">= 1.0.6"
5
+ gem "log4r", ">= 1.0.5"
6
+ gem "activesupport", ">= 2.1.0"
7
+ gem "adhearsion", ">= 1.0.1"
@@ -1,9 +1,11 @@
1
1
  # This file is for the "rake" tool which automates project-related tasks. If you need to automate things, you can create
2
2
  # a new Rake task here. See http://rake.rubyforge.org for more info.
3
3
  require 'rubygems'
4
+ require 'bundler'
5
+ Bundler.setup
6
+ Bundler.require
4
7
 
5
8
  begin
6
- require 'adhearsion'
7
9
  require 'adhearsion/tasks'
8
10
  rescue LoadError
9
11
  STDERR.puts "\nCannot load Adhearsion! Not all Rake tasks will be loaded!\n\n"
@@ -22,4 +24,4 @@ task :gitignore do
22
24
  end
23
25
  end
24
26
  end
25
- end
27
+ end
@@ -1,16 +1,7 @@
1
- unless defined? Adhearsion
2
- if File.exists? File.dirname(__FILE__) + "/../adhearsion/lib/adhearsion.rb"
3
- # For development purposes try to load a local copy of Adhearsion here.
4
- # This will not work if started using "ahn" or "jahn". You must execute
5
- # config/startup.rb directly and have a local checkout of Adhearsion in your
6
- # application directory.
7
- require File.dirname(__FILE__) + "/../adhearsion/lib/adhearsion.rb"
8
- else
9
- require 'rubygems'
10
- gem 'adhearsion', '>= 0.8.2'
11
- require 'adhearsion'
12
- end
13
- end
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ Bundler.setup
4
+ Bundler.require
14
5
 
15
6
  Adhearsion::Configuration.configure do |config|
16
7
 
@@ -71,7 +62,7 @@ Adhearsion::Configuration.configure do |config|
71
62
  # :password => 'password12345',
72
63
  # :allow_anonymous => false,
73
64
  # :try_sasl => false
74
-
65
+
75
66
  # Configure XMPP call controller
76
67
  # config.enable_xmpp :jid => 'active-calls.xmpp.example.com',
77
68
  # :password => 'passwd',
data/bin/ahn CHANGED
@@ -22,6 +22,7 @@
22
22
  $:.unshift File.expand_path(File.dirname(__FILE__) + "/../lib")
23
23
 
24
24
  require 'rubygems'
25
+ require 'bundler/setup'
25
26
  require 'adhearsion'
26
27
  require 'adhearsion/cli'
27
28
 
data/bin/jahn CHANGED
@@ -22,6 +22,7 @@
22
22
  $:.unshift File.expand_path(File.dirname(__FILE__) + "/../lib")
23
23
 
24
24
  require 'rubygems'
25
+ require 'bundler/setup'
25
26
  require 'adhearsion'
26
27
  require 'adhearsion/cli'
27
28
 
@@ -6,6 +6,7 @@ Please upgrade to at least Ruby v1.8.5." if RUBY_VERSION < "1.8.5"
6
6
  $: << File.expand_path(File.dirname(__FILE__))
7
7
 
8
8
  require 'rubygems'
9
+ require 'bundler/setup'
9
10
 
10
11
  require 'adhearsion/version'
11
12
  require 'adhearsion/voip/call'
@@ -14,23 +14,24 @@ Usage:
14
14
  ahn disable component COMPONENT_NAME
15
15
  ahn create component COMPONENT_NAME
16
16
  USAGE
17
+ class << self
17
18
 
18
- def self.execute!
19
- CommandHandler.send(*parse_arguments)
20
- rescue CommandHandler::CLIException => error
21
- fail_and_print_usage error
22
- end
19
+ def execute!
20
+ CommandHandler.send(*parse_arguments)
21
+ rescue CommandHandler::CLIException => error
22
+ fail_and_print_usage error
23
+ end
23
24
 
24
- ##
25
- # Provides a small abstraction of Kernel::abort().
26
- #
27
- def self.fail_and_print_usage(error)
28
- Kernel.abort "#{error.message}\n\n#{USAGE}"
29
- end
25
+ ##
26
+ # Provides a small abstraction of Kernel::abort().
27
+ #
28
+ def fail_and_print_usage(error)
29
+ Kernel.abort "#{error.message}\n\n#{USAGE}"
30
+ end
30
31
 
31
- def self.parse_arguments(args=ARGV.clone)
32
- action = args.shift
33
- case action
32
+ def parse_arguments(args=ARGV.clone)
33
+ action = args.shift
34
+ case action
34
35
  when /^-?-?h(elp)?$/, nil then [:help]
35
36
  when /^-?-?v(ersion)?$/ then [:version]
36
37
  when "create"
@@ -67,6 +68,7 @@ USAGE
67
68
  end
68
69
  else
69
70
  [action, *args]
71
+ end
70
72
  end
71
73
  end
72
74
 
@@ -102,23 +104,72 @@ USAGE
102
104
 
103
105
  app_path = PathString.from_application_subdirectory Dir.pwd
104
106
 
105
- raise PathInvalid.new(Dir.pwd) if app_path.nil?
107
+ if app_path.nil?
108
+ new_component_dir = File.join Dir.pwd, component_name
109
+ else
110
+ puts "Adhearsion application detected. Creating new component at components/#{component_name}"
111
+ new_component_dir = File.join app_path, "components", component_name
112
+ end
106
113
 
107
- new_component_dir = app_path + "/components/#{component_name}"
108
114
  raise ComponentError.new("Component #{component_name} already exists!") if File.exists?(new_component_dir)
109
115
 
110
116
  # Everything's good. Let's create the component
111
117
  Dir.mkdir new_component_dir
112
- File.open(new_component_dir + "/#{component_name}.rb","w") do |file|
118
+
119
+ # Initial component code file
120
+ Dir.mkdir File.join(new_component_dir, "lib")
121
+ fn = File.join("lib", "#{component_name}.rb")
122
+ puts "- #{fn}: Initial component code file"
123
+ File.open(File.join(new_component_dir, fn),"w") do |file|
113
124
  file.puts <<-RUBY
114
125
  # See http://docs.adhearsion.com for more information on how to write components or
115
126
  # look at the examples in newly-created projects.
116
127
  RUBY
117
128
  end
118
- File.open(new_component_dir + "/#{component_name}.yml","w") do |file|
129
+
130
+ # Component configuration
131
+ Dir.mkdir File.join(new_component_dir, "config")
132
+ fn = File.join("config", "#{component_name}.yml")
133
+ puts "- #{fn}: Example component configuration YAML"
134
+ File.open(File.join(new_component_dir, fn),"w") do |file|
119
135
  file.puts '# You can use this file for component-specific configuration.'
120
136
  end
121
- puts "Created blank component '#{component_name}' at components/#{component_name}"
137
+
138
+ # Component example gemspec
139
+ fn = File.join("#{component_name}.gemspec")
140
+ puts "- #{fn}: Example component gemspec"
141
+ File.open(File.join(new_component_dir, fn), "w") do |file|
142
+ file.puts <<-RUBY
143
+ GEM_FILES = %w{
144
+ #{component_name}.gemspec
145
+ lib/#{component_name}.rb
146
+ config/#{component_name}.yml
147
+ }
148
+
149
+ Gem::Specification.new do |s|
150
+ s.name = "#{component_name}"
151
+ s.version = "0.0.1"
152
+
153
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
154
+ s.authors = ["Your Name Here!"]
155
+
156
+ s.date = Date.today.to_s
157
+ s.description = "This Adhearsion component gem has not yet been described."
158
+ s.email = "noreply@example.com"
159
+
160
+ s.files = GEM_FILES
161
+
162
+ s.has_rdoc = false
163
+ s.homepage = "http://adhearsion.com"
164
+ s.require_paths = ["lib"]
165
+ s.rubygems_version = "1.2.0"
166
+ s.summary = "This Adhearsion component gem has no summary."
167
+
168
+ s.specification_version = 2
169
+ end
170
+ RUBY
171
+ end
172
+ puts "Created blank component '#{component_name}' at #{new_component_dir}"
122
173
  else
123
174
  raise CommandHandler::UnknownCommand.new("Provided too many arguments to 'create'")
124
175
  end
@@ -1,3 +1,3 @@
1
1
  class BlankSlate
2
- (instance_methods - %w{instance_eval object_id}).each { |m| undef_method m unless m =~ /^__/ }
3
- end
2
+ (instance_methods.map{|m| m.to_sym} - [:instance_eval, :object_id]).each { |m| undef_method m unless m.to_s =~ /^__/ }
3
+ end
@@ -167,6 +167,7 @@ class EventSocket
167
167
  def new_handler_from_block(&handler_block)
168
168
  handler = Object.new
169
169
  handler.metaclass.send :attr_accessor, :set_callbacks
170
+ handler.metaclass.send :public, :set_callbacks, :set_callbacks=
170
171
  handler.set_callbacks = {:receive_data => false, :disconnected => false, :connected => false }
171
172
 
172
173
  def handler.receive_data(&block)
@@ -246,8 +246,8 @@ module Adhearsion
246
246
 
247
247
  unless acl
248
248
  self.acl = []
249
- overrides[ :deny].to_a.each { |ip| acl << 'deny' << ip }
250
- overrides[:allow].to_a.each { |ip| acl << 'allow' << ip }
249
+ [*overrides[ :deny]].compact.each { |ip| acl << 'deny' << ip }
250
+ [*overrides[:allow]].compact.each { |ip| acl << 'allow' << ip }
251
251
  acl.concat %w[allow 127.0.0.1] if acl.empty?
252
252
  end
253
253
  end
@@ -2,7 +2,7 @@ module Adhearsion #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 1 unless defined? MAJOR
4
4
  MINOR = 0 unless defined? MINOR
5
- TINY = 0 unless defined? TINY
5
+ TINY = 1 unless defined? TINY
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.') unless defined? STRING
8
8
  end
@@ -36,7 +36,7 @@ module Adhearsion
36
36
  variable "TRANSFER_CONTEXT" => options[:context] if options && options.has_key?(:context)
37
37
  extend_dynamic_features_with "atxfer"
38
38
  end,
39
- :blind_transfer => lambda do
39
+ :blind_transfer => lambda do |options|
40
40
  variable "TRANSFER_CONTEXT" => options[:context] if options && options.has_key?(:context)
41
41
  extend_dynamic_features_with 'blindxfer'
42
42
  end
@@ -174,7 +174,9 @@ module Adhearsion
174
174
  # Plays the specified sound file names. This method will handle Time/DateTime objects (e.g. Time.now),
175
175
  # Fixnums (e.g. 1000), Strings which are valid Fixnums (e.g "123"), and direct sound files. When playing
176
176
  # numbers, Adhearsion assumes you're saying the number, not the digits. For example, play("100")
177
- # is pronounced as "one hundred" instead of "one zero zero".
177
+ # is pronounced as "one hundred" instead of "one zero zero". To specify how the Date/Time objects are said
178
+ # pass in as an array with the first parameter as the Date/Time/DateTime object along with a hash with the
179
+ # additional options. See play_time for more information.
178
180
  #
179
181
  # Note: it is not necessary to supply a sound file extension; Asterisk will try to find a sound
180
182
  # file encoded using the current channel's codec, if one exists. If not, it will transcode from
@@ -184,13 +186,17 @@ module Adhearsion
184
186
  # play 'hello-world'
185
187
  # @example Speak current time
186
188
  # play Time.now
189
+ # @example Speak today's date
190
+ # play Date.today
191
+ # @example Speak today's date in a specific format
192
+ # play [Date.today, {:format => 'BdY'}]
187
193
  # @example Play sound file, speak number, play two more sound files
188
194
  # play %w"a-connect-charge-of 22 cents-per-minute will-apply"
189
195
  # @example Play two sound files
190
196
  # play "you-sound-cute", "what-are-you-wearing"
191
197
  #
192
198
  def play(*arguments)
193
- arguments.flatten.each do |argument|
199
+ arguments.each do |argument|
194
200
  play_time(argument) || play_numeric(argument) || play_string(argument)
195
201
  end
196
202
  end
@@ -911,6 +917,62 @@ module Adhearsion
911
917
  nil
912
918
  end
913
919
 
920
+ ##
921
+ # Executes the SayPhonetic command. This command will read the text passed in
922
+ # out load using the NATO phonetic alphabet.
923
+ #
924
+ # @param [String] Passed in as the text to read aloud
925
+ #
926
+ # @see http://www.voip-info.org/wiki/view/Asterisk+cmd+SayPhonetic Asterisk SayPhonetic Command
927
+ def say_phonetic(text)
928
+ execute "sayphonetic", text
929
+ end
930
+
931
+ ##
932
+ # Executes the SayAlpha command. This command will read the text passed in
933
+ # out loud, character-by-character.
934
+ #
935
+ # @param [String] Passed in as the text to read aloud
936
+ #
937
+ # @example Say "one a two dot pound"
938
+ # say_chars "1a2.#"
939
+ #
940
+ # @see http://www.voip-info.org/wiki/view/Asterisk+cmd+SayAlpha Asterisk SayPhonetic Command
941
+ def say_chars(text)
942
+ execute "sayalpha", text
943
+ end
944
+
945
+ # Plays the given Date, Time, or Integer (seconds since epoch)
946
+ # using the given timezone and format.
947
+ #
948
+ # @param [Date|Time|DateTime] Time to be said.
949
+ # @param [Hash] Additional options to specify how exactly to say time specified.
950
+ #
951
+ # +:timezone+ - Sends a timezone to asterisk. See /usr/share/zoneinfo for a list. Defaults to the machine timezone.
952
+ # +:format+ - This is the format the time is to be said in. Defaults to "ABdY 'digits/at' IMp"
953
+ #
954
+ # @see http://www.voip-info.org/wiki/view/Asterisk+cmd+SayUnixTime
955
+ def play_time(*args)
956
+ argument, options = args.flatten
957
+ options ||= {}
958
+
959
+ timezone = options.delete(:timezone) || ''
960
+ format = options.delete(:format) || ''
961
+ epoch = case argument.class.to_s
962
+ when 'Time' then argument.to_i
963
+ when 'DateTime' then argument.to_i
964
+ when 'Date'
965
+ format = 'BdY' unless format.present?
966
+ argument.to_time.to_i
967
+ else
968
+ nil
969
+ end
970
+
971
+ return false if epoch.nil?
972
+
973
+ execute(:sayunixtime, epoch, timezone, format)
974
+ end
975
+
914
976
  protected
915
977
 
916
978
  # wait_for_digits waits for the input of digits based on the number of milliseconds
@@ -929,10 +991,10 @@ module Adhearsion
929
991
  end
930
992
 
931
993
  # allows setting of the callerid number of the call
932
- def set_caller_id_number(caller_id)
933
- return unless caller_id
934
- raise ArgumentError, "Caller ID must be numerical" if caller_id.to_s !~ /^\d+$/
935
- response "SET CALLERID", caller_id
994
+ def set_caller_id_number(caller_id_num)
995
+ return unless caller_id_num
996
+ raise ArgumentError, "Caller ID must be numeric" if caller_id_num.to_s !~ /^\d+$/
997
+ variable "CALLERID(num)" => caller_id_num
936
998
  end
937
999
 
938
1000
  # allows the setting of the callerid name of the call
@@ -1016,11 +1078,6 @@ module Adhearsion
1016
1078
  dial_status ? dial_status.downcase.to_sym : :cancelled
1017
1079
  end
1018
1080
 
1019
- def play_time(argument)
1020
- if argument.kind_of? Time
1021
- execute(:sayunixtime, argument.to_i)
1022
- end
1023
- end
1024
1081
 
1025
1082
  def play_numeric(argument)
1026
1083
  if argument.kind_of?(Numeric) || argument =~ /^\d+$/
@@ -1118,6 +1175,7 @@ module Adhearsion
1118
1175
  ring_style = options.delete :play
1119
1176
  allow_hangup = options.delete :allow_hangup
1120
1177
  allow_transfer = options.delete :allow_transfer
1178
+ agi = options.delete :agi
1121
1179
 
1122
1180
  raise ArgumentError, "Unrecognized args to join!: #{options.inspect}" if options.any?
1123
1181
 
@@ -1146,7 +1204,7 @@ module Adhearsion
1146
1204
 
1147
1205
  terse_character_options = ring_style + allow_transfer + allow_hangup
1148
1206
 
1149
- [terse_character_options, '', announcement, timeout].map(&:to_s)
1207
+ [terse_character_options, '', announcement, timeout, agi].map(&:to_s)
1150
1208
  end
1151
1209
 
1152
1210
  end
@@ -1165,6 +1223,7 @@ module Adhearsion
1165
1223
  # :announce - A sound file to play instead of the normal queue announcement.
1166
1224
  # :allow_transfer - Can be :caller, :agent, or :everyone. Allow someone to transfer the call.
1167
1225
  # :allow_hangup - Can be :caller, :agent, or :everyone. Allow someone to hangup with the * key.
1226
+ # :agi - An AGI script to be called on the calling parties channel just before being connected.
1168
1227
  #
1169
1228
  # @example
1170
1229
  # queue('sales').join!
@@ -1187,6 +1246,8 @@ module Adhearsion
1187
1246
  # @example
1188
1247
  # queue('sales').join! :allow_hangup => :everyone
1189
1248
  # @example
1249
+ # queue('sales').join! :agi => 'agi://localhost/sales_queue_callback'
1250
+ # @example
1190
1251
  # queue('sales').join! :allow_transfer => :agent, :timeout => 30.seconds,
1191
1252
  def join!(options={})
1192
1253
  environment.execute("queue", name, *self.class.format_join_hash_key_arguments(options))
@@ -1240,23 +1301,24 @@ module Adhearsion
1240
1301
  #
1241
1302
  # According to http://www.voip-info.org/wiki/view/Asterisk+cmd+Queue
1242
1303
  # possible values are:
1243
- # TIMEOUT (:timeout
1244
- # FULL (:full)
1245
- # JOINEMPTY (:joinempty)
1246
- # LEAVEEMPTY (:leaveempty)
1247
- # JOINUNAVAIL (:joinunavail)
1248
- # LEAVEUNAVAIL (:leaveunavail)
1249
1304
  #
1250
- # If Adhearsion cannot determine the status then :unknown will be returned.
1305
+ # TIMEOUT => :timeout
1306
+ # FULL => :full
1307
+ # JOINEMPTY => :joinempty
1308
+ # LEAVEEMPTY => :leaveempty
1309
+ # JOINUNAVAIL => :joinunavail
1310
+ # LEAVEUNAVAIL => :leaveunavail
1311
+ # CONTINUE => :continue
1312
+ #
1313
+ # If the QUEUESTATUS variable is not set the call was successfully connected,
1314
+ # and Adhearsion will return :completed.
1251
1315
  #
1252
1316
  # @param [String] QUEUESTATUS variable from Asterisk
1253
1317
  # @return [Symbol] Symbolized version of QUEUESTATUS
1254
1318
  # @raise QueueDoesNotExistError
1255
1319
  def normalize_queue_status_variable(variable)
1256
- variable = "UNKNOWN" if variable.nil?
1257
- variable.downcase.to_sym.tap do |queue_status|
1258
- raise QueueDoesNotExistError.new(name) if queue_status == :unknown
1259
- end
1320
+ variable = "COMPLETED" if variable.nil?
1321
+ variable.downcase.to_sym
1260
1322
  end
1261
1323
 
1262
1324
  class QueueAgentsListProxy
@@ -1285,26 +1347,35 @@ module Adhearsion
1285
1347
  def new(*args)
1286
1348
 
1287
1349
  options = args.last.kind_of?(Hash) ? args.pop : {}
1288
- interface = args.shift || ''
1350
+ interface = args.shift
1289
1351
 
1352
+ raise ArgumentError, "You must specify an interface to add." if interface.nil?
1290
1353
  raise ArgumentError, "You may only supply an interface and a Hash argument!" if args.any?
1291
1354
 
1292
- penalty = options.delete(:penalty) || ''
1293
- name = options.delete(:name) || ''
1355
+ penalty = options.delete(:penalty) || ''
1356
+ name = options.delete(:name) || ''
1357
+ state_interface = options.delete(:state_interface) || ''
1294
1358
 
1295
1359
  raise ArgumentError, "Unrecognized argument(s): #{options.inspect}" if options.any?
1296
1360
 
1297
- proxy.environment.execute("AddQueueMember", proxy.name, interface, penalty, '', name)
1361
+ proxy.environment.execute("AddQueueMember", proxy.name, interface, penalty, '', name, state_interface)
1298
1362
 
1299
- case proxy.environment.variable("AQMSTATUS")
1300
- when "ADDED" then true
1301
- when "MEMBERALREADY" then false
1302
- when "NOSUCHQUEUE" then raise QueueDoesNotExistError.new(proxy.name)
1303
- else
1304
- raise "UNRECOGNIZED AQMSTATUS VALUE!"
1305
- end
1363
+ added = case proxy.environment.variable("AQMSTATUS")
1364
+ when "ADDED" then true
1365
+ when "MEMBERALREADY" then false
1366
+ when "NOSUCHQUEUE" then raise QueueDoesNotExistError.new(proxy.name)
1367
+ else
1368
+ raise "UNRECOGNIZED AQMSTATUS VALUE!"
1369
+ end
1306
1370
 
1307
- # TODO: THIS SHOULD RETURN AN AGENT INSTANCE
1371
+ if added
1372
+ check_agent_cache!
1373
+ AgentProxy.new(interface, proxy).tap do |agent_proxy|
1374
+ @agents << agent_proxy
1375
+ end
1376
+ else
1377
+ false
1378
+ end
1308
1379
  end
1309
1380
 
1310
1381
  # Logs a pre-defined agent into this queue and waits for calls. Pass in :silent => true to stop