adhearsion 0.7.6 → 0.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. data/.version +1 -1
  2. data/CHANGELOG +43 -25
  3. data/Rakefile +0 -5
  4. data/TODO +51 -2
  5. data/ahn +2 -1
  6. data/apps/default/Rakefile +16 -7
  7. data/apps/default/config/adhearsion.yml +22 -1
  8. data/apps/default/config/helpers/manager_proxy.yml +1 -0
  9. data/apps/default/config/helpers/micromenus/images/arrow-off.gif +0 -0
  10. data/apps/default/config/helpers/micromenus/images/arrow-on.gif +0 -0
  11. data/apps/default/config/helpers/micromenus/images/error.gif +0 -0
  12. data/apps/default/config/helpers/micromenus/images/folder-off.gif +0 -0
  13. data/apps/default/config/helpers/micromenus/images/folder-on.gif +0 -0
  14. data/apps/default/config/helpers/micromenus/images/folder.png +0 -0
  15. data/apps/default/config/helpers/micromenus/images/ggbridge.jpg +0 -0
  16. data/apps/default/config/helpers/micromenus/images/green.png +0 -0
  17. data/apps/default/config/helpers/micromenus/images/microbrowser.bg.gif +0 -0
  18. data/apps/default/config/helpers/micromenus/images/red.png +0 -0
  19. data/apps/default/config/helpers/micromenus/images/url-off.gif +0 -0
  20. data/apps/default/config/helpers/micromenus/images/url-on.gif +0 -0
  21. data/apps/default/config/helpers/micromenus/images/yellow.png +0 -0
  22. data/apps/default/config/helpers/micromenus/javascripts/animation.js +1341 -0
  23. data/apps/default/config/helpers/micromenus/javascripts/carousel.js +1238 -0
  24. data/apps/default/config/helpers/micromenus/javascripts/columnav.js +306 -0
  25. data/apps/default/config/helpers/micromenus/javascripts/connection.js +965 -0
  26. data/apps/default/config/helpers/micromenus/javascripts/container.js +4727 -0
  27. data/apps/default/config/helpers/micromenus/javascripts/container_core.js +2915 -0
  28. data/apps/default/config/helpers/micromenus/javascripts/dom.js +892 -0
  29. data/apps/default/config/helpers/micromenus/javascripts/dragdrop.js +2921 -907
  30. data/apps/default/config/helpers/micromenus/javascripts/event.js +1771 -0
  31. data/apps/default/config/helpers/micromenus/javascripts/yahoo.js +433 -0
  32. data/apps/default/config/helpers/micromenus/stylesheets/carousel.css +78 -0
  33. data/apps/default/config/helpers/micromenus/stylesheets/columnav.css +135 -0
  34. data/apps/default/config/helpers/micromenus/stylesheets/microbrowsers.css +42 -0
  35. data/apps/default/config/helpers/multi_messenger.yml +5 -1
  36. data/apps/default/config/migration.rb +10 -0
  37. data/apps/default/extensions.rb +1 -1
  38. data/apps/default/helpers/factorial.alien.c +3 -3
  39. data/apps/default/helpers/lookup.rb +2 -1
  40. data/apps/default/helpers/manager_proxy.rb +67 -15
  41. data/apps/default/helpers/micromenus.rb +173 -31
  42. data/apps/default/helpers/multi_messenger.rb +20 -3
  43. data/lib/adhearsion.rb +218 -88
  44. data/lib/constants.rb +1 -0
  45. data/lib/core_extensions.rb +15 -9
  46. data/lib/phone_number.rb +85 -0
  47. data/lib/rami.rb +3 -2
  48. data/lib/servlet_container.rb +47 -24
  49. data/lib/sexy_migrations.rb +70 -0
  50. data/test/asterisk_module_test.rb +9 -9
  51. data/test/specs/numerical_string_spec.rb +53 -0
  52. metadata +31 -11
  53. data/apps/default/config/helpers/micromenus/javascripts/builder.js +0 -131
  54. data/apps/default/config/helpers/micromenus/javascripts/controls.js +0 -834
  55. data/apps/default/config/helpers/micromenus/javascripts/effects.js +0 -956
  56. data/apps/default/config/helpers/micromenus/javascripts/prototype.js +0 -2319
  57. data/apps/default/config/helpers/micromenus/javascripts/scriptaculous.js +0 -51
  58. data/apps/default/config/helpers/micromenus/javascripts/slider.js +0 -278
  59. data/apps/default/config/helpers/micromenus/javascripts/unittest.js +0 -557
  60. data/apps/default/config/helpers/micromenus/stylesheets/firefox.css +0 -10
  61. data/apps/default/config/helpers/micromenus/stylesheets/firefox.xul.css +0 -44
