adhearsion 0.7.7 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +9 -42
- data/EVENTS +11 -0
- data/README.txt +5 -0
- data/Rakefile +94 -84
- data/adhearsion.gemspec +148 -0
- data/app_generators/ahn/USAGE +5 -0
- data/app_generators/ahn/ahn_generator.rb +87 -0
- data/app_generators/ahn/templates/.ahnrc +34 -0
- data/app_generators/ahn/templates/README +8 -0
- data/app_generators/ahn/templates/Rakefile +23 -0
- data/app_generators/ahn/templates/components/ami_remote/ami_remote.rb +15 -0
- data/app_generators/ahn/templates/components/disabled/HOW_TO_ENABLE +7 -0
- data/app_generators/ahn/templates/components/disabled/stomp_gateway/README.markdown +47 -0
- data/app_generators/ahn/templates/components/disabled/stomp_gateway/config.yml +12 -0
- data/app_generators/ahn/templates/components/disabled/stomp_gateway/stomp_gateway.rb +34 -0
- data/app_generators/ahn/templates/components/restful_rpc/README.markdown +11 -0
- data/app_generators/ahn/templates/components/restful_rpc/config.yml +34 -0
- data/app_generators/ahn/templates/components/restful_rpc/example-client.rb +48 -0
- data/app_generators/ahn/templates/components/restful_rpc/restful_rpc.rb +87 -0
- data/app_generators/ahn/templates/components/restful_rpc/spec/restful_rpc_spec.rb +263 -0
- data/app_generators/ahn/templates/components/simon_game/simon_game.rb +56 -0
- data/app_generators/ahn/templates/config/startup.rb +50 -0
- data/app_generators/ahn/templates/dialplan.rb +3 -0
- data/app_generators/ahn/templates/events.rb +32 -0
- data/bin/ahn +28 -0
- data/bin/ahnctl +68 -0
- data/bin/jahn +42 -0
- data/examples/asterisk_manager_interface/standalone.rb +51 -0
- data/lib/adhearsion.rb +35 -953
- data/lib/adhearsion/cli.rb +223 -0
- data/lib/adhearsion/component_manager.rb +208 -0
- data/lib/adhearsion/component_manager/component_tester.rb +55 -0
- data/lib/adhearsion/component_manager/spec_framework.rb +24 -0
- data/lib/adhearsion/events_support.rb +84 -0
- data/lib/adhearsion/foundation/all.rb +9 -0
- data/lib/adhearsion/foundation/blank_slate.rb +5 -0
- data/lib/adhearsion/foundation/custom_daemonizer.rb +45 -0
- data/lib/adhearsion/foundation/event_socket.rb +203 -0
- data/lib/adhearsion/foundation/future_resource.rb +36 -0
- data/lib/adhearsion/foundation/global.rb +1 -0
- data/lib/adhearsion/foundation/metaprogramming.rb +17 -0
- data/lib/adhearsion/foundation/numeric.rb +13 -0
- data/lib/adhearsion/foundation/pseudo_guid.rb +10 -0
- data/lib/adhearsion/foundation/relationship_properties.rb +42 -0
- data/lib/adhearsion/foundation/string.rb +26 -0
- data/lib/adhearsion/foundation/synchronized_hash.rb +96 -0
- data/lib/adhearsion/foundation/thread_safety.rb +7 -0
- data/lib/adhearsion/host_definitions.rb +67 -0
- data/lib/adhearsion/initializer.rb +373 -0
- data/lib/adhearsion/initializer/asterisk.rb +81 -0
- data/lib/adhearsion/initializer/configuration.rb +254 -0
- data/lib/adhearsion/initializer/database.rb +49 -0
- data/lib/adhearsion/initializer/drb.rb +31 -0
- data/lib/adhearsion/initializer/freeswitch.rb +22 -0
- data/lib/adhearsion/initializer/rails.rb +40 -0
- data/lib/adhearsion/logging.rb +92 -0
- data/lib/adhearsion/tasks.rb +16 -0
- data/lib/adhearsion/tasks/database.rb +5 -0
- data/lib/adhearsion/tasks/deprecations.rb +59 -0
- data/lib/adhearsion/tasks/generating.rb +20 -0
- data/lib/adhearsion/tasks/lint.rb +4 -0
- data/lib/adhearsion/tasks/testing.rb +37 -0
- data/lib/adhearsion/version.rb +9 -0
- data/lib/adhearsion/voip/asterisk.rb +4 -0
- data/lib/adhearsion/voip/asterisk/agi_server.rb +81 -0
- data/lib/adhearsion/voip/asterisk/commands.rb +1284 -0
- data/lib/adhearsion/voip/asterisk/config_generators/agents.conf.rb +140 -0
- data/lib/adhearsion/voip/asterisk/config_generators/config_generator.rb +101 -0
- data/lib/adhearsion/voip/asterisk/config_generators/queues.conf.rb +250 -0
- data/lib/adhearsion/voip/asterisk/config_generators/voicemail.conf.rb +240 -0
- data/lib/adhearsion/voip/asterisk/config_manager.rb +71 -0
- data/lib/adhearsion/voip/asterisk/manager_interface.rb +562 -0
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rb +1754 -0
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rl.rb +286 -0
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_messages.rb +78 -0
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_protocol_lexer_machine.rl +87 -0
- data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +80 -0
- data/lib/adhearsion/voip/asterisk/super_manager.rb +19 -0
- data/lib/adhearsion/voip/call.rb +440 -0
- data/lib/adhearsion/voip/call_routing.rb +64 -0
- data/lib/adhearsion/voip/commands.rb +9 -0
- data/lib/adhearsion/voip/constants.rb +39 -0
- data/lib/adhearsion/voip/conveniences.rb +18 -0
- data/lib/adhearsion/voip/dial_plan.rb +218 -0
- data/lib/adhearsion/voip/dsl/dialing_dsl.rb +151 -0
- data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +37 -0
- data/lib/adhearsion/voip/dsl/dialplan/control_passing_exception.rb +27 -0
- data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +124 -0
- data/lib/adhearsion/voip/dsl/dialplan/parser.rb +71 -0
- data/lib/adhearsion/voip/dsl/dialplan/thread_mixin.rb +16 -0
- data/lib/adhearsion/voip/dsl/numerical_string.rb +117 -0
- data/lib/adhearsion/voip/freeswitch/basic_connection_manager.rb +48 -0
- data/lib/adhearsion/voip/freeswitch/event_handler.rb +58 -0
- data/lib/adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory.rb +129 -0
- data/lib/adhearsion/voip/freeswitch/inbound_connection_manager.rb +38 -0
- data/lib/adhearsion/voip/freeswitch/oes_server.rb +195 -0
- data/lib/adhearsion/voip/menu_state_machine/calculated_match.rb +80 -0
- data/lib/adhearsion/voip/menu_state_machine/matchers.rb +123 -0
- data/lib/adhearsion/voip/menu_state_machine/menu_builder.rb +58 -0
- data/lib/adhearsion/voip/menu_state_machine/menu_class.rb +149 -0
- data/lib/theatre.rb +151 -0
- data/lib/theatre/README.markdown +64 -0
- data/lib/theatre/callback_definition_loader.rb +84 -0
- data/lib/theatre/guid.rb +23 -0
- data/lib/theatre/invocation.rb +121 -0
- data/lib/theatre/namespace_manager.rb +153 -0
- data/lib/theatre/version.rb +2 -0
- metadata +160 -140
- data/.version +0 -1
- data/TODO +0 -71
- data/ahn +0 -223
- data/apps/default/Rakefile +0 -65
- data/apps/default/config/adhearsion.sqlite3 +0 -0
- data/apps/default/config/adhearsion.yml +0 -95
- data/apps/default/config/database.rb +0 -50
- data/apps/default/config/database.yml +0 -12
- data/apps/default/config/helpers/drb_server.yml +0 -43
- data/apps/default/config/helpers/factorial.alien.c.yml +0 -1
- data/apps/default/config/helpers/growler.yml +0 -21
- data/apps/default/config/helpers/lookup.yml +0 -1
- data/apps/default/config/helpers/manager_proxy.yml +0 -8
- data/apps/default/config/helpers/micromenus.yml +0 -1
- data/apps/default/config/helpers/micromenus/collab.rb +0 -60
- data/apps/default/config/helpers/micromenus/images/arrow-off.gif +0 -0
- data/apps/default/config/helpers/micromenus/images/arrow-on.gif +0 -0
- data/apps/default/config/helpers/micromenus/images/error.gif +0 -0
- data/apps/default/config/helpers/micromenus/images/folder-off.gif +0 -0
- data/apps/default/config/helpers/micromenus/images/folder-on.gif +0 -0
- data/apps/default/config/helpers/micromenus/images/folder.png +0 -0
- data/apps/default/config/helpers/micromenus/images/ggbridge.jpg +0 -0
- data/apps/default/config/helpers/micromenus/images/green.png +0 -0
- data/apps/default/config/helpers/micromenus/images/microbrowser.bg.gif +0 -0
- data/apps/default/config/helpers/micromenus/images/red.png +0 -0
- data/apps/default/config/helpers/micromenus/images/tux.bmp +0 -0
- data/apps/default/config/helpers/micromenus/images/url-off.gif +0 -0
- data/apps/default/config/helpers/micromenus/images/url-on.gif +0 -0
- data/apps/default/config/helpers/micromenus/images/yellow.png +0 -0
- data/apps/default/config/helpers/micromenus/javascripts/animation.js +0 -1341
- data/apps/default/config/helpers/micromenus/javascripts/carousel.js +0 -1238
- data/apps/default/config/helpers/micromenus/javascripts/columnav.js +0 -306
- data/apps/default/config/helpers/micromenus/javascripts/connection.js +0 -965
- data/apps/default/config/helpers/micromenus/javascripts/container.js +0 -4727
- data/apps/default/config/helpers/micromenus/javascripts/container_core.js +0 -2915
- data/apps/default/config/helpers/micromenus/javascripts/dom.js +0 -892
- data/apps/default/config/helpers/micromenus/javascripts/dragdrop.js +0 -2958
- data/apps/default/config/helpers/micromenus/javascripts/event.js +0 -1771
- data/apps/default/config/helpers/micromenus/javascripts/yahoo.js +0 -433
- data/apps/default/config/helpers/micromenus/stylesheets/carousel.css +0 -78
- data/apps/default/config/helpers/micromenus/stylesheets/columnav.css +0 -135
- data/apps/default/config/helpers/micromenus/stylesheets/microbrowsers.css +0 -42
- data/apps/default/config/helpers/multi_messenger.yml +0 -9
- data/apps/default/config/helpers/weather.yml +0 -1
- data/apps/default/config/helpers/xbmc.yml +0 -2
- data/apps/default/config/migration.rb +0 -59
- data/apps/default/extensions.rb +0 -41
- data/apps/default/helpers/factorial.alien.c +0 -32
- data/apps/default/helpers/growler.rb +0 -53
- data/apps/default/helpers/lookup.rb +0 -44
- data/apps/default/helpers/manager_proxy.rb +0 -112
- data/apps/default/helpers/micromenus.rb +0 -514
- data/apps/default/helpers/multi_messenger.rb +0 -53
- data/apps/default/helpers/oscar_wilde_quotes.rb +0 -197
- data/apps/default/helpers/weather.rb +0 -85
- data/apps/default/helpers/xbmc.rb +0 -39
- data/apps/default/logs/adhearsion.log +0 -0
- data/apps/default/logs/database.log +0 -0
- data/lib/constants.rb +0 -24
- data/lib/core_extensions.rb +0 -180
- data/lib/drb_server.rb +0 -101
- data/lib/logging.rb +0 -85
- data/lib/phone_number.rb +0 -85
- data/lib/rami.rb +0 -823
- data/lib/servlet_container.rb +0 -174
- data/lib/sexy_migrations.rb +0 -70
- data/test/asterisk_module_test.rb +0 -14
- data/test/core_extensions_test.rb +0 -26
- data/test/dial_test.rb +0 -43
- data/test/specs/numerical_string_spec.rb +0 -53
- 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,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!
|
data/bin/ahnctl
ADDED
@@ -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
|
data/bin/jahn
ADDED
@@ -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
|
data/lib/adhearsion.rb
CHANGED
@@ -1,955 +1,37 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
#
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
require '
|
19
|
-
require '
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
#
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|