xmpp4r 0.5 → 0.5.5

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 (41) hide show
  1. data/CHANGELOG +21 -0
  2. data/README.rdoc +29 -38
  3. data/Rakefile +11 -18
  4. data/data/doc/xmpp4r/examples/advanced/recvfile.rb +5 -4
  5. data/lib/xmpp4r/bytestreams/helper/filetransfer.rb +2 -0
  6. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/base.rb +1 -0
  7. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/initiator.rb +1 -1
  8. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/target.rb +4 -1
  9. data/lib/xmpp4r/caps/helper/generator.rb +2 -2
  10. data/lib/xmpp4r/client.rb +18 -4
  11. data/lib/xmpp4r/connection.rb +8 -5
  12. data/lib/xmpp4r/delay/x/delay.rb +1 -1
  13. data/lib/xmpp4r/entity_time.rb +6 -0
  14. data/lib/xmpp4r/entity_time/iq.rb +45 -0
  15. data/lib/xmpp4r/entity_time/responder.rb +57 -0
  16. data/lib/xmpp4r/httpbinding/client.rb +178 -44
  17. data/lib/xmpp4r/message.rb +46 -0
  18. data/lib/xmpp4r/muc/helper/mucclient.rb +8 -1
  19. data/lib/xmpp4r/observable.rb +9 -0
  20. data/lib/xmpp4r/observable/contact.rb +61 -0
  21. data/lib/xmpp4r/observable/helper.rb +389 -0
  22. data/lib/xmpp4r/observable/observable_thing.rb +191 -0
  23. data/lib/xmpp4r/observable/pubsub.rb +211 -0
  24. data/lib/xmpp4r/observable/subscription.rb +57 -0
  25. data/lib/xmpp4r/observable/thread_store.rb +65 -0
  26. data/lib/xmpp4r/pubsub/children/unsubscribe.rb +16 -1
  27. data/lib/xmpp4r/pubsub/helper/servicehelper.rb +48 -14
  28. data/lib/xmpp4r/rexmladdons.rb +46 -6
  29. data/lib/xmpp4r/roster/helper/roster.rb +19 -9
  30. data/lib/xmpp4r/sasl.rb +1 -1
  31. data/lib/xmpp4r/stream.rb +2 -2
  32. data/lib/xmpp4r/xmpp4r.rb +1 -1
  33. data/test/bytestreams/tc_socks5bytestreams.rb +2 -2
  34. data/test/entity_time/tc_responder.rb +65 -0
  35. data/test/roster/tc_helper.rb +2 -3
  36. data/test/tc_message.rb +52 -1
  37. data/test/tc_stream.rb +2 -2
  38. data/test/tc_streamComponent.rb +11 -1
  39. data/test/ts_xmpp4r.rb +11 -2
  40. data/xmpp4r.gemspec +20 -9
  41. metadata +41 -35
@@ -100,6 +100,52 @@ module Jabber
100
100
  self
101
101
  end
102
102
 
103
+ ##
104
+ # Returns the message's xhtml body, or nil.
105
+ # This is the message's xhtml-text content.
106
+ def xhtml_body
107
+ html = first_element('html', 'http://jabber.org/protocol/xhtml-im')
108
+
109
+ if html
110
+ html.first_element_content('body', 'http://www.w3.org/1999/xhtml')
111
+ else
112
+ first_element_content('body', 'http://www.w3.org/1999/xhtml')
113
+ end
114
+ end
115
+
116
+ ##
117
+ # Sets the message's xhtml body
118
+ #
119
+ # b:: [String] xhtml body to set (Note: must be a valid xhtml)
120
+ def xhtml_body=(b)
121
+ begin
122
+ b = REXML::Document.new("<root>#{b}</root>")
123
+ rescue REXML::ParseException
124
+ raise ArgumentError, "Body is not a valid xhtml. Have you forgot to close some tag?"
125
+ end
126
+
127
+ html = first_element('html', 'http://jabber.org/protocol/xhtml-im')
128
+
129
+ if html
130
+ html.replace_element_content('body', b, 'http://www.w3.org/1999/xhtml')
131
+ else
132
+ el = REXML::Element.new('html')
133
+ el.add_namespace('http://jabber.org/protocol/xhtml-im')
134
+ el.replace_element_content('body', b, 'http://www.w3.org/1999/xhtml')
135
+ add_element(el)
136
+ end
137
+ end
138
+
139
+ ##
140
+ # Sets the message's xhtml body
141
+ #
142
+ # b:: [String] xhtml body to set (Note: must be a valid xhtml)
143
+ # return:: [REXML::Element] self for chaining
144
+ def set_xhtml_body(b)
145
+ self.xhtml_body = b
146
+ self
147
+ end
148
+
103
149
  ##