@@ -19,5 +19,6 @@
19
19
  LOCAL_NUMBER = /^[1-9]\d{6}$/
20
20
  US_NUMBER = /^1?[1-9]\d{2}[1-9]\d{6}$/
21
21
  ISN = /^\d+\*\d+$/ # See http://freenum.org
22
+ SIP_URI = /^sip:[\w\._%+-]+@[\w\.-]+\.[a-zA-Z]{2,4}$/
22
23
 
23
24
  Infinity = 1.0/0.0
@@ -39,14 +39,6 @@ class Object
39
39
  nil
40
40
  end
41
41
 
42
- def is_local_number?
43
- to_s =~ LOCAL_NUMBER
44
- end
45
-
46
- def is_national_number?
47
- to_s =~ US_NUMBER
48
- end
49
-
50
42
  def mutex() @mutex ||= Mutex.new end
51
43
  def synchronize() mutex.synchronize { yield self } end
52
44
 
@@ -63,13 +55,15 @@ class Object
63
55
  name = priority == :normal ? "AFTER_CALL" : "AFTER_CALL_#{priority.to_s.upcase}"
64
56
  eval("$#{name}") << block
65
57
  end
58
+
59
+ def segment(&block) block.call end
66
60
  end
67
61
 
68
62
  class String
69
63
 
70
64
  def String.random_char
71
65
  case r = rand(62)
72
- when 0...10 then r.to_s
66
+ when 0...10 then r.to_s
73
67
  when 10...36 then (r+55).chr
74
68
  when 36...62 then (r+61).chr
75
69
  end
@@ -136,6 +130,8 @@ class Numeric
136
130
  def =~ other
137
131
  to_s =~ other
138
132
  end
133
+ def length() to_s.length end
134
+ alias size length
139
135
 
140
136
  def am() self end
141
137
  def pm() self+12 end
@@ -148,6 +144,16 @@ class Numeric
148
144
  end
149
145
  end
150
146
 
147
+ class Integer
148
+ alias old_to_i to_i
149
+ # Allows 123.to_i(2) to work for base conversions.
150
+ def to_i base=nil
151
+ if base then to_s.to_i base
152
+ else self
153
+ end
154
+ end
155
+ end
156
+
151
157
  class Time
152
158
  def weekday?() (1..5).include? wday end
153
159
  def weekend?() !weekday? end
