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,382 @@
|
|
|
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 'xmpp4r/muc/x/muc'
|
|
6
|
+
|
|
7
|
+
module Jabber
|
|
8
|
+
module MUC
|
|
9
|
+
##
|
|
10
|
+
# The MUCClient Helper handles low-level stuff of the
|
|
11
|
+
# Multi-User Chat (JEP 0045).
|
|
12
|
+
#
|
|
13
|
+
# Use one instance per room.
|
|
14
|
+
#
|
|
15
|
+
# Note that one client cannot join a single room multiple
|
|
16
|
+
# times. At least the clients' resources must be different.
|
|
17
|
+
# This is a protocol design issue. But don't consider it as
|
|
18
|
+
# a bug, it is just a clone-preventing feature.
|
|
19
|
+
class MUCClient
|
|
20
|
+
##
|
|
21
|
+
# Sender JID, set this to use MUCClient from Components
|
|
22
|
+
# my_jid:: [JID] Defaults to nil
|
|
23
|
+
attr_accessor :my_jid
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# MUC room roster
|
|
27
|
+
# roster:: [Hash] of [String] Nick => [Presence]
|
|
28
|
+
attr_reader :roster
|
|
29
|
+
|
|
30
|
+
##
|
|
31
|
+
# MUC JID
|
|
32
|
+
# jid:: [JID] room@component/nick
|
|
33
|
+
attr_reader :jid
|
|
34
|
+
|
|
35
|
+
##
|
|
36
|
+
# Initialize a MUCClient
|
|
37
|
+
#
|
|
38
|
+
# Call MUCClient#join *after* you have registered your
|
|
39
|
+
# callbacks to avoid reception of stanzas after joining
|
|
40
|
+
# and before registration of callbacks.
|
|
41
|
+
# stream:: [Stream] to operate on
|
|
42
|
+
def initialize(stream)
|
|
43
|
+
# Attributes initialization
|
|
44
|
+
@stream = stream
|
|
45
|
+
@my_jid = nil
|
|
46
|
+
@jid = nil
|
|
47
|
+
@roster = {}
|
|
48
|
+
@roster_lock = Mutex.new
|
|
49
|
+
|
|
50
|
+
@active = false
|
|
51
|
+
|
|
52
|
+
@join_cbs = CallbackList.new
|
|
53
|
+
@leave_cbs = CallbackList.new
|
|
54
|
+
@presence_cbs = CallbackList.new
|
|
55
|
+
@message_cbs = CallbackList.new
|
|
56
|
+
@private_message_cbs = CallbackList.new
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
##
|
|
60
|
+
# Join a room
|
|
61
|
+
#
|
|
62
|
+
# This registers its own callbacks on the stream
|
|
63
|
+
# provided to initialize and sends initial presence
|
|
64
|
+
# to the room. May throw ErrorException if joining
|
|
65
|
+
# fails.
|
|
66
|
+
# jid:: [JID] room@component/nick
|
|
67
|
+
# password:: [String] Optional password
|
|
68
|
+
# return:: [MUCClient] self (chain-able)
|
|
69
|
+
def join(jid, password=nil)
|
|
70
|
+
if active?
|
|
71
|
+
raise "MUCClient already active"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
@jid = (jid.kind_of?(JID) ? jid : JID.new(jid))
|
|
75
|
+
activate
|
|
76
|
+
|
|
77
|
+
# Joining
|
|
78
|
+
pres = Presence.new
|
|
79
|
+
pres.to = @jid
|
|
80
|
+
pres.from = @my_jid
|
|
81
|
+
xmuc = XMUC.new
|
|
82
|
+
xmuc.password = password
|
|
83
|
+
pres.add(xmuc)
|
|
84
|
+
|
|
85
|
+
# We don't use Stream#send_with_id here as it's unknown
|
|
86
|
+
# if the MUC component *always* uses our stanza id.
|
|
87
|
+
error = nil
|
|
88
|
+
@stream.send(pres) { |r|
|
|
89
|
+
if from_room?(r.from) and r.kind_of?(Presence) and r.type == :error
|
|
90
|
+
# Error from room
|
|
91
|
+
error = r.error
|
|
92
|
+
true
|
|
93
|
+
# type='unavailable' may occur when the MUC kills our previous instance,
|
|
94
|
+
# but all join-failures should be type='error'
|
|
95
|
+
elsif r.from == jid and r.kind_of?(Presence) and r.type != :unavailable
|
|
96
|
+
# Our own presence reflected back - success
|
|
97
|
+
handle_presence(r, false)
|
|
98
|
+
true
|
|
99
|
+
else
|
|
100
|
+
# Everything else
|
|
101
|
+
false
|
|
102
|
+
end
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if error
|
|
106
|
+
deactivate
|
|
107
|
+
raise ErrorException.new(error)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
self
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
##
|
|
114
|
+
# Exit the room
|
|
115
|
+
#
|
|
116
|
+
# * Sends presence with type='unavailable' with an optional
|
|
117
|
+
# reason in <tt><status/></tt>,
|
|
118
|
+
# * then waits for a reply from the MUC component (will be
|
|
119
|
+
# processed by leave-callbacks),
|
|
120
|
+
# * then deletes callbacks from the stream.
|
|
121
|
+
# reason:: [String] Optional custom exit message
|
|
122
|
+
def exit(reason=nil)
|
|
123
|
+
unless active?
|
|
124
|
+
raise "MUCClient hasn't yet joined"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
pres = Presence.new
|
|
128
|
+
pres.type = :unavailable
|
|
129
|
+
pres.to = jid
|
|
130
|
+
pres.from = @my_jid
|
|
131
|
+
pres.status = reason if reason
|
|
132
|
+
@stream.send(pres) { |r|
|
|
133
|
+
Jabber::debuglog "exit: #{r.to_s.inspect}"
|
|
134
|
+
if r.kind_of?(Presence) and r.type == :unavailable and r.from == jid
|
|
135
|
+
@leave_cbs.process(r)
|
|
136
|
+
true
|
|
137
|
+
else
|
|
138
|
+
false
|
|
139
|
+
end
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
deactivate
|
|
143
|
+
|
|
144
|
+
self
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
##
|
|
148
|
+
# Is the MUC client active?
|
|
149
|
+
#
|
|
150
|
+
# This is false after initialization,
|
|
151
|
+
# true after joining and
|
|
152
|
+
# false after exit/kick
|
|
153
|
+
def active?
|
|
154
|
+
@active
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
##
|
|
158
|
+
# The MUCClient's own nick
|
|
159
|
+
# (= resource)
|
|
160
|
+
# result:: [String] Nickname
|
|
161
|
+
def nick
|
|
162
|
+
@jid ? @jid.resource : nil
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
##
|
|
166
|
+
# Change nick
|
|
167
|
+
#
|
|
168
|
+
# Threading is, again, suggested. This method waits for two
|
|
169
|
+
# <presence/> stanzas, one indicating unavailabilty of the old
|
|
170
|
+
# transient JID, one indicating availability of the new
|
|
171
|
+
# transient JID.
|
|
172
|
+
#
|
|
173
|
+
# If the service denies nick-change, ErrorException will be raisen.
|
|
174
|
+
def nick=(new_nick)
|
|
175
|
+
unless active?
|
|
176
|
+
raise "MUCClient not active"
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
new_jid = JID.new(@jid.node, @jid.domain, new_nick)
|
|
180
|
+
|
|
181
|
+
# Joining
|
|
182
|
+
pres = Presence.new
|
|
183
|
+
pres.to = new_jid
|
|
184
|
+
pres.from = @my_jid
|
|
185
|
+
|
|
186
|
+
error = nil
|
|
187
|
+
# Keeping track of the two stanzas enables us to process stanzas
|
|
188
|
+
# which don't arrive in the order specified by JEP-0045
|
|
189
|
+
presence_unavailable = false
|
|
190
|
+
presence_available = false
|
|
191
|
+
# We don't use Stream#send_with_id here as it's unknown
|
|
192
|
+
# if the MUC component *always* uses our stanza id.
|
|
193
|
+
@stream.send(pres) { |r|
|
|
194
|
+
if from_room?(r.from) and r.kind_of?(Presence) and r.type == :error
|
|
195
|
+
# Error from room
|
|
196
|
+
error = r.error
|
|
197
|
+
elsif r.from == @jid and r.kind_of?(Presence) and r.type == :unavailable and
|
|
198
|
+
r.x and r.x.kind_of?(XMUCUser) and r.x.status_code == 303
|
|
199
|
+
# Old JID is offline, but wait for the new JID and let stanza be handled
|
|
200
|
+
# by the standard callback
|
|
201
|
+
presence_unavailable = true
|
|
202
|
+
handle_presence(r)
|
|
203
|
+
elsif r.from == new_jid and r.kind_of?(Presence) and r.type != :unavailable
|
|
204
|
+
# Our own presence reflected back - success
|
|
205
|
+
presence_available = true
|
|
206
|
+
handle_presence(r)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
if error or (presence_available and presence_unavailable)
|
|
210
|
+
true
|
|
211
|
+
else
|
|
212
|
+
false
|
|
213
|
+
end
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if error
|
|
217
|
+
raise ErrorException.new(error)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Apply new JID
|
|
221
|
+
@jid = new_jid
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
##
|
|
225
|
+
# The room name
|
|
226
|
+
# (= node)
|
|
227
|
+
# result:: [String] Room name
|
|
228
|
+
def room
|
|
229
|
+
@jid ? @jid.node : nil
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
##
|
|
233
|
+
# Send a stanza to the room
|
|
234
|
+
#
|
|
235
|
+
# If stanza is a Jabber::Message, <tt>stanza.type</tt> will be
|
|
236
|
+
# automatically set to :groupchat if directed to room or :chat
|
|
237
|
+
# if directed to participant.
|
|
238
|
+
# stanza:: [XMLStanza] to send
|
|
239
|
+
# to:: [String] Stanza destination recipient, or room if +nil+
|
|
240
|
+
def send(stanza, to=nil)
|
|
241
|
+
if stanza.kind_of? Message
|
|
242
|
+
stanza.type = to ? :chat : :groupchat
|
|
243
|
+
end
|
|
244
|
+
stanza.from = @my_jid
|
|
245
|
+
stanza.to = JID::new(jid.node, jid.domain, to)
|
|
246
|
+
@stream.send(stanza)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
##
|
|
250
|
+
# Add a callback for <presence/> stanzas indicating availability
|
|
251
|
+
# of a MUC participant
|
|
252
|
+
#
|
|
253
|
+
# This callback will *not* be called for initial presences when
|
|
254
|
+
# a client joins a room, but only for the presences afterwards.
|
|
255
|
+
#
|
|
256
|
+
# The callback will be called from MUCClient#handle_presence with
|
|
257
|
+
# one argument: the <presence/> stanza.
|
|
258
|
+
# Note that this stanza will have been already inserted into
|
|
259
|
+
# MUCClient#roster.
|
|
260
|
+
def add_join_callback(prio = 0, ref = nil, &block)
|
|
261
|
+
@join_cbs.add(prio, ref, block)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
##
|
|
265
|
+
# Add a callback for <presence/> stanzas indicating unavailability
|
|
266
|
+
# of a MUC participant
|
|
267
|
+
#
|
|
268
|
+
# The callback will be called with one argument: the <presence/> stanza.
|
|
269
|
+
#
|
|
270
|
+
# Note that this is called just *before* the stanza is removed from
|
|
271
|
+
# MUCClient#roster, so it is still possible to see the last presence
|
|
272
|
+
# in the given block.
|
|
273
|
+
#
|
|
274
|
+
# If the presence's origin is your MUC JID, the MUCClient will be
|
|
275
|
+
# deactivated *afterwards*.
|
|
276
|
+
def add_leave_callback(prio = 0, ref = nil, &block)
|
|
277
|
+
@leave_cbs.add(prio, ref, block)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
##
|
|
281
|
+
# Add a callback for a <presence/> stanza which is neither a join
|
|
282
|
+
# nor a leave. This will be called when a room participant simply
|
|
283
|
+
# changes his status.
|
|
284
|
+
def add_presence_callback(prio = 0, ref = nil, &block)
|
|
285
|
+
@presence_cbs.add(prio, ref, block)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
##
|
|
289
|
+
# Add a callback for <message/> stanza directed to the whole room.
|
|
290
|
+
#
|
|
291
|
+
# See MUCClient#add_private_message_callback for private messages
|
|
292
|
+
# between MUC participants.
|
|
293
|
+
def add_message_callback(prio = 0, ref = nil, &block)
|
|
294
|
+
@message_cbs.add(prio, ref, block)
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
##
|
|
298
|
+
# Add a callback for <message/> stanza with type='chat'.
|
|
299
|
+
#
|
|
300
|
+
# These stanza are normally not broadcasted to all room occupants
|
|
301
|
+
# but are some sort of private messaging.
|
|
302
|
+
def add_private_message_callback(prio = 0, ref = nil, &block)
|
|
303
|
+
@private_message_cbs.add(prio, ref, block)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
##
|
|
307
|
+
# Does this JID belong to that room?
|
|
308
|
+
# jid:: [JID]
|
|
309
|
+
# result:: [true] or [false]
|
|
310
|
+
def from_room?(jid)
|
|
311
|
+
@jid.strip == jid.strip
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
private
|
|
315
|
+
|
|
316
|
+
##
|
|
317
|
+
# call_join_cbs:: [Bool] Do not call them if we receive initial presences from room
|
|
318
|
+
def handle_presence(pres, call_join_cbs=true) # :nodoc:
|
|
319
|
+
if pres.type == :unavailable or pres.type == :error
|
|
320
|
+
@leave_cbs.process(pres)
|
|
321
|
+
@roster_lock.synchronize {
|
|
322
|
+
@roster.delete(pres.from.resource)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if pres.from == jid and !(pres.x and pres.x.kind_of?(XMUCUser) and pres.x.status_code == 303)
|
|
326
|
+
deactivate
|
|
327
|
+
end
|
|
328
|
+
else
|
|
329
|
+
is_join = ! @roster.has_key?(pres.from.resource)
|
|
330
|
+
@roster_lock.synchronize {
|
|
331
|
+
@roster[pres.from.resource] = pres
|
|
332
|
+
}
|
|
333
|
+
if is_join
|
|
334
|
+
@join_cbs.process(pres) if call_join_cbs
|
|
335
|
+
else
|
|
336
|
+
@presence_cbs.process(pres)
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def handle_message(msg) # :nodoc:
|
|
342
|
+
if msg.type == :chat
|
|
343
|
+
@private_message_cbs.process(msg)
|
|
344
|
+
else # type == :groupchat or anything else
|
|
345
|
+
@message_cbs.process(msg)
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def activate # :nodoc:
|
|
350
|
+
@active = true
|
|
351
|
+
|
|
352
|
+
# Callbacks
|
|
353
|
+
@stream.add_presence_callback(150, self) { |presence|
|
|
354
|
+
if from_room?(presence.from)
|
|
355
|
+
handle_presence(presence)
|
|
356
|
+
true
|
|
357
|
+
else
|
|
358
|
+
false
|
|
359
|
+
end
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
@stream.add_message_callback(150, self) { |message|
|
|
363
|
+
if from_room?(message.from)
|
|
364
|
+
handle_message(message)
|
|
365
|
+
true
|
|
366
|
+
else
|
|
367
|
+
false
|
|
368
|
+
end
|
|
369
|
+
}
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def deactivate # :nodoc:
|
|
373
|
+
@active = false
|
|
374
|
+
@jid = nil
|
|
375
|
+
|
|
376
|
+
# Callbacks
|
|
377
|
+
@stream.delete_presence_callback(self)
|
|
378
|
+
@stream.delete_message_callback(self)
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
end
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
require 'xmpp4r/delay/x/delay'
|
|
2
|
+
require 'xmpp4r/muc/helper/mucclient'
|
|
3
|
+
|
|
4
|
+
module Jabber
|
|
5
|
+
module MUC
|
|
6
|
+
##
|
|
7
|
+
# This class attempts to implement a lot of complexity of the
|
|
8
|
+
# Multi-User Chat protocol. If you want to implement JEP0045
|
|
9
|
+
# yourself, use Jabber::MUC::MUCClient for some minor
|
|
10
|
+
# abstraction.
|
|
11
|
+
#
|
|
12
|
+
# Minor flexibility penalty: the on_* callbacks are no
|
|
13
|
+
# CallbackLists and may therefore only used once. A second
|
|
14
|
+
# invocation will overwrite the previous set up block.
|
|
15
|
+
#
|
|
16
|
+
# *Hint:* the parameter time may be nil if the server didn't
|
|
17
|
+
# send it.
|
|
18
|
+
#
|
|
19
|
+
# Example usage:
|
|
20
|
+
# my_muc = Jabber::MUC::SimpleMUCClient.new(my_client)
|
|
21
|
+
# my_muc.on_message { |time,nick,text|
|
|
22
|
+
# puts (time || Time.new).strftime('%I:%M') + " <#{nick}> #{text}"
|
|
23
|
+
# }
|
|
24
|
+
# my_muc.join(Jabber::JID.new('jdev@conference.jabber.org/XMPP4R-Bot'))
|
|
25
|
+
#
|
|
26
|
+
# Please take a look at Jabber::MUC::MUCClient for
|
|
27
|
+
# derived methods, such as MUCClient#join, MUCClient#exit,
|
|
28
|
+
# ...
|
|
29
|
+
class SimpleMUCClient < MUCClient
|
|
30
|
+
##
|
|
31
|
+
# Initialize a SimpleMUCClient
|
|
32
|
+
# stream:: [Stream] to operate on
|
|
33
|
+
# jid:: [JID] room@component/nick
|
|
34
|
+
# password:: [String] Optional password
|
|
35
|
+
def initialize(stream)
|
|
36
|
+
super
|
|
37
|
+
|
|
38
|
+
@room_message_block = nil
|
|
39
|
+
@message_block = nil
|
|
40
|
+
@private_message_block = nil
|
|
41
|
+
@subject_block = nil
|
|
42
|
+
|
|
43
|
+
@subject = nil
|
|
44
|
+
|
|
45
|
+
@join_block = nil
|
|
46
|
+
add_join_callback(999) { |pres|
|
|
47
|
+
# Presence time
|
|
48
|
+
time = nil
|
|
49
|
+
pres.each_element('x') { |x|
|
|
50
|
+
if x.kind_of?(Delay::XDelay)
|
|
51
|
+
time = x.stamp
|
|
52
|
+
end
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# Invoke...
|
|
56
|
+
@join_block.call(time, pres.from.resource) if @join_block
|
|
57
|
+
false
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@leave_block = nil
|
|
61
|
+
@self_leave_block = nil
|
|
62
|
+
add_leave_callback(999) { |pres|
|
|
63
|
+
# Presence time
|
|
64
|
+
time = nil
|
|
65
|
+
pres.each_element('x') { |x|
|
|
66
|
+
if x.kind_of?(Delay::XDelay)
|
|
67
|
+
time = x.stamp
|
|
68
|
+
end
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# Invoke...
|
|
72
|
+
if pres.from == jid
|
|
73
|
+
@self_leave_block.call(time) if @self_leave_block
|
|
74
|
+
else
|
|
75
|
+
@leave_block.call(time, pres.from.resource) if @leave_block
|
|
76
|
+
end
|
|
77
|
+
false
|
|
78
|
+
}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def handle_message(msg)
|
|
84
|
+
super
|
|
85
|
+
|
|
86
|
+
# Message time (e.g. history)
|
|
87
|
+
time = nil
|
|
88
|
+
msg.each_element('x') { |x|
|
|
89
|
+
if x.kind_of?(Delay::XDelay)
|
|
90
|
+
time = x.stamp
|
|
91
|
+
end
|
|
92
|
+
}
|
|
93
|
+
sender_nick = msg.from.resource
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
if msg.subject
|
|
97
|
+
@subject = msg.subject
|
|
98
|
+
@subject_block.call(time, sender_nick, @subject) if @subject_block
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
if msg.body
|
|
102
|
+
if sender_nick.nil?
|
|
103
|
+
@room_message_block.call(time, msg.body) if @room_message_block
|
|
104
|
+
else
|
|
105
|
+
if msg.type == :chat
|
|
106
|
+
@private_message_block.call(time, msg.from.resource, msg.body) if @private_message_block
|
|
107
|
+
elsif msg.type == :groupchat
|
|
108
|
+
@message_block.call(time, msg.from.resource, msg.body) if @message_block
|
|
109
|
+
else
|
|
110
|
+
# ...?
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
public
|
|
117
|
+
|
|
118
|
+
##
|
|
119
|
+
# Room subject/topic
|
|
120
|
+
# result:: [String] The subject
|
|
121
|
+
def subject
|
|
122
|
+
@subject
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
##
|
|
126
|
+
# Change the room's subject/topic
|
|
127
|
+
#
|
|
128
|
+
# This will not be reflected by SimpleMUCClient#subject
|
|
129
|
+
# immediately, wait for SimpleMUCClient#on_subject
|
|
130
|
+
# s:: [String] New subject
|
|
131
|
+
def subject=(s)
|
|
132
|
+
msg = Message.new
|
|
133
|
+
msg.subject = s
|
|
134
|
+
send(msg)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
##
|
|
138
|
+
# Send a simple text message
|
|
139
|
+
# text:: [String] Message body
|
|
140
|
+
# to:: [String] Optional nick if directed to specific user
|
|
141
|
+
def say(text, to=nil)
|
|
142
|
+
send(Message.new(nil, text), to)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
##
|
|
146
|
+
# Request the MUC to invite users to this room
|
|
147
|
+
#
|
|
148
|
+
# Sample usage:
|
|
149
|
+
# my_muc.invite( {'wiccarocks@shakespeare.lit/laptop' => 'This coven needs both wiccarocks and hag66.',
|
|
150
|
+
# 'hag66@shakespeare.lit' => 'This coven needs both hag66 and wiccarocks.'} )
|
|
151
|
+
# recipients:: [Hash] of [JID] => [String] Reason
|
|
152
|
+
def invite(recipients)
|
|
153
|
+
msg = Message.new
|
|
154
|
+
x = msg.add(XMucUser.new)
|
|
155
|
+
recipients.each { |jid,reason|
|
|
156
|
+
x.add(XMucUserInvite.new(jid, reason))
|
|
157
|
+
}
|
|
158
|
+
send(msg)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
##
|
|
162
|
+
# Block to be invoked when a message *from* the room arrives
|
|
163
|
+
#
|
|
164
|
+
# Example:
|
|
165
|
+
# Astro has joined this session
|
|
166
|
+
# block:: Takes two arguments: time, text
|
|
167
|
+
def on_room_message(&block)
|
|
168
|
+
@room_message_block = block
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
##
|
|
172
|
+
# Block to be invoked when a message from a participant to
|
|
173
|
+
# the whole room arrives
|
|
174
|
+
# block:: Takes three arguments: time, sender nickname, text
|
|
175
|
+
def on_message(&block)
|
|
176
|
+
@message_block = block
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
##
|
|
180
|
+
# Block to be invoked when a private message from a participant
|
|
181
|
+
# to you arrives.
|
|
182
|
+
# block:: Takes three arguments: time, sender nickname, text
|
|
183
|
+
def on_private_message(&block)
|
|
184
|
+
@private_message_block = block
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
##
|
|
188
|
+
# Block to be invoked when somebody sets a new room subject
|
|
189
|
+
# block:: Takes three arguments: time, nickname, new subject
|
|
190
|
+
def on_subject(&block)
|
|
191
|
+
@subject_block = block
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
##
|
|
195
|
+
# Block to be called when somebody enters the room
|
|
196
|
+
#
|
|
197
|
+
# If there is a non-nil time passed to the block, chances
|
|
198
|
+
# are great that this is initial presence from a participant
|
|
199
|
+
# after you have joined the room.
|
|
200
|
+
# block:: Takes two arguments: time, nickname
|
|
201
|
+
def on_join(&block)
|
|
202
|
+
@join_block = block
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
##
|
|
206
|
+
# Block to be called when somebody leaves the room
|
|
207
|
+
# block:: Takes two arguments: time, nickname
|
|
208
|
+
def on_leave(&block)
|
|
209
|
+
@leave_block = block
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
##
|
|
213
|
+
# Block to be called when *you* leave the room
|
|
214
|
+
#
|
|
215
|
+
# Deactivation occurs *afterwards*.
|
|
216
|
+
# block:: Takes one argument: time
|
|
217
|
+
def on_self_leave(&block)
|
|
218
|
+
@self_leave_block = block
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
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 'xmpp4r/muc/x/mucuseritem'
|
|
6
|
+
require 'xmpp4r/muc/x/mucuserinvite'
|
|
7
|
+
|
|
8
|
+
module Jabber
|
|
9
|
+
module MUC
|
|
10
|
+
##
|
|
11
|
+
# Class for <x/> elements
|
|
12
|
+
# with namespace http://jabber.org/protocol/muc
|
|
13
|
+
#
|
|
14
|
+
# See JEP-0045 for details
|
|
15
|
+
class XMUC < X
|
|
16
|
+
##
|
|
17
|
+
# Initialize an <x/> element
|
|
18
|
+
# and set namespace to http://jabber.org/protocol/muc
|
|
19
|
+
def initialize
|
|
20
|
+
super
|
|
21
|
+
add_namespace('http://jabber.org/protocol/muc')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
##
|
|
25
|
+
# Text content of the <tt><password/></tt> element
|
|
26
|
+
def password
|
|
27
|
+
first_element_text('password')
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
##
|
|
31
|
+
# Set the password for joining a room
|
|
32
|
+
# (text content of the <tt><password/></tt> element)
|
|
33
|
+
def password=(s)
|
|
34
|
+
if s
|
|
35
|
+
replace_element_text('password', s)
|
|
36
|
+
else
|
|
37
|
+
delete_elements('password')
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
##
|
|
43
|
+
# Class for <x/> elements
|
|
44
|
+
# with namespace http://jabber.org/protocol/muc#user
|
|
45
|
+
#
|
|
46
|
+
# See JEP-0058 for details
|
|
47
|
+
class XMUCUser < X
|
|
48
|
+
##
|
|
49
|
+
# Initialize an <x/> element
|
|
50
|
+
# and set namespace to http://jabber.org/protocol/muc#user
|
|
51
|
+
def initialize
|
|
52
|
+
super
|
|
53
|
+
add_namespace('http://jabber.org/protocol/muc#user')
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
##
|
|
57
|
+
# Add a children element,
|
|
58
|
+
# will be imported to [XMUCUserItem] if name is "item"
|
|
59
|
+
def typed_add(element)
|
|
60
|
+
if element.kind_of?(REXML::Element) && (element.name == 'item')
|
|
61
|
+
super(XMUCUserItem::new.import(element))
|
|
62
|
+
elsif element.kind_of?(REXML::Element) && (element.name == 'invite')
|
|
63
|
+
super(XMUCUserInvite::new.import(element))
|
|
64
|
+
else
|
|
65
|
+
super(element)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
##
|
|
70
|
+
# Retrieve the three-digit code in
|
|
71
|
+
# <tt><x xmlns='http://jabber.org/protocol/muc#user'><status code='...'/></x></tt>
|
|
72
|
+
# result:: [Fixnum] or nil
|
|
73
|
+
def status_code
|
|
74
|
+
e = nil
|
|
75
|
+
each_element('status') { |xe| e = xe }
|
|
76
|
+
if e and e.attributes['code'].size == 3 and e.attributes['code'].to_i != 0
|
|
77
|
+
e.attributes['code'].to_i
|
|
78
|
+
else
|
|
79
|
+
nil
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
##
|
|
84
|
+
# Get all <item/> elements
|
|
85
|
+
# result:: [Array] of [XMUCUserItem]
|
|
86
|
+
def items
|
|
87
|
+
res = []
|
|
88
|
+
each_element('item') { |item|
|
|
89
|
+
res << item
|
|
90
|
+
}
|
|
91
|
+
res
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
X.add_namespaceclass('http://jabber.org/protocol/muc', XMUC)
|
|
96
|
+
X.add_namespaceclass('http://jabber.org/protocol/muc#user', XMUCUser)
|
|
97
|
+
end
|
|
98
|
+
end
|