jabber4r-revive 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -4
- data/.rspec +3 -3
- data/.travis.yml +7 -7
- data/CHANGELOG +11 -1
- data/Gemfile +3 -3
- data/README.md +29 -29
- data/Rakefile +70 -70
- data/jabber4r-revive.gemspec +25 -25
- data/lib/jabber4r.rb +38 -33
- data/lib/jabber4r/bosh.rb +21 -0
- data/lib/jabber4r/bosh/authentication.rb +13 -0
- data/lib/jabber4r/bosh/authentication/non_sasl.rb +219 -0
- data/lib/jabber4r/bosh/authentication/sasl.rb +239 -0
- data/lib/jabber4r/bosh/session.rb +144 -0
- data/lib/jabber4r/connection.rb +259 -258
- data/lib/jabber4r/debugger.rb +60 -60
- data/lib/jabber4r/jid.rb +20 -19
- data/lib/jabber4r/protocol.rb +249 -257
- data/lib/jabber4r/protocol/authentication.rb +14 -0
- data/lib/jabber4r/protocol/authentication/non_sasl.rb +138 -0
- data/lib/jabber4r/protocol/authentication/sasl.rb +88 -0
- data/lib/jabber4r/protocol/iq.rb +259 -259
- data/lib/jabber4r/protocol/message.rb +245 -245
- data/lib/jabber4r/protocol/parsed_xml_element.rb +207 -207
- data/lib/jabber4r/protocol/presence.rb +160 -160
- data/lib/jabber4r/protocol/xml_element.rb +143 -143
- data/lib/jabber4r/rexml_1.8_patch.rb +15 -15
- data/lib/jabber4r/roster.rb +38 -38
- data/lib/jabber4r/session.rb +615 -615
- data/lib/jabber4r/version.rb +10 -3
- data/spec/lib/jabber4r/bosh/authentication/non_sasl_spec.rb +79 -0
- data/spec/lib/jabber4r/bosh/authentication/sasl_spec.rb +42 -0
- data/spec/lib/jabber4r/bosh/session_spec.rb +406 -0
- data/spec/lib/jabber4r/bosh_spec.rb +0 -0
- data/spec/lib/jabber4r/connection_spec.rb +174 -174
- data/spec/lib/jabber4r/debugger_spec.rb +35 -35
- data/spec/lib/jabber4r/jid_spec.rb +197 -197
- data/spec/lib/jabber4r/protocol/authentication/non_sasl_spec.rb +79 -0
- data/spec/lib/jabber4r/protocol/authentication/sasl_spec.rb +42 -0
- data/spec/spec_helper.rb +11 -11
- data/spec/support/mocks/tcp_socket_mock.rb +8 -8
- metadata +61 -45
- data/Gemfile.lock +0 -45
- data/lib/jabber4r/bosh_session.rb +0 -224
- data/spec/lib/jabber4r/bosh_session_spec.rb +0 -150
data/lib/jabber4r/session.rb
CHANGED
@@ -1,615 +1,615 @@
|
|
1
|
-
# License: see LICENSE.txt
|
2
|
-
# Jabber4R - Jabber Instant Messaging Library for Ruby
|
3
|
-
# Copyright (C) 2002 Rich Kilmer <rich@infoether.com>
|
4
|
-
#
|
5
|
-
|
6
|
-
|
7
|
-
module Jabber
|
8
|
-
HEX = "0123456789abcdef"
|
9
|
-
|
10
|
-
##
|
11
|
-
# Generates a random hex string in the following format:
|
12
|
-
# JRR_01234567
|
13
|
-
#
|
14
|
-
# return:: [String] The resource id
|
15
|
-
#
|
16
|
-
def Jabber.gen_random_resource
|
17
|
-
return Jabber.gen_random_id("JRR_", 8)
|
18
|
-
end
|
19
|
-
|
20
|
-
##
|
21
|
-
# Generates a random thread as a hex string in the following format:
|
22
|
-
# JRT_01234567890123456789
|
23
|
-
#
|
24
|
-
# return:: [String] The thread id
|
25
|
-
#
|
26
|
-
def Jabber.gen_random_thread
|
27
|
-
return Jabber.gen_random_id("JRT_", 20)
|
28
|
-
end
|
29
|
-
|
30
|
-
##
|
31
|
-
# Generates a random id as a hex string
|
32
|
-
#
|
33
|
-
# prefix:: [String="Jabber4R_] The prefix for the random hex data
|
34
|
-
# length:: [Integer=16] The number of hex characters
|
35
|
-
# return:: [String] The random id
|
36
|
-
#
|
37
|
-
def Jabber.gen_random_id(prefix="Jabber4R_", length=16)
|
38
|
-
length.times {prefix += HEX[rand(16),1]}
|
39
|
-
prefix
|
40
|
-
end
|
41
|
-
|
42
|
-
class Subscription
|
43
|
-
attr_accessor :type, :from, :id, :session
|
44
|
-
def initialize(session, type, from, id)
|
45
|
-
@session = session
|
46
|
-
@type = type
|
47
|
-
@from = from
|
48
|
-
@id = id
|
49
|
-
end
|
50
|
-
def accept
|
51
|
-
case type
|
52
|
-
when :subscribe
|
53
|
-
@session.connection.send(Jabber::Protocol::Presence.gen_accept_subscription(@id, @from))
|
54
|
-
when :unsubscribe
|
55
|
-
@session.connection.send(Jabber::Protocol::Presence.gen_accept_unsubscription(@id, @from))
|
56
|
-
else
|
57
|
-
raise "Cannot accept a subscription of type #{type.to_s}"
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
##
|
63
|
-
# This is a base class for subscription handlers
|
64
|
-
|
65
|
-
class SubscriptionHandler
|
66
|
-
def subscribe(subscription)
|
67
|
-
end
|
68
|
-
|
69
|
-
def subscribed(subscription)
|
70
|
-
end
|
71
|
-
|
72
|
-
def unsubscribe(subscription)
|
73
|
-
end
|
74
|
-
|
75
|
-
def unsubscribed(subscription)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
class AutoSubscriptionHandler < SubscriptionHandler
|
80
|
-
|
81
|
-
def subscribe(subscription)
|
82
|
-
subscription.accept
|
83
|
-
end
|
84
|
-
|
85
|
-
def unsubscribe(subscription)
|
86
|
-
subscription.accept
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
|
91
|
-
##
|
92
|
-
# The Jabber Session is the main class for dealing with a Jabber service.
|
93
|
-
#
|
94
|
-
class Session
|
95
|
-
|
96
|
-
# The
|
97
|
-
attr_reader :
|
98
|
-
|
99
|
-
# The port (defaults to 5222) that this session is connected to
|
100
|
-
attr_reader :port
|
101
|
-
|
102
|
-
# The Jabber::Protocol::Connection instance
|
103
|
-
attr_reader :connection
|
104
|
-
|
105
|
-
# The Jabber::Roster instance
|
106
|
-
attr_reader :roster
|
107
|
-
|
108
|
-
# The session id sent from the Jabber service upon connection
|
109
|
-
attr_reader :session_id
|
110
|
-
|
111
|
-
# The Jabber::JID of the current session
|
112
|
-
attr_reader :jid
|
113
|
-
|
114
|
-
# The username to use for authenticating this session
|
115
|
-
attr_accessor :username
|
116
|
-
|
117
|
-
# The password to use for authenticating this session
|
118
|
-
attr_accessor :password
|
119
|
-
|
120
|
-
# The resource id for this session
|
121
|
-
attr_accessor :resource
|
122
|
-
|
123
|
-
# The iq handlers for this session
|
124
|
-
attr_accessor :iqHandlers
|
125
|
-
|
126
|
-
##
|
127
|
-
# Session creation factory that creates a session, logs in,
|
128
|
-
# requests the roster, registers message and presence filters
|
129
|
-
# and announces initial presence. Login is done via plaintext
|
130
|
-
# password authentication.
|
131
|
-
#
|
132
|
-
# jid:: [String | JID] The account information ("account@
|
133
|
-
# password:: [String] The account password
|
134
|
-
# port:: [Integer = 5222] The
|
135
|
-
# digest:: [Boolean = false] Use digest authentication?
|
136
|
-
# return:: [Jabber::Session] The new session
|
137
|
-
#
|
138
|
-
def Session.bind(jid, password, port=5222, digest=false)
|
139
|
-
jid = Jabber::JID.new(jid) if jid.kind_of? String
|
140
|
-
session = Session.new(jid.
|
141
|
-
raise "Authentication failed" unless session.authenticate(jid.node, password, jid.resource, digest)
|
142
|
-
session.request_roster
|
143
|
-
session.register_message_filter
|
144
|
-
session.register_presence_filter
|
145
|
-
session.register_iq_filter
|
146
|
-
session.announce_initial_presence
|
147
|
-
session
|
148
|
-
end
|
149
|
-
|
150
|
-
##
|
151
|
-
# Account registration method
|
152
|
-
#
|
153
|
-
def Session.register(jid, password, email="", name="", port=5222)
|
154
|
-
jid = Jabber::JID.new(jid) if jid.kind_of? String
|
155
|
-
session = Session.new(jid.
|
156
|
-
msg_id = session.id
|
157
|
-
registered = false
|
158
|
-
current = Thread.current
|
159
|
-
session.connection.send(Jabber::Protocol::Iq.gen_registration(session, msg_id, jid.node, password, email, name)) do |element|
|
160
|
-
if element.element_tag=="iq" and element.attr_id==msg_id
|
161
|
-
element.consume_element
|
162
|
-
if element.attr_type=="result"
|
163
|
-
registered = true
|
164
|
-
elsif element.attr_type=="error"
|
165
|
-
registered = false
|
166
|
-
end
|
167
|
-
current.wakeup
|
168
|
-
end
|
169
|
-
end
|
170
|
-
Thread.stop
|
171
|
-
session.release
|
172
|
-
return registered
|
173
|
-
end
|
174
|
-
|
175
|
-
##
|
176
|
-
# Session creation factory that creates a session, logs in,
|
177
|
-
# requests the roster, registers message and presence filters
|
178
|
-
# and announces initial presence. Login is done via digest (SHA)
|
179
|
-
# password authentication.
|
180
|
-
#
|
181
|
-
# jid:: [String | JID] The account information ("account@
|
182
|
-
# password:: [String] The account password
|
183
|
-
# port:: [Integer = 5222] The
|
184
|
-
# return:: [Jabber::Session] The new session
|
185
|
-
#
|
186
|
-
def Session.bind_digest(jid, password, port=5222)
|
187
|
-
Session.bind(jid, password, port, true)
|
188
|
-
end
|
189
|
-
|
190
|
-
# Creates a new session connected to the supplied
|
191
|
-
# The method attempts to build a Jabber::Protocol::Connection
|
192
|
-
# object and send the open_stream XML message. It then blocks
|
193
|
-
# to recieve the coorisponding reply open_stream and sets the
|
194
|
-
# session_id from that xml element.
|
195
|
-
#
|
196
|
-
#
|
197
|
-
# port:: [Integer=5222] The port of the Jabber service
|
198
|
-
# raise:: [RuntimeException] If connection fails
|
199
|
-
#
|
200
|
-
def initialize(
|
201
|
-
@id = 1
|
202
|
-
@
|
203
|
-
@port = port
|
204
|
-
@roster = Roster.new(self)
|
205
|
-
@messageListeners = Hash.new
|
206
|
-
@iqHandlers=Hash.new
|
207
|
-
@subscriptionHandler = nil
|
208
|
-
@connection = Jabber::Connection.new(
|
209
|
-
@connection.connect
|
210
|
-
unless @connection.connected?
|
211
|
-
raise "Session Error: Could not connected to #{
|
212
|
-
else
|
213
|
-
@connection.send(Jabber::Protocol.gen_open_stream(
|
214
|
-
if element.element_tag=="stream:stream"
|
215
|
-
element.consume_element
|
216
|
-
@session_id = element.attr_id
|
217
|
-
end
|
218
|
-
end
|
219
|
-
@connection.on_connection_exception do |exception|
|
220
|
-
@session_failure_block.call if @session_failure_block
|
221
|
-
|
222
|
-
if exception.is_a? Jabber::ConnectionForceCloseError
|
223
|
-
@connection.force_close!
|
224
|
-
else
|
225
|
-
self.release
|
226
|
-
end
|
227
|
-
end
|
228
|
-
Thread.stop
|
229
|
-
end
|
230
|
-
end
|
231
|
-
|
232
|
-
##
|
233
|
-
# Set a handler for session exceptions that get caught in
|
234
|
-
# communicating with the Jabber server.
|
235
|
-
#
|
236
|
-
def on_session_failure(&block)
|
237
|
-
@session_failure_block = block
|
238
|
-
end
|
239
|
-
|
240
|
-
##
|
241
|
-
# Counter for message IDs
|
242
|
-
#
|
243
|
-
# return:: [String] A unique message id for this session
|
244
|
-
#
|
245
|
-
def id
|
246
|
-
@id = @id + 1
|
247
|
-
return @id.to_s
|
248
|
-
end
|
249
|
-
|
250
|
-
##
|
251
|
-
# Authenticate (logs into) this session with the supplied credentials.
|
252
|
-
# The method blocks waiting for a reply to the login message. Sets the
|
253
|
-
# authenticated attribute based on result.
|
254
|
-
#
|
255
|
-
# username:: [String] The username to use for authentication
|
256
|
-
# password:: [String] The password to use for authentication
|
257
|
-
# resource:: [String] The resource ID for this session
|
258
|
-
# digest:: [Boolean=false] True to use digest authentication (not sending password in the clear)
|
259
|
-
# return:: [Boolean] Whether the authentication succeeded or failed
|
260
|
-
#
|
261
|
-
def authenticate(username, password, resource, digest=false)
|
262
|
-
@username = username
|
263
|
-
@password = password
|
264
|
-
@resource = resource
|
265
|
-
@jid = JID.new("#{username}@#{@
|
266
|
-
@roster.add(@jid, "both", "Me", "My Resources")
|
267
|
-
|
268
|
-
msg_id = self.id
|
269
|
-
authHandler = Proc.new do |element|
|
270
|
-
if element.element_tag=="iq" and element.attr_id==msg_id
|
271
|
-
element.consume_element
|
272
|
-
if element.attr_type=="result"
|
273
|
-
@authenticated = true
|
274
|
-
elsif element.attr_type=="error"
|
275
|
-
@authenticated = false
|
276
|
-
end
|
277
|
-
end
|
278
|
-
end
|
279
|
-
if digest
|
280
|
-
require 'digest/sha1'
|
281
|
-
authRequest = Jabber::Protocol::Iq.gen_auth_digest(self, msg_id, username, Digest::SHA1.new(@session_id + password).hexdigest, resource)
|
282
|
-
else
|
283
|
-
authRequest = Jabber::Protocol::Iq.gen_auth(self, msg_id, username, password, resource)
|
284
|
-
end
|
285
|
-
@connection.send(authRequest, &authHandler)
|
286
|
-
Thread.stop
|
287
|
-
return @authenticated
|
288
|
-
end
|
289
|
-
|
290
|
-
##
|
291
|
-
# Is this an authenticated session?
|
292
|
-
#
|
293
|
-
# return:: [Boolean] True if the session is authenticated
|
294
|
-
#
|
295
|
-
def is_authenticated?
|
296
|
-
return @authenticated
|
297
|
-
end
|
298
|
-
|
299
|
-
##
|
300
|
-
# Sends the initial presence message to the Jabber service
|
301
|
-
#
|
302
|
-
def announce_initial_presence
|
303
|
-
@connection.send(Jabber::Protocol::Presence.gen_initial(id))
|
304
|
-
end
|
305
|
-
|
306
|
-
##
|
307
|
-
# Sends an extended away presence message
|
308
|
-
#
|
309
|
-
# status:: [String] The status message
|
310
|
-
#
|
311
|
-
def announce_extended_away(status=nil)
|
312
|
-
@connection.send(Jabber::Protocol::Presence.gen_xa(id, status))
|
313
|
-
end
|
314
|
-
|
315
|
-
##
|
316
|
-
# Sends a free for chat presence message
|
317
|
-
#
|
318
|
-
# status:: [String] The status message
|
319
|
-
#
|
320
|
-
def announce_free_for_chat(status=nil)
|
321
|
-
@connection.send(Jabber::Protocol::Presence.gen_chat(id, status))
|
322
|
-
end
|
323
|
-
|
324
|
-
##
|
325
|
-
# Sends a 'normal' presence message
|
326
|
-
#
|
327
|
-
# status:: [String] The status message
|
328
|
-
#
|
329
|
-
def announce_normal(status=nil)
|
330
|
-
@connection.send(Jabber::Protocol::Presence.gen_normal(id, status))
|
331
|
-
end
|
332
|
-
|
333
|
-
##
|
334
|
-
# Sends an away from computer presence message
|
335
|
-
#
|
336
|
-
# status:: [String] The status message
|
337
|
-
#
|
338
|
-
def announce_away_from_computer(status=nil)
|
339
|
-
@connection.send(Jabber::Protocol::Presence.gen_away(id, status))
|
340
|
-
end
|
341
|
-
|
342
|
-
##
|
343
|
-
# Sends a do not disturb presence message
|
344
|
-
#
|
345
|
-
# status:: [String] The status message
|
346
|
-
#
|
347
|
-
def announce_do_not_disturb(status=nil)
|
348
|
-
@connection.send(Jabber::Protocol::Presence.gen_dnd(id, status))
|
349
|
-
end
|
350
|
-
|
351
|
-
##
|
352
|
-
# Sets the handler for subscription requests, notifications, etc.
|
353
|
-
#
|
354
|
-
def set_subscription_handler(handler=nil, &block)
|
355
|
-
@subscriptionHandler = handler.new(self) if handler
|
356
|
-
@subscriptionHandler = block if block_given? and !handler
|
357
|
-
end
|
358
|
-
|
359
|
-
def enable_autosubscription
|
360
|
-
set_subscription_handler AutoSubscriptionHandler
|
361
|
-
end
|
362
|
-
|
363
|
-
def subscribe(to, name="")
|
364
|
-
to = JID.to_jid(to)
|
365
|
-
roster_item = @roster[to]
|
366
|
-
|
367
|
-
if roster_item #if you already have a roster item just send the subscribe request
|
368
|
-
if roster_item.subscription=="to" or roster_item.subscription=="both"
|
369
|
-
return
|
370
|
-
end
|
371
|
-
@connection.send(Jabber::Protocol::Presence.gen_new_subscription(to))
|
372
|
-
return
|
373
|
-
end
|
374
|
-
myid = self.id
|
375
|
-
@connection.send(Jabber::Protocol::Iq.gen_add_rosteritem(self, myid, to, name)) do |element|
|
376
|
-
if element.attr_id==myid
|
377
|
-
element.consume_element
|
378
|
-
if element.attr_type=="result"
|
379
|
-
@connection.send(Jabber::Protocol::Presence.gen_new_subscription(to))
|
380
|
-
end
|
381
|
-
end
|
382
|
-
end
|
383
|
-
end
|
384
|
-
|
385
|
-
##
|
386
|
-
# Adds a filter to the Connection to manage tracking resources that come online/offline
|
387
|
-
#
|
388
|
-
def register_presence_filter
|
389
|
-
@connection.add_filter("presenceAvailableFilter") do |element|
|
390
|
-
if element.element_tag=="presence"
|
391
|
-
type = element.attr_type
|
392
|
-
type = nil if type.nil?
|
393
|
-
case type
|
394
|
-
when nil, "available"
|
395
|
-
element.consume_element
|
396
|
-
from = JID.new(element.attr_from)
|
397
|
-
rItem = @roster[from]
|
398
|
-
show = element.show.element_data
|
399
|
-
show = "chat" unless show
|
400
|
-
status = element.status.element_data
|
401
|
-
status = "" unless status
|
402
|
-
if rItem
|
403
|
-
resource = rItem[from.resource]
|
404
|
-
if resource
|
405
|
-
resource.update(show, status)
|
406
|
-
else
|
407
|
-
rItem.add(from.resource, show, status)
|
408
|
-
end
|
409
|
-
end
|
410
|
-
when "unavailable"
|
411
|
-
element.consume_element
|
412
|
-
from = JID.new(element.attr_from)
|
413
|
-
rItem = @roster[from]
|
414
|
-
resource = rItem.delete(from.resource) if rItem
|
415
|
-
when "subscribe", "unsubscribe", "subscribed", "unsubscribed"
|
416
|
-
element.consume_element
|
417
|
-
from = JID.new(element.attr_from)
|
418
|
-
break unless @subscriptionHandler
|
419
|
-
if @subscriptionHandler.kind_of? Proc
|
420
|
-
@subscriptionHandler.call(Subscription.new(self, type.intern, from, id))
|
421
|
-
else
|
422
|
-
@subscriptionHandler.send(Subscription.new(self, type.intern, from, id))
|
423
|
-
end
|
424
|
-
end
|
425
|
-
end #if presence
|
426
|
-
end #do
|
427
|
-
end
|
428
|
-
|
429
|
-
##
|
430
|
-
# Creates a new message to the supplied JID of type NORMAL
|
431
|
-
#
|
432
|
-
# to:: [Jabber::JID] Who to send the message to
|
433
|
-
# type:: [String = Jabber::Protocol::Message::NORMAL] The type of message to send (see Jabber::Protocol::Message)
|
434
|
-
# return:: [Jabber::Protocol::Message] The new message
|
435
|
-
#
|
436
|
-
def new_message(to, type=Jabber::Protocol::Message::NORMAL)
|
437
|
-
msg = Jabber::Protocol::Message.new(to, type)
|
438
|
-
msg.session=self
|
439
|
-
return msg
|
440
|
-
end
|
441
|
-
|
442
|
-
##
|
443
|
-
# Creates a new message addressed to the supplied JID of type CHAT
|
444
|
-
#
|
445
|
-
# to:: [JID] Who to send the message to
|
446
|
-
# return:: [Jabber::Protocol::Message] The new (chat) message
|
447
|
-
#
|
448
|
-
def new_chat_message(to)
|
449
|
-
self.new_message(to, Jabber::Protocol::Message::CHAT)
|
450
|
-
end
|
451
|
-
|
452
|
-
##
|
453
|
-
# Creates a new message addressed to the supplied JID of type GROUPCHAT
|
454
|
-
#
|
455
|
-
# to:: [JID] Who to send the message to
|
456
|
-
# return:: [Jabber::Protocol::Message] The new (group chat) message
|
457
|
-
#
|
458
|
-
def new_group_chat_message(to)
|
459
|
-
self.new_message(to, Jabber::Protocol::Message::GROUPCHAT)
|
460
|
-
end
|
461
|
-
|
462
|
-
##
|
463
|
-
# Adds a filter to the Connection to manage tracking messages to forward
|
464
|
-
# to registered message listeners.
|
465
|
-
#
|
466
|
-
def register_message_filter
|
467
|
-
@connection.add_filter("messageFilter") do |element|
|
468
|
-
if element.element_tag=="message" and @messageListeners.size > 0
|
469
|
-
element.consume_element
|
470
|
-
message = Jabber::Protocol::Message.from_element(self, element)
|
471
|
-
notify_message_listeners(message)
|
472
|
-
end #if message
|
473
|
-
end #do
|
474
|
-
end
|
475
|
-
|
476
|
-
##
|
477
|
-
# Add a listener for new messages
|
478
|
-
#
|
479
|
-
# Usage::
|
480
|
-
# id = session.add_message_listener do |message|
|
481
|
-
# puts message
|
482
|
-
# end
|
483
|
-
#
|
484
|
-
# &block [Block] The block to process a message
|
485
|
-
# return:: [String] The listener ID...used to remove the listener
|
486
|
-
#
|
487
|
-
def add_message_listener(&block)
|
488
|
-
id = Jabber.gen_random_id("", 10)
|
489
|
-
@messageListeners[id]=block if block
|
490
|
-
return id
|
491
|
-
end
|
492
|
-
|
493
|
-
##
|
494
|
-
# Deletes a message listener
|
495
|
-
#
|
496
|
-
# id:: [String] A messanger ID returned from add_message_listener
|
497
|
-
#
|
498
|
-
def delete_message_listener(lid)
|
499
|
-
@messageListeners.delete(lid)
|
500
|
-
end
|
501
|
-
|
502
|
-
#Cleanup methods
|
503
|
-
|
504
|
-
##
|
505
|
-
# Releases the connection and resets the session. The Session instance
|
506
|
-
# is no longer usable after this method is called
|
507
|
-
#
|
508
|
-
def release
|
509
|
-
begin
|
510
|
-
@connection.on_connection_exception do
|
511
|
-
#Do nothing...we are shutting down
|
512
|
-
end
|
513
|
-
@connection.send(Jabber::Protocol::Presence.gen_unavailable(id))
|
514
|
-
@connection.send(Jabber::Protocol.gen_close_stream)
|
515
|
-
rescue
|
516
|
-
#ignore error
|
517
|
-
end
|
518
|
-
begin
|
519
|
-
@connection.close
|
520
|
-
rescue
|
521
|
-
#ignore error
|
522
|
-
end
|
523
|
-
end
|
524
|
-
|
525
|
-
##
|
526
|
-
# Same as _release
|
527
|
-
#
|
528
|
-
def close
|
529
|
-
release
|
530
|
-
end
|
531
|
-
|
532
|
-
##
|
533
|
-
# Requests the Roster for the (authenticated) account. This method blocks
|
534
|
-
# until a reply to the roster request is received.
|
535
|
-
#
|
536
|
-
def request_roster
|
537
|
-
if @authenticated
|
538
|
-
msg_id = id
|
539
|
-
@connection.send(Jabber::Protocol::Iq.gen_roster(self, msg_id)) do |element|
|
540
|
-
if element.attr_id == msg_id
|
541
|
-
element.consume_element
|
542
|
-
element.query.item.count.times do |i|
|
543
|
-
item = element.query.item[i]
|
544
|
-
@roster.add(item.attr_jid, item.attr_subscription, item.attr_name, item.group.element_data)
|
545
|
-
end
|
546
|
-
end
|
547
|
-
end
|
548
|
-
Thread.stop
|
549
|
-
register_roster_filter
|
550
|
-
end
|
551
|
-
end
|
552
|
-
|
553
|
-
##
|
554
|
-
# Registers the roster filter with the Connection to forward IQ requests
|
555
|
-
# to the IQ listeners(they register by namespace)
|
556
|
-
#
|
557
|
-
def register_iq_filter()
|
558
|
-
@connection.add_filter("iqFilter") do |element|
|
559
|
-
if element.element_tag=="iq" then
|
560
|
-
element.consume_element
|
561
|
-
query=element.query
|
562
|
-
h=@iqHandlers[query.attr_xmlns]
|
563
|
-
h.call(Jabber::Protocol::Iq.from_element(self,element)) if h
|
564
|
-
end
|
565
|
-
end
|
566
|
-
end
|
567
|
-
|
568
|
-
|
569
|
-
##
|
570
|
-
# Registers the roster filter with the Connection to forward roster changes to
|
571
|
-
# the roster listeners.
|
572
|
-
#
|
573
|
-
def register_roster_filter
|
574
|
-
@connection.add_filter("rosterFilter") do |element|
|
575
|
-
if element.element_tag=="iq" and element.query.attr_xmlns=="jabber:iq:roster" and element.attr_type=="set"
|
576
|
-
element.consume_element
|
577
|
-
item = element.query.item
|
578
|
-
if item.attr_subscription=="remove" then
|
579
|
-
@roster.remove(item.attr_jid)
|
580
|
-
else
|
581
|
-
@roster.add(item.attr_jid, item.attr_subscription, item.attr_name, item.group.element_data)
|
582
|
-
end
|
583
|
-
end
|
584
|
-
end
|
585
|
-
end
|
586
|
-
|
587
|
-
##
|
588
|
-
# Registers a listener for roster events
|
589
|
-
#
|
590
|
-
# &block:: [Block] The listener block to process roster changes
|
591
|
-
# return:: [String] A roster ID to use when removing this listener
|
592
|
-
#
|
593
|
-
def add_roster_listener(&block)
|
594
|
-
roster.add_listener(&block)
|
595
|
-
end
|
596
|
-
|
597
|
-
##
|
598
|
-
# Deletes the roster listener
|
599
|
-
#
|
600
|
-
# id:: [String] A roster ID received from the add_roster_listener method
|
601
|
-
#
|
602
|
-
def delete_roster_listener(id)
|
603
|
-
roster.delete_listener(id)
|
604
|
-
end
|
605
|
-
|
606
|
-
##
|
607
|
-
# Notifies message listeners of the received message
|
608
|
-
#
|
609
|
-
# message:: [Jabber::Protocol::Message] The received message
|
610
|
-
#
|
611
|
-
def notify_message_listeners(message)
|
612
|
-
@messageListeners.each_value {|listener| listener.call(message)}
|
613
|
-
end
|
614
|
-
end
|
615
|
-
end
|
1
|
+
# License: see LICENSE.txt
|
2
|
+
# Jabber4R - Jabber Instant Messaging Library for Ruby
|
3
|
+
# Copyright (C) 2002 Rich Kilmer <rich@infoether.com>
|
4
|
+
#
|
5
|
+
|
6
|
+
|
7
|
+
module Jabber
|
8
|
+
HEX = "0123456789abcdef"
|
9
|
+
|
10
|
+
##
|
11
|
+
# Generates a random hex string in the following format:
|
12
|
+
# JRR_01234567
|
13
|
+
#
|
14
|
+
# return:: [String] The resource id
|
15
|
+
#
|
16
|
+
def Jabber.gen_random_resource
|
17
|
+
return Jabber.gen_random_id("JRR_", 8)
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# Generates a random thread as a hex string in the following format:
|
22
|
+
# JRT_01234567890123456789
|
23
|
+
#
|
24
|
+
# return:: [String] The thread id
|
25
|
+
#
|
26
|
+
def Jabber.gen_random_thread
|
27
|
+
return Jabber.gen_random_id("JRT_", 20)
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Generates a random id as a hex string
|
32
|
+
#
|
33
|
+
# prefix:: [String="Jabber4R_] The prefix for the random hex data
|
34
|
+
# length:: [Integer=16] The number of hex characters
|
35
|
+
# return:: [String] The random id
|
36
|
+
#
|
37
|
+
def Jabber.gen_random_id(prefix="Jabber4R_", length=16)
|
38
|
+
length.times {prefix += HEX[rand(16),1]}
|
39
|
+
prefix
|
40
|
+
end
|
41
|
+
|
42
|
+
class Subscription
|
43
|
+
attr_accessor :type, :from, :id, :session
|
44
|
+
def initialize(session, type, from, id)
|
45
|
+
@session = session
|
46
|
+
@type = type
|
47
|
+
@from = from
|
48
|
+
@id = id
|
49
|
+
end
|
50
|
+
def accept
|
51
|
+
case type
|
52
|
+
when :subscribe
|
53
|
+
@session.connection.send(Jabber::Protocol::Presence.gen_accept_subscription(@id, @from))
|
54
|
+
when :unsubscribe
|
55
|
+
@session.connection.send(Jabber::Protocol::Presence.gen_accept_unsubscription(@id, @from))
|
56
|
+
else
|
57
|
+
raise "Cannot accept a subscription of type #{type.to_s}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# This is a base class for subscription handlers
|
64
|
+
|
65
|
+
class SubscriptionHandler
|
66
|
+
def subscribe(subscription)
|
67
|
+
end
|
68
|
+
|
69
|
+
def subscribed(subscription)
|
70
|
+
end
|
71
|
+
|
72
|
+
def unsubscribe(subscription)
|
73
|
+
end
|
74
|
+
|
75
|
+
def unsubscribed(subscription)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class AutoSubscriptionHandler < SubscriptionHandler
|
80
|
+
|
81
|
+
def subscribe(subscription)
|
82
|
+
subscription.accept
|
83
|
+
end
|
84
|
+
|
85
|
+
def unsubscribe(subscription)
|
86
|
+
subscription.accept
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
##
|
92
|
+
# The Jabber Session is the main class for dealing with a Jabber service.
|
93
|
+
#
|
94
|
+
class Session
|
95
|
+
|
96
|
+
# The domain this session is connected to
|
97
|
+
attr_reader :domain
|
98
|
+
|
99
|
+
# The port (defaults to 5222) that this session is connected to
|
100
|
+
attr_reader :port
|
101
|
+
|
102
|
+
# The Jabber::Protocol::Connection instance
|
103
|
+
attr_reader :connection
|
104
|
+
|
105
|
+
# The Jabber::Roster instance
|
106
|
+
attr_reader :roster
|
107
|
+
|
108
|
+
# The session id sent from the Jabber service upon connection
|
109
|
+
attr_reader :session_id
|
110
|
+
|
111
|
+
# The Jabber::JID of the current session
|
112
|
+
attr_reader :jid
|
113
|
+
|
114
|
+
# The username to use for authenticating this session
|
115
|
+
attr_accessor :username
|
116
|
+
|
117
|
+
# The password to use for authenticating this session
|
118
|
+
attr_accessor :password
|
119
|
+
|
120
|
+
# The resource id for this session
|
121
|
+
attr_accessor :resource
|
122
|
+
|
123
|
+
# The iq handlers for this session
|
124
|
+
attr_accessor :iqHandlers
|
125
|
+
|
126
|
+
##
|
127
|
+
# Session creation factory that creates a session, logs in,
|
128
|
+
# requests the roster, registers message and presence filters
|
129
|
+
# and announces initial presence. Login is done via plaintext
|
130
|
+
# password authentication.
|
131
|
+
#
|
132
|
+
# jid:: [String | JID] The account information ("account@domain/resouce")
|
133
|
+
# password:: [String] The account password
|
134
|
+
# port:: [Integer = 5222] The domain port
|
135
|
+
# digest:: [Boolean = false] Use digest authentication?
|
136
|
+
# return:: [Jabber::Session] The new session
|
137
|
+
#
|
138
|
+
def Session.bind(jid, password, port=5222, digest=false)
|
139
|
+
jid = Jabber::JID.new(jid) if jid.kind_of? String
|
140
|
+
session = Session.new(jid.domain, port)
|
141
|
+
raise "Authentication failed" unless session.authenticate(jid.node, password, jid.resource, digest)
|
142
|
+
session.request_roster
|
143
|
+
session.register_message_filter
|
144
|
+
session.register_presence_filter
|
145
|
+
session.register_iq_filter
|
146
|
+
session.announce_initial_presence
|
147
|
+
session
|
148
|
+
end
|
149
|
+
|
150
|
+
##
|
151
|
+
# Account registration method
|
152
|
+
#
|
153
|
+
def Session.register(jid, password, email="", name="", port=5222)
|
154
|
+
jid = Jabber::JID.new(jid) if jid.kind_of? String
|
155
|
+
session = Session.new(jid.domain, port)
|
156
|
+
msg_id = session.id
|
157
|
+
registered = false
|
158
|
+
current = Thread.current
|
159
|
+
session.connection.send(Jabber::Protocol::Iq.gen_registration(session, msg_id, jid.node, password, email, name)) do |element|
|
160
|
+
if element.element_tag=="iq" and element.attr_id==msg_id
|
161
|
+
element.consume_element
|
162
|
+
if element.attr_type=="result"
|
163
|
+
registered = true
|
164
|
+
elsif element.attr_type=="error"
|
165
|
+
registered = false
|
166
|
+
end
|
167
|
+
current.wakeup
|
168
|
+
end
|
169
|
+
end
|
170
|
+
Thread.stop
|
171
|
+
session.release
|
172
|
+
return registered
|
173
|
+
end
|
174
|
+
|
175
|
+
##
|
176
|
+
# Session creation factory that creates a session, logs in,
|
177
|
+
# requests the roster, registers message and presence filters
|
178
|
+
# and announces initial presence. Login is done via digest (SHA)
|
179
|
+
# password authentication.
|
180
|
+
#
|
181
|
+
# jid:: [String | JID] The account information ("account@domain/resouce")
|
182
|
+
# password:: [String] The account password
|
183
|
+
# port:: [Integer = 5222] The domain port
|
184
|
+
# return:: [Jabber::Session] The new session
|
185
|
+
#
|
186
|
+
def Session.bind_digest(jid, password, port=5222)
|
187
|
+
Session.bind(jid, password, port, true)
|
188
|
+
end
|
189
|
+
|
190
|
+
# Creates a new session connected to the supplied domain and port.
|
191
|
+
# The method attempts to build a Jabber::Protocol::Connection
|
192
|
+
# object and send the open_stream XML message. It then blocks
|
193
|
+
# to recieve the coorisponding reply open_stream and sets the
|
194
|
+
# session_id from that xml element.
|
195
|
+
#
|
196
|
+
# domain:: [String] The domain indentificator of the Jabber service
|
197
|
+
# port:: [Integer=5222] The port of the Jabber service
|
198
|
+
# raise:: [RuntimeException] If connection fails
|
199
|
+
#
|
200
|
+
def initialize(domain, port=5222)
|
201
|
+
@id = 1
|
202
|
+
@domain = domain
|
203
|
+
@port = port
|
204
|
+
@roster = Roster.new(self)
|
205
|
+
@messageListeners = Hash.new
|
206
|
+
@iqHandlers=Hash.new
|
207
|
+
@subscriptionHandler = nil
|
208
|
+
@connection = Jabber::Connection.new(domain, port)
|
209
|
+
@connection.connect
|
210
|
+
unless @connection.connected?
|
211
|
+
raise "Session Error: Could not connected to #{domain}:#{port}"
|
212
|
+
else
|
213
|
+
@connection.send(Jabber::Protocol.gen_open_stream(domain)) do |element|
|
214
|
+
if element.element_tag=="stream:stream"
|
215
|
+
element.consume_element
|
216
|
+
@session_id = element.attr_id
|
217
|
+
end
|
218
|
+
end
|
219
|
+
@connection.on_connection_exception do |exception|
|
220
|
+
@session_failure_block.call if @session_failure_block
|
221
|
+
|
222
|
+
if exception.is_a? Jabber::ConnectionForceCloseError
|
223
|
+
@connection.force_close!
|
224
|
+
else
|
225
|
+
self.release
|
226
|
+
end
|
227
|
+
end
|
228
|
+
Thread.stop
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
##
|
233
|
+
# Set a handler for session exceptions that get caught in
|
234
|
+
# communicating with the Jabber server.
|
235
|
+
#
|
236
|
+
def on_session_failure(&block)
|
237
|
+
@session_failure_block = block
|
238
|
+
end
|
239
|
+
|
240
|
+
##
|
241
|
+
# Counter for message IDs
|
242
|
+
#
|
243
|
+
# return:: [String] A unique message id for this session
|
244
|
+
#
|
245
|
+
def id
|
246
|
+
@id = @id + 1
|
247
|
+
return @id.to_s
|
248
|
+
end
|
249
|
+
|
250
|
+
##
|
251
|
+
# Authenticate (logs into) this session with the supplied credentials.
|
252
|
+
# The method blocks waiting for a reply to the login message. Sets the
|
253
|
+
# authenticated attribute based on result.
|
254
|
+
#
|
255
|
+
# username:: [String] The username to use for authentication
|
256
|
+
# password:: [String] The password to use for authentication
|
257
|
+
# resource:: [String] The resource ID for this session
|
258
|
+
# digest:: [Boolean=false] True to use digest authentication (not sending password in the clear)
|
259
|
+
# return:: [Boolean] Whether the authentication succeeded or failed
|
260
|
+
#
|
261
|
+
def authenticate(username, password, resource, digest=false)
|
262
|
+
@username = username
|
263
|
+
@password = password
|
264
|
+
@resource = resource
|
265
|
+
@jid = JID.new("#{username}@#{@domain}/#{resource}")
|
266
|
+
@roster.add(@jid, "both", "Me", "My Resources")
|
267
|
+
|
268
|
+
msg_id = self.id
|
269
|
+
authHandler = Proc.new do |element|
|
270
|
+
if element.element_tag=="iq" and element.attr_id==msg_id
|
271
|
+
element.consume_element
|
272
|
+
if element.attr_type=="result"
|
273
|
+
@authenticated = true
|
274
|
+
elsif element.attr_type=="error"
|
275
|
+
@authenticated = false
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
if digest
|
280
|
+
require 'digest/sha1'
|
281
|
+
authRequest = Jabber::Protocol::Iq.gen_auth_digest(self, msg_id, username, Digest::SHA1.new(@session_id + password).hexdigest, resource)
|
282
|
+
else
|
283
|
+
authRequest = Jabber::Protocol::Iq.gen_auth(self, msg_id, username, password, resource)
|
284
|
+
end
|
285
|
+
@connection.send(authRequest, &authHandler)
|
286
|
+
Thread.stop
|
287
|
+
return @authenticated
|
288
|
+
end
|
289
|
+
|
290
|
+
##
|
291
|
+
# Is this an authenticated session?
|
292
|
+
#
|
293
|
+
# return:: [Boolean] True if the session is authenticated
|
294
|
+
#
|
295
|
+
def is_authenticated?
|
296
|
+
return @authenticated
|
297
|
+
end
|
298
|
+
|
299
|
+
##
|
300
|
+
# Sends the initial presence message to the Jabber service
|
301
|
+
#
|
302
|
+
def announce_initial_presence
|
303
|
+
@connection.send(Jabber::Protocol::Presence.gen_initial(id))
|
304
|
+
end
|
305
|
+
|
306
|
+
##
|
307
|
+
# Sends an extended away presence message
|
308
|
+
#
|
309
|
+
# status:: [String] The status message
|
310
|
+
#
|
311
|
+
def announce_extended_away(status=nil)
|
312
|
+
@connection.send(Jabber::Protocol::Presence.gen_xa(id, status))
|
313
|
+
end
|
314
|
+
|
315
|
+
##
|
316
|
+
# Sends a free for chat presence message
|
317
|
+
#
|
318
|
+
# status:: [String] The status message
|
319
|
+
#
|
320
|
+
def announce_free_for_chat(status=nil)
|
321
|
+
@connection.send(Jabber::Protocol::Presence.gen_chat(id, status))
|
322
|
+
end
|
323
|
+
|
324
|
+
##
|
325
|
+
# Sends a 'normal' presence message
|
326
|
+
#
|
327
|
+
# status:: [String] The status message
|
328
|
+
#
|
329
|
+
def announce_normal(status=nil)
|
330
|
+
@connection.send(Jabber::Protocol::Presence.gen_normal(id, status))
|
331
|
+
end
|
332
|
+
|
333
|
+
##
|
334
|
+
# Sends an away from computer presence message
|
335
|
+
#
|
336
|
+
# status:: [String] The status message
|
337
|
+
#
|
338
|
+
def announce_away_from_computer(status=nil)
|
339
|
+
@connection.send(Jabber::Protocol::Presence.gen_away(id, status))
|
340
|
+
end
|
341
|
+
|
342
|
+
##
|
343
|
+
# Sends a do not disturb presence message
|
344
|
+
#
|
345
|
+
# status:: [String] The status message
|
346
|
+
#
|
347
|
+
def announce_do_not_disturb(status=nil)
|
348
|
+
@connection.send(Jabber::Protocol::Presence.gen_dnd(id, status))
|
349
|
+
end
|
350
|
+
|
351
|
+
##
|
352
|
+
# Sets the handler for subscription requests, notifications, etc.
|
353
|
+
#
|
354
|
+
def set_subscription_handler(handler=nil, &block)
|
355
|
+
@subscriptionHandler = handler.new(self) if handler
|
356
|
+
@subscriptionHandler = block if block_given? and !handler
|
357
|
+
end
|
358
|
+
|
359
|
+
def enable_autosubscription
|
360
|
+
set_subscription_handler AutoSubscriptionHandler
|
361
|
+
end
|
362
|
+
|
363
|
+
def subscribe(to, name="")
|
364
|
+
to = JID.to_jid(to)
|
365
|
+
roster_item = @roster[to]
|
366
|
+
|
367
|
+
if roster_item #if you already have a roster item just send the subscribe request
|
368
|
+
if roster_item.subscription=="to" or roster_item.subscription=="both"
|
369
|
+
return
|
370
|
+
end
|
371
|
+
@connection.send(Jabber::Protocol::Presence.gen_new_subscription(to))
|
372
|
+
return
|
373
|
+
end
|
374
|
+
myid = self.id
|
375
|
+
@connection.send(Jabber::Protocol::Iq.gen_add_rosteritem(self, myid, to, name)) do |element|
|
376
|
+
if element.attr_id==myid
|
377
|
+
element.consume_element
|
378
|
+
if element.attr_type=="result"
|
379
|
+
@connection.send(Jabber::Protocol::Presence.gen_new_subscription(to))
|
380
|
+
end
|
381
|
+
end
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
##
|
386
|
+
# Adds a filter to the Connection to manage tracking resources that come online/offline
|
387
|
+
#
|
388
|
+
def register_presence_filter
|
389
|
+
@connection.add_filter("presenceAvailableFilter") do |element|
|
390
|
+
if element.element_tag=="presence"
|
391
|
+
type = element.attr_type
|
392
|
+
type = nil if type.nil?
|
393
|
+
case type
|
394
|
+
when nil, "available"
|
395
|
+
element.consume_element
|
396
|
+
from = JID.new(element.attr_from)
|
397
|
+
rItem = @roster[from]
|
398
|
+
show = element.show.element_data
|
399
|
+
show = "chat" unless show
|
400
|
+
status = element.status.element_data
|
401
|
+
status = "" unless status
|
402
|
+
if rItem
|
403
|
+
resource = rItem[from.resource]
|
404
|
+
if resource
|
405
|
+
resource.update(show, status)
|
406
|
+
else
|
407
|
+
rItem.add(from.resource, show, status)
|
408
|
+
end
|
409
|
+
end
|
410
|
+
when "unavailable"
|
411
|
+
element.consume_element
|
412
|
+
from = JID.new(element.attr_from)
|
413
|
+
rItem = @roster[from]
|
414
|
+
resource = rItem.delete(from.resource) if rItem
|
415
|
+
when "subscribe", "unsubscribe", "subscribed", "unsubscribed"
|
416
|
+
element.consume_element
|
417
|
+
from = JID.new(element.attr_from)
|
418
|
+
break unless @subscriptionHandler
|
419
|
+
if @subscriptionHandler.kind_of? Proc
|
420
|
+
@subscriptionHandler.call(Subscription.new(self, type.intern, from, id))
|
421
|
+
else
|
422
|
+
@subscriptionHandler.send(Subscription.new(self, type.intern, from, id))
|
423
|
+
end
|
424
|
+
end
|
425
|
+
end #if presence
|
426
|
+
end #do
|
427
|
+
end
|
428
|
+
|
429
|
+
##
|
430
|
+
# Creates a new message to the supplied JID of type NORMAL
|
431
|
+
#
|
432
|
+
# to:: [Jabber::JID] Who to send the message to
|
433
|
+
# type:: [String = Jabber::Protocol::Message::NORMAL] The type of message to send (see Jabber::Protocol::Message)
|
434
|
+
# return:: [Jabber::Protocol::Message] The new message
|
435
|
+
#
|
436
|
+
def new_message(to, type=Jabber::Protocol::Message::NORMAL)
|
437
|
+
msg = Jabber::Protocol::Message.new(to, type)
|
438
|
+
msg.session=self
|
439
|
+
return msg
|
440
|
+
end
|
441
|
+
|
442
|
+
##
|
443
|
+
# Creates a new message addressed to the supplied JID of type CHAT
|
444
|
+
#
|
445
|
+
# to:: [JID] Who to send the message to
|
446
|
+
# return:: [Jabber::Protocol::Message] The new (chat) message
|
447
|
+
#
|
448
|
+
def new_chat_message(to)
|
449
|
+
self.new_message(to, Jabber::Protocol::Message::CHAT)
|
450
|
+
end
|
451
|
+
|
452
|
+
##
|
453
|
+
# Creates a new message addressed to the supplied JID of type GROUPCHAT
|
454
|
+
#
|
455
|
+
# to:: [JID] Who to send the message to
|
456
|
+
# return:: [Jabber::Protocol::Message] The new (group chat) message
|
457
|
+
#
|
458
|
+
def new_group_chat_message(to)
|
459
|
+
self.new_message(to, Jabber::Protocol::Message::GROUPCHAT)
|
460
|
+
end
|
461
|
+
|
462
|
+
##
|
463
|
+
# Adds a filter to the Connection to manage tracking messages to forward
|
464
|
+
# to registered message listeners.
|
465
|
+
#
|
466
|
+
def register_message_filter
|
467
|
+
@connection.add_filter("messageFilter") do |element|
|
468
|
+
if element.element_tag=="message" and @messageListeners.size > 0
|
469
|
+
element.consume_element
|
470
|
+
message = Jabber::Protocol::Message.from_element(self, element)
|
471
|
+
notify_message_listeners(message)
|
472
|
+
end #if message
|
473
|
+
end #do
|
474
|
+
end
|
475
|
+
|
476
|
+
##
|
477
|
+
# Add a listener for new messages
|
478
|
+
#
|
479
|
+
# Usage::
|
480
|
+
# id = session.add_message_listener do |message|
|
481
|
+
# puts message
|
482
|
+
# end
|
483
|
+
#
|
484
|
+
# &block [Block] The block to process a message
|
485
|
+
# return:: [String] The listener ID...used to remove the listener
|
486
|
+
#
|
487
|
+
def add_message_listener(&block)
|
488
|
+
id = Jabber.gen_random_id("", 10)
|
489
|
+
@messageListeners[id]=block if block
|
490
|
+
return id
|
491
|
+
end
|
492
|
+
|
493
|
+
##
|
494
|
+
# Deletes a message listener
|
495
|
+
#
|
496
|
+
# id:: [String] A messanger ID returned from add_message_listener
|
497
|
+
#
|
498
|
+
def delete_message_listener(lid)
|
499
|
+
@messageListeners.delete(lid)
|
500
|
+
end
|
501
|
+
|
502
|
+
#Cleanup methods
|
503
|
+
|
504
|
+
##
|
505
|
+
# Releases the connection and resets the session. The Session instance
|
506
|
+
# is no longer usable after this method is called
|
507
|
+
#
|
508
|
+
def release
|
509
|
+
begin
|
510
|
+
@connection.on_connection_exception do
|
511
|
+
#Do nothing...we are shutting down
|
512
|
+
end
|
513
|
+
@connection.send(Jabber::Protocol::Presence.gen_unavailable(id))
|
514
|
+
@connection.send(Jabber::Protocol.gen_close_stream)
|
515
|
+
rescue
|
516
|
+
#ignore error
|
517
|
+
end
|
518
|
+
begin
|
519
|
+
@connection.close
|
520
|
+
rescue
|
521
|
+
#ignore error
|
522
|
+
end
|
523
|
+
end
|
524
|
+
|
525
|
+
##
|
526
|
+
# Same as _release
|
527
|
+
#
|
528
|
+
def close
|
529
|
+
release
|
530
|
+
end
|
531
|
+
|
532
|
+
##
|
533
|
+
# Requests the Roster for the (authenticated) account. This method blocks
|
534
|
+
# until a reply to the roster request is received.
|
535
|
+
#
|
536
|
+
def request_roster
|
537
|
+
if @authenticated
|
538
|
+
msg_id = id
|
539
|
+
@connection.send(Jabber::Protocol::Iq.gen_roster(self, msg_id)) do |element|
|
540
|
+
if element.attr_id == msg_id
|
541
|
+
element.consume_element
|
542
|
+
element.query.item.count.times do |i|
|
543
|
+
item = element.query.item[i]
|
544
|
+
@roster.add(item.attr_jid, item.attr_subscription, item.attr_name, item.group.element_data)
|
545
|
+
end
|
546
|
+
end
|
547
|
+
end
|
548
|
+
Thread.stop
|
549
|
+
register_roster_filter
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
##
|
554
|
+
# Registers the roster filter with the Connection to forward IQ requests
|
555
|
+
# to the IQ listeners(they register by namespace)
|
556
|
+
#
|
557
|
+
def register_iq_filter()
|
558
|
+
@connection.add_filter("iqFilter") do |element|
|
559
|
+
if element.element_tag=="iq" then
|
560
|
+
element.consume_element
|
561
|
+
query=element.query
|
562
|
+
h=@iqHandlers[query.attr_xmlns]
|
563
|
+
h.call(Jabber::Protocol::Iq.from_element(self,element)) if h
|
564
|
+
end
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
|
569
|
+
##
|
570
|
+
# Registers the roster filter with the Connection to forward roster changes to
|
571
|
+
# the roster listeners.
|
572
|
+
#
|
573
|
+
def register_roster_filter
|
574
|
+
@connection.add_filter("rosterFilter") do |element|
|
575
|
+
if element.element_tag=="iq" and element.query.attr_xmlns=="jabber:iq:roster" and element.attr_type=="set"
|
576
|
+
element.consume_element
|
577
|
+
item = element.query.item
|
578
|
+
if item.attr_subscription=="remove" then
|
579
|
+
@roster.remove(item.attr_jid)
|
580
|
+
else
|
581
|
+
@roster.add(item.attr_jid, item.attr_subscription, item.attr_name, item.group.element_data)
|
582
|
+
end
|
583
|
+
end
|
584
|
+
end
|
585
|
+
end
|
586
|
+
|
587
|
+
##
|
588
|
+
# Registers a listener for roster events
|
589
|
+
#
|
590
|
+
# &block:: [Block] The listener block to process roster changes
|
591
|
+
# return:: [String] A roster ID to use when removing this listener
|
592
|
+
#
|
593
|
+
def add_roster_listener(&block)
|
594
|
+
roster.add_listener(&block)
|
595
|
+
end
|
596
|
+
|
597
|
+
##
|
598
|
+
# Deletes the roster listener
|
599
|
+
#
|
600
|
+
# id:: [String] A roster ID received from the add_roster_listener method
|
601
|
+
#
|
602
|
+
def delete_roster_listener(id)
|
603
|
+
roster.delete_listener(id)
|
604
|
+
end
|
605
|
+
|
606
|
+
##
|
607
|
+
# Notifies message listeners of the received message
|
608
|
+
#
|
609
|
+
# message:: [Jabber::Protocol::Message] The received message
|
610
|
+
#
|
611
|
+
def notify_message_listeners(message)
|
612
|
+
@messageListeners.each_value {|listener| listener.call(message)}
|
613
|
+
end
|
614
|
+
end
|
615
|
+
end
|