@@ -0,0 +1,85 @@
1
+ class NumericalString
2
+
3
+ (instance_methods - %w"__id__ __send__ __real_num __real_string").each do |m|
4
+ undef_method m
5
+ end
6
+
7
+ def initialize str
8
+ @__real_string = str.to_s
9
+ @__real_num = str.to_i if @__real_string =~ /^\d+$/
10
+ end
11
+
12
+ attr_reader :__real_num, :__real_string
13
+
14
+ def method_missing name, *args, &block
15
+ @__real_string.__send__ name, *args, &block
16
+ end
17
+
18
+ def respond_to? m
19
+ @__real_string.respond_to?(m) || m == :__real_num || m == :__real_string
20
+ end
21
+
22
+ end
23
+
24
+ # The PhoneNumber class is used by one object throughout Adhearsion: the AGI
25
+ # "extension" variable. Using some clevery Ruby hackery, the Extension class allows
26
+ # dialplan writers to use the best of Fixnum and String usage such as
27
+ #
28
+ # - Dialing international numbers
29
+ # - Using a regexp in a case statement for "extension"
30
+ # - Using a numerical range against extension -- e.g. (100...200)
31
+ # - Using the thousands separator
32
+ class PhoneNumber < NumericalString
33
+
34
+ # Checks against a pattern identifying US local numbers (i.e numbers
35
+ # without an area code seven digits long)
36
+ def local_number?() to_s =~ LOCAL_NUMBER end
37
+
38
+ # Checks against a pattern identifying US domestic numbers.
39
+ def national_number?() to_s =~ US_NUMBER end
40
+
41
+ # Checks against a pattern identifying an ISN number. See http://freenum.org
42
+ # for more info.
43
+ def isn?() to_s =~ ISN end
44
+
45
+ # Useful for dialing those 1-800-FUDGEME type numbers with letters in them. Letters
46
+ # in the argument will be converted to their appropriate keypad key.
47
+ def self.from_vanity str
48
+ str.gsub(/\W/, '').upcase.tr('A-Z', '22233344455566677778889999')
49
+ end
50
+
51
+ end
52
+
53
+ class Object
54
+
55
+ alias _equalequalequal ===
56
+
57
+ def === arg
58
+ if arg.respond_to? :__real_string
59
+ arg = arg.__real_num if kind_of?(Numeric) || kind_of?(Range)
60
+ _equalequalequal arg
61
+ else
62
+ _equalequalequal arg
63
+ end
64
+ end
65
+ end
66
+
67
+ class Range
68
+ alias _old_equalequalequal ===
69
+ def === arg
70
+ if arg.respond_to? :__real_string
71
+ arg = arg.__real_num if kind_of?(Numeric) || kind_of?(Range)
72
+ _old_equalequalequal arg
73
+ else
74
+ _old_equalequalequal arg
75
+ end
76
+ end
77
+ end
78
+
79
+ class << String
80
+ # Had to do this for ActiveRecord compatability.
81
+ alias __equalequalequal ===
82
+ def === arg
83
+ arg.respond_to?(:__real_string) ? true : __equalequalequal(arg)
84
+ end
85
+ end
@@ -215,7 +215,7 @@ class Client
215
215
  'Exten' =>h['Exten'],
216
216
  'Priority' =>h['Priority'],
217
217
  'Timeout' =>h['Timeout'],
218
- 'CallerID' =>h['CallerID'],
218
+ 'CallerID' =>h['Callerid'] || h['CallerID'],
219
219
  'Variable' =>h['Variable'],
220
220
  'Account' =>h['Account'],
221
221
  'Application' =>h['Application'],
@@ -387,7 +387,8 @@ def connect
387
387
  $HUTDOWN.now!
388
388
  return
389
389
  end
390
- login = {'Action' => 'login', 'Username' => @username, 'Secret' => @secret, 'Events' => 'Off'}
390
+ events = $HELPERS['manager_proxy']['events']
391
+ login = {'Action' => 'login', 'Username' => @username, 'Secret' => @secret, 'Events' => events ? 'On' : 'Off'}
391
392
  writesock(login)
392
393
  accum = {}
393
394
  login = 0
@@ -17,7 +17,7 @@
17
17
 
18
18
  class ServletContainer
19
19
 
20
- # TODO: Port Mongrel server here.
20
+ # TODO: Port more efficient server here. Erlang perhaps?
21
21
  class NativeServer;end
22
22
 
23
23
  require 'gserver'
@@ -30,18 +30,38 @@ class ServletContainer
30
30
  end
31
31
 
32
32
  def read_variables io
33
- call_variables = {}
34
- while(line = io.gets.chomp)
33
+ vars = {}
34
+ while line = io.gets.chomp
35
35
  break if line.empty? # Empty lines signify no more variables
