adhearsion 0.7.7 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (179) hide show
  1. data/CHANGELOG +9 -42
  2. data/EVENTS +11 -0
  3. data/README.txt +5 -0
  4. data/Rakefile +94 -84
  5. data/adhearsion.gemspec +148 -0
  6. data/app_generators/ahn/USAGE +5 -0
  7. data/app_generators/ahn/ahn_generator.rb +87 -0
  8. data/app_generators/ahn/templates/.ahnrc +34 -0
  9. data/app_generators/ahn/templates/README +8 -0
  10. data/app_generators/ahn/templates/Rakefile +23 -0
  11. data/app_generators/ahn/templates/components/ami_remote/ami_remote.rb +15 -0
  12. data/app_generators/ahn/templates/components/disabled/HOW_TO_ENABLE +7 -0
  13. data/app_generators/ahn/templates/components/disabled/stomp_gateway/README.markdown +47 -0
  14. data/app_generators/ahn/templates/components/disabled/stomp_gateway/config.yml +12 -0
  15. data/app_generators/ahn/templates/components/disabled/stomp_gateway/stomp_gateway.rb +34 -0
  16. data/app_generators/ahn/templates/components/restful_rpc/README.markdown +11 -0
  17. data/app_generators/ahn/templates/components/restful_rpc/config.yml +34 -0
  18. data/app_generators/ahn/templates/components/restful_rpc/example-client.rb +48 -0
  19. data/app_generators/ahn/templates/components/restful_rpc/restful_rpc.rb +87 -0
  20. data/app_generators/ahn/templates/components/restful_rpc/spec/restful_rpc_spec.rb +263 -0
  21. data/app_generators/ahn/templates/components/simon_game/simon_game.rb +56 -0
  22. data/app_generators/ahn/templates/config/startup.rb +50 -0
  23. data/app_generators/ahn/templates/dialplan.rb +3 -0
  24. data/app_generators/ahn/templates/events.rb +32 -0
  25. data/bin/ahn +28 -0
  26. data/bin/ahnctl +68 -0
  27. data/bin/jahn +42 -0
  28. data/examples/asterisk_manager_interface/standalone.rb +51 -0
  29. data/lib/adhearsion.rb +35 -953
  30. data/lib/adhearsion/cli.rb +223 -0
  31. data/lib/adhearsion/component_manager.rb +208 -0
  32. data/lib/adhearsion/component_manager/component_tester.rb +55 -0
  33. data/lib/adhearsion/component_manager/spec_framework.rb +24 -0
  34. data/lib/adhearsion/events_support.rb +84 -0
  35. data/lib/adhearsion/foundation/all.rb +9 -0
  36. data/lib/adhearsion/foundation/blank_slate.rb +5 -0
  37. data/lib/adhearsion/foundation/custom_daemonizer.rb +45 -0
  38. data/lib/adhearsion/foundation/event_socket.rb +203 -0
  39. data/lib/adhearsion/foundation/future_resource.rb +36 -0
  40. data/lib/adhearsion/foundation/global.rb +1 -0
  41. data/lib/adhearsion/foundation/metaprogramming.rb +17 -0
  42. data/lib/adhearsion/foundation/numeric.rb +13 -0
  43. data/lib/adhearsion/foundation/pseudo_guid.rb +10 -0
  44. data/lib/adhearsion/foundation/relationship_properties.rb +42 -0
  45. data/lib/adhearsion/foundation/string.rb +26 -0
  46. data/lib/adhearsion/foundation/synchronized_hash.rb +96 -0
  47. data/lib/adhearsion/foundation/thread_safety.rb +7 -0
  48. data/lib/adhearsion/host_definitions.rb +67 -0
  49. data/lib/adhearsion/initializer.rb +373 -0
  50. data/lib/adhearsion/initializer/asterisk.rb +81 -0
  51. data/lib/adhearsion/initializer/configuration.rb +254 -0
  52. data/lib/adhearsion/initializer/database.rb +49 -0
  53. data/lib/adhearsion/initializer/drb.rb +31 -0
  54. data/lib/adhearsion/initializer/freeswitch.rb +22 -0
  55. data/lib/adhearsion/initializer/rails.rb +40 -0
  56. data/lib/adhearsion/logging.rb +92 -0
  57. data/lib/adhearsion/tasks.rb +16 -0
  58. data/lib/adhearsion/tasks/database.rb +5 -0
  59. data/lib/adhearsion/tasks/deprecations.rb +59 -0
  60. data/lib/adhearsion/tasks/generating.rb +20 -0
  61. data/lib/adhearsion/tasks/lint.rb +4 -0
  62. data/lib/adhearsion/tasks/testing.rb +37 -0
  63. data/lib/adhearsion/version.rb +9 -0
  64. data/lib/adhearsion/voip/asterisk.rb +4 -0
  65. data/lib/adhearsion/voip/asterisk/agi_server.rb +81 -0
  66. data/lib/adhearsion/voip/asterisk/commands.rb +1284 -0
  67. data/lib/adhearsion/voip/asterisk/config_generators/agents.conf.rb +140 -0
  68. data/lib/adhearsion/voip/asterisk/config_generators/config_generator.rb +101 -0
  69. data/lib/adhearsion/voip/asterisk/config_generators/queues.conf.rb +250 -0
  70. data/lib/adhearsion/voip/asterisk/config_generators/voicemail.conf.rb +240 -0
  71. data/lib/adhearsion/voip/asterisk/config_manager.rb +71 -0
  72. data/lib/adhearsion/voip/asterisk/manager_interface.rb +562 -0
  73. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rb +1754 -0
  74. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rl.rb +286 -0
  75. data/lib/adhearsion/voip/asterisk/manager_interface/ami_messages.rb +78 -0
  76. data/lib/adhearsion/voip/asterisk/manager_interface/ami_protocol_lexer_machine.rl +87 -0
  77. data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +80 -0
  78. data/lib/adhearsion/voip/asterisk/super_manager.rb +19 -0
  79. data/lib/adhearsion/voip/call.rb +440 -0
  80. data/lib/adhearsion/voip/call_routing.rb +64 -0
  81. data/lib/adhearsion/voip/commands.rb +9 -0
  82. data/lib/adhearsion/voip/constants.rb +39 -0
  83. data/lib/adhearsion/voip/conveniences.rb +18 -0
  84. data/lib/adhearsion/voip/dial_plan.rb +218 -0
  85. data/lib/adhearsion/voip/dsl/dialing_dsl.rb +151 -0
  86. data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +37 -0
  87. data/lib/adhearsion/voip/dsl/dialplan/control_passing_exception.rb +27 -0
  88. data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +124 -0
  89. data/lib/adhearsion/voip/dsl/dialplan/parser.rb +71 -0
  90. data/lib/adhearsion/voip/dsl/dialplan/thread_mixin.rb +16 -0
  91. data/lib/adhearsion/voip/dsl/numerical_string.rb +117 -0
  92. data/lib/adhearsion/voip/freeswitch/basic_connection_manager.rb +48 -0
  93. data/lib/adhearsion/voip/freeswitch/event_handler.rb +58 -0
  94. data/lib/adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory.rb +129 -0
  95. data/lib/adhearsion/voip/freeswitch/inbound_connection_manager.rb +38 -0
  96. data/lib/adhearsion/voip/freeswitch/oes_server.rb +195 -0
  97. data/lib/adhearsion/voip/menu_state_machine/calculated_match.rb +80 -0
  98. data/lib/adhearsion/voip/menu_state_machine/matchers.rb +123 -0
  99. data/lib/adhearsion/voip/menu_state_machine/menu_builder.rb +58 -0
  100. data/lib/adhearsion/voip/menu_state_machine/menu_class.rb +149 -0
  101. data/lib/theatre.rb +151 -0
  102. data/lib/theatre/README.markdown +64 -0
  103. data/lib/theatre/callback_definition_loader.rb +84 -0
  104. data/lib/theatre/guid.rb +23 -0
  105. data/lib/theatre/invocation.rb +121 -0
  106. data/lib/theatre/namespace_manager.rb +153 -0
  107. data/lib/theatre/version.rb +2 -0
  108. metadata +160 -140
  109. data/.version +0 -1
  110. data/TODO +0 -71
  111. data/ahn +0 -223
  112. data/apps/default/Rakefile +0 -65
  113. data/apps/default/config/adhearsion.sqlite3 +0 -0
  114. data/apps/default/config/adhearsion.yml +0 -95
  115. data/apps/default/config/database.rb +0 -50
  116. data/apps/default/config/database.yml +0 -12
  117. data/apps/default/config/helpers/drb_server.yml +0 -43
  118. data/apps/default/config/helpers/factorial.alien.c.yml +0 -1
  119. data/apps/default/config/helpers/growler.yml +0 -21
  120. data/apps/default/config/helpers/lookup.yml +0 -1
  121. data/apps/default/config/helpers/manager_proxy.yml +0 -8
  122. data/apps/default/config/helpers/micromenus.yml +0 -1
  123. data/apps/default/config/helpers/micromenus/collab.rb +0 -60
  124. data/apps/default/config/helpers/micromenus/images/arrow-off.gif +0 -0
  125. data/apps/default/config/helpers/micromenus/images/arrow-on.gif +0 -0
  126. data/apps/default/config/helpers/micromenus/images/error.gif +0 -0
  127. data/apps/default/config/helpers/micromenus/images/folder-off.gif +0 -0
  128. data/apps/default/config/helpers/micromenus/images/folder-on.gif +0 -0
  129. data/apps/default/config/helpers/micromenus/images/folder.png +0 -0
  130. data/apps/default/config/helpers/micromenus/images/ggbridge.jpg +0 -0
  131. data/apps/default/config/helpers/micromenus/images/green.png +0 -0
  132. data/apps/default/config/helpers/micromenus/images/microbrowser.bg.gif +0 -0
  133. data/apps/default/config/helpers/micromenus/images/red.png +0 -0
  134. data/apps/default/config/helpers/micromenus/images/tux.bmp +0 -0
  135. data/apps/default/config/helpers/micromenus/images/url-off.gif +0 -0
  136. data/apps/default/config/helpers/micromenus/images/url-on.gif +0 -0
  137. data/apps/default/config/helpers/micromenus/images/yellow.png +0 -0
  138. data/apps/default/config/helpers/micromenus/javascripts/animation.js +0 -1341
  139. data/apps/default/config/helpers/micromenus/javascripts/carousel.js +0 -1238
  140. data/apps/default/config/helpers/micromenus/javascripts/columnav.js +0 -306
  141. data/apps/default/config/helpers/micromenus/javascripts/connection.js +0 -965
  142. data/apps/default/config/helpers/micromenus/javascripts/container.js +0 -4727
  143. data/apps/default/config/helpers/micromenus/javascripts/container_core.js +0 -2915
  144. data/apps/default/config/helpers/micromenus/javascripts/dom.js +0 -892
  145. data/apps/default/config/helpers/micromenus/javascripts/dragdrop.js +0 -2958
  146. data/apps/default/config/helpers/micromenus/javascripts/event.js +0 -1771
  147. data/apps/default/config/helpers/micromenus/javascripts/yahoo.js +0 -433
  148. data/apps/default/config/helpers/micromenus/stylesheets/carousel.css +0 -78
  149. data/apps/default/config/helpers/micromenus/stylesheets/columnav.css +0 -135
  150. data/apps/default/config/helpers/micromenus/stylesheets/microbrowsers.css +0 -42
  151. data/apps/default/config/helpers/multi_messenger.yml +0 -9
  152. data/apps/default/config/helpers/weather.yml +0 -1
  153. data/apps/default/config/helpers/xbmc.yml +0 -2
  154. data/apps/default/config/migration.rb +0 -59
  155. data/apps/default/extensions.rb +0 -41
  156. data/apps/default/helpers/factorial.alien.c +0 -32
  157. data/apps/default/helpers/growler.rb +0 -53
  158. data/apps/default/helpers/lookup.rb +0 -44
  159. data/apps/default/helpers/manager_proxy.rb +0 -112
  160. data/apps/default/helpers/micromenus.rb +0 -514
  161. data/apps/default/helpers/multi_messenger.rb +0 -53
  162. data/apps/default/helpers/oscar_wilde_quotes.rb +0 -197
  163. data/apps/default/helpers/weather.rb +0 -85
  164. data/apps/default/helpers/xbmc.rb +0 -39
  165. data/apps/default/logs/adhearsion.log +0 -0
  166. data/apps/default/logs/database.log +0 -0
  167. data/lib/constants.rb +0 -24
  168. data/lib/core_extensions.rb +0 -180
  169. data/lib/drb_server.rb +0 -101
  170. data/lib/logging.rb +0 -85
  171. data/lib/phone_number.rb +0 -85
  172. data/lib/rami.rb +0 -823
  173. data/lib/servlet_container.rb +0 -174
  174. data/lib/sexy_migrations.rb +0 -70
  175. data/test/asterisk_module_test.rb +0 -14
  176. data/test/core_extensions_test.rb +0 -26
  177. data/test/dial_test.rb +0 -43
  178. data/test/specs/numerical_string_spec.rb +0 -53
  179. data/test/test_micromenus.rb +0 -0
