adhearsion 1.0.0 → 1.0.1

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