Class: Jabber::MUC::MUCClient

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.

### Methods

active? add_join_callback add_leave_callback add_message_callback add_presence_callback add_private_message_callback configure exit from_room? get_room_configuration join new nick nick= owner? room send send_affiliations submit_room_configuration

### Attributes

jid | [R] | MUC JID
jid: | [JID] room@component/nick
my_jid | [RW] | Sender JID, set this to use MUCClient from Components
my_jid: | [JID] Defaults to nil
roster | [R] | MUC room roster
roster: | [Hash] of [String] Nick => [Presence]

### Public Class methods

new(stream)

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

[Source]

        # 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

### Public Instance methods

active?()

Is the MUC client active?

This is false after initialization, true after joining and false after exit/kick

[Source]

         # File lib/xmpp4r/muc/helper/mucclient.rb, line 161
    161:       def active?
    162:         @active
    163:       end

add_join_callback(prio = 0, ref = nil, &block)

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.

[Source]

         # 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_leave_callback(prio = 0, ref = nil, &block)

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.

[Source]

         # 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_message_callback(prio = 0, ref = nil, &block)

Add a callback for <message/> stanza directed to the whole room.

See MUCClient#add_private_message_callback for private messages between MUC participants.

[Source]

         # 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_presence_callback(prio = 0, ref = nil, &block)

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.

[Source]

         # 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_private_message_callback(prio = 0, ref = nil, &block)

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.

[Source]

         # 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

configure(options={})

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

[Source]

         # 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(reason=nil)

Exit the room

- Sends presence with type=‘unavailable’ with an optional reason in `<status/>`,
- then waits for a reply from the MUC component (will be processed by leave-callbacks),
- then deletes callbacks from the stream.

reason: | [String] Optional custom exit message

[Source]

         # 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

from_room?(jid)

Does this JID belong to that room?

jid: | [JID]
result: | [true] or [false]

[Source]

         # File lib/xmpp4r/muc/helper/mucclient.rb, line 318
    318:       def from_room?(jid)
    319:         @jid.strip == jid.strip
    320:       end

get_room_configuration()

[Source]

         # 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(jid, password=nil)

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)

[Source]

         # 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

nick()

The MUCClient‘s own nick (= resource)

result: | [String] Nickname

[Source]

         # File lib/xmpp4r/muc/helper/mucclient.rb, line 169
    169:       def nick
    170:         @jid ? @jid.resource : nil
    171:       end

nick=(new_nick)

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.

[Source]

         # 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

owner?()

[Source]

         # File lib/xmpp4r/muc/helper/mucclient.rb, line 390
    390:       def owner?
    391:         @affiliation == :owner
    392:       end

room()

The room name (= node)

result: | [String] Room name

[Source]

         # File lib/xmpp4r/muc/helper/mucclient.rb, line 236
    236:       def room
    237:         @jid ? @jid.node : nil
    238:       end

send(stanza, to=nil)

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`

[Source]

         # 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

send_affiliations(items)

Push a list of new affiliations to the room

items: | [Array] of, or single [IqQueryMUCAdminItem]

[Source]

         # 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

submit_room_configuration(options)

[Source]

         # 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

[Validate]