36
36
  variable = line.split(/:\s*/)
37
37
  new_name, new_value = variable.first[4..-1].downcase, variable.last
38
- if new_name == 'extension'
39
- new_value.gsub! '-', '_'
40
- call_variables['str_extension'] = new_value
38
+
39
+ if new_name == 'extension' then vars['extension'] = PhoneNumber.new new_value
40
+ elsif new_name == 'context' then vars['context'] = new_value.gsub '-', '_'
41
+ elsif new_name == 'request'
42
+ uri = URI.parse new_value
43
+ query = {}
44
+ if uri.query
45
+ hash = {}
46
+ uri.query.split('&').each do |a|
47
+ k,v = a.split('=')
48
+ query[k] = v
49
+ end
50
+ context = query['context']
51
+ end
52
+ vars['request'], vars['uri'], vars['query'] = new_value, uri, query
53
+ else
54
+ vars[new_name.to_s] = if new_value =~ /^\d+$/
55
+ if new_value.starts_with?('0') then NumericalString.new(new_value)
56
+ else new_value.to_i
57
+ end
58
+ else new_value
59
+ end
41
60
  end
42
- call_variables["#{new_name}"] = new_value =~ /^\d+$/ ? Integer(new_value) : new_value
61
+
43
62
  end
44
- call_variables
63
+ vars['context'] = vars['query']['context'] if vars['query']['context']
64
+ vars
45
65
  end
46
66
 
47
67
  # GServer allows all functionality to be packed into this one serve() method.
@@ -70,7 +90,7 @@ class ServletContainer
70
90
  end
71
91
 
72
92
  log "Executing call with variables: " + call_variables.inspect
73
-
93
+
74
94
  call_variables.each do |k,v|
75
95
  Thread.current[:container].run_inside do
76
96
  meta_def(k) { v }
@@ -78,17 +98,18 @@ class ServletContainer
78
98
  end
79
99
 
80
100
  # Execute all before_call hooks
81
- [$BEFORE_CALl_HIGH, $BEFORE_CALL, $BEFORE_CALL_LOW].flatten.compact.each(&:call)
82
- +lambda { answer if CONFIG.answer_before_call }
101
+ [$BEFORE_CALL_HIGH, $BEFORE_CALL, $BEFORE_CALL_LOW].flatten.compact.each(&:call)
102
+ +lambda { answer if CONFIG['answer_before_call'] }
83
103
 
84
104
  # A call hook may decree that the call shouldn't be processed further (e.g. if
85
105
  # it processed the call itself). This is done be rewriting the context variable.
86
106
  unless Thread.current[:VARS]['context'] == :interrupted
87
-
107
+
88
108
  begin
109
+
89
110
  Contexts.new.instance_eval do
90
111
  # Interpret the extensions.rb file!
91
- eval File.read(File.join(Dir.pwd, "extensions.rb"))
112
+ eval File.read("extensions.rb")
92
113
  end
93
114
  rescue => detail
94
115
  log "Exception raised in extensions.rb! " << detail.message
@@ -98,21 +119,23 @@ class ServletContainer
98
119
 
99
120
  log "Parsing of extensions.rb complete"
100
121
 
101
- # Because Asterisk allows dashes in its context names and Ruby interprets
102
- # these as minuses, let's convert them to underscores.
103
- call_variables['context'].gsub! '-', '_'
104
-
105
122
  begin
106
123
  target_context = call_variables['context']
107
124
  if target_context
108
125
  Thread.current[:container].run_inside do
109
126
  begin
110
- +send(target_context.to_s.to_sym)
127
+ unless target_context.kind_of? Proc
128
+ target_context = send(target_context.to_s.to_sym)
129
+ end
130
+ +target_context
131
+ rescue ControlPassingException => new_target
132
+ target_context = new_target.target
133
+ retry
111
134
  rescue => e
