jabber4r-revive 0.9.0 → 0.10.0
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.
- 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
|