tp-blather 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
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