jabber4r-revive 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rspec +3 -0
- data/.travis.yml +8 -0
- data/CHANGELOG +45 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +45 -0
- data/LICENSE +12 -0
- data/README.md +29 -0
- data/Rakefile +71 -0
- data/jabber4r-revive.gemspec +25 -0
- data/lib/jabber4r/bosh_session.rb +224 -0
- data/lib/jabber4r/connection.rb +258 -0
- data/lib/jabber4r/debugger.rb +61 -0
- data/lib/jabber4r/jid.rb +125 -0
- data/lib/jabber4r/protocol/iq.rb +260 -0
- data/lib/jabber4r/protocol/message.rb +246 -0
- data/lib/jabber4r/protocol/parsed_xml_element.rb +208 -0
- data/lib/jabber4r/protocol/presence.rb +160 -0
- data/lib/jabber4r/protocol/xml_element.rb +144 -0
- data/lib/jabber4r/protocol.rb +257 -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
- data/lib/jabber4r/version.rb +3 -0
- data/lib/jabber4r.rb +33 -0
- data/spec/lib/jabber4r/bosh_session_spec.rb +150 -0
- data/spec/lib/jabber4r/connection_spec.rb +174 -0
- data/spec/lib/jabber4r/debugger_spec.rb +36 -0
- data/spec/lib/jabber4r/jid_spec.rb +198 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/support/mocks/tcp_socket_mock.rb +8 -0
- metadata +151 -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!
|
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!
|
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!
|
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::Connection.new(host, port)
|
209
|
+
@connection.connect
|
210
|
+
unless @connection.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 |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}@#{@host}/#{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
|