112
135
  error e.inspect + "\n " + e.backtrace * "\n "
113
136
  +lambda {
114
- play 'were-sorry'
115
- hangup
137
+ play 'an-error-has-occurred', 'were-sorry'
138
+ hangup if CONFIG.key?('hangup_after_error') && CONFIG['hangup_after_error']
116
139
  }
117
140
  end
118
141
  end
@@ -122,14 +145,14 @@ class ServletContainer
122
145
  return
123
146
  end
124
147
  rescue => detail
125
- log "ERROR: #{detail.class.name} => #{detail.inspect}"
126
- detail.backtrace.each do |msg| log " "*8 << msg end
148
+ error "ERROR: #{detail.class.name} => #{detail.inspect}"
149
+ detail.backtrace.each do |msg| error " " * 8 << msg end
127
150
  end
128
151
  log "Call routing complete"
129
152
  end
130
153
  rescue => detail
131
- log "Call thread raised an exception! #{detail.message}"
132
- detail.backtrace.each do |msg| log " "*8 << msg end
154
+ error "Call thread raised an exception! #{detail.message}"
155
+ detail.backtrace.each { |msg| error " " * 8 << msg }
133
156
  end
134
157
 
135
158
  [$AFTER_CALL_HIGH, $AFTER_CALL, $AFTER_CALL_LOW].flatten.compact.each(&:call)
@@ -0,0 +1,70 @@
1
+ # Taken from svn://errtheblog.com/svn/plugins/sexy_migrations/lib/sexy_migrations.rb
2
+ module SexyMigrations
3
+ module Table
4
+ attr_reader :fk_references
5
+
6
+ def foreign_key(*args)
7
+ options = args.last.is_a?(Hash) ? args.pop : {}
8
+ args.each do |col|
9
+ column(id = "#{col}_id", :integer, options)
10
+ (@fk_references ||= []) << [id, options[:ref].to_s] if options[:ref]
11
+ end
12
+ end
13
+ alias :fkey :foreign_key
14
+ alias :fkeys :foreign_key
15
+ alias :foreign_keys :foreign_key
16
+
17
+ def timestamps(*extras)
18
+ (Array(extras) + %w(created_at updated_at)).each do |stamp|
19
+ datetime stamp
20
+ end
21
+ end
22
+ alias :timestamps! :timestamps
23
+ alias :auto_dates :timestamps
24
+ alias :auto_dates! :timestamps
25
+
26
+ def polymorphic(name)
27
+ integer "#{name}_id"
28
+ string "#{name}_type"
29
+ end
30
+ alias :polymorphic! :polymorphic
31
+
32
+ def method_missing(name, *args)
33
+ return super unless type = simplified_type(name)
34
+ options = args.last.is_a?(Hash) ? args.pop : {}
35
+ args.each { |col| column(col, type, options) }
36
+ end
37
+
38
+ private
39
+ def simplified_type(type)
40
+ ActiveRecord::ConnectionAdapters::Column.new(:type_check, false, type.to_s).type
41
+ end
42
+ end
43
+
44
+ module Schema
45
+ def create_table(name, options = {}, &block)
46
+ table_definition = ActiveRecord::ConnectionAdapters::TableDefinition.new(self)
47
+ table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false
48
+
49
+ table_definition.instance_eval &block
50
+
51
+ if options[:force]
52
+ drop_table(name, options) rescue nil
53
+ end
54
+
55
+ create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
56
+ create_sql << "#{name} ("
57
+ create_sql << table_definition.to_sql
58
+ create_sql << ") #{options[:options]}"
59
+
60
+ # if any fk contraints, add them
61
+ if table_definition.fk_references
62
+ create_sql << "; "
63
+ table_definition.fk_references.each do |fk|
64
+ create_sql << "ALTER TABLE #{name} ADD FOREIGN KEY (#{fk[0]}) REFERENCES #{fk[1]} (id); "
65
+ end
66
+ end
67
+ execute create_sql
68
+ end
69
+ end
70
+ end
@@ -1,13 +1,13 @@
1
- context "The Asterisk module" do
1
+ context "The PBX object" do
2
2
  specify "should properize properly" do
