pyu-xmpp4r-simple 0.8.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,819 @@
1
+ # Jabber::Simple - An extremely easy-to-use Jabber client library.
2
+ # Copyright 2006 Blaine Cook <blaine@obvious.com>, Obvious Corp.
3
+ #
4
+ # Jabber::Simple is free software; you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation; either version 2 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # Jabber::Simple is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with Jabber::Simple; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17
+
18
+ require 'time'
19
+ require 'rubygems'
20
+ require 'xmpp4r'
21
+ require 'xmpp4r/roster'
22
+ require 'xmpp4r/vcard'
23
+ require 'xmpp4r/pubsub'
24
+ require 'xmpp4r/pubsub/helper/servicehelper'
25
+ require 'xmpp4r/pubsub/helper/nodebrowser'
26
+ require 'xmpp4r/pubsub/helper/nodehelper'
27
+
28
+ module Jabber
29
+
30
+ class ConnectionError < StandardError #:nodoc:
31
+ end
32
+
33
+ class NotConnected < StandardError #:nodoc:
34
+ end
35
+
36
+ class NoPubSubService < StandardError #:nodoc:
37
+ end
38
+
39
+ class AlreadySet < StandardError #:nodoc:
40
+ end
41
+
42
+ class RegistrationError < StandardError #:nodoc:
43
+ end
44
+
45
+ module Roster
46
+ class RosterItem
47
+ def chat_state
48
+ @chat_state
49
+ end
50
+
51
+ def chat_state=(state)
52
+ @chat_state = state
53
+ end
54
+ end
55
+ end
56
+
57
+
58
+
59
+ class Contact #:nodoc:
60
+
61
+ include DRb::DRbUndumped if defined?(DRb::DRbUndumped)
62
+
63
+ def initialize(client, jid)
64
+ @jid = jid.respond_to?(:resource) ? jid : JID.new(jid)
65
+ @client = client
66
+ end
67
+
68
+ def inspect
69
+ "Jabber::Contact #{jid.to_s}"
70
+ end
71
+
72
+ def subscribed?
73
+ [:to, :both].include?(subscription)
74
+ end
75
+
76
+ def subscription
77
+ roster_item && roster_item.subscription
78
+ end
79
+
80
+ def ask_for_authorization!
81
+ subscription_request = Presence.new.set_type(:subscribe)
82
+ subscription_request.to = jid
83
+ client.send!(subscription_request)
84
+ end
85
+
86
+ def unsubscribe!
87
+ unsubscription_request = Presence.new.set_type(:unsubscribe)
88
+ unsubscription_request.to = jid
89
+ client.send!(unsubscription_request)
90
+ client.send!(unsubscription_request.set_type(:unsubscribed))
91
+ end
92
+
93
+ def jid(bare=true)
94
+ bare ? @jid.strip : @jid
95
+ end
96
+
97
+ private
98
+
99
+ def roster_item
100
+ client.roster.items[jid]
101
+ end
102
+
103
+ def client
104
+ @client
105
+ end
106
+ end
107
+
108
+ class Simple
109
+
110
+ include DRb::DRbUndumped if defined?(DRb::DRbUndumped)
111
+
112
+ def self.register(jid, password, status = nil, status_message = "Available")
113
+ new(jid, password, status, status_message, nil, 5222, nil, true)
114
+ end
115
+
116
+ # Create a new Jabber::Simple client. You will be automatically connected
117
+ # to the Jabber server and your status message will be set to the string
118
+ # passed in as the status_message argument.
119
+ # If you'd like to connect to a different talk server than the one which would
120
+ # be guessed from your jid, you may provide a server. For example, to connect
121
+ # to the gmail talk servers with a jid that doesn't end in @gmail.com, just provide
122
+ # 'talk.l.google.com' as the server. You may leave server as nil to use the default.
123
+ #
124
+ # jabber = Jabber::Simple.new("me@example.com", "password", "Chat with me - Please!")
125
+ def initialize(jid, password, status = nil, status_message = "Available", host = nil, port = 5222, server=nil, register = false)
126
+ @jid = jid
127
+ @password = password
128
+ @host = host
129
+ @port = port
130
+ @disconnected = false
131
+ @server = server
132
+ register!(password) if @register = register
133
+ status(status, status_message)
134
+ start_deferred_delivery_thread
135
+
136
+ @pubsub = @pubsub_jid = nil
137
+ begin
138
+ domain = Jabber::JID.new(@jid).domain
139
+ @pubsub_jid = "pubsub." + domain
140
+ set_pubsub_service(@pubsub_jid)
141
+ rescue
142
+ @pubsub = @pubsub_jid = nil
143
+ end
144
+ end
145
+
146
+ def inspect #:nodoc:
147
+ "Jabber::Simple #{@jid}"
148
+ end
149
+
150
+ # Send a message to jabber user jid.
151
+ # It is possible to send message to multiple users at once,
152
+ # just supply array of jids.
153
+ #
154
+ # Example usage:
155
+ #
156
+ # jabber_simple.deliver("foo@example.com", "blabla")
157
+ # jabber_simple.deliver(["foo@example.com", "bar@example.com"], "blabla")
158
+ #
159
+ # Valid message types are:
160
+ #
161
+ # * :normal (default): a normal message.
162
+ # * :chat: a one-to-one chat message.
163
+ # * :groupchat: a group-chat message.
164
+ # * :headline: a "headline" message.
165
+ # * :error: an error message.
166
+ #
167
+ # If the recipient is not in your contacts list, the message will be queued
168
+ # for later delivery, and the Contact will be automatically asked for
169
+ # authorization (see Jabber::Simple#add).
170
+ #
171
+ # message should be a string or a valid Jabber::Message object. In either case,
172
+ # the message recipient will be set to jid.
173
+ def deliver(jids, message, type = :chat, chat_state = :active)
174
+ contacts(jids) do |friend|
175
+ unless subscribed_to? friend
176
+ add(friend.jid)
177
+ deliver_deferred(friend.jid, message, type)
178
+ next
179
+ end
180
+ if message.kind_of?(Jabber::Message)
181
+ msg = message
182
+ msg.to = friend.jid
183
+ else
184
+ msg = Message.new(friend.jid)
185
+ msg.type = type
186
+ msg.chat_state = chat_state
187
+ msg.body = message
188
+ end
189
+ send!(msg)
190
+ end
191
+ end
192
+
193
+ # Set your presence, with a message.
194
+ #
195
+ # Available values for presence are:
196
+ #
197
+ # * nil: online.
198
+ # * :chat: free for chat.
199
+ # * :away: away from the computer.
200
+ # * :dnd: do not disturb.
201
+ # * :xa: extended away.
202
+ #
203
+ # It's not possible to set an offline status - to do that, disconnect! :-)
204
+ def status(presence, message)
205
+ @presence = presence
206
+ @status_message = message
207
+ stat_msg = Presence.new(@presence, @status_message)
208
+
209
+ if !avatar_hash.nil?
210
+ x = Jabber::X.new
211
+ x.add_namespace('vcard-temp:x:update')
212
+ photo = REXML::Element::new("photo")
213
+ photo.add(REXML::Text.new(avatar_hash))
214
+ x.add(photo)
215
+ stat_msg.add_element(x)
216
+ end
217
+
218
+ send!(stat_msg)
219
+ end
220
+
221
+ # Ask the users specified by jids for authorization (i.e., ask them to add
222
+ # you to their contact list). If you are already in the user's contact list,
223
+ # add() will not attempt to re-request authorization. In order to force
224
+ # re-authorization, first remove() the user, then re-add them.
225
+ #
226
+ # Example usage:
227
+ #
228
+ # jabber_simple.add("friend@friendosaurus.com")
229
+ #
230
+ # Because the authorization process might take a few seconds, or might
231
+ # never happen depending on when (and if) the user accepts your
232
+ # request, results are placed in the Jabber::Simple#new_subscriptions queue.
233
+ def add(*jids)
234
+ contacts(*jids) do |friend|
235
+ next if subscribed_to? friend
236
+ friend.ask_for_authorization!
237
+ end
238
+ end
239
+
240
+ # Remove the jabber users specified by jids from the contact list.
241
+ def remove(*jids)
242
+ contacts(*jids) do |unfriend|
243
+ unfriend.unsubscribe!
244
+ end
245
+ end
246
+
247
+ # Returns true if this Jabber account is subscribed to status updates for
248
+ # the jabber user jid, false otherwise.
249
+ def subscribed_to?(jid)
250
+ contacts(jid) do |contact|
251
+ return contact.subscribed?
252
+ end
253
+ end
254
+
255
+ # Returns array of roster items.
256
+ def contact_list
257
+ roster.items.values
258
+ end
259
+
260
+ # Retrieve vCard of an entity
261
+ #
262
+ # Raises exception upon retrieval error, please catch that!
263
+ #
264
+ # Usage of Threads is suggested here as vCards can be very
265
+ # big.
266
+ #
267
+ # jid:: [Jabber::JID] or nil (should be stripped, nil for the client's own vCard)
268
+ # result:: [Jabber::IqVcard] or nil (nil results may be handled as empty vCards)
269
+ def get_info(jid = nil)
270
+ Jabber::Vcard::Helper.new(client).get(jid)
271
+ end
272
+
273
+ # Update your own vCard
274
+ #
275
+ # Raises exception when setting fails
276
+ #
277
+ # Usage of Threads suggested here, too. The function
278
+ # waits for approval from the server.
279
+ # e.g.:
280
+ #
281
+ # info = Jabber::Vcard::IqVcard.new
282
+ # info["NICKNAME"] = "amay"
283
+ # jabber.update_info(info)
284
+ def update_info(vcard)
285
+ Jabber::Vcard::Helper.new(client).set(vcard)
286
+ end
287
+
288
+ # Returns SHA1 hash of the avatar image data.
289
+ #
290
+ # This hash is then included in the user's presence information.
291
+ def avatar_hash
292
+ vcard = get_info
293
+ if !vcard.nil? && !vcard["PHOTO/BINVAL"].nil?
294
+ Digest::SHA1.hexdigest(Base64.decode64(vcard["PHOTO/BINVAL"]))
295
+ else
296
+ nil
297
+ end
298
+ end
299
+
300
+ # Returns true if the Jabber client is connected to the Jabber server,
301
+ # false otherwise.
302
+ def connected?
303
+ @client ||= nil
304
+ connected = @client.respond_to?(:is_connected?) && @client.is_connected?
305
+ return connected
306
+ end
307
+
308
+ # Returns an array of messages received since the last time
309
+ # received_messages was called. Passing a block will yield each message in
310
+ # turn, allowing you to break part-way through processing (especially
311
+ # useful when your message handling code is not thread-safe (e.g.,
312
+ # ActiveRecord).
313
+ #
314
+ # e.g.:
315
+ #
316
+ # jabber.received_messages do |message|
317
+ # puts "Received message from #{message.from}: #{message.body}"
318
+ # end
319
+ def received_messages(&block)
320
+ dequeue(:received_messages, &block)
321
+ end
322
+
323
+ # Returns true if there are unprocessed received messages waiting in the
324
+ # queue, false otherwise.
325
+ def received_messages?
326
+ !queue(:received_messages).empty?
327
+ end
328
+
329
+ # Returns an array of iq stanzas received since the last time
330
+ # iq_stanzas was called. There will be no stanzas in this queue
331
+ # unless you have enabled capture of <iq/> stanzas using the
332
+ # capture_iq_stanzas! method. Passing a block will yield each stanza in
333
+ # turn, allowing you to break part-way through processing (especially
334
+ # useful when your message handling code is not thread-safe (e.g.,
335
+ # ActiveRecord).
336
+ #
337
+ # e.g.:
338
+ #
339
+ # jabber.capture_iq_stanzas!
340
+ # jabber.iq_stanzas do |iq|
341
+ # puts "Received iq stanza from #{iq.from}"
342
+ # end
343
+ def iq_stanza(&block)
344
+ dequeue(:iq_stanzas, &block)
345
+ end
346
+
347
+ # Returns true if there are unprocessed iq stanzas waiting in the
348
+ # queue, false otherwise.
349
+ def iq_stanzas?
350
+ !queue(:iq_stanzas).empty?
351
+ end
352
+
353
+ # Tell the client to capture (or stop capturing) <iq/> stanzas
354
+ # addressed to it. When the client is initially started, this
355
+ # is false.
356
+ def capture_iq_stanzas!(capture=true)
357
+ @iq_mutex.synchronize { @capture_iq_stanzas = capture }
358
+ end
359
+
360
+ # Returns true if iq stanzas will be captured in the queue, as
361
+ # they arrive, false otherwise.
362
+ def capture_iq_stanzas?
363
+ @capture_iq_stanzas
364
+ end
365
+
366
+ # Returns an array of presence updates received since the last time
367
+ # presence_updates was called. Passing a block will yield each update in
368
+ # turn, allowing you to break part-way through processing (especially
369
+ # useful when your presence handling code is not thread-safe (e.g.,
370
+ # ActiveRecord).
371
+ #
372
+ # e.g.:
373
+ #
374
+ # jabber.presence_updates do |friend, new_presence|
375
+ # puts "Received presence update from #{friend}: #{new_presence}"
376
+ # end
377
+ def presence_updates(&block)
378
+ updates = []
379
+ @presence_mutex.synchronize do
380
+ dequeue(:presence_updates) do |friend|
381
+ presence = @presence_updates[friend]
382
+ next unless presence
383
+ new_update = [friend, presence[0], presence[1]]
384
+ yield new_update if block_given?
385
+ updates << new_update
386
+ @presence_updates.delete(friend)
387
+ end
388
+ end
389
+ return updates
390
+ end
391
+
392
+ # Returns true if there are unprocessed presence updates waiting in the
393
+ # queue, false otherwise.
394
+ def presence_updates?
395
+ !queue(:presence_updates).empty?
396
+ end
397
+
398
+ # Returns an array of subscription notifications received since the last
399
+ # time new_subscriptions was called. Passing a block will yield each update
400
+ # in turn, allowing you to break part-way through processing (especially
401
+ # useful when your subscription handling code is not thread-safe (e.g.,
402
+ # ActiveRecord).
403
+ #
404
+ # e.g.:
405
+ #
406
+ # jabber.new_subscriptions do |friend, presence|
407
+ # puts "Received presence update from #{friend.to_s}: #{presence}"
408
+ # end
409
+ def new_subscriptions(&block)
410
+ dequeue(:new_subscriptions, &block)
411
+ end
412
+
413
+ # Returns true if there are unprocessed presence updates waiting in the
414
+ # queue, false otherwise.
415
+ def new_subscriptions?
416
+ !queue(:new_subscriptions).empty?
417
+ end
418
+
419
+ # Returns an array of subscription notifications received since the last
420
+ # time subscription_requests was called. Passing a block will yield each update
421
+ # in turn, allowing you to break part-way through processing (especially
422
+ # useful when your subscription handling code is not thread-safe (e.g.,
423
+ # ActiveRecord).
424
+ #
425
+ # e.g.:
426
+ #
427
+ # jabber.subscription_requests do |friend, presence|
428
+ # puts "Received presence update from #{friend.to_s}: #{presence}"
429
+ # end
430
+ def subscription_requests(&block)
431
+ dequeue(:subscription_requests, &block)
432
+ end
433
+
434
+ # Returns true if auto-accept subscriptions (friend requests) is enabled
435
+ # (default), false otherwise.
436
+ def accept_subscriptions?
437
+ @accept_subscriptions = true if @accept_subscriptions.nil?
438
+ @accept_subscriptions
439
+ end
440
+
441
+ # Change whether or not subscriptions (friend requests) are automatically accepted.
442
+ def accept_subscriptions=(accept_status)
443
+ @accept_subscriptions = accept_status
444
+ end
445
+
446
+ # Direct access to the underlying Roster helper.
447
+ def roster
448
+ return @roster if @roster
449
+ self.roster = Roster::Helper.new(client)
450
+ end
451
+
452
+ # Direct access to the underlying Jabber client.
453
+ def client
454
+ connect!() unless connected?
455
+ @client
456
+ end
457
+ def register!(password)
458
+ attempt! {
459
+ begin
460
+ client.register(password)
461
+ @register = false
462
+ disconnect
463
+ reconnect
464
+ rescue Exception => e
465
+ error_msg = "Error registering: #{e.message}\n\n"
466
+ if e.respond_to?('error') && e.error.type == :modify
467
+ error_msg += "Accepted registration information:\n"
468
+ instructions, fields = client.register_info
469
+ fields.each { |info|
470
+ error_msg += "* #{info}\n"
471
+ }
472
+ error_msg += "(#{instructions})"
473
+ end
474
+ raise RegistrationError, error_msg
475
+ end
476
+ }
477
+ end
478
+ # Send a Jabber stanza over-the-wire.
479
+ def send!(msg)
480
+ attempt! {
481
+ client.send(msg)
482
+ }
483
+ end
484
+ def attempt!
485
+ attempts = 0
486
+ begin
487
+ attempts += 1
488
+ yield
489
+ rescue Errno::EPIPE, IOError => e
490
+ sleep 1
491
+ disconnect
492
+ reconnect
493
+ retry unless attempts > 3
494
+ raise e
495
+ rescue Errno::ECONNRESET => e
496
+ sleep (attempts^2) * 60 + 60
497
+ disconnect
498
+ reconnect
499
+ retry unless attempts > 3
500
+ raise e
501
+ end
502
+ end
503
+
504
+ # Use this to force the client to reconnect after a force_disconnect.
505
+ def reconnect
506
+ @disconnected = false
507
+ connect!
508
+ end
509
+
510
+ # Use this to force the client to disconnect and not automatically
511
+ # reconnect.
512
+ def disconnect
513
+ disconnect!
514
+ end
515
+
516
+ # Queue messages for delivery once a user has accepted our authorization
517
+ # request. Works in conjunction with the deferred delivery thread.
518
+ #
519
+ # You can use this method if you want to manually add friends and still
520
+ # have the message queued for later delivery.
521
+ def deliver_deferred(jid, message, type)
522
+ msg = {:to => jid, :message => message, :type => type}
523
+ queue(:pending_messages) << [msg]
524
+ end
525
+
526
+ # Checks if the PubSub service is set
527
+ def has_pubsub?
528
+ ! @pubsub.nil?
529
+ end
530
+
531
+ # Sets the PubSub service. Just one service is allowed.
532
+ def set_pubsub_service(service)
533
+ raise NotConnected, "You are not connected" if @disconnected
534
+ raise AlreadySet, "You already have a PubSub service. Currently it's not allowed to have more." if has_pubsub?
535
+ @pubsub = PubSub::ServiceHelper.new(@client, service)
536
+ @pubsub_jid = service
537
+
538
+ @pubsub.add_event_callback do |event|
539
+ queue(:received_events) << event
540
+ end
541
+ end
542
+
543
+ # Subscribe to a node.
544
+ def pubsubscribe_to(node)
545
+ raise NoPubSubService, "Have you forgot to call #set_pubsub_service ?" if ! has_pubsub?
546
+ @pubsub.subscribe_to(node)
547
+ end
548
+
549
+ # Unsubscribe from a node.
550
+ def pubunsubscribe_from(node)
551
+ raise NoPubSubService, "Have you forgot to call #set_pubsub_service ?" if ! has_pubsub?
552
+
553
+ # FIXME
554
+ # @pubsub.unsubscribe_from(node)
555
+ # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
556
+ # The above should just work, but I had to reimplement it since XMPP4R doesn't support subids
557
+ # and OpenFire (the Jabber Server I am testing against) seems to require it.
558
+
559
+ subids = find_subids_for(node)
560
+ return if subids.empty?
561
+
562
+ subids.each do |subid|
563
+ iq = Jabber::Iq.new(:set, @pubsub_jid)
564
+ iq.add(Jabber::PubSub::IqPubSub.new)
565
+ iq.from = @jid
566
+ unsub = REXML::Element.new('unsubscribe')
567
+ unsub.attributes['node'] = node
568
+ unsub.attributes['jid'] = @jid
569
+ unsub.attributes['subid'] = subid
570
+ iq.pubsub.add(unsub)
571
+ res = nil
572
+ @client.send_with_id(iq) do |reply|
573
+ res = reply.kind_of?(Jabber::Iq) and reply.type == :result
574
+ end # @stream.send_with_id(iq)
575
+ end
576
+ end
577
+
578
+ # Return the subscriptions we have in the configured PubSub service.
579
+ def pubsubscriptions
580
+ raise NoPubSubService, "Have you forgot to call #set_pubsub_service ?" if ! has_pubsub?
581
+ @pubsub.get_subscriptions_from_all_nodes()
582
+ end
583
+
584
+ # Just like #received_messages, but for PubSub events
585
+ def received_events(&block)
586
+ dequeue(:received_events, &block)
587
+ end
588
+
589
+ # Returns true if there are unprocessed received events waiting in the
590
+ # queue, false otherwise.
591
+ def received_events?
592
+ !queue(:received_events).empty?
593
+ end
594
+
595
+ # Create a PubSub node (Lots of options still have to be encoded!)
596
+ def create_node(node)
597
+ raise NoPubSubService, "Have you forgot to call #set_pubsub_service ?" if ! has_pubsub?
598
+ @pubsub.create_node(node)
599
+ end
600
+
601
+ # Return an array of nodes I own
602
+ def my_nodes
603
+ ret = []
604
+ pubsubscriptions.each do |sub|
605
+ ret << sub.node if sub.attributes['affiliation'] == 'owner'
606
+ end
607
+ return ret
608
+ end
609
+
610
+ # Delete a PubSub node (Lots of options still have to be encoded!)
611
+ def delete_node(node)
612
+ raise NoPubSubService, "Have you forgot to call #set_pubsub_service ?" if ! has_pubsub?
613
+ @pubsub.delete_node(node)
614
+ end
615
+
616
+ # Publish an Item. This infers an item of Jabber::PubSub::Item kind is passed
617
+ def publish_item(node, item)
618
+ raise NoPubSubService, "Have you forgot to call #set_pubsub_service ?" if ! has_pubsub?
619
+ @pubsub.publish_item_to(node, item)
620
+ end
621
+
622
+ # Publish Simple Item. This is an item with one element and some text to it.
623
+ def publish_simple_item(node, text)
624
+ raise NoPubSubService, "Have you forgot to call #set_pubsub_service ?" if ! has_pubsub?
625
+
626
+ item = Jabber::PubSub::Item.new
627
+ xml = REXML::Element.new('value')
628
+ xml.text = text
629
+ item.add(xml)
630
+ publish_item(node, item)
631
+ end
632
+
633
+ # Publish atom Item. This is an item with one atom entry with title, body and time.
634
+ def publish_atom_item(node, title, body, time = Time.now)
635
+ raise NoPubSubService, "Have you forgot to call #set_pubsub_service ?" if ! has_pubsub?
636
+
637
+ item = Jabber::PubSub::Item.new
638
+ entry = REXML::Element.new('entry')
639
+ entry.add_namespace("http://www.w3.org/2005/Atom")
640
+ mytitle = REXML::Element.new('title')
641
+ mytitle.text = title
642
+ entry.add(mytitle)
643
+ mybody = REXML::Element.new('body')
644
+ mybody.text = body
645
+ entry.add(mybody)
646
+ published = REXML::Element.new("published")
647
+ published.text = time.utc.iso8601
648
+ entry.add(published)
649
+ item.add(entry)
650
+ publish_item(node, item)
651
+ end
652
+
653
+ private
654
+
655
+ # If contacts is a single contact, returns a Jabber::Contact object
656
+ # representing that user; if contacts is an array, returns an array of
657
+ # Jabber::Contact objects.
658
+ #
659
+ # When called with a block, contacts will yield each Jabber::Contact object
660
+ # in turn. This is used internally.
661
+ def contacts(*contacts, &block)
662
+ contacts.flatten!
663
+ @contacts ||= {}
664
+ contakts = []
665
+ contacts.each do |contact|
666
+ jid = contact.to_s
667
+ unless @contacts[jid]
668
+ @contacts[jid] = contact.respond_to?(:ask_for_authorization!) ? contact : Contact.new(self, contact)
669
+ end
670
+ yield @contacts[jid] if block_given?
671
+ contakts << @contacts[jid]
672
+ end
673
+ contakts.size > 1 ? contakts : contakts.first
674
+ end
675
+
676
+ def find_subids_for(node)
677
+ ret = []
678
+ pubsubscriptions.each do |subscription|
679
+ if subscription.node == node
680
+ ret << subscription.subid
681
+ end
682
+ end
683
+ return ret
684
+ end
685
+
686
+ def client=(client)
687
+ self.roster = nil # ensure we clear the roster, since that's now associated with a different client.
688
+ @client = client
689
+ end
690
+
691
+ def roster=(new_roster)
692
+ @roster = new_roster
693
+ end
694
+
695
+ def connect!
696
+ raise ConnectionError, "Connections are disabled - use Jabber::Simple::force_connect() to reconnect." if @disconnected
697
+ # Pre-connect
698
+ @connect_mutex ||= Mutex.new
699
+
700
+ # don't try to connect if another thread is already connecting.
701
+ return if @connect_mutex.locked?
702
+
703
+ @connect_mutex.lock
704
+ disconnect!(false) if connected?
705
+
706
+ # Connect
707
+ jid = JID.new(@jid)
708
+ my_client = Client.new(@jid)
709
+ my_client.connect(@host, @port)
710
+ # (TODO: test w/ gtalk --danbri) my_client.connect(@server)
711
+ my_client.auth(@password) unless @register
712
+ self.client = my_client
713
+
714
+ # Post-connect
715
+ register_default_callbacks unless @register
716
+ status(@presence, @status_message) unless @register
717
+ @connect_mutex.unlock
718
+ end
719
+
720
+ def disconnect!(auto_reconnect = true)
721
+ if client.respond_to?(:is_connected?) && client.is_connected?
722
+ begin
723
+ client.close
724
+ rescue Errno::EPIPE, IOError => e
725
+ # probably should log this.
726
+ nil
727
+ end
728
+ end
729
+ client = nil
730
+ @disconnected = auto_reconnect
731
+ end
732
+
733
+ def register_default_callbacks
734
+ client.add_message_callback do |message|
735
+ queue(:received_messages) << message unless message.body.nil?
736
+ from = roster.find(message.from).values.first
737
+ if !message.chat_state.nil? && from.chat_state != message.chat_state
738
+ from.chat_state = message.chat_state
739
+ end
740
+ end
741
+
742
+ roster.add_subscription_callback do |roster_item, presence|
743
+ if presence.type == :subscribed
744
+ queue(:new_subscriptions) << [roster_item, presence]
745
+ end
746
+ end
747
+
748
+ roster.add_subscription_request_callback do |roster_item, presence|
749
+ if accept_subscriptions?
750
+ roster.accept_subscription(presence.from)
751
+ else
752
+ queue(:subscription_requests) << [roster_item, presence]
753
+ end
754
+ end
755
+
756
+ @iq_mutex = Mutex.new
757
+ capture_iq_stanzas!(false)
758
+ client.add_iq_callback do |iq|
759
+ queue(:iq_messages) << iq if capture_iq_stanzas?
760
+ end
761
+
762
+ @presence_updates = {}
763
+ @presence_mutex = Mutex.new
764
+ roster.add_presence_callback do |roster_item, old_presence, new_presence|
765
+ simple_jid = roster_item.jid.strip.to_s
766
+ presence = case new_presence.type
767
+ when nil then new_presence.show || :online
768
+ when :unavailable then :unavailable
769
+ else
770
+ nil
771
+ end
772
+
773
+ if presence && @presence_updates[simple_jid] != presence
774
+ queue(:presence_updates) << simple_jid
775
+ @presence_mutex.synchronize { @presence_updates[simple_jid] = [presence, new_presence.status] }
776
+ end
777
+ end
778
+ end
779
+
780
+ # This thread facilitates the delivery of messages to users who haven't yet
781
+ # accepted an invitation from us. When we attempt to deliver a message, if
782
+ # the user hasn't subscribed, we place the message in a queue for later
783
+ # delivery. Once a user has accepted our authorization request, we deliver
784
+ # any messages that have been queued up in the meantime.
785
+ def start_deferred_delivery_thread #:nodoc:
786
+ Thread.new {
787
+ loop {
788
+ messages = [queue(:pending_messages).pop].flatten
789
+ messages.each do |message|
790
+ if subscribed_to?(message[:to])
791
+ deliver(message[:to], message[:message], message[:type])
792
+ else
793
+ queue(:pending_messages) << message
794
+ end
795
+ end
796
+ sleep 60
797
+ }
798
+ }
799
+ end
800
+
801
+ def queue(queue)
802
+ @queues ||= Hash.new { |h,k| h[k] = Queue.new }
803
+ @queues[queue]
804
+ end
805
+
806
+ def dequeue(queue, non_blocking = true, max_items = 100, &block)
807
+ queue_items = []
808
+ max_items.times do
809
+ break if queue(queue).empty?
810
+ queue_item = queue(queue).pop(non_blocking)
811
+ queue_items << queue_item
812
+ yield queue_item if block_given?
813
+ end
814
+ queue_items
815
+ end
816
+ end
817
+ end
818
+
819
+ true