punchblock 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. data/.document +5 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +3 -0
  4. data/Gemfile +2 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.markdown +31 -0
  7. data/Rakefile +23 -0
  8. data/assets/ozone/ask-1.0.xsd +56 -0
  9. data/assets/ozone/conference-1.0.xsd +17 -0
  10. data/assets/ozone/ozone-1.0.xsd +127 -0
  11. data/assets/ozone/say-1.0.xsd +24 -0
  12. data/assets/ozone/transfer-1.0.xsd +32 -0
  13. data/bin/punchblock-console +125 -0
  14. data/lib/punchblock/command/accept.rb +30 -0
  15. data/lib/punchblock/command/answer.rb +30 -0
  16. data/lib/punchblock/command/dial.rb +88 -0
  17. data/lib/punchblock/command/hangup.rb +25 -0
  18. data/lib/punchblock/command/join.rb +81 -0
  19. data/lib/punchblock/command/mute.rb +7 -0
  20. data/lib/punchblock/command/redirect.rb +49 -0
  21. data/lib/punchblock/command/reject.rb +61 -0
  22. data/lib/punchblock/command/unjoin.rb +50 -0
  23. data/lib/punchblock/command/unmute.rb +7 -0
  24. data/lib/punchblock/command.rb +16 -0
  25. data/lib/punchblock/command_node.rb +46 -0
  26. data/lib/punchblock/component/input.rb +320 -0
  27. data/lib/punchblock/component/output.rb +449 -0
  28. data/lib/punchblock/component/record.rb +216 -0
  29. data/lib/punchblock/component/tropo/ask.rb +197 -0
  30. data/lib/punchblock/component/tropo/conference.rb +328 -0
  31. data/lib/punchblock/component/tropo/say.rb +113 -0
  32. data/lib/punchblock/component/tropo/transfer.rb +178 -0
  33. data/lib/punchblock/component/tropo.rb +12 -0
  34. data/lib/punchblock/component.rb +73 -0
  35. data/lib/punchblock/connection.rb +209 -0
  36. data/lib/punchblock/core_ext/blather/stanza/presence.rb +11 -0
  37. data/lib/punchblock/core_ext/blather/stanza.rb +26 -0
  38. data/lib/punchblock/dsl.rb +46 -0
  39. data/lib/punchblock/event/answered.rb +7 -0
  40. data/lib/punchblock/event/complete.rb +65 -0
  41. data/lib/punchblock/event/dtmf.rb +19 -0
  42. data/lib/punchblock/event/end.rb +15 -0
  43. data/lib/punchblock/event/info.rb +15 -0
  44. data/lib/punchblock/event/joined.rb +50 -0
  45. data/lib/punchblock/event/offer.rb +29 -0
  46. data/lib/punchblock/event/ringing.rb +7 -0
  47. data/lib/punchblock/event/unjoined.rb +50 -0
  48. data/lib/punchblock/event.rb +16 -0
  49. data/lib/punchblock/generic_connection.rb +18 -0
  50. data/lib/punchblock/has_headers.rb +34 -0
  51. data/lib/punchblock/header.rb +47 -0
  52. data/lib/punchblock/media_container.rb +39 -0
  53. data/lib/punchblock/media_node.rb +17 -0
  54. data/lib/punchblock/protocol_error.rb +16 -0
  55. data/lib/punchblock/rayo_node.rb +88 -0
  56. data/lib/punchblock/ref.rb +26 -0
  57. data/lib/punchblock/version.rb +3 -0
  58. data/lib/punchblock.rb +42 -0
  59. data/log/.gitkeep +0 -0
  60. data/punchblock.gemspec +42 -0
  61. data/spec/punchblock/command/accept_spec.rb +13 -0
  62. data/spec/punchblock/command/answer_spec.rb +13 -0
  63. data/spec/punchblock/command/dial_spec.rb +54 -0
  64. data/spec/punchblock/command/hangup_spec.rb +13 -0
  65. data/spec/punchblock/command/join_spec.rb +21 -0
  66. data/spec/punchblock/command/mute_spec.rb +11 -0
  67. data/spec/punchblock/command/redirect_spec.rb +19 -0
  68. data/spec/punchblock/command/reject_spec.rb +43 -0
  69. data/spec/punchblock/command/unjoin_spec.rb +19 -0
  70. data/spec/punchblock/command/unmute_spec.rb +11 -0
  71. data/spec/punchblock/command_node_spec.rb +80 -0
  72. data/spec/punchblock/component/input_spec.rb +188 -0
  73. data/spec/punchblock/component/output_spec.rb +531 -0
  74. data/spec/punchblock/component/record_spec.rb +235 -0
  75. data/spec/punchblock/component/tropo/ask_spec.rb +183 -0
  76. data/spec/punchblock/component/tropo/conference_spec.rb +360 -0
  77. data/spec/punchblock/component/tropo/say_spec.rb +171 -0
  78. data/spec/punchblock/component/tropo/transfer_spec.rb +153 -0
  79. data/spec/punchblock/component_spec.rb +126 -0
  80. data/spec/punchblock/connection_spec.rb +194 -0
  81. data/spec/punchblock/event/answered_spec.rb +23 -0
  82. data/spec/punchblock/event/complete_spec.rb +80 -0
  83. data/spec/punchblock/event/dtmf_spec.rb +24 -0
  84. data/spec/punchblock/event/end_spec.rb +30 -0
  85. data/spec/punchblock/event/info_spec.rb +30 -0
  86. data/spec/punchblock/event/joined_spec.rb +32 -0
  87. data/spec/punchblock/event/offer_spec.rb +35 -0
  88. data/spec/punchblock/event/ringing_spec.rb +23 -0
  89. data/spec/punchblock/event/unjoined_spec.rb +32 -0
  90. data/spec/punchblock/header_spec.rb +44 -0
  91. data/spec/punchblock/protocol_error_spec.rb +9 -0
  92. data/spec/punchblock/ref_spec.rb +21 -0
  93. data/spec/spec_helper.rb +43 -0
  94. metadata +353 -0
