tp-blather 0.8.2

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 (150) hide show
  1. data/.autotest +13 -0
  2. data/.gemtest +0 -0
  3. data/.gitignore +19 -0
  4. data/.rspec +3 -0
  5. data/.travis.yml +8 -0
  6. data/CHANGELOG.md +249 -0
  7. data/Gemfile +4 -0
  8. data/Guardfile +5 -0
  9. data/LICENSE +22 -0
  10. data/README.md +413 -0
  11. data/Rakefile +20 -0
  12. data/TODO.md +2 -0
  13. data/blather.gemspec +51 -0
  14. data/examples/certs/README +20 -0
  15. data/examples/certs/ca-bundle.crt +3987 -0
  16. data/examples/echo.rb +19 -0
  17. data/examples/execute.rb +17 -0
  18. data/examples/ping_pong.rb +38 -0
  19. data/examples/print_hierarchy.rb +77 -0
  20. data/examples/rosterprint.rb +15 -0
  21. data/examples/stream_only.rb +28 -0
  22. data/examples/trusted_echo.rb +21 -0
  23. data/examples/xmpp4r/echo.rb +36 -0
  24. data/lib/blather.rb +112 -0
  25. data/lib/blather/cert_store.rb +53 -0
  26. data/lib/blather/client.rb +95 -0
  27. data/lib/blather/client/client.rb +345 -0
  28. data/lib/blather/client/dsl.rb +320 -0
  29. data/lib/blather/client/dsl/pubsub.rb +174 -0
  30. data/lib/blather/core_ext/eventmachine.rb +125 -0
  31. data/lib/blather/core_ext/ipaddr.rb +20 -0
  32. data/lib/blather/errors.rb +69 -0
  33. data/lib/blather/errors/sasl_error.rb +44 -0
  34. data/lib/blather/errors/stanza_error.rb +110 -0
  35. data/lib/blather/errors/stream_error.rb +84 -0
  36. data/lib/blather/file_transfer.rb +107 -0
  37. data/lib/blather/file_transfer/ibb.rb +68 -0
  38. data/lib/blather/file_transfer/s5b.rb +114 -0
  39. data/lib/blather/jid.rb +141 -0
  40. data/lib/blather/roster.rb +118 -0
  41. data/lib/blather/roster_item.rb +146 -0
  42. data/lib/blather/stanza.rb +167 -0
  43. data/lib/blather/stanza/disco.rb +32 -0
  44. data/lib/blather/stanza/disco/capabilities.rb +161 -0
  45. data/lib/blather/stanza/disco/disco_info.rb +205 -0
  46. data/lib/blather/stanza/disco/disco_items.rb +134 -0
  47. data/lib/blather/stanza/iq.rb +144 -0
  48. data/lib/blather/stanza/iq/command.rb +339 -0
  49. data/lib/blather/stanza/iq/ibb.rb +86 -0
  50. data/lib/blather/stanza/iq/ping.rb +50 -0
  51. data/lib/blather/stanza/iq/query.rb +53 -0
  52. data/lib/blather/stanza/iq/roster.rb +185 -0
  53. data/lib/blather/stanza/iq/s5b.rb +208 -0
  54. data/lib/blather/stanza/iq/si.rb +415 -0
  55. data/lib/blather/stanza/iq/vcard.rb +149 -0
  56. data/lib/blather/stanza/message.rb +428 -0
  57. data/lib/blather/stanza/message/muc_user.rb +119 -0
  58. data/lib/blather/stanza/muc/muc_user_base.rb +54 -0
  59. data/lib/blather/stanza/presence.rb +172 -0
  60. data/lib/blather/stanza/presence/c.rb +100 -0
  61. data/lib/blather/stanza/presence/muc.rb +35 -0
  62. data/lib/blather/stanza/presence/muc_user.rb +147 -0
  63. data/lib/blather/stanza/presence/status.rb +218 -0
  64. data/lib/blather/stanza/presence/subscription.rb +100 -0
  65. data/lib/blather/stanza/pubsub.rb +119 -0
  66. data/lib/blather/stanza/pubsub/affiliations.rb +79 -0
  67. data/lib/blather/stanza/pubsub/create.rb +65 -0
  68. data/lib/blather/stanza/pubsub/errors.rb +18 -0
  69. data/lib/blather/stanza/pubsub/event.rb +139 -0
  70. data/lib/blather/stanza/pubsub/items.rb +103 -0
  71. data/lib/blather/stanza/pubsub/publish.rb +103 -0
  72. data/lib/blather/stanza/pubsub/retract.rb +92 -0
  73. data/lib/blather/stanza/pubsub/subscribe.rb +68 -0
  74. data/lib/blather/stanza/pubsub/subscription.rb +135 -0
  75. data/lib/blather/stanza/pubsub/subscriptions.rb +83 -0
  76. data/lib/blather/stanza/pubsub/unsubscribe.rb +84 -0
  77. data/lib/blather/stanza/pubsub_owner.rb +51 -0
  78. data/lib/blather/stanza/pubsub_owner/delete.rb +52 -0
  79. data/lib/blather/stanza/pubsub_owner/purge.rb +52 -0
  80. data/lib/blather/stanza/x.rb +416 -0
  81. data/lib/blather/stream.rb +266 -0
  82. data/lib/blather/stream/client.rb +32 -0
  83. data/lib/blather/stream/component.rb +39 -0
  84. data/lib/blather/stream/features.rb +70 -0
  85. data/lib/blather/stream/features/register.rb +38 -0
  86. data/lib/blather/stream/features/resource.rb +63 -0
  87. data/lib/blather/stream/features/sasl.rb +190 -0
  88. data/lib/blather/stream/features/session.rb +45 -0
  89. data/lib/blather/stream/features/tls.rb +29 -0
  90. data/lib/blather/stream/parser.rb +102 -0
  91. data/lib/blather/version.rb +3 -0
  92. data/lib/blather/xmpp_node.rb +94 -0
  93. data/spec/blather/client/client_spec.rb +687 -0
  94. data/spec/blather/client/dsl/pubsub_spec.rb +492 -0
  95. data/spec/blather/client/dsl_spec.rb +266 -0
  96. data/spec/blather/errors/sasl_error_spec.rb +33 -0
  97. data/spec/blather/errors/stanza_error_spec.rb +129 -0
  98. data/spec/blather/errors/stream_error_spec.rb +108 -0
  99. data/spec/blather/errors_spec.rb +33 -0
  100. data/spec/blather/file_transfer_spec.rb +135 -0
  101. data/spec/blather/jid_spec.rb +87 -0
  102. data/spec/blather/roster_item_spec.rb +134 -0
  103. data/spec/blather/roster_spec.rb +107 -0
  104. data/spec/blather/stanza/discos/disco_info_spec.rb +247 -0
  105. data/spec/blather/stanza/discos/disco_items_spec.rb +154 -0
  106. data/spec/blather/stanza/iq/command_spec.rb +206 -0
  107. data/spec/blather/stanza/iq/ibb_spec.rb +124 -0
  108. data/spec/blather/stanza/iq/ping_spec.rb +45 -0
  109. data/spec/blather/stanza/iq/query_spec.rb +64 -0
  110. data/spec/blather/stanza/iq/roster_spec.rb +139 -0
  111. data/spec/blather/stanza/iq/s5b_spec.rb +57 -0
  112. data/spec/blather/stanza/iq/si_spec.rb +98 -0
  113. data/spec/blather/stanza/iq/vcard_spec.rb +93 -0
  114. data/spec/blather/stanza/iq_spec.rb +61 -0
  115. data/spec/blather/stanza/message/muc_user_spec.rb +152 -0
  116. data/spec/blather/stanza/message_spec.rb +282 -0
  117. data/spec/blather/stanza/presence/c_spec.rb +56 -0
  118. data/spec/blather/stanza/presence/muc_spec.rb +37 -0
  119. data/spec/blather/stanza/presence/muc_user_spec.rb +83 -0
  120. data/spec/blather/stanza/presence/status_spec.rb +144 -0
  121. data/spec/blather/stanza/presence/subscription_spec.rb +102 -0
  122. data/spec/blather/stanza/presence_spec.rb +125 -0
  123. data/spec/blather/stanza/pubsub/affiliations_spec.rb +57 -0
  124. data/spec/blather/stanza/pubsub/create_spec.rb +56 -0
  125. data/spec/blather/stanza/pubsub/event_spec.rb +98 -0
  126. data/spec/blather/stanza/pubsub/items_spec.rb +79 -0
  127. data/spec/blather/stanza/pubsub/publish_spec.rb +83 -0
  128. data/spec/blather/stanza/pubsub/retract_spec.rb +75 -0
  129. data/spec/blather/stanza/pubsub/subscribe_spec.rb +61 -0
  130. data/spec/blather/stanza/pubsub/subscription_spec.rb +97 -0
  131. data/spec/blather/stanza/pubsub/subscriptions_spec.rb +59 -0
  132. data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +74 -0
  133. data/spec/blather/stanza/pubsub_owner/delete_spec.rb +50 -0
  134. data/spec/blather/stanza/pubsub_owner/purge_spec.rb +50 -0
  135. data/spec/blather/stanza/pubsub_owner_spec.rb +27 -0
  136. data/spec/blather/stanza/pubsub_spec.rb +68 -0
  137. data/spec/blather/stanza/x_spec.rb +231 -0
  138. data/spec/blather/stanza_spec.rb +134 -0
  139. data/spec/blather/stream/client_spec.rb +1090 -0
  140. data/spec/blather/stream/component_spec.rb +108 -0
  141. data/spec/blather/stream/parser_spec.rb +152 -0
  142. data/spec/blather/stream/ssl_spec.rb +32 -0
  143. data/spec/blather/xmpp_node_spec.rb +47 -0
  144. data/spec/blather_spec.rb +34 -0
  145. data/spec/fixtures/pubsub.rb +311 -0
  146. data/spec/spec_helper.rb +17 -0
  147. data/yard/templates/default/class/html/handlers.erb +18 -0
  148. data/yard/templates/default/class/setup.rb +10 -0
  149. data/yard/templates/default/class/text/handlers.erb +1 -0
  150. metadata +459 -0
