jabber4r 0.8.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/CHANGES +8 -0
- data/LICENSE.txt +12 -0
- data/README +180 -0
- data/Rakefile.rb +143 -0
- data/lib/jabber4r/jabber4r.rb +22 -0
- data/lib/jabber4r/jid.rb +93 -0
- data/lib/jabber4r/protocol.rb +1384 -0
- data/lib/jabber4r/rexml_1.8_patch.rb +16 -0
- data/lib/jabber4r/roster.rb +322 -0
- data/lib/jabber4r/session.rb +615 -0
- data/lib/jabber4r/vcard.rb +42 -0
- metadata +55 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
if RUBY_VERSION=="1.8.0"
|
2
|
+
module REXML
|
3
|
+
module Parsers
|
4
|
+
class BaseParser
|
5
|
+
# Returns true if there are more events. Synonymous with !empty?
|
6
|
+
def has_next?
|
7
|
+
return true if @closed # THIS WAS ADDED TO FIX PROBLEM
|
8
|
+
@source.read if @source.buffer.size==0 and !@source.empty?
|
9
|
+
(!@source.empty? and @source.buffer.strip.size>0) or @stack.size>0 or @closed
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
else
|
15
|
+
puts "WARNING: rexml_1.8_patch is needed on Ruby 1.8.0 and not by Ruby #{RUBY_VERSION}"
|
16
|
+
end
|
@@ -0,0 +1,322 @@
|
|
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
|
+
|
9
|
+
##
|
10
|
+
# The Roster class encapsulates the runtime roster of the session instance.
|
11
|
+
# The Roster contains all subscriptions in a Jabber::Roster::RosterItem hash.
|
12
|
+
#
|
13
|
+
class Roster
|
14
|
+
ITEM_ADDED=1
|
15
|
+
ITEM_DELETED=2
|
16
|
+
RESOURCE_ADDED=4
|
17
|
+
RESOURCE_UPDATED=8
|
18
|
+
RESOURCE_DELETED=16
|
19
|
+
|
20
|
+
# The Jabber::Session instance
|
21
|
+
attr_reader :session
|
22
|
+
|
23
|
+
##
|
24
|
+
# Creates a Roster for the session
|
25
|
+
#
|
26
|
+
# session:: [Jabber::Session] The session instance
|
27
|
+
#
|
28
|
+
def initialize(session)
|
29
|
+
@session = session
|
30
|
+
@map = {}
|
31
|
+
@listeners = {}
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# The RosterItem class embodies another Jabber user's status (from
|
36
|
+
# the local user's perspective). RosterItems contain
|
37
|
+
# Jabber::Roster::RosterItem::Resource objects for each resource
|
38
|
+
# location a foreign user is accessing through.
|
39
|
+
#
|
40
|
+
class RosterItem
|
41
|
+
# The Jabber::Roster instance
|
42
|
+
attr_reader :roster
|
43
|
+
|
44
|
+
# The Jabber ID (Jabber::JID)
|
45
|
+
attr_accessor :jid
|
46
|
+
|
47
|
+
# The subscription type
|
48
|
+
attr_accessor :subscription
|
49
|
+
|
50
|
+
# The (nick)name of this account
|
51
|
+
attr_accessor :name
|
52
|
+
|
53
|
+
# The group name for this account
|
54
|
+
attr_accessor :group
|
55
|
+
|
56
|
+
##
|
57
|
+
# Constructs a RosterItem
|
58
|
+
#
|
59
|
+
# roster:: [Jabber::Roster] The roster instance
|
60
|
+
# subscription:: [String] The subscription type
|
61
|
+
# name:: [String] The (nick)name
|
62
|
+
# group:: [String=nil] The group this account belongs to
|
63
|
+
#
|
64
|
+
def initialize(roster, jid, subscription, name, group=nil)
|
65
|
+
@jid = jid
|
66
|
+
@subscription = subscription
|
67
|
+
@name = name
|
68
|
+
@group = group if group
|
69
|
+
@resources = {}
|
70
|
+
@roster = roster
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# The Resource class embodies a Resource endpoint in Jabber.
|
75
|
+
# The resource endpoint it what maintains a status (not an account).
|
76
|
+
#
|
77
|
+
class Resource
|
78
|
+
|
79
|
+
# The name of the resource
|
80
|
+
attr_reader :name
|
81
|
+
|
82
|
+
# How the resource should be shown
|
83
|
+
attr_reader :show
|
84
|
+
|
85
|
+
# The status message of the resource
|
86
|
+
attr_reader :status
|
87
|
+
|
88
|
+
##
|
89
|
+
# Constructs a new Resource instance
|
90
|
+
#
|
91
|
+
# item:: [Jabber::Roster::RosterItem] The roster item this resource belongs to
|
92
|
+
# name:: [String] The resource name
|
93
|
+
# show:: [String] How the resource should be shown
|
94
|
+
# status:: [String] The status message of the resource
|
95
|
+
#
|
96
|
+
def initialize(item, name, show, status)
|
97
|
+
@item = item
|
98
|
+
@name = name
|
99
|
+
@show = show
|
100
|
+
@status = status
|
101
|
+
end
|
102
|
+
|
103
|
+
##
|
104
|
+
# Updates the state of a resource and notifies listeners.
|
105
|
+
#
|
106
|
+
# show:: [String] How the resource should be shown
|
107
|
+
# status:: [String] The status message of the resource
|
108
|
+
#
|
109
|
+
def update(show, status)
|
110
|
+
@show = show
|
111
|
+
@status = status
|
112
|
+
@item.roster.notify_listeners(RESOURCE_UPDATED, self)
|
113
|
+
end
|
114
|
+
|
115
|
+
##
|
116
|
+
# Dumps the Resource as a string
|
117
|
+
#
|
118
|
+
# return:: [String] The resource encoded as a string.
|
119
|
+
#
|
120
|
+
def to_s
|
121
|
+
"RESOURCE:#{@name} SHOW:#{@show} STATUS:#{@status}"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
##
|
126
|
+
# Retrieves the VCard for this (RosterItem) account. This method
|
127
|
+
# blocks until the the vcard is returned.
|
128
|
+
#
|
129
|
+
# return:: [Jabber::VCard] The VCard object for this account
|
130
|
+
#
|
131
|
+
def get_vcard
|
132
|
+
ct = Thread.current
|
133
|
+
queryID = @roster.session.id
|
134
|
+
result = nil
|
135
|
+
@roster.session.connection.send(Jabber::Protocol::Iq.gen_vcard(self, queryID, jid)) { |je|
|
136
|
+
if je.element_tag == "iq" and je.attr_type=="result" and je.attr_id == queryID
|
137
|
+
je.consume_element
|
138
|
+
result = Jabber::VCard.from_element(je.VCARD)
|
139
|
+
ct.wakeup
|
140
|
+
else
|
141
|
+
end
|
142
|
+
}
|
143
|
+
Thread.stop
|
144
|
+
return result
|
145
|
+
end
|
146
|
+
|
147
|
+
##
|
148
|
+
# Adds a new resource to the Roster item and notifies listeners
|
149
|
+
#
|
150
|
+
# resourceName:: [String] The name of the resource
|
151
|
+
# show:: [String] How the resource is to be viewed
|
152
|
+
# status:: [String] The status message
|
153
|
+
# return:: [Jabber::Roster:RosterItem::Resource] The new Resource instance
|
154
|
+
#
|
155
|
+
def add(resourceName, show, status)
|
156
|
+
resource = Resource.new(self, resourceName, show, status)
|
157
|
+
@resources[resourceName] = resource
|
158
|
+
@roster.notify_listeners(RESOURCE_ADDED, resource)
|
159
|
+
resource
|
160
|
+
end
|
161
|
+
|
162
|
+
##
|
163
|
+
# Deletes a resource from this roster item and notifies listeners
|
164
|
+
#
|
165
|
+
# resourceName:: [String] The name of the resource
|
166
|
+
# return:: [Jabber::Roster:RosterItem::Resource] The deleted Resource
|
167
|
+
#
|
168
|
+
def delete(resourceName)
|
169
|
+
resource = @resources.delete(resourceName)
|
170
|
+
@roster.notify_listeners(RESOURCE_DELETED, resource) if resource
|
171
|
+
resource
|
172
|
+
end
|
173
|
+
|
174
|
+
##
|
175
|
+
# Retrieves a resource object
|
176
|
+
#
|
177
|
+
# resourceName:: [String] The name of the resource
|
178
|
+
# return:: [Jabber::Roster:RosterItem::Resource] The Resource instance
|
179
|
+
#
|
180
|
+
def [](resourceName)
|
181
|
+
return @resources[resourceName]
|
182
|
+
end
|
183
|
+
|
184
|
+
##
|
185
|
+
# Iterates over the list of available resources
|
186
|
+
#
|
187
|
+
# yield:: |Jabber::Roster:RosterItem::Resource| The resource instance
|
188
|
+
#
|
189
|
+
def each_resource
|
190
|
+
@resources.each_value {|resource| yield resource}
|
191
|
+
end
|
192
|
+
|
193
|
+
##
|
194
|
+
# Dumps the roster item
|
195
|
+
#
|
196
|
+
# return:: [String] The roster item dumped as a String
|
197
|
+
def to_s
|
198
|
+
"ITEM:#{@jid.to_s} SUBSCRIPTION:#{@subscription} NAME:#{@name} GROUP:#{@group}"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
##
|
203
|
+
# Adds a listener to the roster to process roster changes
|
204
|
+
#
|
205
|
+
# &block:: [Block |event, rosteritem|] The block to process roster changes
|
206
|
+
# return:: [String] The listener id to use to deregister
|
207
|
+
#
|
208
|
+
def add_listener(&block)
|
209
|
+
id = Jabber.gen_random_id("", 10)
|
210
|
+
@listeners[id]=block if block
|
211
|
+
return id
|
212
|
+
end
|
213
|
+
|
214
|
+
##
|
215
|
+
# Deletes a listener for processing roster messages
|
216
|
+
#
|
217
|
+
# id:: [String] A listener id (given by add_listener)
|
218
|
+
#
|
219
|
+
def delete_listener(id)
|
220
|
+
@listeners.delete(id)
|
221
|
+
end
|
222
|
+
|
223
|
+
##
|
224
|
+
# Adds a subscription to be tracked in the Roster
|
225
|
+
#
|
226
|
+
# jid:: [JID | String] The Jabber ID
|
227
|
+
# subscription:: [String] The subscription type (both)
|
228
|
+
# name:: [String] The nickname
|
229
|
+
# group:: [String = nil] The name of the group of the roster item.
|
230
|
+
#
|
231
|
+
def add(jid, subscription, name, group=nil)
|
232
|
+
if jid.kind_of? String
|
233
|
+
jid = JID.new(jid)
|
234
|
+
jid.strip_resource
|
235
|
+
elsif jid.kind_of? JID
|
236
|
+
jid = JID.new(jid.node+"@"+jid.host)
|
237
|
+
else
|
238
|
+
return
|
239
|
+
end
|
240
|
+
begin
|
241
|
+
item = RosterItem.new(self, jid, subscription, name, group)
|
242
|
+
@map[jid.to_s] = item
|
243
|
+
notify_listeners(ITEM_ADDED, item)
|
244
|
+
rescue => ex
|
245
|
+
puts ex.backtrace.join("\n")
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
##
|
250
|
+
# Returns a Jabber::Roster::RosterItem based on the JID
|
251
|
+
#
|
252
|
+
# jid:: [Jabber::JID | String] The Jabber ID
|
253
|
+
# return:: [Jabber::Roster::RosterItem] The roster item
|
254
|
+
#
|
255
|
+
def [](jid)
|
256
|
+
if jid.kind_of? String
|
257
|
+
jid = JID.new(jid)
|
258
|
+
jid.strip_resource
|
259
|
+
elsif jid.kind_of? JID
|
260
|
+
jid = JID.new(jid.node+"@"+jid.host)
|
261
|
+
else
|
262
|
+
return
|
263
|
+
end
|
264
|
+
return @map[jid.to_s]
|
265
|
+
end
|
266
|
+
|
267
|
+
##
|
268
|
+
# Deletes a roster item based on the supplied Jabber ID
|
269
|
+
#
|
270
|
+
# jid:: [Jabber::JID | String]
|
271
|
+
#
|
272
|
+
def delete(jid)
|
273
|
+
if jid.kind_of? String
|
274
|
+
jid = JID.new(jid)
|
275
|
+
jid.strip_resource
|
276
|
+
elsif jid.kind_of? JID
|
277
|
+
jid = JID.new(jid.node+"@"+jid.host)
|
278
|
+
else
|
279
|
+
return
|
280
|
+
end
|
281
|
+
item = @map.delete(jid.to_s)
|
282
|
+
notify_listeners(ITEM_DELETED, item) if item
|
283
|
+
item
|
284
|
+
end
|
285
|
+
|
286
|
+
##
|
287
|
+
# Iterates over each RosterItem
|
288
|
+
#
|
289
|
+
# yield:: [Jabber::Roster::RosterItem] The roster item.
|
290
|
+
#
|
291
|
+
def each_item
|
292
|
+
@map.each_value {|item| yield item}
|
293
|
+
end
|
294
|
+
|
295
|
+
##
|
296
|
+
# Dumps the Roster state as a string
|
297
|
+
#
|
298
|
+
# return:: [String] The roster state
|
299
|
+
#
|
300
|
+
def to_s
|
301
|
+
result = "ROSTER DUMP\n"
|
302
|
+
each_item do |item|
|
303
|
+
result += (item.to_s+"\n")
|
304
|
+
item.each_resource {|resource| result+= " #{resource.to_s}\n"}
|
305
|
+
end
|
306
|
+
return result
|
307
|
+
end
|
308
|
+
|
309
|
+
##
|
310
|
+
# Notifies listeners of a roster change event
|
311
|
+
#
|
312
|
+
# event:: [Integer] The roster event
|
313
|
+
# object:: [RosterItem] The modified item
|
314
|
+
#
|
315
|
+
def notify_listeners(event, object)
|
316
|
+
@listeners.each_value {|listener| listener.call(event, object)}
|
317
|
+
end
|
318
|
+
|
319
|
+
end
|
320
|
+
|
321
|
+
end
|
322
|
+
|
@@ -0,0 +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 host this session is connected to
|
97
|
+
attr_reader :host
|
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@host/resouce")
|
133
|
+
# password:: [String] The account password
|
134
|
+
# port:: [Integer = 5222] The host 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.host, 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.host, 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@host/resouce")
|
182
|
+
# password:: [String] The account password
|
183
|
+
# port:: [Integer = 5222] The host 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 host 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
|
+
# host:: [String] The hostname of the Jabber service
|
197
|
+
# port:: [Integer=5222] The port of the Jabber service
|
198
|
+
# raise:: [RuntimeException] If connection fails
|
199
|
+
#
|
200
|
+
def initialize(host, port=5222)
|
201
|
+
@id = 1
|
202
|
+
@host = host
|
203
|
+
@port = port
|
204
|
+
@roster = Roster.new(self)
|
205
|
+
@messageListeners = Hash.new
|
206
|
+
@iqHandlers=Hash.new
|
207
|
+
@subscriptionHandler = nil
|
208
|
+
@connection = Jabber::Protocol::Connection.new(host, port)
|
209
|
+
@connection.connect
|
210
|
+
unless @connection.is_connected?
|
211
|
+
raise "Session Error: Could not connected to #{host}:#{port}"
|
212
|
+
else
|
213
|
+
@connection.send(Jabber::Protocol.gen_open_stream(host)) 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
|
220
|
+
if @session_failure_block
|
221
|
+
self.release
|
222
|
+
@session_failure_block.call
|
223
|
+
end
|
224
|
+
end
|
225
|
+
Thread.stop
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
##
|
230
|
+
# Set a handler for session exceptions that get caught in
|
231
|
+
# communicating with the Jabber server.
|
232
|
+
#
|
233
|
+
def on_session_failure(&block)
|
234
|
+
@session_failure_block = block
|
235
|
+
end
|
236
|
+
|
237
|
+
##
|
238
|
+
# Counter for message IDs
|
239
|
+
#
|
240
|
+
# return:: [String] A unique message id for this session
|
241
|
+
#
|
242
|
+
def id
|
243
|
+
@id = @id + 1
|
244
|
+
return @id.to_s
|
245
|
+
end
|
246
|
+
|
247
|
+
##
|
248
|
+
# Authenticate (logs into) this session with the supplied credentials.
|
249
|
+
# The method blocks waiting for a reply to the login message. Sets the
|
250
|
+
# authenticated attribute based on result.
|
251
|
+
#
|
252
|
+
# username:: [String] The username to use for authentication
|
253
|
+
# password:: [String] The password to use for authentication
|
254
|
+
# resource:: [String] The resource ID for this session
|
255
|
+
# digest:: [Boolean=false] True to use digest authentication (not sending password in the clear)
|
256
|
+
# return:: [Boolean] Whether the authentication succeeded or failed
|
257
|
+
#
|
258
|
+
def authenticate(username, password, resource, digest=false)
|
259
|
+
@username = username
|
260
|
+
@password = password
|
261
|
+
@resource = resource
|
262
|
+
@jid = JID.new("#{username}@#{@host}/#{resource}")
|
263
|
+
@roster.add(@jid, "both", "Me", "My Resources")
|
264
|
+
|
265
|
+
msg_id = self.id
|
266
|
+
authHandler = Proc.new do |element|
|
267
|
+
if element.element_tag=="iq" and element.attr_id==msg_id
|
268
|
+
element.consume_element
|
269
|
+
if element.attr_type=="result"
|
270
|
+
@authenticated = true
|
271
|
+
elsif element.attr_type=="error"
|
272
|
+
@authenticated = false
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
if digest
|
277
|
+
require 'digest/sha1'
|
278
|
+
authRequest = Jabber::Protocol::Iq.gen_auth_digest(self, msg_id, username, Digest::SHA1.new(@session_id + password).hexdigest, resource)
|
279
|
+
else
|
280
|
+
authRequest = Jabber::Protocol::Iq.gen_auth(self, msg_id, username, password, resource)
|
281
|
+
end
|
282
|
+
@connection.send(authRequest, &authHandler)
|
283
|
+
Thread.stop
|
284
|
+
return @authenticated
|
285
|
+
end
|
286
|
+
|
287
|
+
##
|
288
|
+
# Is this an authenticated session?
|
289
|
+
#
|
290
|
+
# return:: [Boolean] True if the session is authenticated
|
291
|
+
#
|
292
|
+
def is_authenticated?
|
293
|
+
return @authenticated
|
294
|
+
end
|
295
|
+
|
296
|
+
##
|
297
|
+
# Sends the initial presence message to the Jabber service
|
298
|
+
#
|
299
|
+
def announce_initial_presence
|
300
|
+
@connection.send(Jabber::Protocol::Presence.gen_initial(id))
|
301
|
+
end
|
302
|
+
|
303
|
+
##
|
304
|
+
# Sends an extended away presence message
|
305
|
+
#
|
306
|
+
# status:: [String] The status message
|
307
|
+
#
|
308
|
+
def announce_extended_away(status=nil)
|
309
|
+
@connection.send(Jabber::Protocol::Presence.gen_xa(id, status))
|
310
|
+
end
|
311
|
+
|
312
|
+
##
|
313
|
+
# Sends a free for chat presence message
|
314
|
+
#
|
315
|
+
# status:: [String] The status message
|
316
|
+
#
|
317
|
+
def announce_free_for_chat(status=nil)
|
318
|
+
@connection.send(Jabber::Protocol::Presence.gen_chat(id, status))
|
319
|
+
end
|
320
|
+
|
321
|
+
##
|
322
|
+
# Sends a 'normal' presence message
|
323
|
+
#
|
324
|
+
# status:: [String] The status message
|
325
|
+
#
|
326
|
+
def announce_normal(status=nil)
|
327
|
+
@connection.send(Jabber::Protocol::Presence.gen_normal(id, status))
|
328
|
+
end
|
329
|
+
|
330
|
+
##
|
331
|
+
# Sends an away from computer presence message
|
332
|
+
#
|
333
|
+
# status:: [String] The status message
|
334
|
+
#
|
335
|
+
def announce_away_from_computer(status=nil)
|
336
|
+
@connection.send(Jabber::Protocol::Presence.gen_away(id, status))
|
337
|
+
end
|
338
|
+
|
339
|
+
##
|
340
|
+
# Sends a do not disturb presence message
|
341
|
+
#
|
342
|
+
# status:: [String] The status message
|
343
|
+
#
|
344
|
+
def announce_do_not_disturb(status=nil)
|
345
|
+
@connection.send(Jabber::Protocol::Presence.gen_dnd(id, status))
|
346
|
+
end
|
347
|
+
|
348
|
+
##
|
349
|
+
# Sets the handler for subscription requests, notifications, etc.
|
350
|
+
#
|
351
|
+
def set_subscription_handler(handler=nil, &block)
|
352
|
+
@subscriptionHandler = handler.new(self) if handler
|
353
|
+
@subscriptionHandler = block if block_given? and !handler
|
354
|
+
end
|
355
|
+
|
356
|
+
def enable_autosubscription
|
357
|
+
set_subscription_handler AutoSubscriptionHandler
|
358
|
+
end
|
359
|
+
|
360
|
+
def subscribe(to, name="")
|
361
|
+
to = JID.to_jid(to)
|
362
|
+
roster_item = @roster[to]
|
363
|
+
|
364
|
+
if roster_item #if you already have a roster item just send the subscribe request
|
365
|
+
if roster_item.subscription=="to" or roster_item.subscription=="both"
|
366
|
+
return
|
367
|
+
end
|
368
|
+
@connection.send(Jabber::Protocol::Presence.gen_new_subscription(to))
|
369
|
+
return
|
370
|
+
end
|
371
|
+
myid = self.id
|
372
|
+
@connection.send(Jabber::Protocol::Iq.gen_add_rosteritem(self, myid, to, name)) do |element|
|
373
|
+
if element.attr_id==myid
|
374
|
+
element.consume_element
|
375
|
+
if element.attr_type=="result"
|
376
|
+
@connection.send(Jabber::Protocol::Presence.gen_new_subscription(to))
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
##
|
383
|
+
# Adds a filter to the Connection to manage tracking resources that come online/offline
|
384
|
+
#
|
385
|
+
def register_presence_filter
|
386
|
+
@connection.add_filter("presenceAvailableFilter") do |element|
|
387
|
+
if element.element_tag=="presence"
|
388
|
+
type = element.attr_type
|
389
|
+
type = nil if type.nil?
|
390
|
+
case type
|
391
|
+
when nil, "available"
|
392
|
+
element.consume_element
|
393
|
+
from = JID.new(element.attr_from)
|
394
|
+
rItem = @roster[from]
|
395
|
+
show = element.show.element_data
|
396
|
+
show = "chat" unless show
|
397
|
+
status = element.status.element_data
|
398
|
+
status = "" unless status
|
399
|
+
if rItem
|
400
|
+
resource = rItem[from.resource]
|
401
|
+
if resource
|
402
|
+
resource.update(show, status)
|
403
|
+
else
|
404
|
+
rItem.add(from.resource, show, status)
|
405
|
+
end
|
406
|
+
end
|
407
|
+
when "unavailable"
|
408
|
+
element.consume_element
|
409
|
+
from = JID.new(element.attr_from)
|
410
|
+
rItem = @roster[from]
|
411
|
+
resource = rItem.delete(from.resource) if rItem
|
412
|
+
when "subscribe", "unsubscribe", "subscribed", "unsubscribed"
|
413
|
+
element.consume_element
|
414
|
+
from = JID.new(element.attr_from)
|
415
|
+
break unless @subscriptionHandler
|
416
|
+
if @subscriptionHandler.kind_of? Proc
|
417
|
+
@subscriptionHandler.call(Subscription.new(self, type.intern, from, id))
|
418
|
+
else
|
419
|
+
@subscriptionHandler.send(Subscription.new(self, type.intern, from, id))
|
420
|
+
end
|
421
|
+
end
|
422
|
+
end #if presence
|
423
|
+
end #do
|
424
|
+
end
|
425
|
+
|
426
|
+
##
|
427
|
+
# Creates a new message to the supplied JID of type NORMAL
|
428
|
+
#
|
429
|
+
# to:: [Jabber::JID] Who to send the message to
|
430
|
+
# type:: [String = Jabber::Protocol::Message::NORMAL] The type of message to send (see Jabber::Protocol::Message)
|
431
|
+
# return:: [Jabber::Protocol::Message] The new message
|
432
|
+
#
|
433
|
+
def new_message(to, type=Jabber::Protocol::Message::NORMAL)
|
434
|
+
msg = Jabber::Protocol::Message.new(to, type)
|
435
|
+
msg.session=self
|
436
|
+
return msg
|
437
|
+
end
|
438
|
+
|
439
|
+
##
|
440
|
+
# Creates a new message addressed to the supplied JID of type CHAT
|
441
|
+
#
|
442
|
+
# to:: [JID] Who to send the message to
|
443
|
+
# return:: [Jabber::Protocol::Message] The new (chat) message
|
444
|
+
#
|
445
|
+
def new_chat_message(to)
|
446
|
+
self.new_message(to, Jabber::Protocol::Message::CHAT)
|
447
|
+
end
|
448
|
+
|
449
|
+
##
|
450
|
+
# Creates a new message addressed to the supplied JID of type GROUPCHAT
|
451
|
+
#
|
452
|
+
# to:: [JID] Who to send the message to
|
453
|
+
# return:: [Jabber::Protocol::Message] The new (group chat) message
|
454
|
+
#
|
455
|
+
def new_group_chat_message(to)
|
456
|
+
self.new_message(to, Jabber::Protocol::Message::GROUPCHAT)
|
457
|
+
end
|
458
|
+
|
459
|
+
##
|
460
|
+
# Adds a filter to the Connection to manage tracking messages to forward
|
461
|
+
# to registered message listeners.
|
462
|
+
#
|
463
|
+
def register_message_filter
|
464
|
+
@connection.add_filter("messageFilter") do |element|
|
465
|
+
if element.element_tag=="message" and @messageListeners.size > 0
|
466
|
+
element.consume_element
|
467
|
+
message = Jabber::Protocol::Message.from_element(self, element)
|
468
|
+
notify_message_listeners(message)
|
469
|
+
end #if message
|
470
|
+
end #do
|
471
|
+
end
|
472
|
+
|
473
|
+
##
|
474
|
+
# Add a listener for new messages
|
475
|
+
#
|
476
|
+
# Usage::
|
477
|
+
# id = session.add_message_listener do |message|
|
478
|
+
# puts message
|
479
|
+
# end
|
480
|
+
#
|
481
|
+
# &block [Block] The block to process a message
|
482
|
+
# return:: [String] The listener ID...used to remove the listener
|
483
|
+
#
|
484
|
+
def add_message_listener(&block)
|
485
|
+
id = Jabber.gen_random_id("", 10)
|
486
|
+
@messageListeners[id]=block if block
|
487
|
+
return id
|
488
|
+
end
|
489
|
+
|
490
|
+
##
|
491
|
+
# Deletes a message listener
|
492
|
+
#
|
493
|
+
# id:: [String] A messanger ID returned from add_message_listener
|
494
|
+
#
|
495
|
+
def delete_message_listener(lid)
|
496
|
+
@messageListeners.delete(lid)
|
497
|
+
end
|
498
|
+
|
499
|
+
#Cleanup methods
|
500
|
+
|
501
|
+
##
|
502
|
+
# Releases the connection and resets the session. The Session instance
|
503
|
+
# is no longer usable after this method is called
|
504
|
+
#
|
505
|
+
def release
|
506
|
+
begin
|
507
|
+
@connection.on_connection_exception do
|
508
|
+
#Do nothing...we are shutting down
|
509
|
+
end
|
510
|
+
@connection.send(Jabber::Protocol::Presence.gen_unavailable(id))
|
511
|
+
@connection.send(Jabber::Protocol.gen_close_stream)
|
512
|
+
rescue
|
513
|
+
#ignore error
|
514
|
+
end
|
515
|
+
begin
|
516
|
+
@connection.close
|
517
|
+
rescue
|
518
|
+
#ignore error
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
##
|
523
|
+
# Same as _release
|
524
|
+
#
|
525
|
+
def close
|
526
|
+
release
|
527
|
+
end
|
528
|
+
|
529
|
+
##
|
530
|
+
# Requests the Roster for the (authenticated) account. This method blocks
|
531
|
+
# until a reply to the roster request is received.
|
532
|
+
#
|
533
|
+
def request_roster
|
534
|
+
if @authenticated
|
535
|
+
msg_id = id
|
536
|
+
@connection.send(Jabber::Protocol::Iq.gen_roster(self, msg_id)) do |element|
|
537
|
+
if element.attr_id == msg_id
|
538
|
+
element.consume_element
|
539
|
+
element.query.item.count.times do |i|
|
540
|
+
item = element.query.item[i]
|
541
|
+
@roster.add(item.attr_jid, item.attr_subscription, item.attr_name, item.group.element_data)
|
542
|
+
end
|
543
|
+
end
|
544
|
+
end
|
545
|
+
Thread.stop
|
546
|
+
register_roster_filter
|
547
|
+
end
|
548
|
+
end
|
549
|
+
|
550
|
+
##
|
551
|
+
# Registers the roster filter with the Connection to forward IQ requests
|
552
|
+
# to the IQ listeners(they register by namespace)
|
553
|
+
#
|
554
|
+
def register_iq_filter()
|
555
|
+
@connection.add_filter("iqFilter") do |element|
|
556
|
+
if element.element_tag=="iq" then
|
557
|
+
element.consume_element
|
558
|
+
query=element.query
|
559
|
+
h=@iqHandlers[query.attr_xmlns]
|
560
|
+
h.call(Jabber::Protocol::Iq.from_element(self,element)) if h
|
561
|
+
end
|
562
|
+
end
|
563
|
+
end
|
564
|
+
|
565
|
+
|
566
|
+
##
|
567
|
+
# Registers the roster filter with the Connection to forward roster changes to
|
568
|
+
# the roster listeners.
|
569
|
+
#
|
570
|
+
def register_roster_filter
|
571
|
+
@connection.add_filter("rosterFilter") do |element|
|
572
|
+
if element.element_tag=="iq" and element.query.attr_xmlns=="jabber:iq:roster" and element.attr_type=="set"
|
573
|
+
element.consume_element
|
574
|
+
item = element.query.item
|
575
|
+
if item.attr_subscription=="remove" then
|
576
|
+
@roster.remove(item.attr_jid)
|
577
|
+
else
|
578
|
+
@roster.add(item.attr_jid, item.attr_subscription, item.attr_name, item.group.element_data)
|
579
|
+
end
|
580
|
+
end
|
581
|
+
end
|
582
|
+
end
|
583
|
+
|
584
|
+
##
|
585
|
+
# Registers a listener for roster events
|
586
|
+
#
|
587
|
+
# &block:: [Block] The listener block to process roster changes
|
588
|
+
# return:: [String] A roster ID to use when removing this listener
|
589
|
+
#
|
590
|
+
def add_roster_listener(&block)
|
591
|
+
roster.add_listener(&block)
|
592
|
+
end
|
593
|
+
|
594
|
+
##
|
595
|
+
# Deletes the roster listener
|
596
|
+
#
|
597
|
+
# id:: [String] A roster ID received from the add_roster_listener method
|
598
|
+
#
|
599
|
+
def delete_roster_listener(id)
|
600
|
+
roster.delete_listener(id)
|
601
|
+
end
|
602
|
+
|
603
|
+
##
|
604
|
+
# Notifies message listeners of the received message
|
605
|
+
#
|
606
|
+
# message:: [Jabber::Protocol::Message] The received message
|
607
|
+
#
|
608
|
+
def notify_message_listeners(message)
|
609
|
+
@messageListeners.each_value {|listener| listener.call(message)}
|
610
|
+
end
|
611
|
+
|
612
|
+
end
|
613
|
+
|
614
|
+
end
|
615
|
+
|