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