@@ -0,0 +1,345 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. blather])
2
+
3
+ module Blather
4
+ # # Blather Client
5
+ #
6
+ # Blather's Client class provides a set of helpers for working with common
7
+ # XMPP tasks such as setting up and starting the connection, settings
8
+ # status, registering and dispatching filters and handlers and roster
9
+ # management.
10
+ #
11
+ # Client can be used separately from the DSL if you'd like to implement your
12
+ # own DSL Here's the echo example using the client without the DSL:
13
+ #
14
+ # require 'blather/client/client'
15
+ # client = Client.setup 'echo@jabber.local', 'echo'
16
+ #
17
+ # client.register_handler(:ready) do
18
+ # puts "Connected ! send messages to #{client.jid.stripped}."
19
+ # end
20
+ #
21
+ # client.register_handler :subscription, :request? do |s|
22
+ # client.write s.approve!
23
+ # end
24
+ #
25
+ # client.register_handler :message, :chat?, :body => 'exit' do |m|
26
+ # client.write Blather::Stanza::Message.new(m.from, 'Exiting...')
27
+ # client.close
28
+ # end
29
+ #
30
+ # client.register_handler :message, :chat?, :body do |m|
31
+ # client.write Blather::Stanza::Message.new(m.from, "You sent: #{m.body}")
32
+ # end
33
+ #
34
+ class Client
35
+ attr_reader :jid,
36
+ :roster,
37
+ :caps
38
+
39
+ # Create a new client and set it up
40
+ #
41
+ # @param [Blather::JID, #to_s] jid the JID to authorize with
42
+ # @param [String] password the password to authorize with
43
+ # @param [String] host if this isn't set it'll be resolved off the JID's
44
+ # domain
45
+ # @param [Fixnum, String] port the port to connect to.
46
+ #
47
+ # @return [Blather::Client]
48
+ def self.setup(jid, password, host = nil, port = nil, certs = nil, connect_timeout = nil)
49
+ self.new.setup(jid, password, host, port, certs, connect_timeout)
50
+ end
51
+
52
+ def initialize # @private
53
+ @state = :initializing
54
+
55
+ @status = Stanza::Presence::Status.new
56
+ @handlers = {}
57
+ @tmp_handlers = {}
58
+ @filters = {:before => [], :after => []}
59
+ @roster = Roster.new self
60
+ @caps = Stanza::Capabilities.new
61
+
62
+ @handler_queue = GirlFriday::WorkQueue.new :handle_stanza, :size => 5 do |stanza|
63
+ handle_data stanza
64
+ end
65
+
66
+ setup_initial_handlers
67
+ end
68
+
69
+ # Check whether the client is currently connected.
70
+ def connected?
71
+ setup? && !@stream.nil? && !@stream.stopped?
72
+ end
73
+
74
+ # Get the current status. Taken from the `state` attribute of Status
75
+ def status
76
+ @status.state
77
+ end
78
+
79
+ # Set the status. Status can be set with either a single value or an array
80
+ # containing
81
+ #
82
+ # [state, message, to].
83
+ def status=(state)
84
+ state, msg, to = state
85
+
86
+ status = Stanza::Presence::Status.new state, msg
87
+ status.to = to
88
+ @status = status unless to
89
+
90
+ write status
91
+ end
92
+
93
+ # Start the connection.
94
+ #
95
+ # The stream type used is based on the JID. If a node exists it uses
96
+ # Blather::Stream::Client otherwise Blather::Stream::Component
97
+ def run
98
+ raise 'not setup!' unless setup?
99
+ klass = @setup[0].node ? Blather::Stream::Client : Blather::Stream::Component
100
+ klass.start self, *@setup
101
+ end
102
+ alias_method :connect, :run
103
+
104
+ # Register a filter to be run before or after the handler chain is run.
105
+ #
106
+ # @param [<:before, :after>] type the filter type
107
+ # @param [Symbol, nil] handler set the filter on a specific handler
108
+ # @param [guards] guards take a look at the guards documentation
109
+ # @yield [Blather::Stanza] stanza the incomming stanza
110
+ def register_filter(type, handler = nil, *guards, &filter)
111
+ unless [:before, :after].include?(type)
112
+ raise "Invalid filter: #{type}. Must be :before or :after"
113
+ end
114
+ @filters[type] << [guards, handler, filter]
115
+ end
116
+
117
+ # Register a temporary handler. Temporary handlers are based on the ID of
118
+ # the JID and live only until a stanza with said ID is received.
119
+ #
120
+ # @param [#to_s] id the ID of the stanza that should be handled
121
+ # @yield [Blather::Stanza] stanza the incomming stanza
122
+ def register_tmp_handler(id, &handler)
123
+ @tmp_handlers[id.to_s] = handler
124
+ end
125
+
126
+ # Clear handlers with given guards
127
+ #
128
+ # @param [Symbol, nil] type remove filters for a specific handler
129
+ # @param [guards] guards take a look at the guards documentation
130
+ def clear_handlers(type, *guards)
131
+ @handlers[type].delete_if { |g, _| g == guards }
132
+ end
133
+
134
+ # Register a handler
135
+ #
136
+ # @param [Symbol, nil] type set the filter on a specific handler
137
+ # @param [guards] guards take a look at the guards documentation
138
+ # @yield [Blather::Stanza] stanza the incomming stanza
139
+ def register_handler(type, *guards, &handler)
140
+ check_handler type, guards
141
+ @handlers[type] ||= []
142
+ @handlers[type] << [guards, handler]
143
+ end
144
+
145
+ # Write data to the stream
146
+ #
147
+ # @param [#to_xml, #to_s] stanza the content to send down the wire
148
+ def write(stanza)
149
+ self.stream.send(stanza)
150
+ end
151
+
152
+ # Helper that will create a temporary handler for the stanza being sent
153
+ # before writing it to the stream.
154
+ #
155
+ # client.write_with_handler(stanza) { |s| "handle stanza here" }
156
+ #
157
+ # is equivalent to:
158
+ #
159
+ # client.register_tmp_handler(stanza.id) { |s| "handle stanza here" }
160
+ # client.write stanza
161
+ #
162
+ # @param [Blather::Stanza] stanza the stanza to send down the wire
163
+ # @yield [Blather::Stanza] stanza the reply stanza
164
+ def write_with_handler(stanza, &handler)
165
+ register_tmp_handler stanza.id, &handler
166
+ write stanza
167
+ end
168
+
169
+ # Close the connection
170
+ def close
171
+ self.stream.close_connection_after_writing
172
+ end
173
+
174
+ # @private
175
+ def post_init(stream, jid = nil)
176
+ @stream = stream
177
+ @jid = JID.new(jid) if jid
178
+ self.jid.node ? client_post_init : ready!
179
+ end
180
+
181
+ # @private
182
+ def unbind
183
+ call_handler_for(:disconnected, nil) || (EM.reactor_running? && EM.stop)
184
+ end
185
+
186
+ # @private
187
+ def receive_data(stanza)
188
+ @handler_queue << stanza
189
+ end
190
+
191
+ def handle_data(stanza)
192
+ catch(:halt) do
193
+ run_filters :before, stanza
194
+ handle_stanza stanza
195
+ run_filters :after, stanza
196
+ end
197
+ end
198
+
199
+ # @private
200
+ def setup?
201
+ @setup.is_a? Array
202
+ end
203
+
204
+ # @private
205
+ def setup(jid, password, host = nil, port = nil, certs = nil, connect_timeout = nil)
206
+ @jid = JID.new(jid)
207
+ @setup = [@jid, password]
208
+ @setup << host
209
+ @setup << port
210
+ @setup << certs
211
+ @setup << connect_timeout
212
+ self
213
+ end
214
+
215
+ protected
216
+
217
+ def stream
218
+ @stream || raise('Stream not ready!')
219
+ end
220
+
221
+ def check_handler(type, guards)
222
+ Blather.logger.warn "Handler for type \"#{type}\" will never be called as it's not a registered type" unless current_handlers.include?(type)
223
+ check_guards guards
224
+ end
225
+
226
+ def current_handlers
227
+ [:ready, :disconnected] + Stanza.handler_list + BlatherError.handler_list
228
+ end
229
+
230
+ def setup_initial_handlers
231
+ register_handler :error do |err|
232
+ raise err
233
+ end
234
+
235
+ # register_handler :iq, :type => [:get, :set] do |iq|
236
+ # write StanzaError.new(iq, 'service-unavailable', :cancel).to_node
237
+ # end
238
+
239
+ register_handler :ping, :type => :get do |ping|
240
+ write ping.reply
241
+ end
242
+
243
+ register_handler :status do |status|
244
+ roster[status.from].status = status if roster[status.from]
245
+ nil
246
+ end
247
+
248
+ register_handler :roster do |node|
249
+ roster.process node
250
+ end
251
+ end
252
+
253
+ def ready!
254
+ @state = :ready
255
+ call_handler_for :ready, nil
256
+ end
257
+
258
+ def client_post_init
259
+ write_with_handler Stanza::Iq::Roster.new do |node|
260
+ roster.process node
261
+ write @status
262
+ ready!
263
+ end
264
+ end
265
+
266
+ def run_filters(type, stanza)
267
+ @filters[type].each do |guards, handler, filter|
268
+ next if handler && !stanza.handler_hierarchy.include?(handler)
269
+ catch(:pass) { call_handler filter, guards, stanza }
270
+ end
271
+ end
272
+
273
+ def handle_stanza(stanza)
274
+ if handler = @tmp_handlers.delete(stanza.id)
275
+ handler.call stanza
276
+ else
277
+ stanza.handler_hierarchy.each do |type|
278
+ break if call_handler_for(type, stanza)
279
+ end
280
+ end
281
+ end
282
+
283
+ def call_handler_for(type, stanza)
284
+ return unless handler = @handlers[type]
285
+ handler.find do |guards, handler|
286
+ catch(:pass) { call_handler handler, guards, stanza }
287
+ end
288
+ end
289
+
290
+ def call_handler(handler, guards, stanza)
291
+ if guards.first.respond_to?(:to_str)
292
+ result = stanza.find(*guards)
293
+ handler.call(stanza, result) unless result.empty?
294
+ else
295
+ handler.call(stanza) unless guarded?(guards, stanza)
296
+ end
297
+ end
298
+
299
+ # If any of the guards returns FALSE this returns true
300
+ # the logic is reversed to allow short circuiting
301
+ # (why would anyone want to loop over more values than necessary?)
302
+ #
303
+ # @private
304
+ def guarded?(guards, stanza)
305
+ guards.find do |guard|
306
+ case guard
307
+ when Symbol
308
+ !stanza.__send__(guard)
309
+ when Array
310
+ # return FALSE if any item is TRUE
311
+ !guard.detect { |condition| !guarded?([condition], stanza) }
312
+ when Hash
313
+ # return FALSE unless any inequality is found
314
+ guard.find do |method, test|
315
+ value = stanza.__send__(method)
316
+ # last_match is the only method found unique to Regexp classes
317
+ if test.class.respond_to?(:last_match)
318
+ !(test =~ value.to_s)
319
+ elsif test.is_a?(Array)
320
+ !test.include? value
321
+ else
322
+ test != value
323
+ end
324
+ end
325
+ when Proc
326
+ !guard.call(stanza)
327
+ end
328
+ end
329
+ end
330
+
331
+ def check_guards(guards)
332
+ guards.each do |guard|
333
+ case guard
334
+ when Array
335
+ guard.each { |g| check_guards([g]) }
336
+ when Symbol, Proc, Hash, String
337
+ nil
338
+ else
339
+ raise "Bad guard: #{guard.inspect}"
340
+ end
341
+ end
342
+ end
343
+ end # Client
344
+
345
+ end # Blather
@@ -0,0 +1,320 @@
1
+ require File.join(File.dirname(__FILE__), 'client')
2
+
3
+ module Blather
4
+
5
+ # # Blather DSL
6
+ #
7
+ # The DSL is a set of methods that enables you to write cleaner code. Being a
8
+ # module means it can be included in or extend any class you may want to
9
+ # create.
10
+ #
11
+ # Every stanza handler is registered as a method on the DSL.
12
+ #
13
+ # @example Include the DSL in the top level namespace.
14
+ #
15
+ # require 'blather/client'
16
+ # when_ready { puts "Connected ! send messages to #{jid.stripped}." }
17
+ #
18
+ # subscription :request? do |s|
19
+ # write_to_stream s.approve!
20
+ # end
21
+ #
22
+ # message :chat?, :body => 'exit' do |m|
23
+ # say m.from, 'Exiting ...'
24
+ # shutdown
25
+ # end
26
+ #
27
+ # message :chat?, :body do |m|
28
+ # say m.from, "You sent: #{m.body}"
29
+ # end
30
+ #
31
+ # @example Set the DSL to its own namespace.
32
+ #
33
+ # require 'blather/client/dsl'
34
+ # module Echo
35
+ # extend Blather::DSL
36
+ #
37
+ # when_ready { puts "Connected ! send messages to #{jid.stripped}." }
38
+ #
39
+ # subscription :request? do |s|
40
+ # write_to_stream s.approve!
41
+ # end
42
+ #
43
+ # message :chat?, :body => 'exit' do |m|
44
+ # say m.from, 'Exiting ...'
45
+ # shutdown
46
+ # end
47
+ #
48
+ # message :chat?, :body do |m|
49
+ # say m.from, "You sent: #{m.body}"
50
+ # end
51
+ # end
52
+ #
53
+ # Echo.setup 'foo@bar.com', 'foobar'
54
+ #
55
+ # EM.run { Echo.run }
56
+ #
57
+ # @example Create a class out of it
58
+ #
59
+ # require 'blather/client/dsl'
60
+ # class Echo
61
+ # include Blather::DSL
62
+ # end
63
+ #
64
+ # echo = Echo.new
65
+ # echo.setup 'foo@bar.com', 'foobar'
66
+ # echo.when_ready { puts "Connected ! send messages to #{jid.stripped}." }
67
+ #
68
+ # echo.subscription :request? do |s|
69
+ # write_to_stream s.approve!
70
+ # end
71
+ #
72
+ # echo.message :chat?, :body => 'exit' do |m|
73
+ # say m.from, 'Exiting ...'
74
+ # shutdown
75
+ # end
76
+ #
77
+ # echo.message :chat?, :body do |m|
78
+ # say m.from, "You sent: #{m.body}"
79
+ # end
80
+ #
81
+ # EM.run { echo.run }
82
+ #
83
+ module DSL
84
+
85
+ autoload :PubSub, File.expand_path(File.join(File.dirname(__FILE__), *%w[dsl pubsub]))
86
+
87
+ def self.append_features(o)
88
+ Blather::Stanza.handler_list.each do |handler_name|
89
+ o.__send__ :remove_method, handler_name if !o.is_a?(Class) && o.method_defined?(handler_name)
90
+ end
91
+ super
92
+ end
93
+
94
+ # The actual client connection
95
+ #
96
+ # @return [Blather::Client]
97
+ def client
98
+ @client ||= Client.new
99
+ end
100
+ module_function :client
101
+
102
+ # A pubsub helper
103
+ #
104
+ # @return [Blather::PubSub]
105
+ def pubsub
106
+ @pubsub ||= PubSub.new client, jid.domain
107
+ end
108
+
109
+ # Push data to the stream
110
+ # This works such that it can be chained:
111
+ # self << stanza1 << stanza2 << "raw data"
112
+ #
113
+ # @param [#to_xml, #to_s] stanza data to send down the wire
114
+ # @return [self]
115
+ def <<(stanza)
116
+ client.write stanza
117
+ self
118
+ end
119
+
120
+ # Prepare server settings
121
+ #
122
+ # @param [#to_s] jid the JID to authenticate with
123
+ # @param [#to_s] password the password to authenticate with
124
+ # @param [String] host (optional) the host to connect to (can be an IP). If
125
+ # this is `nil` the domain on the JID will be used
126
+ # @param [Fixnum, String] (optional) port the port to connect on
127
+ # @param [Fixnum] (optional) connection_timeout the time to wait for connection to succeed before timing out
128
+ def setup(jid, password, host = nil, port = nil, certs = nil, connection_timeout = nil)
129
+ client.setup(jid, password, host, port, certs, connection_timeout)
130
+ end
131
+
132
+ # Connect to the server. Must be run in the EventMachine reactor
133
+ def run
134
+ client.run
135
+ end
136
+
137
+ # Shutdown the connection.
138
+ # Flushes the write buffer then stops EventMachine
139
+ def shutdown
140
+ client.close
141
+ end
142
+
143
+ # Setup a before filter
144
+ #
145
+ # @param [Symbol] handler (optional) the stanza handler the filter should
146
+ # run before
147
+ # @param [guards] guards (optional) a set of guards to check the stanza
148
+ # against
149
+ # @yield [Blather::Stanza] stanza
150
+ def before(handler = nil, *guards, &block)
151
+ client.register_filter :before, handler, *guards, &block
152
+ end
153
+
154
+ # Setup an after filter
155
+ #
156
+ # @param [Symbol] handler (optional) the stanza handler the filter should
157
+ # run after
158
+ # @param [guards] guards (optional) a set of guards to check the stanza
159
+ # against
160
+ # @yield [Blather::Stanza] stanza
161
+ def after(handler = nil, *guards, &block)
162
+ client.register_filter :after, handler, *guards, &block
163
+ end
164
+
165
+ # Set handler for a stanza type
166
+ #
167
+ # @param [Symbol] handler the stanza type it should handle
168
+ # @param [guards] guards (optional) a set of guards to check the stanza
169
+ # against
170
+ # @yield [Blather::Stanza] stanza
171
+ def handle(handler, *guards, &block)
172
+ client.register_handler handler, *guards, &block
173
+ end
174
+
175
+ # Wrapper for "handle :ready" (just a bit of syntactic sugar)
176
+ #
177
+ # This is run after the connection has been completely setup
178
+ def when_ready(&block)
179
+ handle :ready, &block
180
+ end
181
+
182
+ # Wrapper for "handle :disconnected"
183
+ #
184
+ # This is run after the connection has been shut down.
185
+ #
186
+ # @example Reconnect after a disconnection
187
+ # disconnected { client.run }
188
+ def disconnected(&block)
189
+ handle :disconnected, &block
190
+ end
191
+
192
+ # Set current status
193
+ #
194
+ # @param [Blather::Stanza::Presence::State::VALID_STATES] state the current
195
+ # state
196
+ # @param [#to_s] msg the status message to use
197
+ def set_status(state = nil, msg = nil)
198
+ client.status = state, msg
199
+ end
200
+
201
+ # Direct access to the roster
202
+ #
203
+ # @return [Blather::Roster]
204
+ def my_roster
205
+ client.roster
206
+ end
207
+
208
+ # Write data to the stream
209
+ #
210
+ # @param [#to_xml, #to_s] stanza the data to send down the wire.
211
+ def write_to_stream(stanza)
212
+ client.write stanza
213
+ end
214
+
215
+ # Helper method to join a MUC room
216
+ #
217
+ # @overload join(room_jid, nickname)
218
+ # @param [Blather::JID, #to_s] room the JID of the room to join
219
+ # @param [#to_s] nickname the nickname to join the room as
220
+ # @overload join(room_jid, nickname)
221
+ # @param [#to_s] room the name of the room to join
222
+ # @param [Blather::JID, #to_s] service the service domain the room is hosted at
223
+ # @param [#to_s] nickname the nickname to join the room as
224
+ def join(room, service, nickname = nil)
225
+ join = Blather::Stanza::Presence::MUC.new
226
+ join.to = if nickname
227
+ "#{room}@#{service}/#{nickname}"
228
+ else
229
+ "#{room}/#{service}"
230
+ end
231
+ client.write join
232
+ end
233
+
234
+ # Helper method to make sending basic messages easier
235
+ #
236
+ # @param [Blather::JID, #to_s] to the JID of the message recipient
237
+ # @param [#to_s] msg the message to send
238
+ # @param [#to_sym] the stanza method to use
239
+ def say(to, msg, using = :chat)
240
+ client.write Blather::Stanza::Message.new(to, msg, using)
241
+ end
242
+
243
+ # The JID according to the server
244
+ #
245
+ # @return [Blather::JID]
246
+ def jid
247
+ client.jid
248
+ end
249
+
250
+ # Halt the handler chain
251
+ #
252
+ # Use this to stop the propogation of the stanza though the handler chain.
253
+ #
254
+ # @example Ignore all IQ stanzas
255
+ #
256
+ # before(:iq) { halt }
257
+ def halt
258
+ throw :halt
259
+ end
260
+
261
+ # Pass responsibility to the next handler
262
+ #
263
+ # Use this to jump out of the current handler and let the next registered
264
+ # handler take care of the stanza
265
+ #
266
+ # @example Pass a message to the next handler
267
+ #
268
+ # This is contrive and should be handled with guards, but pass a message
269
+ # to the next handler based on the content
270
+ #
271
+ # message { |s| puts "message caught" }
272
+ # message { |s| pass if s.body =~ /pass along/ }
273
+ def pass
274
+ throw :pass
275
+ end
276
+
277
+ # Request items or info from an entity
278
+ # discover (items|info), [jid], [node] do |response|
279
+ # end
280
+ def discover(what, who, where, &callback)
281
+ stanza = Blather::Stanza.class_from_registration(:query, "http://jabber.org/protocol/disco##{what}").new
282
+ stanza.to = who
283
+ stanza.node = where
284
+
285
+ client.register_tmp_handler stanza.id, &callback
286
+ client.write stanza
287
+ end
288
+
289
+ # Set the capabilities of the client
290
+ #
291
+ # @param [String] node the URI
292
+ # @param [Array<Hash>] identities an array of identities
293
+ # @param [Array<Hash>] features an array of features
294
+ def set_caps(node, identities, features)
295
+ client.caps.node = node
296
+ client.caps.identities = identities
297
+ client.caps.features = features
298
+ end
299
+
300
+ # Send capabilities to the server
301
+ def send_caps
302
+ client.register_handler :disco_info, :type => :get, :node => client.caps.node do |s|
303
+ r = client.caps.dup
304
+ r.to = s.from
305
+ r.id = s.id
306
+ client.write r
307
+ end
308
+ client.write client.caps.c
309
+ end
310
+
311
+ # Generate a method for every stanza handler that exists.
312
+ Blather::Stanza.handler_list.each do |handler_name|
313
+ module_eval <<-METHOD, __FILE__, __LINE__
314
+ def #{handler_name}(*args, &callback)
315
+ handle :#{handler_name}, *args, &callback
316
+ end
317
+ METHOD
318
+ end
319
+ end # DSL
320
+ end # Blather