| 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 42
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
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 265
265: def add_join_callback(prio = 0, ref = nil, &block)
266: @join_cbs.add(prio, ref, block)
267: 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 281
281: def add_leave_callback(prio = 0, ref = nil, &block)
282: @leave_cbs.add(prio, ref, block)
283: 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 298
298: def add_message_callback(prio = 0, ref = nil, &block)
299: @message_cbs.add(prio, ref, block)
300: 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 289
289: def add_presence_callback(prio = 0, ref = nil, &block)
290: @presence_cbs.add(prio, ref, block)
291: 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 307
307: def add_private_message_callback(prio = 0, ref = nil, &block)
308: @private_message_cbs.add(prio, ref, block)
309: end
# File lib/xmpp4r/muc/helper/mucclient.rb, line 391
391: def configure(options={})
392: raise 'You are not the owner' unless owner?
393:
394: iq = Iq.new(:get, jid)
395: iq.to = @jid
396: iq.from = @my_jid
397: iq.add(IqQueryMUCOwner.new)
398:
399: fields = []
400:
401: answer = @stream.send_with_id(iq)
402: raise "Configuration not possible for this room" unless answer.query && answer.query.x(XData)
403:
404: answer.query.x(XData).fields.each { |field|
405: if (var = field.attributes['var'])
406: fields << var
407: end
408: }
409:
410:
411: # fill out the reply form
412: iq = Iq.new(:set, jid)
413: iq.to = @jid
414: iq.from = @my_jid
415: query = IqQueryMUCOwner.new
416: form = Dataforms::XData.new
417: form.type = :submit
418: options.each do |var, values|
419: field = Dataforms::XDataField.new
420: values = [values] unless values.is_a?(Array)
421: field.var, field.values = var, values
422: form.add(field)
423: end
424: query.add(form)
425: iq.add(query)
426:
427: @stream.send_with_id(iq)
428: end
Exit the room
| reason: | [String] Optional custom exit message |
# File lib/xmpp4r/muc/helper/mucclient.rb, line 127
127: def exit(reason=nil)
128: unless active?
129: raise "MUCClient hasn't yet joined"
130: end
131:
132: pres = Presence.new
133: pres.type = :unavailable
134: pres.to = jid
135: pres.from = @my_jid
136: pres.status = reason if reason
137: @stream.send(pres) { |r|
138: Jabber::debuglog "exit: #{r.to_s.inspect}"
139: if r.kind_of?(Presence) and r.type == :unavailable and r.from == jid
140: @leave_cbs.process(r)
141: true
142: else
143: false
144: end
145: }
146:
147: deactivate
148:
149: self
150: end
Join a room
This registers its own callbacks on the stream provided to initialize and sends initial presence to the room. May throw ErrorException 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 69
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: if r.x(XMUCUser) and (i = r.x(XMUCUser).items.first)
98: @affiliation = i.affiliation # we're interested in if it's :owner
99: @role = i.role # :moderator ?
100: end
101:
102: handle_presence(r, false)
103: true
104: else
105: # Everything else
106: false
107: end
108: }
109:
110: if error
111: deactivate
112: raise ErrorException.new(error)
113: end
114:
115: self
116: 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, ErrorException will be raisen.
# File lib/xmpp4r/muc/helper/mucclient.rb, line 179
179: def nick=(new_nick)
180: unless active?
181: raise "MUCClient not active"
182: end
183:
184: new_jid = JID.new(@jid.node, @jid.domain, new_nick)
185:
186: # Joining
187: pres = Presence.new
188: pres.to = new_jid
189: pres.from = @my_jid
190:
191: error = nil
192: # Keeping track of the two stanzas enables us to process stanzas
193: # which don't arrive in the order specified by JEP-0045
194: presence_unavailable = false
195: presence_available = false
196: # We don't use Stream#send_with_id here as it's unknown
197: # if the MUC component *always* uses our stanza id.
198: @stream.send(pres) { |r|
199: if from_room?(r.from) and r.kind_of?(Presence) and r.type == :error
200: # Error from room
201: error = r.error
202: elsif r.from == @jid and r.kind_of?(Presence) and r.type == :unavailable and
203: r.x and r.x.kind_of?(XMUCUser) and r.x.status_code == 303
204: # Old JID is offline, but wait for the new JID and let stanza be handled
205: # by the standard callback
206: presence_unavailable = true
207: handle_presence(r)
208: elsif r.from == new_jid and r.kind_of?(Presence) and r.type != :unavailable
209: # Our own presence reflected back - success
210: presence_available = true
211: handle_presence(r)
212: end
213:
214: if error or (presence_available and presence_unavailable)
215: true
216: else
217: false
218: end
219: }
220:
221: if error
222: raise ErrorException.new(error)
223: end
224:
225: # Apply new JID
226: @jid = new_jid
227: end
# File lib/xmpp4r/muc/helper/mucclient.rb, line 387
387: def owner?
388: @affiliation == :owner
389: 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 245
245: def send(stanza, to=nil)
246: if stanza.kind_of? Message
247: stanza.type = to ? :chat : :groupchat
248: end
249: stanza.from = @my_jid
250: stanza.to = JID::new(jid.node, jid.domain, to)
251: @stream.send(stanza)
252: end