xmpp4r 0.3

Sign up to get free protection for your applications and to get access to all the features.
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