@@ -0,0 +1,178 @@
1
+ module Punchblock
2
+ module Component
3
+ module Tropo
4
+ class Transfer < ComponentNode
5
+ register :transfer, :transfer
6
+
7
+ include HasHeaders
8
+
9
+ ##
10
+ # Creates an Rayo transfer command
11
+ #
12
+ # @param [Hash] options for transferring a call
13
+ # @option options [String, Array[String]] :to The destination(s) for the call transfer (ie - tel:+14155551212 or sip:you@sip.tropo.com). Can be an array to hunt.
14
+ # @option options [String] :from The caller ID for the call transfer (ie - tel:+14155551212 or sip:you@sip.tropo.com)
15
+ # @option options [String, Optional] :terminator The string key press required to abort the transfer.
16
+ # @option options [Integer, Optional] :timeout How long to wait - in seconds - for an answer, busy signal, or other event to occur.
17
+ # @option options [Boolean, Optional] :answer_on_media If set to true, the call will be considered "answered" and audio will begin playing as soon as media is received from the far end (ringing / busy signal / etc)
18
+ # @option options [Symbol, Optional] :media Rules for handling media. Can be :direct, where parties negotiate media directly, or :bridge where the media server will bridge audio, allowing media features like recording and ASR.
19
+ # @option options [Ring, Hash, Optional] :ring to play to the caller untill connected
20
+ #
21
+ # @return [Message::Transfer] an Rayo "transfer" message
22
+ #
23
+ # @example
24
+ # Transfer.new(:to => 'sip:you@yourdomain.com', :from => 'sip:myapp@mydomain.com', :terminator => '#').to_xml
25
+ #
26
+ # returns:
27
+ # <transfer xmlns="urn:xmpp:tropo:transfer:1" from="sip:myapp@mydomain.com" terminator="#">
28
+ # <to>sip:you@yourdomain.com</to>
29
+ # </transfer>
30
+ #
31
+ def self.new(options = {})
32
+ super().tap do |new_node|
33
+ options.each_pair { |k,v| new_node.send :"#{k}=", v }
34
+ end
35
+ end
36
+
37
+ ##
38
+ # @return [Array[String]] The destination(s) for the call transfer
39
+ #
40
+ def to
41
+ find('ns:to', :ns => self.class.registered_ns).map &:text
42
+ end
43
+
44
+ ##
45
+ # @param [String, Array[String]] :to The destination(s) for the call transfer (ie - tel:+14155551212 or sip:you@sip.tropo.com). Can be an array to hunt.
46
+ #
47
+ def to=(transfer_to)
48
+ find('//ns:to', :ns => self.class.registered_ns).each &:remove
49
+ if transfer_to
50
+ [transfer_to].flatten.each do |i|
51
+ to = RayoNode.new :to
52
+ to << i
53
+ self << to
54
+ end
55
+ end
56
+ end
57
+
58
+ ##
59
+ # @return [String] The caller ID for the call transfer
60
+ #
61
+ def from
62
+ read_attr :from
63
+ end
64
+
65
+ ##
66
+ # @param [String, Array[String]] :to The destination(s) for the call transfer (ie - tel:+14155551212 or sip:you@sip.tropo.com). Can be an array to hunt.
67
+ #
68
+ def from=(transfer_from)
69
+ write_attr :from, transfer_from
70
+ end
71
+
72
+ ##
73
+ # @return [String] The string key press required to abort the transfer.
74
+ #
75
+ def terminator
76
+ read_attr :terminator
77
+ end
78
+
79
+ ##
80
+ # @param [String] terminator The string key press required to abort the transfer.
81
+ #
82
+ def terminator=(terminator)
83
+ write_attr :terminator, terminator
84
+ end
85
+
86
+ ##
87
+ # @return [Integer] How long to wait - in seconds - for an answer, busy signal, or other event to occur.
88
+ #
89
+ def timeout
90
+ read_attr :timeout, :to_i
91
+ end
92
+
93
+ ##
94
+ # @param [Integer] timeout How long to wait - in seconds - for an answer, busy signal, or other event to occur.
95
+ #
96
+ def timeout=(timeout)
97
+ write_attr :timeout, timeout
98
+ end
99
+
100
+ ##
101
+ # @return [Boolean] If true, the call will be considered "answered" and audio will begin playing as soon as media is received from the far end (ringing / busy signal / etc)
102
+ #
103
+ def answer_on_media
104
+ read_attr('answer-on-media') == 'true'
105
+ end
106
+
107
+ ##
108
+ # @param [Boolean] aom If set to true, the call will be considered "answered" and audio will begin playing as soon as media is received from the far end (ringing / busy signal / etc)
109
+ #
110
+ def answer_on_media=(aom)
111
+ write_attr 'answer-on-media', aom.to_s
112
+ end
113
+
114
+ ##
115
+ # @return [Symbol] Rules for handling media. Can be :direct, where parties negotiate media directly, or :bridge where the media server will bridge audio, allowing media features like recording and ASR.
116
+ #
117
+ def media
118
+ read_attr 'media', :to_sym
119
+ end
120
+
121
+ ##
122
+ # @param [Symbol] media Rules for handling media. Can be :direct, where parties negotiate media directly, or :bridge where the media server will bridge audio, allowing media features like recording and ASR.
123
+ #
124
+ def media=(media)
125
+ write_attr 'media', media
126
+ end
127
+
128
+ ##
129
+ # @return [Ring] the ringer to play to the caller while transferring
130
+ #
131
+ def ring
132
+ node = find_first '//ns:ring', :ns => self.registered_ns
133
+ Ring.new node if node
134
+ end
135
+
136
+ ##
137
+ # @param [Hash] ring
138
+ # @option ring [String] :text Text to speak to the caller as an announcement
139
+ # @option ring [String] :url URL to play to the caller as an announcement
140
+ #
141
+ def ring=(ring)
142
+ ring = Ring.new(ring) unless ring.is_a?(Ring)
143
+ self << ring
144
+ end
145
+
146
+ class Ring < Say
147
+ register :ring, :transfer
148
+ end
149
+
150
+ def inspect_attributes # :nodoc:
151
+ [:to, :from, :terminator, :timeout, :answer_on_media] + super
152
+ end
153
+
154
+ class Complete
155
+ class Success < Event::Complete::Reason
156
+ register :success, :transfer_complete
157
+ end
158
+
159
+ class Timeout < Event::Complete::Reason
160
+ register :timeout, :transfer_complete
161
+ end
162
+
163
+ class Terminator < Event::Complete::Reason
164
+ register :terminator, :transfer_complete
165
+ end
166
+
167
+ class Busy < Event::Complete::Reason
168
+ register :busy, :transfer_complete
169
+ end
170
+
171
+ class Reject < Event::Complete::Reason
172
+ register :reject, :transfer_complete
173
+ end
174
+ end
175
+ end # Transfer
176
+ end # Tropo
177
+ end # Command
178
+ end # Punchblock
@@ -0,0 +1,12 @@
1
+ module Punchblock
2
+ module Component
3
+ module Tropo
4
+ extend ActiveSupport::Autoload
5
+
6
+ autoload :Ask
7
+ autoload :Conference
8
+ autoload :Say
9
+ autoload :Transfer
10
+ end
11
+ end # Command
12
+ end # Punchblock
@@ -0,0 +1,73 @@
1
+ module Punchblock
2
+ module Component
3
+ extend ActiveSupport::Autoload
4
+
5
+ autoload :Input
6
+ autoload :Output
7
+ autoload :Record
8
+ autoload :Tropo
9
+
10
+ InvalidActionError = Class.new StandardError
11
+
12
+ class ComponentNode < CommandNode
13
+ attr_accessor :event_queue, :complete_event, :event_callback
14
+
15
+ def initialize(*args)
16
+ super
17
+ @event_queue = Queue.new
18
+ @complete_event = FutureResource.new
19
+ @event_callback = nil
20
+ end
21
+
22
+ def add_event(event)
23
+ event.original_component = self
24
+ transition_state! event
25
+ complete_event.resource = event if event.is_a? Event::Complete
26
+ if event_callback.respond_to?(:call)
27
+ add_event_to_queue = event_callback.call event
28
+ end
29
+ @event_queue << event unless add_event_to_queue
30
+ end
31
+
32
+ def transition_state!(event)
33
+ complete! if event.is_a? Event::Complete
34
+ end
35
+
36
+ def write_action(action)
37
+ connection.write call_id, action, component_id
38
+ end
39
+
40
+ def response=(other)
41
+ super
42
+ if other.is_a?(Blather::Stanza::Iq)
43
+ ref = other.rayo_node
44
+ if ref.is_a?(Ref)
45
+ @component_id = ref.id
46
+ @connection.record_command_id_for_iq_id @component_id, other.id
47
+ end
48
+ end
49
+ end
50
+
51
+ ##
52
+ # Create an Rayo stop message
53
+ #
54
+ # @return [Stop] an Rayo stop message
55
+ #
56
+ def stop_action
57
+ Stop.new :component_id => component_id, :call_id => call_id
58
+ end
59
+
60
+ ##
61
+ # Sends an Rayo stop message for the current component
62
+ #
63
+ def stop!(options = {})
64
+ raise InvalidActionError, "Cannot stop a #{self.class.name.split("::").last} that is not executing" unless executing?
65
+ stop_action.tap { |action| write_action action }
66
+ end
67
+ end
68
+
69
+ class Stop < CommandNode # :nodoc:
70
+ register :stop, :core
71
+ end
72
+ end # Component
73
+ end # Punchblock
@@ -0,0 +1,209 @@
1
+ %w{
2
+ timeout
3
+ blather/client/dsl
4
+ punchblock/core_ext/blather/stanza
5
+ punchblock/core_ext/blather/stanza/presence
6
+ }.each { |f| require f }
7
+
8
+ module Punchblock
9
+ class Connection < GenericConnection
10
+ include Blather::DSL
11
+
12
+ ##
13
+ # Initialize the required connection attributes
14
+ #
15
+ # @param [Hash] options
16
+ # @option options [String] :username client JID
17
+ # @option options [String] :password XMPP password
18
+ # @option options [String] :rayo_domain the domain on which Rayo is running
19
+ # @option options [Logger] :wire_logger to which all XMPP transactions will be logged
20
+ # @option options [Boolean, Optional] :auto_reconnect whether or not to auto reconnect
21
+ # @option options [Numeric, Optional] :write_timeout for which to wait on a command response
22
+ # @option options [Numeric, nil, Optional] :ping_period interval in seconds on which to ping the server. Nil or false to disable
23
+ #
24
+ def initialize(options = {})
25
+ super
26
+ raise ArgumentError unless @username = options.delete(:username)
27
+ raise ArgumentError unless options.has_key? :password
28
+ @rayo_domain = options[:rayo_domain] || Blather::JID.new(@username).domain
29
+
30
+ setup @username, options.delete(:password)
31
+
32
+ @callmap = {} # This hash maps call IDs to their XMPP domain.
33
+
34
+ @component_id_to_iq_id = {}
35
+ @iq_id_to_command = {}
36
+
37
+ @auto_reconnect = !!options[:auto_reconnect]
38
+ @reconnect_attempts = 0
39
+
40
+ @write_timeout = options[:write_timeout] || 3
41
+
42
+ @ping_period = options.has_key?(:ping_period) ? options[:ping_period] : 60
43
+
44
+ Blather.logger = options.delete(:wire_logger) if options.has_key?(:wire_logger)
45
+ end
46
+
47
+ ##
48
+ # Write a command to the Rayo server for a particular call
49
+ #
50
+ # @param [String] call the call ID on which to act
51
+ # @param [CommandNode] cmd the command to execute on the call
52
+ # @param [String, Optional] component_id the component_id on which to execute
53
+ #
54
+ # @raise Exception if there is a server-side error
55
+ #
56
+ # @return true
57
+ #
58
+ def write(call_id, cmd, component_id = nil)
59
+ async_write call_id, cmd, component_id
60
+ cmd.response(@write_timeout).tap { |result| raise result if result.is_a? Exception }
61
+ end
62
+
63
+ ##
64
+ # @return [Queue] Pop this queue to determine result of command execution. Will be true or an exception
65
+ def async_write(call_id, cmd, component_id = nil)
66
+ iq = prep_command_for_execution call_id, cmd, component_id
67
+ write_to_stream iq
68
+ cmd.request!
69
+ end
70
+
71
+ def prep_command_for_execution(call_id, cmd, component_id = nil)
72
+ cmd.connection = self
73
+ cmd.call_id = call_id
74
+ jid = cmd.is_a?(Command::Dial) ? @rayo_domain : "#{call_id}@#{@callmap[call_id]}"
75
+ jid << "/#{component_id}" if component_id
76
+ create_iq(jid).tap do |iq|
77
+ @logger.debug "Sending IQ ID #{iq.id} #{cmd.inspect} to #{jid}" if @logger
78
+ iq << cmd
79
+ @iq_id_to_command[iq.id] = cmd
80
+ end
81
+ end
82
+
83
+ ##
84
+ # Fire up the connection
85
+ #
86
+ def run
87
+ register_handlers
88
+ connect
89
+ end
90
+
91
+ def connect
92
+ begin
93
+ EM.run { client.run }
94
+ rescue Blather::SASLError, Blather::StreamError => e
95
+ raise ProtocolError.new(e.class.to_s, e.message)
96
+ end
97
+ end
98
+
99
+ def stop
100
+ @reconnect_attempts = nil
101
+ client.close
102
+ end
103
+
104
+ def connected?
105
+ client.connected?
106
+ end
107
+
108
+ ##
109
+ #
110
+ # Get the original command issued by command ID
111
+ #
112
+ # @param [String] component_id
113
+ #
114
+ # @return [RayoNode]
115
+ #
116
+ def original_component_from_id(component_id)
117
+ @iq_id_to_command[@component_id_to_iq_id[component_id]]
118
+ end
119
+
120
+ def record_command_id_for_iq_id(command_id, iq_id)
121
+ @component_id_to_iq_id[command_id] = iq_id
122
+ end
123
+
124
+ private
125
+
126
+ def handle_presence(p)
127
+ throw :pass unless p.rayo_event?
128
+ @logger.info "Receiving event for call ID #{p.call_id}" if @logger
129
+ @callmap[p.call_id] = p.from.domain
130
+ @logger.debug p.inspect if @logger
131
+ event = p.event
132
+ event.connection = self
133
+ if event.source
134
+ event.source.add_event event
135
+ else
136
+ @event_queue.push event
137
+ end
138
+ end
139
+
140
+ def handle_iq_result(iq)
141
+ # FIXME: Do we need to raise a warning if the domain changes?
142
+ throw :pass unless command = @iq_id_to_command[iq.id]
143
+ @callmap[iq.from.node] = iq.from.domain
144
+ @logger.debug "Command #{iq.id} completed successfully" if @logger
145
+ command.response = iq
146
+ end
147
+
148
+ def handle_error(iq)
149
+ e = Blather::StanzaError.import iq
150
+
151
+ protocol_error = ProtocolError.new e.name, e.text, iq.call_id, iq.component_id
152
+
153
+ throw :pass unless command = @iq_id_to_command[iq.id]
154
+
155
+ command.response = protocol_error
156
+ end
157
+
158
+ def register_handlers
159
+ # Push a message to the queue and the log that we connected
160
+ when_ready do
161
+ @event_queue.push connected
162
+ @logger.info "Connected to XMPP as #{@username}" if @logger
163
+ @reconnect_attempts = 0
164
+ @rayo_ping = EM::PeriodicTimer.new(@ping_period) { ping_rayo } if @ping_period
165
+ end
166
+
167
+ disconnected do
168
+ @rayo_ping.cancel if @rayo_ping
169
+ if @auto_reconnect && @reconnect_attempts
170
+ timer = 30 * 2 ** @reconnect_attempts
171
+ @logger.warn "XMPP disconnected. Tried to reconnect #{@reconnect_attempts} times. Reconnecting in #{timer}s." if @logger
172
+ sleep timer
173
+ @logger.info "Trying to reconnect..." if @logger
174
+ @reconnect_attempts += 1
175
+ connect
176
+ end
177
+ end
178
+
179
+ # Read/handle call control messages. These are mostly just acknowledgement of commands
180
+ iq :result? do |msg|
181
+ handle_iq_result msg
182
+ end
183
+
184
+ # Read/handle error IQs
185
+ iq :error? do |e|
186
+ handle_error e
187
+ end
188
+
189
+ # Read/handle presence requests. This is how we get events.
190
+ presence do |msg|
191
+ handle_presence msg
192
+ end
193
+ end
194
+
195
+ def ping_rayo
196
+ client.write_with_handler Blather::Stanza::Iq::Ping.new(:set, @rayo_domain) do |response|
197
+ begin
198
+ handle_error response if response.is_a? Blather::BlatherError
199
+ rescue ProtocolError => e
200
+ raise e unless e.name == :feature_not_implemented
201
+ end
202
+ end
203
+ end
204
+
205
+ def create_iq(jid = nil)
206
+ Blather::Stanza::Iq.new :set, jid || @call_id
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,11 @@
1
+ module Blather
2
+ class Stanza
3
+ class Presence
4
+ alias :event :rayo_node
5
+
6
+ def rayo_event?
7
+ event.is_a? Punchblock::Event
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,26 @@
1
+ module Blather
2
+ class Stanza
3
+ ##
4
+ # @return [Punchblock::RayoNode] a child of RayoNode
5
+ # representing the Rayo command/event contained within the stanza
6
+ #
7
+ def rayo_node
8
+ first_child = children.first
9
+ Punchblock::RayoNode.import first_child, call_id, component_id if first_child
10
+ end
11
+
12
+ ##
13
+ # @return [String] the call ID this stanza applies to
14
+ #
15
+ def call_id
16
+ from.node
17
+ end
18
+
19
+ ##
20
+ # @return [String] the command ID this stanza applies to
21
+ #
22
+ def component_id
23
+ from.resource
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,46 @@
1
+ ##
2
+ # DO NOT USE THIS API!
3
+ # This file is temporary, to help make testing Punchblock easier.
4
+ # THIS IS IMPERMANENT AND WILL DISAPPEAR
5
+ module Punchblock
6
+ class DSL
7
+ def initialize(protocol, call, queue) # :nodoc:
8
+ @protocol, @call, @queue = protocol, call, queue
9
+ end
10
+
11
+ def accept # :nodoc:
12
+ write @protocol.class::Command::Accept.new
13
+ end
14
+
15
+ def answer # :nodoc:
16
+ write @protocol.class::Command::Answer.new
17
+ end
18
+
19
+ def hangup # :nodoc:
20
+ write @protocol.class::Command::Hangup.new
21
+ end
22
+
23
+ def reject(reason = nil) # :nodoc:
24
+ write @protocol.class::Command::Reject.new(:reason => reason)
25
+ end
26
+
27
+ def redirect(dest) # :nodoc:
28
+ write @protocol.class::Command::Redirect.new(:to => dest)
29
+ end
30
+
31
+ def record(options = {})
32
+ write @protocol.class::Component::Record.new(options)
33
+ end
34
+
35
+ def say(string, type = :text) # :nodoc:
36
+ write @protocol.class::Component::Tropo::Say.new(type => string)
37
+ puts "Waiting on the queue..."
38
+ response = @queue.pop
39
+ # TODO: Error handling
40
+ end
41
+
42
+ def write(msg) # :nodoc:
43
+ @protocol.write @call, msg
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,7 @@
1
+ module Punchblock
2
+ class Event
3
+ class Answered < Event
4
+ register :answered, :core
5
+ end # End
6
+ end # Event
7
+ end # Punchblock
@@ -0,0 +1,65 @@
1
+ module Punchblock
2
+ class Event
3
+ class Complete < Event
4
+ # TODO: Validate response and return response type.
5
+ # -----
6
+ # <complete xmlns="urn:xmpp:rayo:ext:1"/>
7
+
8
+ register :complete, :ext
9
+
10
+ def reason
11
+ element = find_first('*')
12
+ if element
13
+ RayoNode.import(element).tap do |reason|
14
+ reason.call_id = call_id
15
+ reason.component_id = component_id
16
+ end
17
+ end
18
+ end
19
+
20
+ def recording
21
+ element = find_first('//ns:recording', :ns => RAYO_NAMESPACES[:record_complete])
22
+ if element
23
+ RayoNode.import(element).tap do |recording|
24
+ recording.call_id = call_id
25
+ recording.component_id = component_id
26
+ end
27
+ end
28
+ end
29
+
30
+ def inspect_attributes # :nodoc:
31
+ [:reason, :recording] + super
32
+ end
33
+
34
+ class Reason < RayoNode
35
+ def name
36
+ super.to_sym
37
+ end
38
+
39
+ def inspect_attributes # :nodoc:
40
+ [:name] + super
41
+ end
42
+ end
43
+
44
+ class Stop < Reason
45
+ register :stop, :ext_complete
46
+ end
47
+
48
+ class Hangup < Reason
49
+ register :hangup, :ext_complete
50
+ end
51
+
52
+ class Error < Reason
53
+ register :error, :ext_complete
54
+
55
+ def details
56
+ text.strip
57
+ end
58
+
59
+ def inspect_attributes # :nodoc:
60
+ [:details] + super
61
+ end
62
+ end
63
+ end # Complete
64
+ end
65
+ end # Punchblock
@@ -0,0 +1,19 @@
1
+ module Punchblock
2
+ class Event
3
+ class DTMF < Event
4
+ register :dtmf, :core
5
+
6
+ def signal
7
+ read_attr :signal
8
+ end
9
+
10
+ def signal=(other)
11
+ write_attr :signal, other
12
+ end
13
+
14
+ def inspect_attributes # :nodoc:
15
+ [:signal] + super
16
+ end
17
+ end # End
18
+ end
19
+ end # Punchblock
@@ -0,0 +1,15 @@
1
+ module Punchblock
2
+ class Event
3
+ class End < Event
4
+ register :end, :core
5
+
6
+ def reason
7
+ children.select { |c| c.is_a? Nokogiri::XML::Element }.first.name.to_sym
8
+ end
9
+
10
+ def inspect_attributes # :nodoc:
11
+ [:reason] + super
12
+ end
13
+ end # End
14
+ end
15
+ end # Punchblock
@@ -0,0 +1,15 @@
1
+ module Punchblock
2
+ class Event
3
+ class Info < Event
4
+ register :info, :core
5
+
6
+ def event_name
7
+ children.select { |c| c.is_a? Nokogiri::XML::Element }.first.name.to_sym
8
+ end
9
+
10
+ def inspect_attributes # :nodoc:
11
+ [:event_name] + super
12
+ end
13
+ end # Info
14
+ end
15
+ end # Punchblock