adhearsion 2.6.4 → 3.0.0.beta1

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 (242) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -9
  3. data/CHANGELOG.md +22 -7
  4. data/Gemfile +2 -0
  5. data/README.markdown +4 -5
  6. data/Rakefile +1 -2
  7. data/adhearsion.gemspec +19 -8
  8. data/features/cli_create.feature +19 -3
  9. data/features/step_definitions/cli_steps.rb +0 -11
  10. data/features/support/env.rb +3 -4
  11. data/lib/adhearsion.rb +48 -27
  12. data/lib/adhearsion/call.rb +34 -50
  13. data/lib/adhearsion/call_controller.rb +6 -12
  14. data/lib/adhearsion/call_controller/dial.rb +15 -53
  15. data/lib/adhearsion/call_controller/input.rb +39 -162
  16. data/lib/adhearsion/call_controller/input/ask_grammar_builder.rb +44 -0
  17. data/lib/adhearsion/call_controller/input/menu_builder.rb +136 -0
  18. data/lib/adhearsion/call_controller/input/prompt_builder.rb +78 -0
  19. data/lib/adhearsion/call_controller/input/result.rb +46 -0
  20. data/lib/adhearsion/call_controller/output.rb +48 -67
  21. data/lib/adhearsion/call_controller/output/abstract_player.rb +3 -3
  22. data/lib/adhearsion/call_controller/output/async_player.rb +3 -3
  23. data/lib/adhearsion/call_controller/output/player.rb +1 -1
  24. data/lib/adhearsion/call_controller/record.rb +23 -8
  25. data/lib/adhearsion/calls.rb +1 -1
  26. data/lib/adhearsion/cli_commands/ahn_command.rb +2 -65
  27. data/lib/adhearsion/cli_commands/thor_errors.rb +0 -6
  28. data/lib/adhearsion/configuration.rb +91 -39
  29. data/lib/adhearsion/core_ext/blather/stanza.rb +41 -0
  30. data/lib/adhearsion/core_ext/blather/stanza/presence.rb +13 -0
  31. data/lib/adhearsion/error.rb +5 -0
  32. data/lib/adhearsion/event.rb +21 -0
  33. data/lib/adhearsion/event/active_speaker.rb +11 -0
  34. data/lib/adhearsion/event/answered.rb +11 -0
  35. data/lib/adhearsion/event/asterisk.rb +10 -0
  36. data/lib/adhearsion/event/asterisk/ami.rb +34 -0
  37. data/lib/adhearsion/event/complete.rb +75 -0
  38. data/lib/adhearsion/event/dtmf.rb +11 -0
  39. data/lib/adhearsion/event/end.rb +22 -0
  40. data/lib/adhearsion/event/input_timers_started.rb +9 -0
  41. data/lib/adhearsion/event/joined.rb +17 -0
  42. data/lib/adhearsion/event/offer.rb +14 -0
  43. data/lib/adhearsion/event/ringing.rb +11 -0
  44. data/lib/adhearsion/event/started_speaking.rb +13 -0
  45. data/lib/adhearsion/event/stopped_speaking.rb +13 -0
  46. data/lib/adhearsion/event/unjoined.rb +17 -0
  47. data/lib/adhearsion/events.rb +47 -66
  48. data/lib/adhearsion/foundation.rb +0 -1
  49. data/lib/adhearsion/foundation/object.rb +0 -5
  50. data/lib/adhearsion/generators/app/app_generator.rb +4 -1
  51. data/lib/adhearsion/generators/app/templates/Gemfile.erb +2 -10
  52. data/lib/adhearsion/generators/app/templates/adhearsion.erb +9 -9
  53. data/lib/adhearsion/generators/app/templates/config.ru +7 -0
  54. data/lib/adhearsion/generators/app/templates/en.yml +4 -0
  55. data/lib/adhearsion/generators/app/templates/events.erb +2 -2
  56. data/lib/adhearsion/generators/app/templates/hello_world.wav +0 -0
  57. data/lib/adhearsion/generators/app/templates/simon_game.rb +2 -1
  58. data/lib/adhearsion/generators/app/templates/simon_game_spec.rb +2 -2
  59. data/lib/adhearsion/has_headers.rb +34 -0
  60. data/lib/adhearsion/http_server.rb +37 -0
  61. data/lib/adhearsion/initializer.rb +19 -153
  62. data/lib/adhearsion/logging.rb +6 -25
  63. data/lib/adhearsion/outbound_call.rb +5 -5
  64. data/lib/adhearsion/plugin.rb +1 -0
  65. data/lib/adhearsion/protocol_error.rb +26 -0
  66. data/lib/adhearsion/rayo.rb +30 -0
  67. data/lib/adhearsion/rayo/client.rb +62 -0
  68. data/lib/adhearsion/rayo/client/component_registry.rb +33 -0
  69. data/lib/adhearsion/rayo/command.rb +21 -0
  70. data/lib/adhearsion/rayo/command/accept.rb +16 -0
  71. data/lib/adhearsion/rayo/command/answer.rb +16 -0
  72. data/lib/adhearsion/rayo/command/dial.rb +57 -0
  73. data/lib/adhearsion/rayo/command/hangup.rb +16 -0
  74. data/lib/adhearsion/rayo/command/join.rb +43 -0
  75. data/lib/adhearsion/rayo/command/mute.rb +13 -0
  76. data/lib/adhearsion/rayo/command/redirect.rb +23 -0
  77. data/lib/adhearsion/rayo/command/reject.rb +40 -0
  78. data/lib/adhearsion/rayo/command/unjoin.rb +24 -0
  79. data/lib/adhearsion/rayo/command/unmute.rb +13 -0
  80. data/lib/adhearsion/rayo/command_node.rb +47 -0
  81. data/lib/adhearsion/rayo/component.rb +21 -0
  82. data/lib/adhearsion/rayo/component/asterisk.rb +13 -0
  83. data/lib/adhearsion/rayo/component/asterisk/agi.rb +14 -0
  84. data/lib/adhearsion/rayo/component/asterisk/agi/command.rb +46 -0
  85. data/lib/adhearsion/rayo/component/asterisk/ami.rb +14 -0
  86. data/lib/adhearsion/rayo/component/asterisk/ami/action.rb +61 -0
  87. data/lib/adhearsion/rayo/component/component_node.rb +90 -0
  88. data/lib/adhearsion/rayo/component/input.rb +186 -0
  89. data/lib/adhearsion/rayo/component/output.rb +471 -0
  90. data/lib/adhearsion/rayo/component/prompt.rb +53 -0
  91. data/lib/adhearsion/rayo/component/receive_fax.rb +26 -0
  92. data/lib/adhearsion/rayo/component/record.rb +165 -0
  93. data/lib/adhearsion/rayo/component/send_fax.rb +64 -0
  94. data/lib/adhearsion/rayo/component/stop.rb +11 -0
  95. data/lib/adhearsion/rayo/connection.rb +12 -0
  96. data/lib/adhearsion/rayo/connection/asterisk.rb +74 -0
  97. data/lib/adhearsion/rayo/connection/connected.rb +22 -0
  98. data/lib/adhearsion/rayo/connection/generic_connection.rb +22 -0
  99. data/lib/adhearsion/rayo/connection/xmpp.rb +198 -0
  100. data/lib/adhearsion/rayo/disconnected_error.rb +22 -0
  101. data/lib/adhearsion/{punchblock_plugin → rayo}/initializer.rb +19 -19
  102. data/lib/adhearsion/rayo/rayo_node.rb +127 -0
  103. data/lib/adhearsion/rayo/ref.rb +57 -0
  104. data/lib/adhearsion/statistics.rb +1 -1
  105. data/lib/adhearsion/tasks.rb +1 -2
  106. data/lib/adhearsion/tasks/configuration.rb +1 -1
  107. data/lib/adhearsion/tasks/environment.rb +0 -2
  108. data/lib/adhearsion/tasks/i18n.rb +49 -0
  109. data/lib/adhearsion/translator.rb +11 -0
  110. data/lib/adhearsion/translator/asterisk.rb +234 -0
  111. data/lib/adhearsion/translator/asterisk/agi_app.rb +17 -0
  112. data/lib/adhearsion/translator/asterisk/agi_command.rb +45 -0
  113. data/lib/adhearsion/translator/asterisk/ami_error_converter.rb +20 -0
  114. data/lib/adhearsion/translator/asterisk/call.rb +416 -0
  115. data/lib/adhearsion/translator/asterisk/channel.rb +43 -0
  116. data/lib/adhearsion/translator/asterisk/component.rb +88 -0
  117. data/lib/adhearsion/translator/asterisk/component/asterisk.rb +15 -0
  118. data/lib/adhearsion/translator/asterisk/component/asterisk/agi_command.rb +42 -0
  119. data/lib/adhearsion/translator/asterisk/component/asterisk/ami_action.rb +68 -0
  120. data/lib/adhearsion/translator/asterisk/component/composed_prompt.rb +76 -0
  121. data/lib/adhearsion/translator/asterisk/component/dtmf_recognizer.rb +137 -0
  122. data/lib/adhearsion/translator/asterisk/component/input.rb +34 -0
  123. data/lib/adhearsion/translator/asterisk/component/input_component.rb +90 -0
  124. data/lib/adhearsion/translator/asterisk/component/mrcp_native_prompt.rb +71 -0
  125. data/lib/adhearsion/translator/asterisk/component/mrcp_prompt.rb +55 -0
  126. data/lib/adhearsion/translator/asterisk/component/mrcp_recog_prompt.rb +165 -0
  127. data/lib/adhearsion/translator/asterisk/component/output.rb +233 -0
  128. data/lib/adhearsion/translator/asterisk/component/record.rb +101 -0
  129. data/lib/adhearsion/translator/asterisk/component/stop_by_redirect.rb +30 -0
  130. data/lib/adhearsion/translator/asterisk/unimrcp_app.rb +28 -0
  131. data/lib/adhearsion/uri_list.rb +21 -0
  132. data/lib/adhearsion/version.rb +1 -1
  133. data/spec/adhearsion/call_controller/dial_spec.rb +79 -1420
  134. data/spec/adhearsion/call_controller/input_spec.rb +1141 -237
  135. data/spec/adhearsion/call_controller/output/async_player_spec.rb +10 -10
  136. data/spec/adhearsion/call_controller/output/player_spec.rb +8 -8
  137. data/spec/adhearsion/call_controller/output_spec.rb +162 -215
  138. data/spec/adhearsion/call_controller/record_spec.rb +15 -16
  139. data/spec/adhearsion/call_controller_spec.rb +23 -40
  140. data/spec/adhearsion/call_spec.rb +123 -129
  141. data/spec/adhearsion/calls_spec.rb +3 -3
  142. data/spec/adhearsion/configuration_spec.rb +94 -108
  143. data/spec/adhearsion/event/answered_spec.rb +50 -0
  144. data/spec/adhearsion/event/asterisk/Find Results +402 -0
  145. data/spec/adhearsion/event/asterisk/ami_spec.rb +81 -0
  146. data/spec/adhearsion/event/complete_spec.rb +176 -0
  147. data/spec/adhearsion/event/dtmf_spec.rb +35 -0
  148. data/spec/adhearsion/event/end_spec.rb +85 -0
  149. data/spec/adhearsion/event/input_timers_started_spec.rb +19 -0
  150. data/spec/adhearsion/event/joined_spec.rb +53 -0
  151. data/spec/adhearsion/event/offer_spec.rb +106 -0
  152. data/spec/adhearsion/event/ringing_spec.rb +50 -0
  153. data/spec/adhearsion/event/started_speaking_spec.rb +37 -0
  154. data/spec/adhearsion/event/stopped_speaking_spec.rb +37 -0
  155. data/spec/adhearsion/event/unjoined_spec.rb +48 -0
  156. data/spec/adhearsion/event/untitled +0 -0
  157. data/spec/adhearsion/events_spec.rb +19 -45
  158. data/spec/adhearsion/initializer_spec.rb +12 -184
  159. data/spec/adhearsion/logging_spec.rb +5 -20
  160. data/spec/adhearsion/outbound_call_spec.rb +13 -13
  161. data/spec/adhearsion/plugin_spec.rb +3 -4
  162. data/spec/adhearsion/protocol_error_spec.rb +91 -0
  163. data/spec/adhearsion/rayo/client/component_registry_spec.rb +26 -0
  164. data/spec/adhearsion/rayo/client_spec.rb +134 -0
  165. data/spec/adhearsion/rayo/command/accept_spec.rb +63 -0
  166. data/spec/adhearsion/rayo/command/answer_spec.rb +73 -0
  167. data/spec/adhearsion/rayo/command/dial_spec.rb +156 -0
  168. data/spec/adhearsion/rayo/command/hangup_spec.rb +63 -0
  169. data/spec/adhearsion/rayo/command/join_spec.rb +158 -0
  170. data/spec/adhearsion/rayo/command/mute_spec.rb +32 -0
  171. data/spec/adhearsion/rayo/command/redirect_spec.rb +89 -0
  172. data/spec/adhearsion/rayo/command/reject_spec.rb +117 -0
  173. data/spec/adhearsion/rayo/command/unjoin_spec.rb +82 -0
  174. data/spec/adhearsion/rayo/command/unmute_spec.rb +32 -0
  175. data/spec/adhearsion/rayo/command_node_spec.rb +101 -0
  176. data/spec/adhearsion/rayo/component/asterisk/agi/command_spec.rb +111 -0
  177. data/spec/adhearsion/rayo/component/asterisk/ami/action_spec.rb +173 -0
  178. data/spec/adhearsion/rayo/component/component_node_spec.rb +110 -0
  179. data/spec/adhearsion/rayo/component/input_spec.rb +715 -0
  180. data/spec/adhearsion/rayo/component/output_spec.rb +1030 -0
  181. data/spec/adhearsion/rayo/component/prompt_spec.rb +171 -0
  182. data/spec/adhearsion/rayo/component/receive_fax_spec.rb +136 -0
  183. data/spec/adhearsion/rayo/component/record_spec.rb +497 -0
  184. data/spec/adhearsion/rayo/component/send_fax_spec.rb +144 -0
  185. data/spec/adhearsion/rayo/connection/asterisk_spec.rb +118 -0
  186. data/spec/adhearsion/rayo/connection/xmpp_spec.rb +449 -0
  187. data/spec/adhearsion/rayo/initializer_spec.rb +353 -0
  188. data/spec/adhearsion/rayo/ref_spec.rb +168 -0
  189. data/spec/adhearsion/rayo_spec.rb +7 -0
  190. data/spec/adhearsion/router/route_spec.rb +1 -1
  191. data/spec/adhearsion/statistics_spec.rb +2 -5
  192. data/spec/adhearsion/translator/asterisk/call_spec.rb +2047 -0
  193. data/spec/adhearsion/translator/asterisk/component/asterisk/agi_command_spec.rb +256 -0
  194. data/spec/adhearsion/translator/asterisk/component/asterisk/ami_action_spec.rb +151 -0
  195. data/spec/adhearsion/translator/asterisk/component/composed_prompt_spec.rb +257 -0
  196. data/spec/adhearsion/translator/asterisk/component/input_spec.rb +571 -0
  197. data/spec/adhearsion/translator/asterisk/component/mrcp_native_prompt_spec.rb +774 -0
  198. data/spec/adhearsion/translator/asterisk/component/mrcp_prompt_spec.rb +1244 -0
  199. data/spec/adhearsion/translator/asterisk/component/output_spec.rb +1850 -0
  200. data/spec/adhearsion/translator/asterisk/component/record_spec.rb +426 -0
  201. data/spec/adhearsion/translator/asterisk/component/stop_by_redirect_spec.rb +62 -0
  202. data/spec/adhearsion/translator/asterisk/component_spec.rb +83 -0
  203. data/spec/adhearsion/translator/asterisk_spec.rb +685 -0
  204. data/spec/adhearsion/uri_list_spec.rb +88 -0
  205. data/spec/adhearsion_spec.rb +89 -14
  206. data/spec/fixtures/locale/en.yml +13 -0
  207. data/spec/fixtures/locale/it.yml +13 -0
  208. data/spec/spec_helper.rb +18 -5
  209. data/spec/support/call_controller_test_helpers.rb +3 -2
  210. data/spec/support/initializer_stubs.rb +3 -1
  211. data/spec/support/punchblock_examples.rb +65 -0
  212. data/spec/support/punchblock_mocks.rb +12 -0
  213. metadata +412 -70
  214. data/features/cli_daemon.feature +0 -20
  215. data/features/cli_restart.feature +0 -52
  216. data/features/cli_stop.feature +0 -50
  217. data/lib/adhearsion/call_controller/menu_dsl.rb +0 -21
  218. data/lib/adhearsion/call_controller/menu_dsl/array_match_calculator.rb +0 -26
  219. data/lib/adhearsion/call_controller/menu_dsl/calculated_match.rb +0 -43
  220. data/lib/adhearsion/call_controller/menu_dsl/calculated_match_collection.rb +0 -45
  221. data/lib/adhearsion/call_controller/menu_dsl/fixnum_match_calculator.rb +0 -11
  222. data/lib/adhearsion/call_controller/menu_dsl/match_calculator.rb +0 -40
  223. data/lib/adhearsion/call_controller/menu_dsl/menu.rb +0 -207
  224. data/lib/adhearsion/call_controller/menu_dsl/menu_builder.rb +0 -92
  225. data/lib/adhearsion/call_controller/menu_dsl/range_match_calculator.rb +0 -60
  226. data/lib/adhearsion/call_controller/menu_dsl/string_match_calculator.rb +0 -25
  227. data/lib/adhearsion/call_controller/utility.rb +0 -77
  228. data/lib/adhearsion/foundation/custom_daemonizer.rb +0 -52
  229. data/lib/adhearsion/punchblock_plugin.rb +0 -63
  230. data/scripts/cloc-1.64.pl +0 -10483
  231. data/spec/adhearsion/call_controller/menu_dsl/array_match_calculator_spec.rb +0 -76
  232. data/spec/adhearsion/call_controller/menu_dsl/calculated_match_collection_spec.rb +0 -60
  233. data/spec/adhearsion/call_controller/menu_dsl/calculated_match_spec.rb +0 -61
  234. data/spec/adhearsion/call_controller/menu_dsl/fixnum_match_calculator_spec.rb +0 -39
  235. data/spec/adhearsion/call_controller/menu_dsl/match_calculator_spec.rb +0 -17
  236. data/spec/adhearsion/call_controller/menu_dsl/menu_builder_spec.rb +0 -165
  237. data/spec/adhearsion/call_controller/menu_dsl/menu_spec.rb +0 -420
  238. data/spec/adhearsion/call_controller/menu_dsl/range_match_calculator_spec.rb +0 -32
  239. data/spec/adhearsion/call_controller/menu_dsl/string_match_calculator_spec.rb +0 -40
  240. data/spec/adhearsion/call_controller/utility_spec.rb +0 -90
  241. data/spec/adhearsion/punchblock_plugin/initializer_spec.rb +0 -356
  242. data/spec/adhearsion/punchblock_plugin_spec.rb +0 -59
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+
3
+ module Adhearsion
4
+ module Translator
5
+ class Asterisk
6
+ module AMIErrorConverter
7
+ def self.convert(result = ->(e) { raise ChannelGoneError, e.message } )
8
+ yield
9
+ rescue RubyAMI::Error => e
10
+ case e.message
11
+ when 'No such channel', /Channel (\S+) does not exist./
12
+ result.call e if result
13
+ else
14
+ raise e
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,416 @@
1
+ # encoding: utf-8
2
+
3
+ require 'has_guarded_handlers'
4
+ require 'adhearsion/translator/asterisk/ami_error_converter'
5
+
6
+ module Adhearsion
7
+ module Translator
8
+ class Asterisk
9
+ class Call
10
+ include HasGuardedHandlers
11
+
12
+ InvalidCommandError = Class.new Error
13
+
14
+ OUTBOUND_CHANNEL_MATCH = /.* <(?<channel>.*)>/.freeze
15
+
16
+ attr_reader :id, :channel, :translator, :agi_env, :direction, :pending_joins
17
+
18
+ HANGUP_CAUSE_TO_END_REASON = Hash.new { :error }
19
+ HANGUP_CAUSE_TO_END_REASON[0] = :hungup
20
+ HANGUP_CAUSE_TO_END_REASON[16] = :hungup
21
+ HANGUP_CAUSE_TO_END_REASON[17] = :busy
22
+ HANGUP_CAUSE_TO_END_REASON[18] = :timeout
23
+ HANGUP_CAUSE_TO_END_REASON[19] = :reject
24
+ HANGUP_CAUSE_TO_END_REASON[21] = :reject
25
+ HANGUP_CAUSE_TO_END_REASON[22] = :reject
26
+ HANGUP_CAUSE_TO_END_REASON[102] = :timeout
27
+
28
+ def initialize(channel, translator, ami_client, connection, agi_env = nil, id = nil)
29
+ @channel, @translator, @ami_client, @connection = channel, translator, ami_client, connection
30
+ @agi_env = agi_env || {}
31
+ @id = id || Adhearsion.new_uuid
32
+ @components = {}
33
+ @answered = false
34
+ @pending_joins = {}
35
+ @progress_sent = false
36
+ @block_commands = false
37
+ @channel_variables = {}
38
+ @hangup_cause = nil
39
+ end
40
+
41
+ def register_component(component)
42
+ @components[component.id] ||= component
43
+ end
44
+
45
+ def deregister_component(id)
46
+ @components.delete id
47
+ end
48
+
49
+ def component_with_id(component_id)
50
+ @components[component_id]
51
+ end
52
+
53
+ def send_offer
54
+ @direction = :inbound
55
+ send_pb_event offer_event
56
+ end
57
+
58
+ def channel_var(variable)
59
+ @channel_variables[variable] || fetch_channel_var(variable)
60
+ end
61
+
62
+ def to_s
63
+ "#<#{self.class}:#{id} Channel: #{channel.inspect}>"
64
+ end
65
+ alias :inspect :to_s
66
+
67
+ def dial(dial_command)
68
+ @direction = :outbound
69
+ channel = dial_command.to || ''
70
+ channel.match(OUTBOUND_CHANNEL_MATCH) { |m| channel = m[:channel] }
71
+ params = { :async => true,
72
+ :context => REDIRECT_CONTEXT,
73
+ :exten => REDIRECT_EXTENSION,
74
+ :priority => REDIRECT_PRIORITY,
75
+ :channel => channel,
76
+ :callerid => dial_command.from
77
+ }
78
+ params[:variable] = variable_for_headers dial_command.headers
79
+ params[:timeout] = dial_command.timeout unless dial_command.timeout.nil?
80
+
81
+ originate_action = Adhearsion::Rayo::Component::Asterisk::AMI::Action.new :name => 'Originate',
82
+ :params => params
83
+ originate_action.request!
84
+ translator.async.execute_global_command originate_action
85
+ dial_command.response = Adhearsion::Rayo::Ref.new uri: id
86
+ end
87
+
88
+ def outbound?
89
+ direction == :outbound
90
+ end
91
+
92
+ def inbound?
93
+ direction == :inbound
94
+ end
95
+
96
+ def answered?
97
+ @answered
98
+ end
99
+
100
+ def send_progress
101
+ return if answered? || outbound? || @progress_sent
102
+ @progress_sent = true
103
+ execute_agi_command "EXEC Progress"
104
+ end
105
+
106
+ def channel=(other)
107
+ @channel = other
108
+ end
109
+
110
+ def process_ami_event(ami_event)
111
+ if Asterisk.event_passes_filter?(ami_event)
112
+ send_pb_event Adhearsion::Event::Asterisk::AMI.new(name: ami_event.name, headers: ami_event.headers)
113
+ end
114
+
115
+ case ami_event.name
116
+ when 'Hangup'
117
+ handle_hangup_event ami_event['Cause'].to_i, ami_event.best_time
118
+ when 'AsyncAGI'
119
+ component_for_command_id_handle ami_event
120
+
121
+ if @answered == false && ami_event['SubEvent'] == 'Start'
122
+ @answered = true
123
+ send_pb_event Adhearsion::Event::Answered.new(timestamp: ami_event.best_time)
124
+ end
125
+ when 'AsyncAGIStart'
126
+ component_for_command_id_handle ami_event
127
+
128
+ if @answered == false
129
+ @answered = true
130
+ send_pb_event Adhearsion::Event::Answered.new(timestamp: ami_event.best_time)
131
+ end
132
+ when 'AsyncAGIExec', 'AsyncAGIEnd'
133
+ component_for_command_id_handle ami_event
134
+ when 'Newstate'
135
+ case ami_event['ChannelState']
136
+ when '5'
137
+ send_pb_event Adhearsion::Event::Ringing.new(timestamp: ami_event.best_time)
138
+ end
139
+ when 'BridgeEnter'
140
+ if other_call_channel = translator.bridges.delete(ami_event['BridgeUniqueid'])
141
+ if other_call = translator.call_for_channel(other_call_channel)
142
+ join_command = other_call.pending_joins.delete channel
143
+ join_command.response = true if join_command
144
+
145
+ event = Adhearsion::Event::Joined.new call_uri: other_call.id, timestamp: ami_event.best_time
146
+ send_pb_event event
147
+
148
+ other_call_event = Adhearsion::Event::Joined.new call_uri: id, timestamp: ami_event.best_time
149
+ other_call_event.target_call_id = other_call.id
150
+ translator.handle_pb_event other_call_event
151
+ end
152
+ else
153
+ translator.bridges[ami_event['BridgeUniqueid']] = ami_event['Channel']
154
+ end
155
+ when 'BridgeLeave'
156
+ if other_call_channel = translator.bridges.delete(ami_event['BridgeUniqueid'] + '_leave')
157
+ if other_call = translator.call_for_channel(other_call_channel)
158
+ event = Adhearsion::Event::Unjoined.new call_uri: other_call.id, timestamp: ami_event.best_time
159
+ send_pb_event event
160
+
161
+ other_call_event = Adhearsion::Event::Unjoined.new call_uri: id, timestamp: ami_event.best_time
162
+ other_call_event.target_call_id = other_call.id
163
+ translator.handle_pb_event other_call_event
164
+ end
165
+ else
166
+ translator.bridges[ami_event['BridgeUniqueid'] + '_leave'] = ami_event['Channel']
167
+ end
168
+ when 'OriginateResponse'
169
+ if ami_event['Response'] == 'Failure' && ami_event['Uniqueid'] == '<null>'
170
+ send_end_event :error, nil, ami_event.best_time
171
+ end
172
+ when 'BridgeExec'
173
+ join_command = @pending_joins.delete ami_event['Channel1']
174
+ join_command ||= @pending_joins.delete ami_event['Channel2']
175
+ join_command.response = true if join_command
176
+ when 'Bridge'
177
+ other_call_channel = ([ami_event['Channel1'], ami_event['Channel2']] - [channel]).first
178
+ if other_call = translator.call_for_channel(other_call_channel)
179
+ event = case ami_event['Bridgestate']
180
+ when 'Link'
181
+ Adhearsion::Event::Joined.new call_uri: other_call.id, timestamp: ami_event.best_time
182
+ when 'Unlink'
183
+ Adhearsion::Event::Unjoined.new call_uri: other_call.id, timestamp: ami_event.best_time
184
+ end
185
+ send_pb_event event
186
+ end
187
+ when 'Unlink'
188
+ other_call_channel = ([ami_event['Channel1'], ami_event['Channel2']] - [channel]).first
189
+ if other_call = translator.call_for_channel(other_call_channel)
190
+ send_pb_event Adhearsion::Event::Unjoined.new(call_uri: other_call.id, timestamp: ami_event.best_time)
191
+ end
192
+ when 'VarSet'
193
+ @channel_variables[ami_event['Variable']] = ami_event['Value']
194
+ end
195
+ trigger_handler :ami, ami_event
196
+ end
197
+
198
+ def send_message(body)
199
+ execute_agi_command 'EXEC SendText', body
200
+ rescue
201
+ end
202
+
203
+ def execute_command(command)
204
+ if @block_commands
205
+ command.response = Adhearsion::ProtocolError.new.setup :item_not_found, "Could not find a call with ID #{id}", id
206
+ return
207
+ end
208
+ if command.component_id
209
+ if component = component_with_id(command.component_id)
210
+ component.execute_command command
211
+ else
212
+ command.response = Adhearsion::ProtocolError.new.setup :item_not_found, "Could not find a component with ID #{command.component_id} for call #{id}", id, command.component_id
213
+ end
214
+ end
215
+ case command
216
+ when Adhearsion::Rayo::Command::Accept
217
+ if outbound?
218
+ command.response = true
219
+ else
220
+ execute_agi_command 'EXEC RINGING'
221
+ command.response = true
222
+ end
223
+ when Adhearsion::Rayo::Command::Answer
224
+ execute_agi_command 'ANSWER'
225
+ @answered = true
226
+ command.response = true
227
+ when Adhearsion::Rayo::Command::Hangup
228
+ send_hangup_command
229
+ @hangup_cause = :hangup_command
230
+ command.response = true
231
+ when Adhearsion::Rayo::Command::Join
232
+ other_call = translator.call_with_id command.call_uri
233
+ if other_call
234
+ @pending_joins[other_call.channel] = command
235
+ execute_agi_command 'EXEC Bridge', "#{other_call.channel},F(#{REDIRECT_CONTEXT},#{REDIRECT_EXTENSION},#{REDIRECT_PRIORITY})"
236
+ else
237
+ command.response = Adhearsion::ProtocolError.new.setup :service_unavailable, "Could not find join party with address #{command.call_uri}", id
238
+ end
239
+ when Adhearsion::Rayo::Command::Unjoin
240
+ other_call = translator.call_with_id command.call_uri
241
+ redirect_back other_call
242
+ command.response = true
243
+ when Adhearsion::Rayo::Command::Reject
244
+ case command.reason
245
+ when :busy
246
+ execute_agi_command 'EXEC Busy'
247
+ when :decline
248
+ send_hangup_command 21
249
+ when :error
250
+ execute_agi_command 'EXEC Congestion'
251
+ else
252
+ execute_agi_command 'EXEC Congestion'
253
+ end
254
+ command.response = true
255
+ when Adhearsion::Rayo::Command::Redirect
256
+ execute_agi_command 'EXEC Transfer', command.to
257
+ status = channel_var 'TRANSFERSTATUS'
258
+ command.response = case status
259
+ when 'SUCCESS'
260
+ true
261
+ else
262
+ Adhearsion::ProtocolError.new.setup 'error', "TRANSFERSTATUS was #{status}", id
263
+ end
264
+ when Adhearsion::Rayo::Component::Asterisk::AGI::Command
265
+ execute_component Component::Asterisk::AGICommand, command
266
+ when Adhearsion::Rayo::Component::Output
267
+ execute_component Component::Output, command
268
+ when Adhearsion::Rayo::Component::Input
269
+ execute_component Component::Input, command
270
+ when Adhearsion::Rayo::Component::Prompt
271
+ component_class = case command.input.recognizer
272
+ when 'unimrcp'
273
+ case command.output.renderer
274
+ when 'unimrcp'
275
+ Component::MRCPPrompt
276
+ when 'asterisk'
277
+ Component::MRCPNativePrompt
278
+ else
279
+ raise InvalidCommandError, 'Invalid recognizer/renderer combination'
280
+ end
281
+ else
282
+ Component::ComposedPrompt
283
+ end
284
+ execute_component component_class, command
285
+ when Adhearsion::Rayo::Component::Record
286
+ execute_component Component::Record, command
287
+ else
288
+ command.response = Adhearsion::ProtocolError.new.setup 'command-not-acceptable', "Did not understand command for call #{id}", id
289
+ end
290
+ rescue InvalidCommandError => e
291
+ command.response = Adhearsion::ProtocolError.new.setup :invalid_command, e.message, id
292
+ rescue ChannelGoneError
293
+ command.response = Adhearsion::ProtocolError.new.setup :item_not_found, "Could not find a call with ID #{id}", id
294
+ rescue RubyAMI::Error => e
295
+ command.response = Adhearsion::ProtocolError.new.setup 'error', e.message, id
296
+ rescue Celluloid::DeadActorError
297
+ command.response = Adhearsion::ProtocolError.new.setup :item_not_found, "Could not find a component with ID #{command.component_id} for call #{id}", id, command.component_id
298
+ end
299
+
300
+ #
301
+ # @return [Hash] AGI result
302
+ #
303
+ # @raises RubyAMI::Error, ChannelGoneError
304
+ def execute_agi_command(command, *params)
305
+ agi = AGICommand.new Adhearsion.new_uuid, channel, command, *params
306
+ response = Celluloid::Future.new
307
+ register_tmp_handler :ami, [{name: 'AsyncAGI', [:[], 'SubEvent'] => 'Exec'}, {name: 'AsyncAGIExec'}], [{[:[], 'CommandID'] => agi.id}, {[:[], 'CommandId'] => agi.id}] do |event|
308
+ response.signal Celluloid::SuccessResponse.new(nil, event)
309
+ end
310
+ agi.execute @ami_client
311
+ event = response.value
312
+ return unless event
313
+ agi.parse_result event
314
+ end
315
+
316
+ def logger_id
317
+ "#{self.class}: #{id}"
318
+ end
319
+
320
+ def redirect_back(other_call = nil)
321
+ redirect_options = {
322
+ 'Channel' => channel,
323
+ 'Exten' => Asterisk::REDIRECT_EXTENSION,
324
+ 'Priority' => Asterisk::REDIRECT_PRIORITY,
325
+ 'Context' => Asterisk::REDIRECT_CONTEXT
326
+ }
327
+ redirect_options.merge!({
328
+ 'ExtraChannel' => other_call.channel,
329
+ 'ExtraExten' => Asterisk::REDIRECT_EXTENSION,
330
+ 'ExtraPriority' => Asterisk::REDIRECT_PRIORITY,
331
+ 'ExtraContext' => Asterisk::REDIRECT_CONTEXT
332
+ }) if other_call
333
+ send_ami_action 'Redirect', redirect_options
334
+ end
335
+
336
+ def handle_hangup_event(code = nil, timestamp = nil)
337
+ code ||= 16
338
+ reason = @hangup_cause || HANGUP_CAUSE_TO_END_REASON[code]
339
+ @block_commands = true
340
+ @components.each_pair do |id, component|
341
+ component.call_ended
342
+ end
343
+ send_end_event reason, code, timestamp
344
+ end
345
+
346
+ def after(*args, &block)
347
+ translator.after(*args, &block)
348
+ end
349
+
350
+ private
351
+
352
+ def fetch_channel_var(variable)
353
+ result = @ami_client.send_action 'GetVar', 'Channel' => channel, 'Variable' => variable
354
+ result['Value'] == '(null)' ? nil : result['Value']
355
+ end
356
+
357
+ def send_hangup_command(cause_code = 16)
358
+ send_ami_action 'Hangup', 'Channel' => channel, 'Cause' => cause_code
359
+ end
360
+
361
+ def send_ami_action(name, headers = {})
362
+ AMIErrorConverter.convert { @ami_client.send_action name, headers }
363
+ end
364
+
365
+ def send_end_event(reason, code = nil, timestamp = nil)
366
+ end_event = Adhearsion::Event::End.new(reason: reason, platform_code: code, timestamp: timestamp)
367
+ send_pb_event end_event
368
+ translator.deregister_call id, channel
369
+ end
370
+
371
+ def execute_component(type, command, options = {})
372
+ type.new(command, self).tap do |component|
373
+ register_component component
374
+ component.execute
375
+ end
376
+ end
377
+
378
+ def send_pb_event(event)
379
+ event.target_call_id = id
380
+ translator.handle_pb_event event
381
+ end
382
+
383
+ def offer_event
384
+ Adhearsion::Event::Offer.new :to => agi_env.values_at(:agi_dnid, :agi_extension).detect { |e| e && e != 'unknown' },
385
+ :from => "#{agi_env[:agi_calleridname]} <#{[agi_env[:agi_type], agi_env[:agi_callerid]].join('/')}>",
386
+ :headers => sip_headers
387
+ end
388
+
389
+ def sip_headers
390
+ agi_env.to_a.inject({}) do |accumulator, element|
391
+ accumulator['X-' + element[0].to_s] = element[1] || ''
392
+ accumulator
393
+ end
394
+ end
395
+
396
+ def component_for_command_id_handle(ami_event)
397
+ if component = component_with_id(ami_event['CommandID'] || ami_event['CommandId'])
398
+ component.handle_ami_event ami_event
399
+ end
400
+ end
401
+
402
+ def variable_for_headers(headers)
403
+ variables = { :adhearsion_call_id => id }
404
+ header_counter = 51
405
+ headers.each do |name, value|
406
+ variables["SIPADDHEADER#{header_counter}"] = "\"#{name}: #{value}\""
407
+ header_counter += 1
408
+ end
409
+ variables.inject([]) do |a, (k, v)|
410
+ a << "#{k}=#{v}"
411
+ end.join(',')
412
+ end
413
+ end
414
+ end
415
+ end
416
+ end
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+
3
+ require 'delegate'
4
+
5
+ module Adhearsion
6
+ module Translator
7
+ class Asterisk
8
+ class Channel < SimpleDelegator
9
+ NORMALIZATION_REGEXP = /^(?<prefix>Bridge\/)*(?<name>[^<>]*)(?<suffix><.*>)*$/.freeze
10
+
11
+ def self.new(other)
12
+ other.is_a?(self) ? other : super
13
+ end
14
+
15
+ def name
16
+ matchdata[:name]
17
+ end
18
+
19
+ def prefix
20
+ matchdata[:prefix]
21
+ end
22
+
23
+ def suffix
24
+ matchdata[:suffix]
25
+ end
26
+
27
+ def bridged?
28
+ @bridged ||= (prefix || suffix)
29
+ end
30
+
31
+ def to_s
32
+ __getobj__
33
+ end
34
+
35
+ private
36
+
37
+ def matchdata
38
+ @matchdata ||= __getobj__.match(NORMALIZATION_REGEXP)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end