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