104
150
  # sets the message's subject
105
151
  #
@@ -68,8 +68,9 @@ module Jabber
68
68
  # fails.
69
69
  # jid:: [JID] room@component/nick
70
70
  # password:: [String] Optional password
71
+ # opts:: [Hash] If the parameter :history => false is passed then you will receive no history messages after initial presence
71
72
  # return:: [MUCClient] self (chain-able)
72
- def join(jid, password=nil)
73
+ def join(jid, password=nil, opts={})
73
74
  if active?
74
75
  raise "MUCClient already active"
75
76
  end
@@ -83,6 +84,12 @@ module Jabber
83
84
  pres.from = @my_jid
84
85
  xmuc = XMUC.new
85
86
  xmuc.password = password
87
+
88
+ if !opts[:history]
89
+ history = REXML::Element.new( 'history').tap {|h| h.add_attribute('maxstanzas','0') }
90
+ xmuc.add_element history
91
+ end
92
+
86
93
  pres.add(xmuc)
87
94
 
88
95
  # We don't use Stream#send_with_id here as it's unknown
@@ -0,0 +1,9 @@
1
+ # =XMPP4R - XMPP Library for Ruby
2
+ #
3
+ # This file's copyright (c) 2009 by Pablo Lorenzzoni <pablo@propus.com.br>
4
+ # License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
5
+ # Website::http://home.gna.org/xmpp4r/
6
+
7
+ require 'xmpp4r/observable/thread_store.rb'
8
+ require 'xmpp4r/observable/observable_thing.rb'
9
+ require 'xmpp4r/observable/helper.rb'
@@ -0,0 +1,61 @@
1
+ # =XMPP4R - XMPP Library for Ruby
2
+ #
3
+ # This file's copyright (c) 2009 by Pablo Lorenzzoni <pablo@propus.com.br>
4
+ # License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
5
+ # Website::http://home.gna.org/xmpp4r/
6
+ #
7
+ module Jabber
8
+ class Observable
9
+ # Jabber::Observable::Contact - Convenience subclass to deal with Contacts
10
+ class Contact
11
+
12
+ # Creates a new Jabber::Observable::Contact
13
+ #
14
+ # jid:: jid to be used
15
+ # observable:: observable to be used
16
+ def initialize(jid, observable)
17
+ @jid = jid.respond_to?(:resource) ? jid : JID.new(jid)
18
+ @observable = observable
19
+ end
20
+
21
+ # Returns the stripped version of the JID
22
+ def jid; @jid.strip; end
23
+
24
+ def inspect #:nodoc:
25
+ sprintf("#<%s:0x%x @jid=%s>", self.class.name, __id__, @jid.to_s)
26
+ end
27
+
28
+ # Are e subscribed?
29
+ def subscribed?
30
+ [:to, :both].include?(subscription)
31
+ end
32
+
33
+ # Get the subscription from the roster_item
34
+ def subscription
35
+ items = @observable.roster.items
36
+ return false unless items.include?(jid)
37
+ items[jid].subscription
38
+ end
39
+
40
+ # Send a request asking for authorization
41
+ def ask_for_authorization!
42
+ request!(:subscribe)
43
+ end
44
+
45
+ # Sends a request asking for unsubscription
46
+ def unsubscribe!
47
+ request!(:unsubscribe)
48
+ end
49
+
50
+ private
51
+
52
+ # Really send the request.
53
+ def request!(type)
54
+ request = Jabber::Presence.new.set_type(type)
55
+ request.to = jid
56
+ @observable.send!(request)
57
+ end
58
+
59
+ end # of class Contact
60
+ end # of class Observable
61
+ end # of module Jabber
@@ -0,0 +1,389 @@
1
+ # =XMPP4R - XMPP Library for Ruby
2
+ #
3
+ # This file's copyright (c) 2009 by Pablo Lorenzzoni <pablo@propus.com.br>
4
+ # License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
5
+ # Website::http://home.gna.org/xmpp4r/
6
+ #
7
+ # This is the helper for xmpp4r/observable, and was based on XMPP4R-Observable
8
+ # by Pablo Lorenzoni <pablo@propus.com.br>.
9
+ require 'time'
10
+ require 'xmpp4r'
11
+ require 'xmpp4r/roster'
12
+ require 'xmpp4r/observable/contact'
13
+ require 'xmpp4r/observable/pubsub'
14
+ require 'xmpp4r/observable/subscription'
15
+ module Jabber
16
+
17
+ class NotConnected < StandardError; end #:nodoc:
18
+
19
+ # Jabber::Observable - Creates observable Jabber Clients
20
+ class Observable
21
+
22
+ # This is what actually makes our object Observable.
23
+ include ObservableThing
24
+
25
+ attr_reader :subs, :pubsub, :jid, :auto
26
+
27
+ # Create a new Jabber::Observable client. You will be automatically connected
28
+ # to the Jabber server and your status message will be set to the string
29
+ # passed in as the status_message argument.
30
+ #
31
+ # jabber = Jabber::Observable.new("me@example.com", "password", nil, "Talk to me - Please!")
32
+ #
33
+ # jid:: your jid (either a string or a JID object)
34
+ # password:: your password
35
+ # status:: your status. Check Jabber::Observable#status for documentation
36
+ # status_message:: some funny string
37
+ # host:: the server host (if different from the one in the jid)
38
+ # port:: the server port (default: 5222)
39
+ def initialize(jid, password, status=nil, status_message="Available", host=nil, port=5222)
40
+
41
+ # Basic stuff
42
+ @jid = jid.respond_to?(:resource) ? jid : Jabber::JID.new(jid)
43
+ @password = password
44
+ @host = host
45
+ @port = port
46
+
47
+ # Message dealing
48
+ @delivered_messages = 0
49
+ @deferred_messages = Queue.new
50
+ start_deferred_delivery_thread
51
+
52
+ # Connection stuff
53
+ @connect_mutex = Mutex.new
54
+ @client = nil
55
+ @roster = nil
56
+ @disconnected = false
57
+
58
+ # Tell everybody I am here
59
+ status(status, status_message)
60
+
61
+ # Subscription Accessor
62
+ @subs = Jabber::Observable::Subscriptions.new(self)
63
+
64
+ # PubSub Accessor
65
+ @pubsub = Jabber::Observable::PubSub.new(self)
66
+
67
+ # Auto Observer placeholder
68
+ @auto = nil
69
+
70
+ # Our contacts Hash
71
+ @contacts = Hash.new
72
+ end
73
+
74
+ def inspect # :nodoc:
75
+ sprintf("#<%s:0x%x @jid=%s, @delivered_messages=%d, @deferred_messages=%d, @observer_count=%s, @notification_count=%s, @pubsub=%s>", self.class.name, __id__, @jid, @delivered_messages, @deferred_messages.length, observer_count.inspect, notification_count.inspect, @pubsub.inspect)
76
+ end
77
+
78
+ # Count the registered observers in each thing
79
+ def observer_count
80
+ h = {}
81
+ [ :message, :presence, :iq, :new_subscription, :subscription_request, :event ].each do |thing|
82
+ h[thing] = count_observers(thing)
83
+ end
84
+ h
85
+ end
86
+
87
+ # Count the notifications really sent for each thing
88
+ def notification_count
89
+ h = {}
90
+ [ :message, :presence, :iq, :new_subscription, :subscription_request, :event ].each do |thing|
91
+ h[thing] = count_notifications(thing)
92
+ end
93
+ h
94
+ end
95
+
96
+ # Attach an auto-observer based on QObserver
97
+ def attach_auto_observer
98
+ raise StandardError, "Already attached." unless @auto.nil?
99
+
100
+ @auto = QObserver.new
101
+ [ :message, :presence, :iq, :new_subscription, :subscription_request, :event ].each do |thing|
102
+ self.add_observer(thing, @auto)
103
+ end
104
+ end
105
+
106
+ # Dettach the auto-observer
107
+ def dettach_auto_observer
108
+ raise StandardError, "Not attached." if @auto.nil?
109
+
110
+ [ :message, :presence, :iq, :new_subscription, :subscription_request, :event ].each do |thing|
111
+ self.delete_observer(thing, @auto)
112
+ end
113
+ @auto = nil
114
+ end
115
+
116
+ # Send a message to jabber user jid.
117
+ #
118
+ # jid:: jid of the recipient
119
+ # message:: what is to be delivered (either a string or a Jabber::Message)
120
+ # type:: can be either one of:
121
+ # * :normal: a normal message.
122
+ # * :chat (default): a one-to-one chat message.
123
+ # * :groupchat: a group-chat message.
124
+ # * :headline: a "headline" message.
125
+ # * :error: an error message.
126
+ #
127
+ # If the recipient is not in your contacts list, the message will be queued
128
+ # for later delivery, and the Contact will be automatically asked for
129
+ # authorization (see Jabber::Observable#add).
130
+ def deliver(jid, message, type=nil)
131
+ contacts(jid).each do |contact|
132
+ # Check if we're subscribed to contact
133
+ if @subs.subscribed_to?(contact)
134
+ # Yes! we are!
135
+ if message.instance_of?(Jabber::Message)
136
+ msg = message
137
+ msg.to = contact.jid
138
+ msg.type = type unless type.nil? # Let's keep the Jabber::Message type unless passed
139
+ else
140
+ msg = Jabber::Message.new(contact.jid)
141
+ msg.body = message
142
+ msg.type = type.nil? ? :chat : type
143
+ end
144
+ @delivered_messages += 1
145
+ send!(msg)
146
+ else
147
+ # No... Let's add it and defer the delivery.
148
+ @subs.add(contact.jid)
149
+ deliver_deferred(contact.jid, message, type)
150
+ end
151
+ end
152
+ end
153
+
154
+ # Set your presence, with a message.
155
+ #
156
+ # presence:: any of these:
157
+ # * nil: online.
158
+ # * :chat: free for chat.
159
+ # * :away: away from the computer.
160
+ # * :dnd: do not disturb.
161
+ # * :xa: extended away.
162
+ # message:: a string that you wish your contacts see when you change your presence.
163
+ def status(presence, message)
164
+ @status_message = message
165
+ @presence = presence
166
+ send!(Jabber::Presence.new(@presence, @status_message))
167
+ end
168
+
169
+ # Transform a passed list of contacts in one or more Jabber::Observable::Contact objects.
170
+ #
171
+ # contact:: one of more jids of contacts
172
+ def contacts(*contact)
173
+ ret = []
174
+ contact.each do |c|
175
+ jid = c.to_s
176
+ # Do we already have it?
177
+ if ! @contacts.include?(jid)
178
+ # Nope.
179
+ @contacts[jid] = c.instance_of?(Jabber::Observable::Contact) ? c : Jabber::Observable::Contact.new(c, self)
180
+ end
181
+ ret << @contacts[jid]
182
+ end
183
+ ret
184
+ end
185
+
186
+ # Returns true if the Jabber client is connected to the Jabber server,
187
+ # false otherwise.
188
+ def connected?
189
+ @client.respond_to?(:is_connected?) && @client.is_connected?
190
+ end
191
+
192
+ # Pass the underlying Roster helper.
193
+ def roster
194
+ return @roster unless @roster.nil?
195
+ @roster = Jabber::Roster::Helper.new(client)
196
+ end
197
+
198
+ # Pass the underlying Jabber client.
199
+ def client
200
+ connect! unless connected?
201
+ @client
202
+ end
203
+
204
+ # Send a Jabber stanza over-the-wire.
205
+ #
206
+ # msg:: the stanza to be sent.
207
+ def send!(msg)
208
+ retries = 0
209
+ max = 4
210
+ begin
211
+ retries += 1
212
+ client.send(msg)
213
+ rescue Errno::ECONNRESET => e
214
+ # Connection reset. Sleep progressively and retry until success or exhaustion.
215
+ sleep ((retries^2) * 60) + 30
216
+ disconnect
217
+ reconnect
218
+ retry unless retries > max
219
+ raise e
220
+ rescue Errno::EPIPE, IOError => e
221
+ # Some minor error. Sleep 2 seconds and retry until success or exhaustion.
222
+ sleep 2
223
+ disconnect
224
+ reconnect
225
+ retry unless retries > max
226
+ raise e
227
+ end
228
+ end
229
+
230
+ # Use this to force the client to reconnect after a disconnect.
231
+ def reconnect
232
+ @disconnected = false
233
+ connect!
234
+ end
235
+
236
+ # Use this to force the client to disconnect and not automatically
237
+ # reconnect.
238
+ def disconnect
239
+ disconnect!(false)
240
+ end
241
+
242
+ # Queue messages for delivery once a user has accepted our authorization
243
+ # request. Works in conjunction with the deferred delivery thread.
244
+ #
245
+ # You can use this method if you want to manually add friends and still
246
+ # have the message queued for later delivery.
247
+ def deliver_deferred(jid, message, type)
248
+ msg = {:to => jid, :message => message, :type => type, :time => Time.now}
249
+ @deferred_messages.enq msg
250
+ end
251
+
252
+ # Sets the maximum time to wait for a message to be delivered (in
253
+ # seconds). It will be removed of the queue afterwards.
254
+ def deferred_max_wait=(seconds)
255
+ @deferred_max_wait = seconds
256
+ end
257
+
258
+ # Get the maximum time to wait for a message to be delivered. Default: 600
259
+ # seconds (10 minutes).
260
+ def deferred_max_wait
261
+ @deferred_max_wait || 600
262
+ end
263
+
264
+ private
265
+
266
+ # Create the infrastructure for connection and do it.
267
+ #
268
+ # Note that we use a Mutex to prevent double connection attempts and will
269
+ # raise a SecurityError if that happens.
270
+ def connect!
271
+ raise RuntimeError, "Connections disabled - use Jabber::Observable::reconnect() to reconnect." if @disconnected
272
+ raise SecurityError, "Connection attempt while already trying to connect!" if @connect_mutex.locked?
273
+
274
+ @connect_mutex.synchronize do
275
+ # Assure we're not connected.
276
+ disconnect!(false) if connected?
277
+
278
+ # Connection
279
+ jid = Jabber::JID.new(@jid)
280
+ my_client = Jabber::Client.new(jid)
281
+ my_client.connect(@host, @port)
282
+ my_client.auth(@password)
283
+ @roster = nil
284
+ @client = my_client
285
+
286
+ # Post-connect
287
+ register_default_callbacks
288
+ status(@presence, @status_message)
289
+ if @pubsub.nil?
290
+ @pubsub = Jabber::Observable::PubSub.new(self)
291
+ else
292
+ @pubsub.attach!
293
+ end
294
+ end
295
+ end
296
+
297
+ # Really disconnect the client
298
+ #
299
+ # auto_reconnect:: Sets the flag for auto-reconnection
300
+ def disconnect!(auto_reconnect = true)
301
+ if connected?
302
+ client.close
303
+ end
304
+ @roster = nil
305
+ @client = nil
306
+ @pubsub.set_service(nil)
307
+ @disconnected = auto_reconnect
308
+ end
309
+
310
+ # This will register callbacks for every "thing" we made observable.
311
+ #
312
+ # The observable things we register here are :message, :presence, :iq,
313
+ # :new_subscription, and :subscription_request
314
+ #
315
+ # Note we can also observe :event, but that is dealt with in
316
+ # Jabber::Observable::PubSub
317
+ def register_default_callbacks
318
+
319
+ # The three basic "things": :message, :presence and :iq
320
+ # (note that :presence is based on roster)
321
+ client.add_message_callback do |message|
322
+ unless message.body.nil?
323
+ changed(:message)
324
+ notify_observers(:message, message)
325
+ end
326
+ end
327
+
328
+ roster.add_presence_callback do |roster_item, old_presence, new_presence|
329
+ simple_jid = roster_item.jid.strip.to_s
330
+ presence = case new_presence.type
331
+ when nil then new_presence.show || :online
332
+ when :unavailable then :unavailable
333
+ else
334
+ nil
335
+ end
336
+
337
+ changed(:presence)
338
+ notify_observers(:presence, simple_jid, presence, new_presence)
339
+ end
340
+
341
+ client.add_iq_callback do |iq|
342
+ changed(:iq)
343
+ notify_observers(:iq, iq)
344
+ end
345
+
346
+ # We'll also expose roster's :new_subscription and :subscription_request
347
+ roster.add_subscription_callback do |roster_item, presence|
348
+ if presence.type == :subscribed
349
+ changed(:new_subscription)
350
+ notify_observers(:new_subscription, [roster_item, presence])
351
+ end
352
+ end
353
+
354
+ roster.add_subscription_request_callback do |roster_item, presence|
355
+ roster.accept_subscription(presence.from) if @subs.accept?
356
+ changed(:subscription_request)
357
+ notify_observers(:subscription_request, [roster_item, presence])
358
+ end
359
+ end
360
+
361
+ # Starts the deferred delivery thread
362
+ #
363
+ # This will monitor the @deferred_messages queue and try to deliver messages.
364
+ def start_deferred_delivery_thread
365
+ return if ! @deferred_delivery_thread.nil? and @deferred_delivery_thread.alive?
366
+
367
+ @deferred_delivery_thread = Thread.new do
368
+ loop do
369
+ # Check the queue every 10 seconds. Effectivelly block if nothing is queued.
370
+ sleep 10 while @deferred_messages.empty?
371
+
372
+ # Hm... something has been queued
373
+ message = @deferred_messages.deq
374
+ if @subs.subscribed_to?(message[:to])
375
+ # Great! We're subscribed!
376
+ deliver(message[:to], message[:message], message[:type])
377
+ else
378
+ # Still not subscribed. Enqueue it again (unless deferred_max_wait was reached)
379
+ @deferred_messages.enq message unless Time.now > (deferred_max_wait + message[:time])
380
+ end
381
+
382
+ # Wait 5 seconds between every message still in the queue
383
+ sleep 5
384
+ end
385
+ end
386
+ end
387
+ end # of class Observable
388
+ end # of module Jabber
389
+