| Class | Jabber::MUC::MUCClient |
| In: |
lib/xmpp4r/muc/helper/mucclient.rb
|
| Parent: | Object |
The MUCClient Helper handles low-level stuff of the Multi-User Chat (JEP 0045).
Use one instance per room.
Note that one client cannot join a single room multiple times. At least the clients’ resources must be different. This is a protocol design issue. But don‘t consider it as a bug, it is just a clone-preventing feature.
Initialize a MUCClient
Call MUCClient#join after you have registered your callbacks to avoid reception of stanzas after joining and before registration of callbacks.
| stream: | [Stream] to operate on |
# File lib/xmpp4r/muc/helper/mucclient.rb, line 45
45: def initialize(stream)
46: # Attributes initialization
47: @stream = stream
48: @my_jid = nil
49: @jid = nil
50: @roster = {}
51: @roster_lock = Mutex.new
52:
53: @active = false
54:
55: @join_cbs = CallbackList.new
56: @leave_cbs = CallbackList.new
57: @presence_cbs = CallbackList.new
58: @message_cbs = CallbackList.new
59: @private_message_cbs = CallbackList.new
60: end
Add a callback for <presence/> stanzas indicating availability of a MUC participant
This callback will not be called for initial presences when a client joins a room, but only for the presences afterwards.
The callback will be called from MUCClient#handle_presence with one argument: the <presence/> stanza. Note that this stanza will have been already inserted into MUCClient#roster.
# File lib/xmpp4r/muc/helper/mucclient.rb, line 268
268: def add_join_callback(prio = 0, ref = nil, &block)
269: @join_cbs.add(prio, ref, block)
270: end
Add a callback for <presence/> stanzas indicating unavailability of a MUC participant
The callback will be called with one argument: the <presence/> stanza.
Note that this is called just before the stanza is removed from MUCClient#roster, so it is still possible to see the last presence in the given block.
If the presence‘s origin is your MUC JID, the MUCClient will be deactivated afterwards.
# File lib/xmpp4r/muc/helper/mucclient.rb, line 284
284: def add_leave_callback(prio = 0, ref = nil, &block)
285: @leave_cbs.add(prio, ref, block)
286: end
Add a callback for <message/> stanza directed to the whole room.
See MUCClient#add_private_message_callback for private messages between MUC participants.
# File lib/xmpp4r/muc/helper/mucclient.rb, line 301
301: def add_message_callback(prio = 0, ref = nil, &block)
302: @message_cbs.add(prio, ref, block)
303: end
Add a callback for a <presence/> stanza which is neither a join nor a leave. This will be called when a room participant simply changes his status.
# File lib/xmpp4r/muc/helper/mucclient.rb, line 292
292: def add_presence_callback(prio = 0, ref = nil, &block)
293: @presence_cbs.add(prio, ref, block)
294: end
Add a callback for <message/> stanza with type=‘chat’.
These stanza are normally not broadcasted to all room occupants but are some sort of private messaging.
# File lib/xmpp4r/muc/helper/mucclient.rb, line 310
310: def add_private_message_callback(prio = 0, ref = nil, &block)
311: @private_message_cbs.add(prio, ref, block)
312: end
Use this method to configure a MUC room of which you are the owner.
| options: | [Hash] where keys are the features of the room you wish |
to configure. See www.xmpp.org/extensions/xep-0045.html#registrar-formtype-owner
# File lib/xmpp4r/muc/helper/mucclient.rb, line 399
399: def configure(options={})
400: get_room_configuration
401: submit_room_configuration(options)
402: end
Exit the room
| reason: | [String] Optional custom exit message |
# File lib/xmpp4r/muc/helper/mucclient.rb, line 130
130: def exit(reason=nil)
131: unless active?
132: raise "MUCClient hasn't yet joined"
133: end
134:
135: pres = Presence.new
136: pres.type = :unavailable
137: pres.to = jid
138: pres.from = @my_jid
139: pres.status = reason if reason
140: @stream.send(pres) { |r|
141: Jabber::debuglog "exit: #{r.to_s.inspect}"
142: if r.kind_of?(Presence) and r.type == :unavailable and r.from == jid
143: @leave_cbs.process(r)
144: true
145: else
146: false
147: end
148: }
149:
150: deactivate
151:
152: self
153: end
# File lib/xmpp4r/muc/helper/mucclient.rb, line 404
404: def get_room_configuration
405: raise 'You are not the owner' unless owner?
406:
407: iq = Iq.new(:get, jid.strip)
408: iq.from = my_jid
409: iq.add(IqQueryMUCOwner.new)
410:
411: fields = []
412:
413: @stream.send_with_id(iq) do |answer|
414: raise "Configuration not possible for this room" unless answer.query && answer.query.x(Dataforms::XData)
415:
416: answer.query.x(Dataforms::XData).fields.each do |field|
417: if (var = field.attributes['var'])
418: fields << var
419: end
420: end
421: end
422:
423: fields
424: end
Join a room
This registers its own callbacks on the stream provided to initialize and sends initial presence to the room. May throw ServerError if joining fails.
| jid: | [JID] room@component/nick |
| password: | [String] Optional password |
| return: | [MUCClient] self (chain-able) |
# File lib/xmpp4r/muc/helper/mucclient.rb, line 72
72: def join(jid, password=nil)
73: if active?
74: raise "MUCClient already active"
75: end
76:
77: @jid = (jid.kind_of?(JID) ? jid : JID.new(jid))
78: activate
79:
80: # Joining
81: pres = Presence.new
82: pres.to = @jid
83: pres.from = @my_jid
84: xmuc = XMUC.new
85: xmuc.password = password
86: pres.add(xmuc)
87:
88: # We don't use Stream#send_with_id here as it's unknown
89: # if the MUC component *always* uses our stanza id.
90: error = nil
91: @stream.send(pres) { |r|
92: if from_room?(r.from) and r.kind_of?(Presence) and r.type == :error
93: # Error from room
94: error = r.error
95: true
96: # type='unavailable' may occur when the MUC kills our previous instance,
97: # but all join-failures should be type='error'
98: elsif r.from == jid and r.kind_of?(Presence) and r.type != :unavailable
99: # Our own presence reflected back - success
100: if r.x(XMUCUser) and (i = r.x(XMUCUser).items.first)
101: @affiliation = i.affiliation # we're interested in if it's :owner
102: @role = i.role # :moderator ?
103: end
104:
105: handle_presence(r, false)
106: true
107: else
108: # Everything else
109: false
110: end
111: }
112:
113: if error
114: deactivate
115: raise ServerError.new(error)
116: end
117:
118: self
119: end
Change nick
Threading is, again, suggested. This method waits for two <presence/> stanzas, one indicating unavailabilty of the old transient JID, one indicating availability of the new transient JID.
If the service denies nick-change, ServerError will be raised.
# File lib/xmpp4r/muc/helper/mucclient.rb, line 182
182: def nick=(new_nick)
183: unless active?
184: raise "MUCClient not active"
185: end
186:
187: new_jid = JID.new(@jid.node, @jid.domain, new_nick)
188:
189: # Joining
190: pres = Presence.new
191: pres.to = new_jid
192: pres.from = @my_jid
193:
194: error = nil
195: # Keeping track of the two stanzas enables us to process stanzas
196: # which don't arrive in the order specified by JEP-0045
197: presence_unavailable = false
198: presence_available = false
199: # We don't use Stream#send_with_id here as it's unknown
200: # if the MUC component *always* uses our stanza id.
201: @stream.send(pres) { |r|
202: if from_room?(r.from) and r.kind_of?(Presence) and r.type == :error
203: # Error from room
204: error = r.error
205: elsif r.from == @jid and r.kind_of?(Presence) and r.type == :unavailable and
206: r.x and r.x.kind_of?(XMUCUser) and r.x.status_code == 303
207: # Old JID is offline, but wait for the new JID and let stanza be handled
208: # by the standard callback
209: presence_unavailable = true
210: handle_presence(r)
211: elsif r.from == new_jid and r.kind_of?(Presence) and r.type != :unavailable
212: # Our own presence reflected back - success
213: presence_available = true
214: handle_presence(r)
215: end
216:
217: if error or (presence_available and presence_unavailable)
218: true
219: else
220: false
221: end
222: }
223:
224: if error
225: raise ServerError.new(error)
226: end
227:
228: # Apply new JID
229: @jid = new_jid
230: end
# File lib/xmpp4r/muc/helper/mucclient.rb, line 390
390: def owner?
391: @affiliation == :owner
392: end
Send a stanza to the room
If stanza is a Jabber::Message, stanza.type will be automatically set to :groupchat if directed to room or :chat if directed to participant.
| stanza: | [XMPPStanza] to send |
| to: | [String] Stanza destination recipient, or room if nil |
# File lib/xmpp4r/muc/helper/mucclient.rb, line 248
248: def send(stanza, to=nil)
249: if stanza.kind_of? Message
250: stanza.type = to ? :chat : :groupchat
251: end
252: stanza.from = @my_jid
253: stanza.to = JID.new(jid.node, jid.domain, to)
254: @stream.send(stanza)
255: end
Push a list of new affiliations to the room
| items: | [Array] of, or single [IqQueryMUCAdminItem] |
# File lib/xmpp4r/muc/helper/mucclient.rb, line 448
448: def send_affiliations(items)
449: iq = Iq.new(:set, jid.strip)
450: iq.from = my_jid
451: iq.add(IqQueryMUCAdmin.new)
452:
453: items = [item] unless items.kind_of? Array
454: items.each { |item|
455: iq.query.add(item)
456: }
457:
458: @stream.send_with_id(iq)
459: end
# File lib/xmpp4r/muc/helper/mucclient.rb, line 426
426: def submit_room_configuration(options)
427: # fill out the reply form
428: iq = Iq.new(:set, jid.strip)
429: iq.from = my_jid
430: query = IqQueryMUCOwner.new
431: form = Dataforms::XData.new
432: form.type = :submit
433: options.each do |var, values|
434: field = Dataforms::XDataField.new
435: values = [values] unless values.is_a?(Array)
436: field.var, field.values = var, values
437: form.add(field)
438: end
439: query.add(form)
440: iq.add(query)
441:
442: @stream.send_with_id(iq)
443: end