@@ -0,0 +1,56 @@
1
+ methods_for :dialplan do
2
+ def simon_game
3
+ SimonGame.new(self).start
4
+ end
5
+ end
6
+
7
+ class SimonGame
8
+
9
+ def initialize(call)
10
+ @call = call
11
+ reset
12
+ end
13
+
14
+ def start
15
+ loop do
16
+ say_number
17
+ collect_attempt
18
+ verify_attempt
19
+ end
20
+ end
21
+
22
+ def random_number
23
+ rand(10).to_s
24
+ end
25
+
26
+ def update_number
27
+ @number << random_number
28
+ end
29
+
30
+ def say_number
31
+ update_number
32
+ @call.say_digits @number
33
+ end
34
+
35
+ def collect_attempt
36
+ @attempt = @call.input @number.length
37
+ end
38
+
39
+ def verify_attempt
40
+ if attempt_correct?
41
+ @call.play 'good'
42
+ else
43
+ @call.play %W[#{@number.length-1} times wrong-try-again-smarty]
44
+ reset
45
+ end
46
+ end
47
+
48
+ def attempt_correct?
49
+ @attempt == @number
50
+ end
51
+
52
+ def reset
53
+ @attempt, @number = '', ''
54
+ end
55
+
56
+ end
@@ -0,0 +1,50 @@
1
+ unless defined? Adhearsion
2
+ if File.exists? File.dirname(__FILE__) + "/../adhearsion/lib/adhearsion.rb"
3
+ # If you wish to freeze a copy of Adhearsion to this app, simply place a copy of Adhearsion
4
+ # into a folder named "adhearsion" within this app's main directory.
5
+ require File.dirname(__FILE__) + "/../adhearsion/lib/adhearsion.rb"
6
+ else
7
+ require 'rubygems'
8
+ gem 'adhearsion', '>= 0.7.999'
9
+ require 'adhearsion'
10
+ end
11
+ end
12
+
13
+ Adhearsion::Configuration.configure do |config|
14
+
15
+ # Supported levels (in increasing severity) -- :debug < :info < :warn < :error < :fatal
16
+ config.logging :level => :info
17
+
18
+ # Whether incoming calls be automatically answered. Defaults to true.
19
+ # config.automatically_answer_incoming_calls = false
20
+
21
+ # Whether the other end hanging up should end the call immediately. Defaults to true.
22
+ # config.end_call_on_hangup = false
23
+
24
+ # Whether to end the call immediately if an unrescued exception is caught. Defaults to true.
25
+ # config.end_call_on_error = false
26
+
27
+ # By default Asterisk is enabled with the default settings
28
+ config.enable_asterisk
29
+ # config.asterisk.enable_ami :host => "127.0.0.1", :username => "admin", :password => "password", :events => true
30
+
31
+ # config.enable_drb
32
+
33
+ # Streamlined Rails integration! The first argument should be a relative or absolute path to
34
+ # the Rails app folder with which you're integrating. The second argument must be one of the
35
+ # the following: :development, :production, or :test.
36
+
37
+ # config.enable_rails :path => 'gui', :env => :development
38
+
39
+ # Note: You CANNOT do enable_rails and enable_database at the same time. When you enable Rails,
40
+ # it will automatically connect to same database Rails does and load the Rails app's models.
41
+
42
+ # Configure a database to use ActiveRecord-backed models. See ActiveRecord::Base.establish_connection
43
+ # for the appropriate settings here.
44
+ # config.enable_database :adapter => 'mysql',
45
+ # :username => 'joe',
46
+ # :password => 'secret',
47
+ # :host => 'db.example.org'
48
+ end
49
+
50
+ Adhearsion::Initializer.start_from_init_file(__FILE__, File.dirname(__FILE__) + "/..")
@@ -0,0 +1,3 @@
1
+ adhearsion {
2
+ simon_game
3
+ }
@@ -0,0 +1,32 @@
1
+ ##
2
+ # In this file you can define callbacks for different aspects of the framework. Below is an example:
3
+ ##
4
+ #
5
+ # events.asterisk.before_call.each do |call|
6
+ # # This simply logs the extension for all calls going through this Adhearsion app.
7
+ # extension = call.variables[:extension]
8
+ # ahn_log "Got a new call with extension #{extension}"
9
+ # end
10
+ #
11
+ ##
12
+ # Asterisk Manager Interface example:
13
+ #
14
+ # events.asterisk.manager_interface.each do |event|
15
+ # ahn_log.events event.inspect
16
+ # end
17
+ #
18
+ # This assumes you gave :events => true to the config.asterisk.enable_ami method in config/startup.rb
19
+ #
20
+ ##
21
+ # Here is a list of the events included by default:
22
+ #
23
+ # - events.asterisk.manager_interface
24
+ # - events.after_initialized
25
+ # - events.shutdown
26
+ # - events.asterisk.before_call
27
+ # - events.asterisk.failed_call
28
+ # - events.asterisk.call_hangup
29
+ #
30
+ #
31
+ # Note: events are mostly for components to register and expose to you.
32
+ ##
data/bin/ahn ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # This is the main executable file.
4
+
5
+ # Adhearsion, open source collaboration framework
6
+ # Copyright (C) 2006,2007,2008 Jay Phillips
7
+ #
8
+ # This library is free software; you can redistribute it and/or modify it under
9
+ # the terms of the GNU Lesser General Public License as published by the Free
10
+ # Software Foundation; either version 2.1 of the License, or (at your option)
11
+ # any later version.
12
+ #
13
+ # This library is distributed in the hope that it will be useful, but WITHOUT
14
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15
+ # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
16
+ # details.
17
+ #
18
+ # You should have received a copy of the GNU Lesser General Public License along
19
+ # with this library; if not, write to the Free Software Foundation, Inc.,
20
+ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
+
22
+ $:.unshift File.expand_path(File.dirname(__FILE__) + "/../lib")
23
+
24
+ require 'rubygems'
25
+ require 'adhearsion'
26
+ require 'adhearsion/cli'
27
+
28
+ Adhearsion::CLI::AhnCommand.execute!
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # ahnctl - Adhearsion daemon controller
4
+ #
5
+ # Adhearsion, open source collaboration framework
6
+ # Copyright (C) 2006,2007,2008 Jay Phillips
7
+ #
8
+ # This library is free software; you can redistribute it and/or
9
+ # modify it under the terms of the GNU Lesser General Public
10
+ # License as published by the Free Software Foundation; either
11
+ # version 2.1 of the License, or (at your option) any later version.
12
+ #
13
+ # This library is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
+ # Lesser General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU Lesser General Public
19
+ # License along with this library; if not, write to the Free Software
20
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
+
22
+ USAGE = "Usage: ahnctl start|stop|restart /path/to/adhearsion/app [--pid-file=/path/to/pid_file.pid]"
23
+
24
+ # Blow up if the CLI arguments are invalid
25
+ abort USAGE unless (2..3).include?(ARGV.size) && %w[start stop restart].include?(ARGV.first)
26
+
27
+ # By default, ahnctl will use the version of Adhearsion it was installed with.
28
+
29
+ pid_file = ARGV.pop if ARGV.size == 3
30
+ ahn_command = File.expand_path File.dirname(__FILE__) + "/ahn"
31
+ app_dir = File.expand_path ARGV.last
32
+
33
+ pid_file_path_regexp = /^--pid-file=(.+)$/
34
+ abort USAGE if pid_file && pid_file !~ pid_file_path_regexp
35
+
36
+ # If pid_file is not nil, let's extract the path specified.
37
+ pid_file &&= File.expand_path pid_file[pid_file_path_regexp,1]
38
+
39
+ # If there was no third argument and pid_file is still nil, let's use the default.
40
+ pid_file ||= app_dir + '/adhearsion.pid'
41
+
42
+ abort "Directory is not an Adhearsion application!" unless File.exists?(app_dir + "/.ahnrc")
43
+
44
+ def terminate(pid) `kill -s TERM #{pid} 2> /dev/null` end
45
+ def kill(pid) `kill -s KILL #{pid} 2> /dev/null` end
46
+
47
+ # Even if we're starting Adhearsion, we need to make sure to clean up after any stale
48
+ # pid files. In effect, start is the same as restart.
49
+ puts "Stopping Adhearsion app at #{app_dir}" if %w[stop restart].include? ARGV.first
50
+
51
+ if File.exists?(pid_file)
52
+ # An Adhearsion process may still be running. Let's kill the other one as cleanly as possible
53
+ pid = File.read(pid_file).to_i
54
+
55
+ # Time to spend waiting for Adhearsion to exit
56
+ waiting_timeout = Time.now + 15
57
+
58
+ terminate pid
59
+ sleep 0.25 until `ps -p #{pid} | sed -e '1d'`.strip.empty? || Time.now > waiting_timeout
60
+ kill pid
61
+
62
+ `rm -f #{pid_file}`
63
+ end
64
+
65
+ if ['start', 'restart'].include? ARGV.first
66
+ puts "Starting Adhearsion app at #{app_dir}"
67
+ `#{ahn_command} start daemon #{app_dir} --pid-file=#{pid_file}`
68
+ end
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env jruby
2
+
3
+ # This "jahn" script is "ahn" for JRUBY!
4
+
5
+ # Adhearsion, open source collaboration framework
6
+ # Copyright (C) 2006,2007,2008 Jay Phillips
7
+ #
8
+ # This library is free software; you can redistribute it and/or modify it under
9
+ # the terms of the GNU Lesser General Public License as published by the Free
10
+ # Software Foundation; either version 2.1 of the License, or (at your option)
11
+ # any later version.
12
+ #
13
+ # This library is distributed in the hope that it will be useful, but WITHOUT
14
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15
+ # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
16
+ # details.
17
+ #
18
+ # You should have received a copy of the GNU Lesser General Public License along
19
+ # with this library; if not, write to the Free Software Foundation, Inc.,
20
+ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
+
22
+ $:.unshift File.expand_path(File.dirname(__FILE__) + "/../lib")
23
+
24
+ require 'rubygems'
25
+ require 'adhearsion'
26
+ require 'adhearsion/cli'
27
+
28
+ # jahn will soon have JRuby-specific behavior such as creating a compiled .jar
29
+ # file from an app.
30
+ # require 'adhearsion/jruby'
31
+
32
+ abort(<<-MESSAGE) unless RUBY_PLATFORM =~ /java/
33
+ You must use jahn with JRuby! Are you running this script after installing Adhearsion with non-JRuby RubyGems?
34
+
35
+ If you don't want to install Adhearsion with the JRuby version of RubyGems, try running jahn directly with an absolute path:
36
+
37
+ #{File.expand_path(__FILE__)} start your_app_name
38
+
39
+ Note: You must have the jruby executable in your $PATH and $JRUBY_HOME set.
40
+ MESSAGE
41
+
42
+ Adhearsion::CLI::AhnCommand.execute!
@@ -0,0 +1,51 @@
1
+ # This is a file which shows you how to use the Asterisk Manager Interface library in a standalone Ruby script.
2
+
3
+ PATH_TO_ADHEARSION = File.join(File.dirname(__FILE__), "/../..")
4
+
5
+ MANAGER_CONNECTION_INFORMATION = {
6
+ :host => "10.0.1.97",
7
+ :username => "jicksta",
8
+ :password => "roflcopter",
9
+ :events => true
10
+ }
11
+
12
+ require 'rubygems'
13
+ begin
14
+ require 'adhearsion'
15
+ rescue LoadError
16
+ begin
17
+ require File.join(PATH_TO_ADHEARSION, "/lib/adhearsion")
18
+ rescue LoadError
19
+ abort "Could not find Adhearsion! Please update the PATH_TO_ADHEARSION constant in this file"
20
+ end
21
+ end
22
+
23
+ require 'adhearsion/voip/asterisk/manager_interface'
24
+
25
+ # If you'd like to see the AMI protocol data, change this to :debug
26
+ Adhearsion::Logging.logging_level = :warn
27
+
28
+ # This makes addressing the ManagerInterface class a little cleaner
29
+ include Adhearsion::VoIP::Asterisk::Manager
30
+
31
+ # Let's instantiate a new ManagerInterface object and have it automatically connect using the Hash we defined above.
32
+ interface = ManagerInterface.connect MANAGER_CONNECTION_INFORMATION
33
+
34
+ # Send an AMI action with our new ManagerInterface object. This will return an Array of SIPPeer events.
35
+ sip_peers = interface.send_action "SIPPeers"
36
+
37
+ # Pretty-print the SIP peers on the server
38
+
39
+ if sip_peers.any?
40
+ sip_peers.each do |peer|
41
+ # Uncomment the following line to view all the headers for each peer.
42
+ # p peer.headers
43
+
44
+ peer_name = peer.headers["ObjectName"]
45
+ peer_status = peer.headers["Status"]
46
+
47
+ puts "#{peer_name}: #{peer_status}"
48
+ end
49
+ else
50
+ puts "This Asterisk server has no SIP peers!"
51
+ end
@@ -1,955 +1,37 @@
1
- # Adhearsion, open source technology integrator
2
- # Copyright (C) 2006,2007 Jay Phillips
3
- #
4
- # This library is free software; you can redistribute it and/or
5
- # modify it under the terms of the GNU Lesser General Public
6
- # License as published by the Free Software Foundation; either
7
- # version 2.1 of the License, or (at your option) any later version.
8
- #
9
- # This library is distributed in the hope that it will be useful,
10
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
- # Lesser General Public License for more details.
13
- #
14
- # You should have received a copy of the GNU Lesser General Public
15
- # License along with this library; if not, write to the Free Software
16
- # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
-
18
- require 'core_extensions'
19
- require 'phone_number'
20
-
21
- module Asterisk
22
-
23
- # The exec method allows any traditional Asterisk applications to be called
24
- # within Adhearsion. The first argument should be the case-insensitive name
25
- # of the application and any arguments needed by the application can simply
26
- # by trailed onto the end of the method. For a full list of Asterisk's
27
- # applications, see
28
- # "this":http://www.voip-info.org/wiki/index.php?page=Asterisk+-+documentation+of+application+commands
29
- def exec(app, *options)
30
- result = rawr "EXEC #{app} " + (options * '|')
31
- result = result[/-?\d+$/]
32
- result == "-2" ? false : result
33
- end
34
-
35
- # If this method is invoked during a call, the Call Detail Records (the logs
36
- # Asterisk keeps on its calls) will not be written when the call completes.
37
- def no_cdr() rawr :NoCDR end
38
- alias do_not_bill no_cdr
39
-
40
- # This method of receiving user input uses the fantastic Asterisk
41
- # Read() application, but its results are pretty inconsitent over
42
- # AGI. This code has been kept here in case these bugs are fixed.
43
- def old_input digits=nil, user_options=Hash.new('')
44
- used_options = {:variable => String.random, :digits => digits}
45
- used_options.default = ''
46
- used_options.merge! user_options
47
- args = []
48
- %w(variable soundfile digits option attempts timeout).each do |x|
49
- args << used_options[x.to_sym]
50
- end
51
- log "THESE ARE THE INPUT ARGS: " + args.inspect
52
- exec :read, args
53
- $OSCAR[:VARS] << args.first
54
- x = get_variable(args.first).gsub(/[\(\)]/, "")
55
- debug "RETURN: #{x.inspect}"
56
- x.simplify
57
- end
58
-
59
- # Input is used to receive keypad input from the user, pausing until they
60
- # have entered the desired number of digits (specified with the first
61
- # parameter) or the timeout has been reached (specified as a hash argument
62
- # with the key :timeout). By default, there is no timeout, waiting infinitely.
63
- #
64
- # If you desire a sound to be played other than a simple beep to instruct
65
- # the callee to input data, pass the filename as an hash argument with either
66
- # the :play or :file key.
67
- #
68
- # When called without any arguments (or a first argument of -1), the user is
69
- # able to enter digits ad infinitum until they press the pound (#) key.
70
- #
71
- # Note: input() does NOT catch "#" character! Use wait_for_digit instead.
72
- def input digits=nil, hash={}
73
- timeout, file = (hash[:timeout] || -1), (hash[:play] || hash[:file] || 'beep')
74
- result = rawr "GET DATA #{file} #{timeout} #{digits}"
75
- result = result[/\=-?[\d*]+/]
76
- result ? result[1..-1] : false
77
- end
78
-
79
- # Does as you'd imagine: returns the amount of time the given block takes to
80
- # execute. This is particularly useful in VoIP since billing and such often
81
- # requires time keeping beyond the Call Detail Records. This method is also
82
- # aliased to bill() as well.
83
- def time
84
- return 0 unless block_given?
85
- start = Time.now
86
- yield start
87
- Time.now - start
88
- end
89
- alias bill time
90
-
91
- # When called, this command will record the current channel to a file. The
92
- # recording will stop either when the caller hangs up or when she presses
93
- # the # key. Four arguments exist for your use:
94
- #
95
- # - :file defaults to "/tmp/recording_%d.wav" where %d is a number cleverly
96
- # incremented by Asterisk. If you override this argument, you too can
97
- # use '%d' in the filename to have it automatically incremented for you.
98
- # The return value of record() will be whatever filename Asterisk just used.
99
- # Filenames can be either an absolute path or simple name. When no apparent
100
- # directory is given, Asterisk will use /var/lib/asterisk/sounds. Ensure this
101
- # directory is writable to the user account with which Asterisk runs.
102
- #
103
- # - :silence specifies the number of seconds Asterisk should wait in silence
104
- # before ending the call.
105
- #
106
- # - :max is the maximum length of the sound file in seconds. Feel free to use
107
- # Adhearsion's Fixnum convenience methods here (e.g. 1.minute, 12.hours)
108
- #
109
- # - :options can be 'skip' to return immediately if the line is not up, or
110
- # 'noanswer' to record even if the line is not up.
111
- #
112
- # === Notes
113
- #
114
- # Keep in mind if you user decides to hang up to end the recording, the thread
115
- # handling the call will end but the file will still be saved. Try to do any
116
- # necessary processing before-hand. If you're trying to use the return value
117
- # of record(), you may not get it if the thread gets terminated. Instead, create
118
- # your own filename and save it somewhere safe (in a database for example) and
119
- # then record the user to your pre-created filename.
120
- #
121
- # === Examples
122
- #
123
- # record # A filename will be made for you and returned
124
- # record :file => 'foo.wav'
125
- # record :max => 2.minutes + 30.seconds
126
- # record :file => '/tmp/recordings/COMPLAINT_%d.gsm', :silence => 5
127
- #
128
- def record hash={}
129
- raise NotImplementedError if block_given? # TODO support recording a block of code
130
- defaults = {:file => "/tmp/recording_%d.wav", :silence => 0, :max => 0}
131
- args = defaults.merge hash
132
-
133
- exec :Record, args[:file], args[:silence], args[:max], args[:options]
134
- args[:file].index('%d') ? get_variable('RECORDED_FILE')[1..-2] : args[:file]
135
- end
136
-
137
- # Invokes the AGI RECORD FILE method as outlined below
138
- # Note that the timeout value must be expressed in milliseconds
139
- def record_file file, hash={}
140
- #RECORD FILE <filename> <format> <escape digits> <timeout>
141
- format, digits, timeout, beep, silence = (hash[:format] || 'gsm'), (hash[:digits] || '#'),
142
- (hash[:timeout] || 15000), (hash[:beep] ? 'BEEP' : nil), ("s=#{hash[:silence]}" || nil)
143
- result = rawr "RECORD FILE #{file} #{format} \"#{digits}\" #{timeout} #{beep} #{silence}"
144
- result = result[/\=-?\d+/]
145
- result ? result[1..-1] : false
146
- end
147
-
148
- # Waits for single digit numpad key response from the user. If no timeout argument
149
- # is given, the system will wait indefinitely. The argument is the desired time
150
- # to wait for the response in seconds. Feel free to use the ActiveSupport extensions
151
- # for using this method like wait_for_digit(2.minutes) or something similar. When the
152
- # timeout is encountered (or an error occurs receiving the input), the method
153
- # returns nil. If the asterisk or pound key was pressed, a String is returned of that
154
- # response. In all other cases, the Fixnum of the number pressed is returned.
155
- def wait_for_digit timeout=-1
156
- digit = rawr("WAIT FOR DIGIT #{timeout < 0 ? -1 : timeout * 1000.0}").match(/=(.*)$/)[1].to_i
157
- return nil if digit <= 0 # If there was an error or timeout
158
- return digit.chr if (32..45).include? digit
159
- digit - ?0
160
- end
161
-
162
- # An abstracted remote mutator for setting Asterisk variables.
163
- # You may optionally pass as a third argument ":normally" to
164
- # have the variable set with Set() instead of AGI.
165
- def set_variable(key,value,mode=:abnormally)
166
- case mode
167
- when :abnormally then rawr "SET VARIABLE #{key} #{value}"
168
- when :normally then exec :Set, "#{key}=#{value}"
169
- else raise "Invalid mode when setting variable!"
170
- end
171
- end
172
-
173
- def detach!
174
- PBX.io.close
175
- end
176
-
177
- # An abstracted remote accessor for retrieving Asterisk variables.
178
- def get_variable(key, default=nil)
179
- result = rawr "GET VARIABLE #{key}"
180
- result[0..12] == "200 result=0" ? default : result[14..-2].strip
181
- end
182
-
183
- # Plays a tone over the call.
184
- #
185
- # Usage:
186
- # - tone:busy
187
- # - tone:dial
188
- # - tone:info
189
- # - tone:record
190
- # - tone "3333/33,0/15000" # Random custom tone
191
- def tone type
192
- exec 'PlayTones', (case type
193
- when :busy then "480+620/500,0/500"
194
- when :dial then "440+480/2000,0/4000"
195
- when :info then "!950/330,!1400/330,!1800/330,0"
196
- when :record then "1400/500,0/15000"
197
- else type
198
- end)
199
- end
200
-
201
- # == Dialing Users
202
- #
203
- # This method will be used in most dial plans written with Adhearsion.
204
- # The first argument is the destination which can be given in a number
205
- # of syntactically sweet formats:
206
- #
207
- # - A "Group" of users. This is determined by whether the passed group
208
- # responds to the members, users, member, or user methods. If
209
- # so, all members of that group will ring simultaneously. This would
210
- # be especially useful for, say, technical support groups which all
211
- # share the same line. When the first person picks up the line, the
212
- # rest of the phones will stop ringing. The array of users supplied
213
- # by one of the aforementioned methods can follow any of the
214
- # standards below.
215
- # - An Array of "Users".
216
- # - A single "User". A User is defined by something that responds to the
217
- # extension or extensions methods. The return value of these two
218
- # methods can either be any of the following formats:
219
- # - A String. The String can contain a simple number or be a fully
220
- # qualified Asterisk-ready String. e.g. 'SIP/codemecca@sipphone'.
221
- # - A Numeric or Symbol. In this case, the SIP technology is used by
222
- # default. See the examples below.
223
- #
224
- # == Dialing Examples
225
- #
226
- # - dial 123 # => SIP/123
227
- # - dial "123" # => SIP/123
228
- # - dial SIP/123 # => SIP/123
229
- # - dial "SIP/123" # => SIP/123
230
- # - dial IAX/123 # => IAX2/123
231
- # - dial IAX/:jay # => IAX2/jay
232
- # - dial IAX2/123 # => IAX2/123
233
- # - dial ZAP/123 # => Zap/123
234
- # - dial Zap/:out # => Zap/out
235
- # - dial :mikey # => SIP/mikey
236
- # - dial SIP/:mikey # => SIP/mikey
237
- # - dial "SIP/:mikey" # Don't do this
238
- # - dial SIP/'mikey' # => SIP/mikey
239
- # - dial 1_555_123_7890 # => SIP/1_555_123_7890
240
- # - dial 'SIP/15551237890@sipphone' # => SIP/15551237890@sipphone
241
- #
242
- # Note, in the last example, the part following the @ sign is a section
243
- # from sip.conf.
244
- #
245
- # == More complex usage
246
- #
247
- # If you should want to have the phone ring for only a pre-determined set
248
- # of time (which you likely will to have the call redirect to voicemail for
249
- # example), then a :for => 15.seconds type of syntax can be used. Examples:
250
- #
251
- # dial :jay, :for => 2.minutes
252
- #
253
- # The result of the last call can be retrieved by using the dial plan
254
- # methods last_call_successful?, last_call_unsuccessful?, or
255
- # last_dial_status. Note: last_dial_status returns a Symbol representing
256
- # the result of the last call. For more information, see its appropriate
257
- # documentation.
258
- #
259
- # === Setting CallerID
260
- #
261
- # To set the callerid number, simply pass either a number or a String of the
262
- # digits to dial() as a Symbol key argument.
263
- #
264
- # dial :crusher, :callerid => 1_555_123_4567
265
- # dial :picard, :callerid => 12224561701
266
- # dial :laforge, :callerid => '14442223333'
267
- #
268
- # === Dial options
269
- #
270
- # Additionally, you can use the :options hash key argument to specify options
271
- # which Asterisk's Dial() application normally takes. For example:
272
- #
273
- # dial :jay, :for => 30.seconds, :options => 'tmw'
274
- # dial 1_444_555_6666, :options => 'S(30)'
275
- #
276
- # == Resources
277
- #
278
- # For more information, see the VoIP-Info.org page on dialing in Asterisk at
279
- # http://www.voip-info.org/wiki-Asterisk+cmd+Dial
280
- def dial who, hash={}
281
- # if who.is_a_group? TODO: Set the CDR GROUP() value if a Group is dialed.
282
-
283
- # TODO Must be provided as a number until the Set(CALLERID(name)=something)
284
- # stuff is figured out.
285
- cid = hash.delete :callerid
286
- if cid
287
- cid = cid.to_s
288
- cid = "1" << cid if cid.size == 10
289
- rawr %(SET CALLERID "#{cid}") if cid
290
- end
291
-
292
- exec :dial, PBX.properize(who), *[hash[:for], hash[:options]]
293
- end
294
-
295
- # As you might expect, last_call_successful? returns a boolean depending on whether
296
- # the last call to dial() finished successfully. Success is determined only if the
297
- # call was answered.
298
- def last_call_successful?
299
- last_dial_status == :answer
300
- end
301
-
302
- # The opposite of last_call_successful?. Here for syntactical sugar.
303
- def last_call_unsuccessful?
304
- !last_call_successful?
305
- end
306
-
307
- # Send a DTMF tone over the line
308
- def dtmf digits
309
- exec "SendDTMF", digits.to_s
310
- end
311
-
312
- # The stream_file() method comes right over from the AGI protocol.
313
- # Its use is very similar to the Background() application from
314
- # Asterisk's extensions.conf language. A file is played in the
315
- # background while optionally waiting for input. In the event
316
- # the second argument is given, Asterisk will listen for that
317
- # sequence of digits and return control to Adhearsion, informing
318
- # us of the digits the user pressed. These digits are returned
319
- # as a String. If you would also like to know the ending position
320
- # at which the streaming stopped, see stream_file_with_offset().
321
- def stream_file file, digits='', offset=0
322
- stream_file_with_offset(file, digits, offset).first
323
- end
324
-
325
- # Similar to stream_file(), but returning an array of length 2
326
- # instead. If supplied a first argument, the return value will
327
- # be digits pressed by the user to end
328
- def stream_file_with_offset file, digits='', offset=0
329
- response = rawr "STREAM FILE #{file} #{digits} #{offset}"
330
- return nil unless response.starts_with? '200'
331
-
332
- result_index = response.index(?=) + 1
333
- spacer_index = response.rindex(' ')
334
- endpos_index = spacer_index + 8 # " endpos=".length == 8
335
-
336
- result = response[result_index...spacer_index].to_i
337
- endpos = response[endpos_index..-1].to_i
338
-
339
- return [nil,nil] if (result.zero? && endpos.zero?) || result == -1
340
-
341
- [result, endpos]
342
- end
343
-
344
- # Festival is pretty buggy (and pretty inefficient). In theory,
345
- # this should speak out any text supplied to it.
346
- def speak text
347
- text.gsub! "\n",''
348
- text.gsub! "\r",''
349
- text.strip!
350
- exec :Festival, "#{text.inspect}"
351
- end
352
-
353
- def build_local first, second=nil
354
- num = 1
355
- context = nil
356
- if first.kind_of? Fixnum
357
- num = first
358
- context = second if second
359
- else
360
- context = first
361
- num = second if second
362
- end
363
- returning("Local/#{num}") do |s|
364
- s << "@#{context}" if context
365
- end
366
- end
367
-
368
- def external_invoke *args
369
- dial build_local(*args)
370
- end
371
-
372
- # Since voicemail is used so frequently, extra effort has been made to
373
- # make it syntactically sweet. voicemail() takes the mailbox number of
374
- # a user as its first or second argument and the mailbox type as its
375
- # first or second argument (the order doesn't matter). If a specific
376
- # voicemail context is necessary, trail it to the end of the method
377
- # with the ":at => 'contextname'" syntax. The mailbox type can be
378
- # :busy, :unavailable, or :normal, though if no type is given, the
379
- # type :normal is assumed.
380
- #
381
- # Usage: voicemail :busy, 4544
382
- # or: voicemail 4544
383
- # or: voicemail 4544, :unavailable, :at => 'codemecca'
384
- def voicemail *args
385
- hash = args.last.is_a?(Hash) ? args.pop : {}
386
- type = [:skip, :busy, :unavailable] & args
387
-
388
- box = (args - type).to_s # Destination mailbox
389
- box = type.map { |x| x.to_s[0].chr }.to_s + box
390
- box += "@#{hash[:at]}" if hash[:at]
391
-
392
- exec :Voicemail, box
393
- end
394
-
395
- # Execute VoiceMailMain behind the scenes to check a specified
396
- # user's voicemail inbox or, if no arguments are passed, prompt
397
- # the user for their mailbox number.
398
- #
399
- # Usage: check_voicemail 'jay', :at => 'default', :args => 's'
400
- # or : check_voicemail
401
- # or : check_voicemail 'hubbard'
402
- def check_voicemail name=nil, hash={}
403
- args = [ [name, hash[:at]].compact * '@', hash[:args]]
404
- exec :VoiceMailMain, args.compact
405
- end
406
-
407
- # Used to join a particular conference with the MeetMe application. To
408
- # use MeetMe, be sure you have a proper timing device configured on your
409
- # Asterisk box. MeetMe is Asterisk's built-in conferencing program.
410
- # More info: http://www.voip-info.org/wiki-Asterisk+cmd+MeetMe
411
- def join conference=nil, hash={}
412
- unless conference.kind_of? Numeric
413
- raise ArgumentError, "If you want to use named conferences, " +
414
- "specify them in adhearsion.yml" unless CONFIG['conferences']
415
- conference = CONFIG['conferences'][conference.to_s]
416
- end
417
- exec :MeetMe, conference, hash[:options], hash[:pin]
418
- end
419
- alias meetme join
420
-
421
- # Play is a long-overdue easy way to play audio files in a dialplan with Asterisk. It can take a
422
- # single String of the filename (defaults to /var/lib/asterisk/sounds) just as you
423
- # would give it to Playback(), or it can take any number of trailed on Strings.
424
- # If you pass it an Array, it will traverse the array, playing each of the items
425
- # in order. If you're doing this, make your life *incredibly* easier by using the
426
- # fantastic Ruby Array literal for Arrays of Strings: %w(). Within this, one
427
- # specifies separate, simple Strings by placing whitespace bewtween them. Since
428
- # no Asterisk files contain whitespace, this works _very_ well! Example:
429
- #
430
- # play %w(a-connect-charge-of 22 cents-per-minute will-apply)
431
- #
432
- # Note here that numbers can be play()ed as well. By convention, play() will call
433
- # SayNumber() on these indices instead of Playback().
434
- def play *files
435
- files.flatten!
436
- files.each do |f|
437
- if f.kind_of? Time
438
- exec :SayUnixTime, f.to_i
439
- elsif f.kind_of? Symbol then exec :playback, f
440
- elsif f[0] == ?$
441
- # Speak a currency. TODO: support currencies other than dollars.
442
- full,dollars,decimal,cents = *f.match(/^\$(\d+)(\.(\d{1,2}).*)?$/)
443
- if full
444
- exec :saynumber, dollars
445
- exec :playback, (dollars == '1') ? 'dollar' : 'dollars'
446
- if decimal
447
- exec :playback, 'and'
448
- exec :saynumber, cents
449
- exec :playback, (cents == '1') ? 'cent' : 'cents'
450
- end
451
- else exec :playback, f
452
- end
453
- elsif f.simplify.kind_of? Numeric then exec :saynumber, f
454
- else exec :playback, f
455
- end
456
- end
457
- end
458
-
459
- # Compiles the provided Asterisk dialplan pattern into a Ruby regular
460
- # expression. For more usage of Asterisk's pattern syntax, see
461
- # http://www.voip-info.org/wiki/view/Asterisk+Dialplan+Patterns
462
- def _ pattern
463
- # Uncomment the following code fragment for complete compatability.
464
- # The fragment handles the seldom-used hyphen number spacer with no
465
- # meaning.
466
- Regexp.new '^' << pattern.#gsub(/(?!\[[\w+-]+)-(?![\w-]+\])/,'').
467
- gsub('X', '[0-9]').gsub('Z', '[1-9]').gsub('N','[2-9]').
468
- gsub('.','.+').gsub('!','.*') << '$'
469
- end
470
-
471
- # == Dialplan Instruction Caching
472
- #
473
- # If a cynic ever says Ruby doesn't make an efficient dial plan manager,
474
- # point them to this little method. The cache() method is given a block
475
- # of code and will automagically cache whatever's inside it. The caching
476
- # mechanism can even be refined by specifying a time-to-live on the cache
477
- # and special variables on which the cache depends.
478
- #
479
- # Here is a sample block of dial plan code that would be cached indefinitely:
480
- #
481
- # play_commands {
482
- # cache do
483
- # File.read('commands.txt').each { |sound_file| play sound_file }
484
- # end
485
- # }
486
- #
487
- # This example reads commands.txt, an arbitrary file containing sound file names
488
- # on each line and plays them. Because the file access and String
489
- # manipulation may be resource intensive, the commands sent to Asterisk
490
- # will be cached indefinitely (until Adhearsion restarts in this case), but
491
- # the file will be read and parsed only once, improving performance significantly.
492
- #
493
- # == Caching for a Certain Amount of Time
494
- #
495
- # Often you may want to cache a block of code, but not indefinitely. A
496
- # hash key argument can be given to the cache method to have the stored
497
- # code expire after a certain duration.
498
- #
499
- # cache :for => 1.hour do
500
- # play weather_report('Dallas, Texas')
501
- # end
502
- #
503
- # This example uses the weather helper to pull down information from the internet
504
- # and process it. While on a small scale calling this method often would only be
505
- # inconvenient to the user waiting for the content, having potentially hundreds
506
- # of users calling this method would greatly drag the system down. Caching it for
507
- # one hour keeps the system snappy and the users happy.
508
- #
509
- # == Caching Per a Variable
510
- #
511
- # cache :per => extension do
512
- # employee = User.find_by_extension extension
513
- # dial extension
514
- # end
515
- #
516
- # Another more creative example:
517
- #
518
- # cache :per => extension / 100 do
519
- # # This would be useful if you separated your IVR's endpoints into
520
- # # blocks of 100. For example, say every employee had an extension
521
- # # between 100 and 199. In this case, (extension / 100) would
522
- # # return 1. In this way, any numbers between 100 and 199 would have
523
- # # instructions unique to them cached. Say extension 200 through 299
524
- # # dialed in conference. In this case, instructions pertaining only
525
- # # to conferences would be cached (extension/100 == 2).
526
- # end
527
- #
528
- # But don't do this:
529
- #
530
- # cache :per => extension do
531
- # user = User.find_by_extension extension
532
- # speak "Calling #{user.name}"
533
- # im user.jabber, "Incoming call!"
534
- # dial user
535
- # end
536
- #
537
- # In this case, the instant message will be sent only once until the cache
538
- # expires. This is probably not what you wanted. A working (albeit trivially
539
- # simplistic) way to do this would be:
540
- #
541
- # user = User.find_by_extension extension
542
- # cache :per => extension do
543
- # speak "Calling #{user.name}"
544
- # end
545
- # im user.jabber, "Incoming call from #{calleridname}!"
546
- # cache :per => extension do
547
- # dial user
548
- # end
549
- #
550
- # Yet another cool example:
551
- #
552
- # cache :per => Time.now.wday, :for => 1.month do
553
- # # Cache your instructions based on the day of the week, refreshing
554
- # # no more than once a month.
555
- # end
556
- #
557
- # === Caching Per Multiple Variables
558
- #
559
- # If you should wish to cache per several variables, simply encapsulate them in an
560
- # Array. For example:
561
- #
562
- # cache :per => [extension, callerid], :for => 1.day+2.hours+30.minutes do
563
- # # Do your crazy crap here
564
- # end
565
- #
566
- # == Notes on Caching
567
- #
568
- # - All caches are flushed completely when Adhearsion restarts
569
- #
570
- # - If a phone call ends before a cache block finishes, nothing will be cached (a good thing).
571
- #
572
- # - Presently, you cannot nest cache blocks. Doing this will produce bizarre results.
573
- #
574
- # - Common sense should tell you not to cache per constantly changing things.
575
- # For example, if you're receiving calls from all over the country, don't
576
- # cache per everyone's phone number. This will probably result in an
577
- # OutOfMemoryError as everyone's number will have cached instructions. The
578
- # better way to do this would be to cache the blocks that all numbers share,
579
- # then execute content-specific
580
- #
581
- # - Variables declared inside of the block won't be accessible outside of the block.
582
- # If you don't want this, declare them outside of the block and use them inside.
583
- #
584
- # - If you're doing non-dialplan related stuff within a cache, it will only be
585
- # performed once. Don't put your logic to count billing time or something
586
- # similar because that varies between each call. Caching only caches what is
587
- # sent to Asterisk, not your Ruby code (of course).
588
- #
589
- # - If cached code relies on data from a database, it's best to set its expiration
590
- # to something brief (5 or 10 minutes) because database content is *supposed*
591
- # to change, but the demand of pulling the content out may affect scaling. Having
592
- # the data cached for a short amount of time is happy middle ground.
593
- def cache hash={}, &block
594
- raise ArgumentError, "Must provide a block to cache!" unless block_given?
595
-
596
- constraint = hash[:per]
597
- ttl = hash[:for]
598
-
599
- # $cache_dir is a Hash of caller()s. The repository from $cache_dir is
600
- # another Hash mapping the :per elements to the cached queue (Array) of
601
- # rawr() commands.
602
-
603
- repo = nil
604
- $cache_dir.synchronize do |dir|
605
- repo = dir[caller]
606
- repo = dir[caller] = {} unless repo
607
- end
608
-
609
- # The first element of each queue is a Time to be used for comparison
610
- # with Time.now to determine whether the cache is stale.
611
- queue = repo[constraint]
612
- queue = nil if queue && queue.first && queue.first < Time.now
613
-
614
- # If a unexpired queue exists, let's ship its commands off to rawr()
615
- if queue then queue[1..-1].each { |cmd| rawr cmd }
616
- else
617
- # Here, several things may have happened. The call to cache
618
- # could have been executed for the first time, there was no cached
619
- # queue for a particular constraint, or the cached expiration's
620
- # date has been met.
621
-
622
- # We cache the commands by setting Thread.current[:cache] which rawr()
623
- # will fill with commands it gets. yield then executes the block and
624
- # we pull out the commands rawr() gave.
625
- Thread.current[:cache] = []
626
- yield
627
- queue = Thread.current[:cache]
628
- queue.unshift ttl ? ttl.from_now : nil
629
- $cache_dir.synchronize { repo[constraint] = queue }
630
- Thread.current[:cache] = nil
631
- end
632
- end
633
- $cache_dir = {}
634
-
635
- # The rawr() method is the main way of receiving a raw response from the Asterisk
636
- # server. When no argument is given, it will immediately ask for a response,
637
- # returning that String. When an argument +is+ given, it will first send that
638
- # command and then return the response that command generated. Everything is
639
- # chomp()ed before returned.
640
- def rawr(what=nil)
641
- if what
642
- putc what
643
- Thread.current[:cache] << what if Thread.current[:cache]
644
- end
645
- PBX.io.gets.chomp!
646
- rescue
647
- log "Call likely ended (socket closed). It's okay if exceptions follow."
648
- end
649
-
650
- # Very simply sends the command over the AGI IO socket and forgets about it.
651
- def putc(what) PBX.io.print "#{what}" end
652
-
653
- # Returns the status of the last dial(). Possible dial
654
- # statuses include :answer, :busy, :noanswer, :cancel,
655
- # :congestion, and :chanunavail. If :cancel is
656
- # returned, the caller hung up before the callee picked
657
- # up. If :congestion is returned, the dialed extension
658
- # probably doesn't exist. If :chanunavail, the callee
659
- # phone may not be registered.
660
- def last_dial_status
661
- get_variable(:DIALSTATUS)[1..-2].downcase.to_sym
662
- end
663
-
664
- # Answer the channel. Adhearsion is configured by default to automatically do this
665
- # when a call comes in. This behavior can be specified in adhearsion.yml.
666
- def answer() rawr 'ANSWER' end
667
- # Hangs up the channel. Adhearsion is configured by default to matically do this
668
- # when a context completes execution. This behavior can be specified in adhearsion.yml.
669
- def hangup() rawr 'HANGUP' end
670
- # Direct translation of the Asterisk NoOp() application. Used primarily for
671
- # viewing debug information in the Asterisk CLI.
672
- def noop(*options) rawr "NOOP #{options}" end
673
-
674
- end
675
-
676
- # # This Endpoint module is only used when using the
677
- # # forward-slash operator on the dialplan technology
678
- # # classes such as IAX, SIP, Zap, Local, SCCP, etc.
679
- # # The String's eigencalss returned from IAX/:foobar
680
- # # extends this module.
681
- # module Endpoint
682
- # def /(x) to_s << "/" << x.to_s end
683
- # end
684
- #
685
- # # Use like IAX/:trunk/12345
686
- # class IAX
687
- # def self./(x) "IAX2/#{x}".extend Endpoint end
688
- # end
689
- # # Use like SIP/:trunk/12345
690
- # class SIP
691
- # def self./(x) "SIP/#{x}".extend Endpoint end
692
- # end
693
- # # Use like SCCP/:trunk/12345
694
- # class SCCP
695
- # def self./(x) "SCCP/#{x}".extend Endpoint end
696
- # end
697
- # # Use like Zap/:channel/12345
698
- # class Zap
699
- # def self./(x) "ZAP/#{x}".extend Endpoint end
700
- # end
701
- # # Use like Local/'extension@context'
702
- # class Local
703
- # def self./(x) "Local/#{x}".extend Endpoint end
704
- # end
705
- # class GTalk
706
- # def self./(x) "gtalk/#{x}".extend Endpoint end
707
- # end
708
- #
709
- # # Use like IAX2/:trunk/12345
710
- # class IAX2 < IAX;end
711
- # # Use like ZAP/:channel/12345
712
- # class ZAP < Zap;end
713
-
714
- # Contributed by Bruce Williams of Five Runs.
715
- module DialplanTechnologies
716
- module Endpoint
717
- def /(x) to_s << "/" << x.to_s end
718
- end
719
-
720
- class Base
721
- def self./(x) "#{self}/#{x}".extend(Endpoint) end
722
- end
723
-
724
- class << self
725
- def define(&block)
726
- instance_eval(&block)
727
- end
728
- def add(*technologies)
729
- options = technologies.last.is_a?(Hash) ? technologies.pop : {}
730
- technologies.each do |name|
731
- eval %{class ::#{name} < Base; def self.to_s; "#{options[:as] || name}"; end; end}
732
- end
733
- end
734
- end
735
-
736
- end
737
-
738
- DialplanTechnologies.define do
739
- add :IAX2, :SIP, :SCCP, :ZAP, :Local
740
- add :GTalk, :as => :gtalk
741
- add :Zap, :as => :ZAP
742
- add :IAX, :as => :IAX2
743
- end
744
-
745
-
746
- # The PBX object is an object manifestation of the PBX with which Adhearsion will be
747
- # associated. Helpers often open up this class and add methods to it. Note: when doing
748
- # this in your own helpers, all methods will need to be class (a.k.a. static) methods
749
- # since no actual instance of this object is passed around.
750
- class PBX
751
- def self.method_missing name
752
- raise NameError, "Method #{name} not found! Are you sure manager_proxy is configured properly?"
753
- end
754
-
755
- # The magical method that handles how objects passed to dial() are converted to their
756
- # corresponding Asterisk-recognizable technology/extension identifier. This likely
757
- # wouldn't be used much outside of dial(), but you may find it useful.
758
- def self.properize who
759
-
760
- group_method = who.is_a_group?
761
-
762
- # If 'who' has any of the possible_methods, replace 'who' with the return value
763
- # of that method
764
- who = who.send group_method if group_method
765
- return nil unless who
766
-
767
- # In case we can't perform collection algorithms on 'who', let's encapsulate it in
768
- # an Array
769
- who = [who] unless who.kind_of?(Enumerable) && !who.kind_of?(String)
770
-
771
- user_method = who.first.is_a_user?
772
- # If the first thing in the Enumerable responds to a User-like convention, then
773
- # set 'who' equal to the extension(s) accessor of those objects. We refine the "who"
774
- # argument more and more in this way until it's finally in a way Asterisk can read.
775
- who.map! { |p| p.send user_method }.compact! if user_method
776
-
777
- # Now replace each item in the Enumerable with its form converted to
778
- # extension (assuming it's not already in that format)
779
- who.map! do |ext|
780
- (ext.kind_of?(String) && ext.index(?/)) ? ext : "SIP/#{ext}"
781
- end
782
- who * '&' # Finally, join() anything left in the Array with an '&'
783
- end
784
-
785
- def self.io() Thread.current[:io] end
786
- end
787
-
788
- # The Contexts class is a blank slate in which the extensions.rb file is evaluated.
789
- # Its method_missing() simply takes a block and, given the name of attempted method,
790
- # meta_def()s a new accessor method in Contexts with this name that returns the block
791
- # given. This is how contexts can be included anywhere in extensions.rb using the
792
- # easy "+context_name" syntax.
793
- class Contexts
794
-
795
- (instance_methods - %w(__send__ __id__ define_method instance_eval)).each do |m|
796
- undef_method m
797
- end
798
-
799
- def method_missing name, *args, &block
800
- super(name, *args, &block) unless block
801
- Thread.current[:container].run_inside do
802
- meta_def name do
803
- block
804
- end
805
- end
806
- end
807
-
808
- # This Container object is a container in which each context is executed (not instantiated).
809
- # It extends the Asterisk module from adhearsion.rb and thus inherits all of the functionality
810
- # declared there. This class can be monkey patched if necessary.
811
- class Container
812
- include Asterisk
813
- def initialize
814
- class << self
815
- def metaclass; class << self; self; end; end
816
- def meta_eval &blk; metaclass.instance_eval(&blk); end
817
- def meta_def name, &blk
818
- meta_eval { define_method name, &blk }
819
- end
820
- end
821
- end
822
-
823
- def method_missing name, *args, &block
824
- Object::method_missing name, *args, &block
825
- end
826
-
827
- def run_inside &code
828
- instance_eval(&code)
829
- end
830
-
831
- def eval_inside code
832
- run_inside { eval code }
833
- end
834
- end
835
- end
836
-
837
- # An Exception thrown when the directory supplied in the constructor of a
838
- # new RailsApp object is invalid.
839
- class InvalidRailsDirectory < Exception;end
840
-
841
- # When instantiated with an absolute location to a Rails app, the new RailsApp will perform
842
- # a number of useful observations about the files and directories available, such as loading
843
- # the database configuration and listing all of the models. All observations are made accessible
844
- # through attribute accessors. Note: This implementation is experimental.
845
- class RailsApp
846
- def initialize path=Dir.pwd
847
- update! path
848
- end
849
-
850
- # Performs the observations on the Rails app once more.
851
- def update! path
852
- @path = path
853
- @database_config_file = File.join @path, 'config', 'database.yml'
854
- if File.readable? @database_config_file
855
- @database_config = YAML.load_file @database_config_file
856
- else raise InvalidRailsDirectory.new("Database config file #{@database_config_file} not found!")
857
- end
858
- @models_folder = File.join path, 'app', 'models'
859
- @models_files = Dir[ File.join(@models_folder, '*.rb') ]
860
- @time_updated = Time.now
861
- end
862
- attr_reader :database_config_file, :database_config, :path, :models_folder, :models_files,
863
- :models_names, :time_updated
864
- end
865
-
866
- # This class is the subsystem for anything that may facilitate instant messaging with Adhearsion.
867
- # If you've seen the im() method used in Adhearsion before, its features can be attributed to
868
- # the way the InstantMessenger class handles any number of helpers wanting to provide IM
869
- # functionality.
870
- #
871
- # If you're wanting to write your own instant messaging helper, you simply need to create some
872
- # kind of object that has all of the InstantMessenger::MANDATORY_METHODS. Calling
873
- # InstantMessenger.use_service(your_instance) and passing this conforming object will keep it
874
- # referenced internally and potentially used when im() is called.
875
- class InstantMessenger
876
-
877
- # Here is a synopsis of what the MANDATORY_METHODS do:
878
- # * your_username?(screenname) takes a screenname and checks whether its own instance
879
- # uses it.
880
- # * may_be_yours?(screename) takes a screenname account and returns a boolean
881
- # for whether the screen name *could* belong to the instance. For example, in the
882
- # MultiMessenger helper, this argument is checked against a regular expression for
883
- # email addresses because all Jabber accounts use this format.
884
- # * im(sn,msg) is a method all instances ultimately receive to send a message.
885
- # The Hash argument can be :through (for service) and :with (for username)
886
- MANDATORY_METHODS = [:your_username?, :may_be_yours?, :im, :your_service?, :connected?]
887
- @@messengers ||= []
1
+ # Check the Ruby version
2
+ STDERR.puts "WARNING: You are running Adhearsion in an unsupported
3
+ version of Ruby (Ruby #{RUBY_VERSION} #{RUBY_RELEASE_DATE})!
4
+ Please upgrade to at least Ruby v1.8.5." if RUBY_VERSION < "1.8.5"
5
+
6
+ $: << File.expand_path(File.dirname(__FILE__))
7
+
8
+ require 'rubygems'
9
+
10
+ require 'adhearsion/version'
11
+ require 'adhearsion/voip/call'
12
+ require 'adhearsion/voip/dial_plan'
13
+ require 'adhearsion/voip/asterisk/special_dial_plan_managers'
14
+ require 'adhearsion/foundation/all'
15
+ require 'adhearsion/events_support'
16
+ require 'adhearsion/logging'
17
+ require 'adhearsion/component_manager'
18
+ require 'adhearsion/initializer/configuration'
19
+ require 'adhearsion/initializer'
20
+ require 'adhearsion/voip/dsl/numerical_string'
21
+ require 'adhearsion/voip/dsl/dialplan/parser'
22
+ require 'adhearsion/voip/commands'
23
+ require 'adhearsion/voip/asterisk/commands'
24
+ require 'adhearsion/voip/dsl/dialing_dsl'
25
+ require 'adhearsion/voip/call_routing'
26
+
27
+ module Adhearsion
28
+ # Sets up the Gem require path.
29
+ AHN_INSTALL_DIR = File.expand_path(File.dirname(__FILE__) + "/..")
30
+ AHN_CONFIG = Configuration.new
31
+
32
+ ##
33
+ # This Array holds all the Threads whose life matters. Adhearsion will not exit until all of these have died.
34
+ #
35
+ IMPORTANT_THREADS = []
888
36
 
889
- # If you're writing your own instant messaging system, you would call this method
890
- # method on the object that conforms to InstantMessenger::MANDATORY_METHODS. It's
891
- # placed in a class Array variable and available to the im() method throughout Adhearsion.
892
- def InstantMessenger.use_service instance
893
- MANDATORY_METHODS.each do |m|
894
- raise ArgumentError, "#{instance} must implement '#{m}'!" unless instance.respond_to? m
895
- end
896
- @@messengers << instance
897
- end
898
-
899
- cattr_reader :messengers
900
-
901
- def InstantMessenger.im sn, msg, hash={}
902
- if hash[:through] && hash[:with]
903
- candidates = @@messengers.select do |c|
904
- # Find the service that both matches the service name and the username.
905
- c.your_service?(hash[:through]) && c.your_username?(hash[:with]) && c.connected?
906
- end
907
- elsif hash[:through] || hash[:with]
908
- candidates = @@messengers.select(&:connected?)
909
- if hash[:through]
910
- candidates += @@messengers.select do |c|
911
- c.your_service?(hash[:through]) && c.connected?
912
- end
913
- end
914
- if hash[:with]
915
- candidates += @@messengers.select do |c|
916
- c.your_username?(hash[:with]) && c.connected?
917
- end
918
- end
919
- else
920
- candidates = @@messengers.select { |x| x.may_be_yours? sn }
921
- end
922
-
923
- if candidates.size == 1
924
- # We've found our match!
925
- candidates.first.im sn, msg
926
- elsif candidates.empty?
927
- # The app creator probably didn't enable their IMing helper
928
- raise ArgumentError, "No instant messaging services found! Did you enable your helper?"
929
- else
930
- raise ArgumentError, "Too ambiguous! " +
931
- "Please pass a :with => 'screenname' or :through => :your_service argument to clarify!"
932
- end
933
- end
934
37
  end
935
-
936
- # Send a message to anyone, provided a proper instant messaging helper has been installed
937
- # and configured. Simply pass the screen name and message of the user. If you're using
938
- # multiple helpers for different services, you may need to pass in Hash-key arguments to
939
- # clarify.
940
- #
941
- # Understood Hash-key arguments include:
942
- # * :through => The service name (as a symbol or string) for the instant message.
943
- # * :with => The username with which the account is signed on
944
- #
945
- # It is sometimes unnecessary to provide Hash-key arguments when your helper can intelligently
946
- # distinguish its format from others'. For example, if you have both an AIM IM helper and a
947
- # GTalk/Jabber/XMPP helper registered, then the latter knows its screen names take the format
948
- # of email addresses, thus im("Jicksta", "AIM!!!") and im("x@Gmail.com", "GTalk!!!") both
949
- # work without having to do im("Jicksta", "AIM!!!", :through => :AIM)
950
- # or im(x@Gmail.com,"GTalk!!!", :through => :GTalk). If multiple AIM users were logged on, you
951
- # would *need* to do im("Jicksta","Was gibt's?", :with => "RegisteredUser") where "RegisteredUser"
952
- # is one of the names you're signed into AIM with. Note: this example doesn't use the :through
953
- # Hash-key argument because it assumes no other helpers are installed with this same format.
954
- # If you had a MSN or Yahoo helper installed too, you'd need to do specify :through because
955
- def im(sn, msg, hash={}) InstantMessenger.im(sn,msg,hash) end