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.
Files changed (142) hide show
  1. data/COPYING +340 -0
  2. data/ChangeLog +28 -0
  3. data/LICENSE +59 -0
  4. data/README +20 -0
  5. data/Rakefile +103 -0
  6. data/UPDATING +40 -0
  7. data/data/doc/xmpp4r/examples/advanced/adventure/README +57 -0
  8. data/data/doc/xmpp4r/examples/advanced/adventure/adventure.rb +23 -0
  9. data/data/doc/xmpp4r/examples/advanced/adventure/adventuremuc.rb +136 -0
  10. data/data/doc/xmpp4r/examples/advanced/adventure/cube.xml +15 -0
  11. data/data/doc/xmpp4r/examples/advanced/adventure/tower.xml +69 -0
  12. data/data/doc/xmpp4r/examples/advanced/adventure/world.rb +425 -0
  13. data/data/doc/xmpp4r/examples/advanced/fileserve.conf +11 -0
  14. data/data/doc/xmpp4r/examples/advanced/fileserve.rb +344 -0
  15. data/data/doc/xmpp4r/examples/advanced/getonline.rb +56 -0
  16. data/data/doc/xmpp4r/examples/advanced/gtkmucclient.rb +315 -0
  17. data/data/doc/xmpp4r/examples/advanced/migrate.rb +89 -0
  18. data/data/doc/xmpp4r/examples/advanced/minimuc.rb +266 -0
  19. data/data/doc/xmpp4r/examples/advanced/recvfile.rb +83 -0
  20. data/data/doc/xmpp4r/examples/advanced/rosterdiscovery.rb +130 -0
  21. data/data/doc/xmpp4r/examples/advanced/sendfile.conf +10 -0
  22. data/data/doc/xmpp4r/examples/advanced/sendfile.rb +72 -0
  23. data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr.rb +51 -0
  24. data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr_jabber.rb +43 -0
  25. data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr_test.rb +10 -0
  26. data/data/doc/xmpp4r/examples/advanced/versionpoll.rb +90 -0
  27. data/data/doc/xmpp4r/examples/advanced/xmpping.rb +134 -0
  28. data/data/doc/xmpp4r/examples/advanced/xmppingrc.sample +9 -0
  29. data/data/doc/xmpp4r/examples/basic/change_password.rb +41 -0
  30. data/data/doc/xmpp4r/examples/basic/client.rb +68 -0
  31. data/data/doc/xmpp4r/examples/basic/component.rb +11 -0
  32. data/data/doc/xmpp4r/examples/basic/echo_nonthreaded.rb +32 -0
  33. data/data/doc/xmpp4r/examples/basic/echo_threaded.rb +32 -0
  34. data/data/doc/xmpp4r/examples/basic/jabbersend.rb +41 -0
  35. data/data/doc/xmpp4r/examples/basic/mass_sender.rb +67 -0
  36. data/data/doc/xmpp4r/examples/basic/mucinfo.rb +39 -0
  37. data/data/doc/xmpp4r/examples/basic/mucsimplebot.rb +83 -0
  38. data/data/doc/xmpp4r/examples/basic/register.rb +25 -0
  39. data/data/doc/xmpp4r/examples/basic/remove_registration.rb +18 -0
  40. data/data/doc/xmpp4r/examples/basic/roster.rb +42 -0
  41. data/data/doc/xmpp4r/examples/basic/rosterprint.rb +50 -0
  42. data/data/doc/xmpp4r/examples/basic/rosterrename.rb +34 -0
  43. data/data/doc/xmpp4r/examples/basic/rosterwatch.rb +172 -0
  44. data/data/doc/xmpp4r/examples/basic/send_vcard.rb +68 -0
  45. data/data/doc/xmpp4r/examples/basic/versionbot.rb +75 -0
  46. data/data/doc/xmpp4r/examples/buggy/jabber2jabber/jabber2jabber.rb +18 -0
  47. data/data/doc/xmpp4r/examples/buggy/miniedgarr_cgi.rb +192 -0
  48. data/data/doc/xmpp4r/examples/buggy/miniedgarr_watch.rb +82 -0
  49. data/lib/callbacks.rb +122 -0
  50. data/lib/xmpp4r/authenticationfailure.rb +13 -0
  51. data/lib/xmpp4r/bytestreams/helper/filetransfer.rb +315 -0
  52. data/lib/xmpp4r/bytestreams/helper/ibb/base.rb +256 -0
  53. data/lib/xmpp4r/bytestreams/helper/ibb/initiator.rb +30 -0
  54. data/lib/xmpp4r/bytestreams/helper/ibb/target.rb +46 -0
  55. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/base.rb +151 -0
  56. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/initiator.rb +85 -0
  57. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/server.rb +178 -0
  58. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/socks5.rb +56 -0
  59. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/target.rb +61 -0
  60. data/lib/xmpp4r/bytestreams/iq/bytestreams.rb +177 -0
  61. data/lib/xmpp4r/bytestreams/iq/si.rb +221 -0
  62. data/lib/xmpp4r/bytestreams.rb +11 -0
  63. data/lib/xmpp4r/client.rb +249 -0
  64. data/lib/xmpp4r/component.rb +103 -0
  65. data/lib/xmpp4r/connection.rb +166 -0
  66. data/lib/xmpp4r/dataforms/x/data.rb +248 -0
  67. data/lib/xmpp4r/dataforms.rb +1 -0
  68. data/lib/xmpp4r/debuglog.rb +34 -0
  69. data/lib/xmpp4r/delay/x/delay.rb +100 -0
  70. data/lib/xmpp4r/delay.rb +1 -0
  71. data/lib/xmpp4r/discovery/iq/discoinfo.rb +225 -0
  72. data/lib/xmpp4r/discovery/iq/discoitems.rb +162 -0
  73. data/lib/xmpp4r/discovery.rb +2 -0
  74. data/lib/xmpp4r/error.rb +230 -0
  75. data/lib/xmpp4r/errorexception.rb +32 -0
  76. data/lib/xmpp4r/feature_negotiation/iq/feature.rb +42 -0
  77. data/lib/xmpp4r/feature_negotiation.rb +1 -0
  78. data/lib/xmpp4r/idgenerator.rb +37 -0
  79. data/lib/xmpp4r/iq.rb +229 -0
  80. data/lib/xmpp4r/jid.rb +167 -0
  81. data/lib/xmpp4r/message.rb +171 -0
  82. data/lib/xmpp4r/muc/helper/mucbrowser.rb +107 -0
  83. data/lib/xmpp4r/muc/helper/mucclient.rb +382 -0
  84. data/lib/xmpp4r/muc/helper/simplemucclient.rb +222 -0
  85. data/lib/xmpp4r/muc/x/muc.rb +98 -0
  86. data/lib/xmpp4r/muc/x/mucuserinvite.rb +58 -0
  87. data/lib/xmpp4r/muc/x/mucuseritem.rb +148 -0
  88. data/lib/xmpp4r/muc.rb +6 -0
  89. data/lib/xmpp4r/presence.rb +255 -0
  90. data/lib/xmpp4r/query.rb +43 -0
  91. data/lib/xmpp4r/rexmladdons.rb +826 -0
  92. data/lib/xmpp4r/roster/helper/roster.rb +514 -0
  93. data/lib/xmpp4r/roster/iq/roster.rb +244 -0
  94. data/lib/xmpp4r/roster/x/roster.rb +155 -0
  95. data/lib/xmpp4r/roster.rb +4 -0
  96. data/lib/xmpp4r/sasl.rb +167 -0
  97. data/lib/xmpp4r/stream.rb +543 -0
  98. data/lib/xmpp4r/streamparser.rb +77 -0
  99. data/lib/xmpp4r/vcard/helper/vcard.rb +86 -0
  100. data/lib/xmpp4r/vcard/iq/vcard.rb +102 -0
  101. data/lib/xmpp4r/vcard.rb +3 -0
  102. data/lib/xmpp4r/version/helper/responder.rb +71 -0
  103. data/lib/xmpp4r/version/helper/simpleresponder.rb +44 -0
  104. data/lib/xmpp4r/version/iq/version.rb +118 -0
  105. data/lib/xmpp4r/version.rb +3 -0
  106. data/lib/xmpp4r/x.rb +43 -0
  107. data/lib/xmpp4r/xmlstanza.rb +174 -0
  108. data/lib/xmpp4r/xmpp4r.rb +16 -0
  109. data/lib/xmpp4r.rb +122 -0
  110. data/setup.rb +1360 -0
  111. data/test/bytestreams/tc_ibb.rb +186 -0
  112. data/test/bytestreams/tc_socks5bytestreams.rb +57 -0
  113. data/test/delay/tc_xdelay.rb +51 -0
  114. data/test/lib/clienttester.rb +110 -0
  115. data/test/muc/tc_muc_mucclient.rb +569 -0
  116. data/test/muc/tc_muc_simplemucclient.rb +72 -0
  117. data/test/roster/.tc_helper.rb.swp +0 -0
  118. data/test/roster/tc_helper.rb +389 -0
  119. data/test/roster/tc_iqqueryroster.rb +140 -0
  120. data/test/roster/tc_xroster.rb +70 -0
  121. data/test/tc_callbacks.rb +128 -0
  122. data/test/tc_class_names.rb +129 -0
  123. data/test/tc_client.rb +30 -0
  124. data/test/tc_error.rb +103 -0
  125. data/test/tc_idgenerator.rb +30 -0
  126. data/test/tc_iq.rb +109 -0
  127. data/test/tc_iqquery.rb +31 -0
  128. data/test/tc_jid.rb +202 -0
  129. data/test/tc_message.rb +114 -0
  130. data/test/tc_presence.rb +148 -0
  131. data/test/tc_stream.rb +182 -0
  132. data/test/tc_streamError.rb +87 -0
  133. data/test/tc_streamSend.rb +59 -0
  134. data/test/tc_streamThreaded.rb +168 -0
  135. data/test/tc_xmlstanza.rb +76 -0
  136. data/test/ts_xmpp4r.rb +34 -0
  137. data/test/vcard/tc_iqvcard.rb +52 -0
  138. data/test/version/tc_helper.rb +46 -0
  139. data/test/version/tc_iqqueryversion.rb +96 -0
  140. data/tools/doctoweb.bash +30 -0
  141. data/tools/gen_requires.bash +10 -0
  142. 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