3
- properize("123").should == 'SIP/123'
4
- properize(1_555_444_1234).should == 'SIP/15554441234'
5
- properize(:jay).should == 'SIP/jay'
6
- properize("jay").should == 'SIP/jay'
7
- properize("SIP/123").should == 'SIP/123'
8
- properize("IAX2/jay").should == 'IAX2/jay'
9
- properize((100..110).to_a).should == 'SIP/100&SIP/101&SIP/102&SIP/103&SIP/104&SIP/105&SIP/106&SIP/107&SIP/108&SIP/109&SIP/110'
10
- properize(["SIP/123", "SIP/456"]).should == 'SIP/123&SIP/456'
3
+ PBX.properize("123").should == 'SIP/123'
4
+ PBX.properize(1_555_444_1234).should == 'SIP/15554441234'
5
+ PBX.properize(:jay).should == 'SIP/jay'
6
+ PBX.properize("jay").should == 'SIP/jay'
7
+ PBX.properize("SIP/123").should == 'SIP/123'
8
+ PBX.properize("IAX2/jay").should == 'IAX2/jay'
9
+ PBX.properize((100..110).to_a).should == 'SIP/100&SIP/101&SIP/102&SIP/103&SIP/104&SIP/105&SIP/106&SIP/107&SIP/108&SIP/109&SIP/110'
10
+ PBX.properize(["SIP/123", "SIP/456"]).should == 'SIP/123&SIP/456'
11
11
  # TODO: Should mock up dialing a user
12
12
  # TODO: Should mock up dialing a group
13
13
  end
@@ -0,0 +1,53 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'phone_number.rb')
2
+ require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'constants.rb')
3
+
4
+ context "A NumericalString" do
5
+ specify "should be accessable as a Fixnum to a case statement" do
6
+ (123 === NumericalString.new("123")).should == true
7
+ (987 === NumericalString.new("0987")).should == true
8
+ end
9
+ specify "should be accessable as a String to a case statement" do
10
+ ("123" === NumericalString.new("123")).should == true
11
+ ("0987" === NumericalString.new("0987")).should == true
12
+ end
13
+
14
+ specify "should satisfy Ranges" do
15
+ (100..200).should === NumericalString.new("150")
16
+ (100..200).should === NumericalString.new("0150")
17
+ ((100..200) === NumericalString.new("1000000")).should === false
18
+ end
19
+
20
+ specify "should satify regular expressions" do
21
+ /^\d+$/.should === NumericalString.new("027316287")
22
+ end
23
+ end
24
+ #
25
+ # case extension
26
+ # when US_NUMBER
27
+ # when (100..200)
28
+ # when _('12Z')
29
+ # when 123
30
+ # when "123"
31
+ # end
32
+
33
+ context "A PhoneNumber" do
34
+ specify "should have an ISN pattern-matching method" do
35
+ !! PhoneNumber.new("0115544332211").isn?.should == false
36
+ !! PhoneNumber.new("1*548").isn?.should === false
37
+ end
38
+
39
+ specify "should have a US local number pattern-matching method" do
40
+ !! PhoneNumber.new("18887776665555").local_number?.should == false
41
+ !! PhoneNumber.new("18887776665555").national_number?.should == true
42
+
43
+ !! PhoneNumber.new("8887776665555").local_number?.should == false
44
+ !! PhoneNumber.new("8887776665555").national_number?.should == true
45
+
46
+ !! PhoneNumber.new("4445555").local_number?.should == true
47
+ !! PhoneNumber.new("4445555").national_number?.should == false
48
+ end
49
+
50
+ specify "should convert from vanity numbers properly" do
51
+ PhoneNumber.from_vanity("1-800-FUDGEME").should == "18003834363"
52
+ end
53
+ end