xmpp4r 0.3
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/COPYING +340 -0
- data/ChangeLog +28 -0
- data/LICENSE +59 -0
- data/README +20 -0
- data/Rakefile +103 -0
- data/UPDATING +40 -0
- data/data/doc/xmpp4r/examples/advanced/adventure/README +57 -0
- data/data/doc/xmpp4r/examples/advanced/adventure/adventure.rb +23 -0
- data/data/doc/xmpp4r/examples/advanced/adventure/adventuremuc.rb +136 -0
- data/data/doc/xmpp4r/examples/advanced/adventure/cube.xml +15 -0
- data/data/doc/xmpp4r/examples/advanced/adventure/tower.xml +69 -0
- data/data/doc/xmpp4r/examples/advanced/adventure/world.rb +425 -0
- data/data/doc/xmpp4r/examples/advanced/fileserve.conf +11 -0
- data/data/doc/xmpp4r/examples/advanced/fileserve.rb +344 -0
- data/data/doc/xmpp4r/examples/advanced/getonline.rb +56 -0
- data/data/doc/xmpp4r/examples/advanced/gtkmucclient.rb +315 -0
- data/data/doc/xmpp4r/examples/advanced/migrate.rb +89 -0
- data/data/doc/xmpp4r/examples/advanced/minimuc.rb +266 -0
- data/data/doc/xmpp4r/examples/advanced/recvfile.rb +83 -0
- data/data/doc/xmpp4r/examples/advanced/rosterdiscovery.rb +130 -0
- data/data/doc/xmpp4r/examples/advanced/sendfile.conf +10 -0
- data/data/doc/xmpp4r/examples/advanced/sendfile.rb +72 -0
- data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr.rb +51 -0
- data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr_jabber.rb +43 -0
- data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr_test.rb +10 -0
- data/data/doc/xmpp4r/examples/advanced/versionpoll.rb +90 -0
- data/data/doc/xmpp4r/examples/advanced/xmpping.rb +134 -0
- data/data/doc/xmpp4r/examples/advanced/xmppingrc.sample +9 -0
- data/data/doc/xmpp4r/examples/basic/change_password.rb +41 -0
- data/data/doc/xmpp4r/examples/basic/client.rb +68 -0
- data/data/doc/xmpp4r/examples/basic/component.rb +11 -0
- data/data/doc/xmpp4r/examples/basic/echo_nonthreaded.rb +32 -0
- data/data/doc/xmpp4r/examples/basic/echo_threaded.rb +32 -0
- data/data/doc/xmpp4r/examples/basic/jabbersend.rb +41 -0
- data/data/doc/xmpp4r/examples/basic/mass_sender.rb +67 -0
- data/data/doc/xmpp4r/examples/basic/mucinfo.rb +39 -0
- data/data/doc/xmpp4r/examples/basic/mucsimplebot.rb +83 -0
- data/data/doc/xmpp4r/examples/basic/register.rb +25 -0
- data/data/doc/xmpp4r/examples/basic/remove_registration.rb +18 -0
- data/data/doc/xmpp4r/examples/basic/roster.rb +42 -0
- data/data/doc/xmpp4r/examples/basic/rosterprint.rb +50 -0
- data/data/doc/xmpp4r/examples/basic/rosterrename.rb +34 -0
- data/data/doc/xmpp4r/examples/basic/rosterwatch.rb +172 -0
- data/data/doc/xmpp4r/examples/basic/send_vcard.rb +68 -0
- data/data/doc/xmpp4r/examples/basic/versionbot.rb +75 -0
- data/data/doc/xmpp4r/examples/buggy/jabber2jabber/jabber2jabber.rb +18 -0
- data/data/doc/xmpp4r/examples/buggy/miniedgarr_cgi.rb +192 -0
- data/data/doc/xmpp4r/examples/buggy/miniedgarr_watch.rb +82 -0
- data/lib/callbacks.rb +122 -0
- data/lib/xmpp4r/authenticationfailure.rb +13 -0
- data/lib/xmpp4r/bytestreams/helper/filetransfer.rb +315 -0
- data/lib/xmpp4r/bytestreams/helper/ibb/base.rb +256 -0
- data/lib/xmpp4r/bytestreams/helper/ibb/initiator.rb +30 -0
- data/lib/xmpp4r/bytestreams/helper/ibb/target.rb +46 -0
- data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/base.rb +151 -0
- data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/initiator.rb +85 -0
- data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/server.rb +178 -0
- data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/socks5.rb +56 -0
- data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/target.rb +61 -0
- data/lib/xmpp4r/bytestreams/iq/bytestreams.rb +177 -0
- data/lib/xmpp4r/bytestreams/iq/si.rb +221 -0
- data/lib/xmpp4r/bytestreams.rb +11 -0
- data/lib/xmpp4r/client.rb +249 -0
- data/lib/xmpp4r/component.rb +103 -0
- data/lib/xmpp4r/connection.rb +166 -0
- data/lib/xmpp4r/dataforms/x/data.rb +248 -0
- data/lib/xmpp4r/dataforms.rb +1 -0
- data/lib/xmpp4r/debuglog.rb +34 -0
- data/lib/xmpp4r/delay/x/delay.rb +100 -0
- data/lib/xmpp4r/delay.rb +1 -0
- data/lib/xmpp4r/discovery/iq/discoinfo.rb +225 -0
- data/lib/xmpp4r/discovery/iq/discoitems.rb +162 -0
- data/lib/xmpp4r/discovery.rb +2 -0
- data/lib/xmpp4r/error.rb +230 -0
- data/lib/xmpp4r/errorexception.rb +32 -0
- data/lib/xmpp4r/feature_negotiation/iq/feature.rb +42 -0
- data/lib/xmpp4r/feature_negotiation.rb +1 -0
- data/lib/xmpp4r/idgenerator.rb +37 -0
- data/lib/xmpp4r/iq.rb +229 -0
- data/lib/xmpp4r/jid.rb +167 -0
- data/lib/xmpp4r/message.rb +171 -0
- data/lib/xmpp4r/muc/helper/mucbrowser.rb +107 -0
- data/lib/xmpp4r/muc/helper/mucclient.rb +382 -0
- data/lib/xmpp4r/muc/helper/simplemucclient.rb +222 -0
- data/lib/xmpp4r/muc/x/muc.rb +98 -0
- data/lib/xmpp4r/muc/x/mucuserinvite.rb +58 -0
- data/lib/xmpp4r/muc/x/mucuseritem.rb +148 -0
- data/lib/xmpp4r/muc.rb +6 -0
- data/lib/xmpp4r/presence.rb +255 -0
- data/lib/xmpp4r/query.rb +43 -0
- data/lib/xmpp4r/rexmladdons.rb +826 -0
- data/lib/xmpp4r/roster/helper/roster.rb +514 -0
- data/lib/xmpp4r/roster/iq/roster.rb +244 -0
- data/lib/xmpp4r/roster/x/roster.rb +155 -0
- data/lib/xmpp4r/roster.rb +4 -0
- data/lib/xmpp4r/sasl.rb +167 -0
- data/lib/xmpp4r/stream.rb +543 -0
- data/lib/xmpp4r/streamparser.rb +77 -0
- data/lib/xmpp4r/vcard/helper/vcard.rb +86 -0
- data/lib/xmpp4r/vcard/iq/vcard.rb +102 -0
- data/lib/xmpp4r/vcard.rb +3 -0
- data/lib/xmpp4r/version/helper/responder.rb +71 -0
- data/lib/xmpp4r/version/helper/simpleresponder.rb +44 -0
- data/lib/xmpp4r/version/iq/version.rb +118 -0
- data/lib/xmpp4r/version.rb +3 -0
- data/lib/xmpp4r/x.rb +43 -0
- data/lib/xmpp4r/xmlstanza.rb +174 -0
- data/lib/xmpp4r/xmpp4r.rb +16 -0
- data/lib/xmpp4r.rb +122 -0
- data/setup.rb +1360 -0
- data/test/bytestreams/tc_ibb.rb +186 -0
- data/test/bytestreams/tc_socks5bytestreams.rb +57 -0
- data/test/delay/tc_xdelay.rb +51 -0
- data/test/lib/clienttester.rb +110 -0
- data/test/muc/tc_muc_mucclient.rb +569 -0
- data/test/muc/tc_muc_simplemucclient.rb +72 -0
- data/test/roster/.tc_helper.rb.swp +0 -0
- data/test/roster/tc_helper.rb +389 -0
- data/test/roster/tc_iqqueryroster.rb +140 -0
- data/test/roster/tc_xroster.rb +70 -0
- data/test/tc_callbacks.rb +128 -0
- data/test/tc_class_names.rb +129 -0
- data/test/tc_client.rb +30 -0
- data/test/tc_error.rb +103 -0
- data/test/tc_idgenerator.rb +30 -0
- data/test/tc_iq.rb +109 -0
- data/test/tc_iqquery.rb +31 -0
- data/test/tc_jid.rb +202 -0
- data/test/tc_message.rb +114 -0
- data/test/tc_presence.rb +148 -0
- data/test/tc_stream.rb +182 -0
- data/test/tc_streamError.rb +87 -0
- data/test/tc_streamSend.rb +59 -0
- data/test/tc_streamThreaded.rb +168 -0
- data/test/tc_xmlstanza.rb +76 -0
- data/test/ts_xmpp4r.rb +34 -0
- data/test/vcard/tc_iqvcard.rb +52 -0
- data/test/version/tc_helper.rb +46 -0
- data/test/version/tc_iqqueryversion.rb +96 -0
- data/tools/doctoweb.bash +30 -0
- data/tools/gen_requires.bash +10 -0
- metadata +232 -0
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
# =XMPP4R - XMPP Library for Ruby
|
|
2
|
+
# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
|
|
3
|
+
# Website::http://home.gna.org/xmpp4r/
|
|
4
|
+
|
|
5
|
+
require 'callbacks'
|
|
6
|
+
require 'thread'
|
|
7
|
+
require 'xmpp4r/roster/iq/roster'
|
|
8
|
+
|
|
9
|
+
module Jabber
|
|
10
|
+
module Roster
|
|
11
|
+
##
|
|
12
|
+
# The Roster helper intercepts <tt><iq/></tt> stanzas with Jabber::IqQueryRoster
|
|
13
|
+
# and <tt><presence/></tt> stanzas, but provides cbs which allow the programmer
|
|
14
|
+
# to keep track of updates.
|
|
15
|
+
class Helper
|
|
16
|
+
##
|
|
17
|
+
# All items in your roster
|
|
18
|
+
# items:: [Hash] ([JID] => [Roster::Helper::RosterItem])
|
|
19
|
+
attr_reader :items
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
# Initialize a new Roster helper
|
|
23
|
+
#
|
|
24
|
+
# Registers its cbs (prio = 120, ref = self)
|
|
25
|
+
#
|
|
26
|
+
# Request a roster
|
|
27
|
+
# (Remember to send initial presence afterwards!)
|
|
28
|
+
def initialize(stream)
|
|
29
|
+
@stream = stream
|
|
30
|
+
@items = {}
|
|
31
|
+
@items_lock = Mutex.new
|
|
32
|
+
@query_cbs = CallbackList.new
|
|
33
|
+
@update_cbs = CallbackList.new
|
|
34
|
+
@presence_cbs = CallbackList.new
|
|
35
|
+
@subscription_cbs = CallbackList.new
|
|
36
|
+
@subscription_request_cbs = CallbackList.new
|
|
37
|
+
|
|
38
|
+
# Register cbs
|
|
39
|
+
stream.add_iq_callback(120, self) { |iq|
|
|
40
|
+
handle_iq(iq)
|
|
41
|
+
}
|
|
42
|
+
stream.add_presence_callback(120, self) { |pres|
|
|
43
|
+
handle_presence(pres)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# Request the roster
|
|
47
|
+
rosterget = Iq.new_rosterget
|
|
48
|
+
stream.send(rosterget)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
##
|
|
52
|
+
# Add a callback to be called when a query has been processed
|
|
53
|
+
#
|
|
54
|
+
# Because update callbacks are called for each roster item,
|
|
55
|
+
# this may be appropriate to notify that *anything* has updated.
|
|
56
|
+
#
|
|
57
|
+
# Arguments for callback block: The received <tt><iq/></tt> stanza
|
|
58
|
+
def add_query_callback(prio = 0, ref = nil, &block)
|
|
59
|
+
@query_cbs.add(prio, ref, block)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
##
|
|
63
|
+
# Add a callback for Jabber::Roster::Helper::RosterItem updates
|
|
64
|
+
#
|
|
65
|
+
# Note that this will be called much after initialization
|
|
66
|
+
# for the answer of the initial roster request
|
|
67
|
+
#
|
|
68
|
+
# The block receives two objects:
|
|
69
|
+
# * the old Jabber::Roster::Helper::RosterItem
|
|
70
|
+
# * the new Jabber::Roster::Helper::RosterItem
|
|
71
|
+
def add_update_callback(prio = 0, ref = nil, &block)
|
|
72
|
+
@update_cbs.add(prio, ref, block)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
##
|
|
76
|
+
# Add a callback for Jabber::Presence updates
|
|
77
|
+
#
|
|
78
|
+
# This will be called for <tt><presence/></tt> stanzas for known RosterItems.
|
|
79
|
+
# Unknown JIDs may still pass and can be caught via Jabber::Stream#add_presence_callback.
|
|
80
|
+
#
|
|
81
|
+
# The block receives three objects:
|
|
82
|
+
# * the Jabber::Roster::Helper::RosterItem
|
|
83
|
+
# * the old Jabber::Presence (or nil)
|
|
84
|
+
# * the new Jabber::Presence (or nil)
|
|
85
|
+
def add_presence_callback(prio = 0, ref = nil, &block)
|
|
86
|
+
@presence_cbs.add(prio, ref, block)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
##
|
|
90
|
+
# Add a callback for subscription updates,
|
|
91
|
+
# which will be called upon receiving a <tt><presence/></tt> stanza
|
|
92
|
+
# with type:
|
|
93
|
+
# * :subscribed
|
|
94
|
+
# * :unsubscribe
|
|
95
|
+
# * :unsubscribed
|
|
96
|
+
#
|
|
97
|
+
# The block receives two objects:
|
|
98
|
+
# * the Jabber::Roster::Helper::RosterItem (or nil)
|
|
99
|
+
# * the <tt><presence/></tt> stanza
|
|
100
|
+
def add_subscription_callback(prio = 0, ref = nil, &block)
|
|
101
|
+
@subscription_cbs.add(prio, ref, block)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
##
|
|
105
|
+
# Add a callback for subscription requests,
|
|
106
|
+
# which will be called upon receiving a <tt><presence type='subscribe'/></tt> stanza
|
|
107
|
+
#
|
|
108
|
+
# The block receives two objects:
|
|
109
|
+
# * the Jabber::Roster::Helper::RosterItem (or nil)
|
|
110
|
+
# * the <tt><presence/></tt> stanza
|
|
111
|
+
#
|
|
112
|
+
# Response to this event can be taken with accept_subscription
|
|
113
|
+
# and decline_subscription.
|
|
114
|
+
#
|
|
115
|
+
# Example usage:
|
|
116
|
+
# my_roster.add_subscription_request_callback do |item,presence|
|
|
117
|
+
# if accept_subscription_requests
|
|
118
|
+
# my_roster.accept_subscription(presence.from)
|
|
119
|
+
# else
|
|
120
|
+
# my_roster.decline_subscription(presence.from)
|
|
121
|
+
# end
|
|
122
|
+
# end
|
|
123
|
+
def add_subscription_request_callback(prio = 0, ref = nil, &block)
|
|
124
|
+
@subscription_request_cbs.add(prio, ref, block)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
##
|
|
130
|
+
# Handle received <tt><iq/></tt> stanzas,
|
|
131
|
+
# used internally
|
|
132
|
+
def handle_iq(iq)
|
|
133
|
+
if iq.query.kind_of?(IqQueryRoster)
|
|
134
|
+
# If the <iq/> contains <error/> we just ignore that
|
|
135
|
+
# and assume an empty roster
|
|
136
|
+
iq.query.each_element('item') do |item|
|
|
137
|
+
# Handle deletion of item
|
|
138
|
+
if item.subscription == :remove
|
|
139
|
+
@items_lock.synchronize {
|
|
140
|
+
@items.delete(item.jid)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
else
|
|
144
|
+
olditem = nil
|
|
145
|
+
@items_lock.synchronize {
|
|
146
|
+
if @items.has_key?(item.jid)
|
|
147
|
+
olditem = RosterItem.new(@stream).import(@items[item.jid])
|
|
148
|
+
|
|
149
|
+
# Clear first, because import doesn't
|
|
150
|
+
@items[item.jid].iname = nil
|
|
151
|
+
@items[item.jid].subscription = nil
|
|
152
|
+
@items[item.jid].ask = nil
|
|
153
|
+
|
|
154
|
+
@items[item.jid].import(item)
|
|
155
|
+
else
|
|
156
|
+
@items[item.jid] = RosterItem.new(@stream).import(item)
|
|
157
|
+
end
|
|
158
|
+
}
|
|
159
|
+
@update_cbs.process(olditem, @items[item.jid])
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
@query_cbs.process(iq)
|
|
164
|
+
else
|
|
165
|
+
false
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
##
|
|
170
|
+
# Handle received <tt><presence/></tt> stanzas,
|
|
171
|
+
# used internally
|
|
172
|
+
def handle_presence(pres)
|
|
173
|
+
item = self[pres.from]
|
|
174
|
+
if [:subscribed, :unsubscribe, :unsubscribed].include?(pres.type)
|
|
175
|
+
@subscription_cbs.process(item, pres)
|
|
176
|
+
true
|
|
177
|
+
elsif pres.type == :subscribe
|
|
178
|
+
@subscription_request_cbs.process(item, pres)
|
|
179
|
+
true
|
|
180
|
+
else
|
|
181
|
+
unless item.nil?
|
|
182
|
+
update_presence(item, pres)
|
|
183
|
+
true # Callback consumed stanza
|
|
184
|
+
else
|
|
185
|
+
false # Callback did not consume stanza
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
##
|
|
191
|
+
# Update the presence of an item,
|
|
192
|
+
# used internally
|
|
193
|
+
#
|
|
194
|
+
# Callbacks are called here
|
|
195
|
+
def update_presence(item, pres)
|
|
196
|
+
|
|
197
|
+
# This requires special handling, to announce all resources offline
|
|
198
|
+
if pres.from.resource.nil? and pres.type == :error
|
|
199
|
+
oldpresences = []
|
|
200
|
+
item.each_presence do |oldpres|
|
|
201
|
+
oldpresences << oldpres
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
item.add_presence(pres)
|
|
205
|
+
oldpresences.each { |oldpres|
|
|
206
|
+
@presence_cbs.process(item, oldpres, pres)
|
|
207
|
+
}
|
|
208
|
+
else
|
|
209
|
+
oldpres = item.presence(pres.from).nil? ?
|
|
210
|
+
nil :
|
|
211
|
+
Presence.new.import(item.presence(pres.from))
|
|
212
|
+
|
|
213
|
+
item.add_presence(pres)
|
|
214
|
+
@presence_cbs.process(item, oldpres, pres)
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
public
|
|
219
|
+
|
|
220
|
+
##
|
|
221
|
+
# Get an item by jid
|
|
222
|
+
#
|
|
223
|
+
# If not available tries to look for it with the resource stripped
|
|
224
|
+
def [](jid)
|
|
225
|
+
jid = JID.new(jid) unless jid.kind_of? JID
|
|
226
|
+
|
|
227
|
+
@items_lock.synchronize {
|
|
228
|
+
if @items.has_key?(jid)
|
|
229
|
+
@items[jid]
|
|
230
|
+
elsif @items.has_key?(jid.strip)
|
|
231
|
+
@items[jid.strip]
|
|
232
|
+
else
|
|
233
|
+
nil
|
|
234
|
+
end
|
|
235
|
+
}
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
##
|
|
239
|
+
# Returns the list of RosterItems which, stripped, are equal to the
|
|
240
|
+
# one you are looking for.
|
|
241
|
+
def find(jid)
|
|
242
|
+
jid = JID.new(jid) unless jid.kind_of? JID
|
|
243
|
+
|
|
244
|
+
j = jid.strip
|
|
245
|
+
l = {}
|
|
246
|
+
@items_lock.synchronize {
|
|
247
|
+
@items.each_pair do |k, v|
|
|
248
|
+
l[k] = v if k.strip == j
|
|
249
|
+
end
|
|
250
|
+
}
|
|
251
|
+
l
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
##
|
|
255
|
+
# Groups in this Roster,
|
|
256
|
+
# sorted by name
|
|
257
|
+
#
|
|
258
|
+
# Contains +nil+ if there are ungrouped items
|
|
259
|
+
# result:: [Array] containing group names (String)
|
|
260
|
+
def groups
|
|
261
|
+
res = []
|
|
262
|
+
@items_lock.synchronize {
|
|
263
|
+
@items.each_pair do |jid,item|
|
|
264
|
+
res += item.groups
|
|
265
|
+
res += [nil] if item.groups == []
|
|
266
|
+
end
|
|
267
|
+
}
|
|
268
|
+
res.uniq.sort { |a,b| a.to_s <=> b.to_s }
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
##
|
|
272
|
+
# Get items in a group
|
|
273
|
+
#
|
|
274
|
+
# When group is nil, return ungrouped items
|
|
275
|
+
# group:: [String] Group name
|
|
276
|
+
# result:: Array of [RosterItem]
|
|
277
|
+
def find_by_group(group)
|
|
278
|
+
res = []
|
|
279
|
+
@items_lock.synchronize {
|
|
280
|
+
@items.each_pair do |jid,item|
|
|
281
|
+
res.push(item) if item.groups.include?(group)
|
|
282
|
+
res.push(item) if item.groups == [] and group.nil?
|
|
283
|
+
end
|
|
284
|
+
}
|
|
285
|
+
res
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
##
|
|
289
|
+
# Add a user to your roster
|
|
290
|
+
#
|
|
291
|
+
# Threading is encouraged as the function waits for
|
|
292
|
+
# a result. ErrorException is thrown upon error.
|
|
293
|
+
#
|
|
294
|
+
# See Jabber::Roster::Helper::RosterItem#subscribe for details
|
|
295
|
+
# about subscribing. (This method isn't used here but the
|
|
296
|
+
# same functionality applies.)
|
|
297
|
+
#
|
|
298
|
+
# If the item is already in the local roster
|
|
299
|
+
# it will simply send itself
|
|
300
|
+
# jid:: [JID] to add
|
|
301
|
+
# iname:: [String] Optional item name
|
|
302
|
+
# subscribe:: [Boolean] Whether to subscribe to this jid
|
|
303
|
+
def add(jid, iname=nil, subscribe=false)
|
|
304
|
+
if self[jid]
|
|
305
|
+
self[jid].send
|
|
306
|
+
else
|
|
307
|
+
request = Iq.new_rosterset
|
|
308
|
+
request.query.add(Jabber::Roster::RosterItem.new(jid, iname))
|
|
309
|
+
@stream.send_with_id(request) { true }
|
|
310
|
+
# Adding to list is handled by handle_iq
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
if subscribe
|
|
314
|
+
# Actually the item *should* already be known now,
|
|
315
|
+
# but we do it manually to exclude conditions.
|
|
316
|
+
pres = Presence.new.set_type(:subscribe).set_to(jid.strip)
|
|
317
|
+
@stream.send(pres)
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
##
|
|
322
|
+
# Accept a subscription request
|
|
323
|
+
# * Sends a <presence type='subscribed'/> stanza
|
|
324
|
+
# * Adds the contact to your roster
|
|
325
|
+
# jid:: [JID] of contact
|
|
326
|
+
# iname:: [String] Optional roster item name
|
|
327
|
+
def accept_subscription(jid, iname=nil)
|
|
328
|
+
pres = Presence.new.set_type(:subscribed).set_to(jid.strip)
|
|
329
|
+
@stream.send(pres)
|
|
330
|
+
|
|
331
|
+
unless self[jid.strip]
|
|
332
|
+
request = Iq.new_rosterset
|
|
333
|
+
request.query.add(Jabber::Roster::RosterItem.new(jid.strip, iname))
|
|
334
|
+
@stream.send_with_id(request) { true }
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
##
|
|
339
|
+
# Decline a subscription request
|
|
340
|
+
# * Sends a <presence type='unsubscribed'/> stanza
|
|
341
|
+
def decline_subscription(jid)
|
|
342
|
+
pres = Presence.new.set_type(:unsubscribed).set_to(jid.strip)
|
|
343
|
+
@stream.send(pres)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
##
|
|
347
|
+
# These are extensions to RosterItem to carry presence information.
|
|
348
|
+
# This information is *not* stored in XML!
|
|
349
|
+
class RosterItem < Jabber::Roster::RosterItem
|
|
350
|
+
##
|
|
351
|
+
# Tracked (online) presences of this RosterItem
|
|
352
|
+
attr_reader :presences
|
|
353
|
+
|
|
354
|
+
##
|
|
355
|
+
# Initialize an empty RosterItem
|
|
356
|
+
def initialize(stream)
|
|
357
|
+
super()
|
|
358
|
+
@stream = stream
|
|
359
|
+
@presences = []
|
|
360
|
+
@presences_lock = Mutex.new
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
##
|
|
364
|
+
# Import another element,
|
|
365
|
+
# also import presences if xe is a RosterItem
|
|
366
|
+
# return:: [RosterItem] self
|
|
367
|
+
def import(xe)
|
|
368
|
+
super
|
|
369
|
+
if xe.kind_of?(RosterItem)
|
|
370
|
+
xe.each_presence { |pres|
|
|
371
|
+
add_presence(Presence.new.import(pres))
|
|
372
|
+
}
|
|
373
|
+
end
|
|
374
|
+
self
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
##
|
|
378
|
+
# Send the updated RosterItem to the server,
|
|
379
|
+
# i.e. if you modified iname, groups, ...
|
|
380
|
+
def send
|
|
381
|
+
request = Iq.new_rosterset
|
|
382
|
+
request.query.add(self)
|
|
383
|
+
@stream.send(request)
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
##
|
|
387
|
+
# Remove item
|
|
388
|
+
#
|
|
389
|
+
# This cancels both subscription *from* the contact to you
|
|
390
|
+
# and from you *to* the contact.
|
|
391
|
+
#
|
|
392
|
+
# The methods waits for a roster push from the server (success)
|
|
393
|
+
# or throws ErrorException upon failure.
|
|
394
|
+
def remove
|
|
395
|
+
request = Iq.new_rosterset
|
|
396
|
+
request.query.add(Jabber::Roster::RosterItem.new(jid, nil, :remove))
|
|
397
|
+
@stream.send_with_id(request) { true }
|
|
398
|
+
# Removing from list is handled by Roster#handle_iq
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
##
|
|
402
|
+
# Is any presence of this person on-line?
|
|
403
|
+
#
|
|
404
|
+
# (Or is there any presence? Unavailable presences are
|
|
405
|
+
# deleted.)
|
|
406
|
+
def online?
|
|
407
|
+
@presences_lock.synchronize {
|
|
408
|
+
@presences.select { |pres|
|
|
409
|
+
pres.type.nil?
|
|
410
|
+
}.size > 0
|
|
411
|
+
}
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
##
|
|
415
|
+
# Iterate through all received <tt><presence/></tt> stanzas
|
|
416
|
+
def each_presence(&block)
|
|
417
|
+
# Don't lock here, we don't know what block does...
|
|
418
|
+
@presences.each { |pres|
|
|
419
|
+
yield(pres)
|
|
420
|
+
}
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
##
|
|
424
|
+
# Get specific presence
|
|
425
|
+
# jid:: [JID] Full JID
|
|
426
|
+
def presence(jid)
|
|
427
|
+
@presences_lock.synchronize {
|
|
428
|
+
@presences.each { |pres|
|
|
429
|
+
return(pres) if pres.from == jid
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
nil
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
##
|
|
436
|
+
# Add presence and sort presences
|
|
437
|
+
# (unless type is :unavailable or :error)
|
|
438
|
+
#
|
|
439
|
+
# This overwrites previous stanzas with the same destination
|
|
440
|
+
# JID to keep track of resources. Presence stanzas with
|
|
441
|
+
# <tt>type == :unavailable</tt> or <tt>type == :error</tt> will
|
|
442
|
+
# be deleted as this indicates that this resource has gone
|
|
443
|
+
# offline.
|
|
444
|
+
#
|
|
445
|
+
# If <tt>type == :error</tt> and the presence's origin has no
|
|
446
|
+
# specific resource the contact is treated completely offline.
|
|
447
|
+
def add_presence(newpres)
|
|
448
|
+
@presences_lock.synchronize {
|
|
449
|
+
# Delete old presences with the same JID
|
|
450
|
+
@presences.delete_if do |pres|
|
|
451
|
+
pres.from == newpres.from or pres.from.resource.nil?
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
if newpres.type == :error and newpres.from.resource.nil?
|
|
455
|
+
# Replace by single error presence
|
|
456
|
+
@presences = [newpres]
|
|
457
|
+
else
|
|
458
|
+
# Add new presence
|
|
459
|
+
@presences.push(newpres)
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
@presences.sort!
|
|
463
|
+
}
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
##
|
|
467
|
+
# Send subscription request to the user
|
|
468
|
+
#
|
|
469
|
+
# The block given to Jabber::Roster::Roster#add_update_callback will
|
|
470
|
+
# be called, carrying the RosterItem with ask="subscribe"
|
|
471
|
+
#
|
|
472
|
+
# This function returns immediately after sending the subscription
|
|
473
|
+
# request and will not wait of approval or declination as it may
|
|
474
|
+
# take months for the contact to decide. ;-)
|
|
475
|
+
def subscribe
|
|
476
|
+
pres = Presence.new.set_type(:subscribe).set_to(jid.strip)
|
|
477
|
+
@stream.send(pres)
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
##
|
|
481
|
+
# Unsubscribe from a contact's presence
|
|
482
|
+
#
|
|
483
|
+
# This method waits for a presence with type='unsubscribed'
|
|
484
|
+
# from the contact. It may throw ErrorException upon failure.
|
|
485
|
+
#
|
|
486
|
+
# subscription attribute of the item is *from* or *none*
|
|
487
|
+
# afterwards. As long as you don't remove that item and
|
|
488
|
+
# subscription='from' the contact is subscribed to your
|
|
489
|
+
# presence.
|
|
490
|
+
def unsubscribe
|
|
491
|
+
pres = Presence.new.set_type(:unsubscribe).set_to(jid.strip)
|
|
492
|
+
@stream.send(pres) { |answer|
|
|
493
|
+
answer.type == :unsubscribed and
|
|
494
|
+
answer.from.strip == pres.to
|
|
495
|
+
}
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
##
|
|
499
|
+
# Deny the contact to see your presence.
|
|
500
|
+
#
|
|
501
|
+
# This method will not wait and returns immediately
|
|
502
|
+
# as you will need no confirmation for this action.
|
|
503
|
+
#
|
|
504
|
+
# Though, you will get a roster update for that item,
|
|
505
|
+
# carrying either subscription='to' or 'none'.
|
|
506
|
+
def cancel_subscription
|
|
507
|
+
pres = Presence.new.set_type(:unsubscribed).set_to(jid)
|
|
508
|
+
@stream.send(pres)
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
end #Class Roster
|
|
512
|
+
end #Module Roster
|
|
513
|
+
end #Module Jabber
|
|
514
|
+
|