Class: Jabber::MUC::MUCClient

Inherits:
Object
  • Object
show all
Defined in:
lib/gems/xmpp4r-0.4/lib/xmpp4r/muc/helper/mucclient.rb

Overview

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.

Direct Known Subclasses

SimpleMUCClient

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(stream) ⇒ MUCClient

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



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/gems/xmpp4r-0.4/lib/xmpp4r/muc/helper/mucclient.rb', line 45

def initialize(stream)
  # Attributes initialization
  @stream = stream
  @my_jid = nil
  @jid = nil
  @roster = {}
  @roster_lock = Mutex.new

  @active = false

  @join_cbs = CallbackList.new
  @leave_cbs = CallbackList.new
  @presence_cbs = CallbackList.new
  @message_cbs = CallbackList.new
  @private_message_cbs = CallbackList.new
end

Instance Attribute Details

#jidObject (readonly)

MUC JID

jid
JID

room@component/nick



36
37
38
# File 'lib/gems/xmpp4r-0.4/lib/xmpp4r/muc/helper/mucclient.rb', line 36

def jid
  @jid
end

#my_jidObject

Sender JID, set this to use MUCClient from Components

my_jid
JID

Defaults to nil



26
27
28
# File 'lib/gems/xmpp4r-0.4/lib/xmpp4r/muc/helper/mucclient.rb', line 26

def my_jid
  @my_jid
end

#rosterObject (readonly)

MUC room roster

roster
Hash

of [String] Nick => [Presence]



31
32
33
# File 'lib/gems/xmpp4r-0.4/lib/xmpp4r/muc/helper/mucclient.rb', line 31

def roster
  @roster
end

Instance Method Details

#active?Boolean

Is the MUC client active?

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

Returns:

  • (Boolean)


161
162
163
# File 'lib/gems/xmpp4r-0.4/lib/xmpp4r/muc/helper/mucclient.rb', line 161

def active?
  @active
end

#add_join_callback(prio = 0, ref = nil, &block) ⇒ Object

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.



268
269
270
# File 'lib/gems/xmpp4r-0.4/lib/xmpp4r/muc/helper/mucclient.rb', line 268

def add_join_callback(prio = 0, ref = nil, &block)
  @join_cbs.add(prio, ref, block)
end

#add_leave_callback(prio = 0, ref = nil, &block) ⇒ Object

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.



284
285
286
# File 'lib/gems/xmpp4r-0.4/lib/xmpp4r/muc/helper/mucclient.rb', line 284

def add_leave_callback(prio = 0, ref = nil, &block)
  @leave_cbs.add(prio, ref, block)
end

#add_message_callback(prio = 0, ref = nil, &block) ⇒ Object

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

See MUCClient#add_private_message_callback for private messages between MUC participants.



301
302
303
# File 'lib/gems/xmpp4r-0.4/lib/xmpp4r/muc/helper/mucclient.rb', line 301

def add_message_callback(prio = 0, ref = nil, &block)
  @message_cbs.add(prio, ref, block)
end

#add_presence_callback(prio = 0, ref = nil, &block) ⇒ Object

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.



292
293
294
# File 'lib/gems/xmpp4r-0.4/lib/xmpp4r/muc/helper/mucclient.rb', line 292

def add_presence_callback(prio = 0, ref = nil, &block)
  @presence_cbs.add(prio, ref, block)
end

#add_private_message_callback(prio = 0, ref = nil, &block) ⇒ Object

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.



310
311
312
# File 'lib/gems/xmpp4r-0.4/lib/xmpp4r/muc/helper/mucclient.rb', line 310

def add_private_message_callback(prio = 0, ref = nil, &block)
  @private_message_cbs.add(prio, ref, block)
end

#configure(options = {}) ⇒ Object

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



399
400
401
402
# File 'lib/gems/xmpp4r-0.4/lib/xmpp4r/muc/helper/mucclient.rb', line 399

def configure(options={})
  get_room_configuration
  submit_room_configuration(options)
end

#exit(reason = nil) ⇒ Object

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



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/gems/xmpp4r-0.4/lib/xmpp4r/muc/helper/mucclient.rb', line 130

def exit(reason=nil)
  unless active?
    raise "MUCClient hasn't yet joined"
  end

  pres = Presence.new
  pres.type = :unavailable
  pres.to = jid
  pres.from = @my_jid
  pres.status = reason if reason
  @stream.send(pres) { |r|
    Jabber::debuglog "exit: #{r.to_s.inspect}"
    if r.kind_of?(Presence) and r.type == :unavailable and r.from == jid
      @leave_cbs.process(r)
      true
    else
      false
    end
  }

  deactivate

  self
end

#from_room?(jid) ⇒ Boolean

Does this JID belong to that room?

jid
JID
result
true

or [false]

Returns:

  • (Boolean)


318
319
320
# File 'lib/gems/xmpp4r-0.4/lib/xmpp4r/muc/helper/mucclient.rb', line 318

def from_room?(jid)
  @jid.strip == jid.strip
end

#get_room_configurationObject



404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
# File 'lib/gems/xmpp4r-0.4/lib/xmpp4r/muc/helper/mucclient.rb', line 404

def get_room_configuration
  raise 'You are not the owner' unless owner?

  iq = Iq.new(:get, jid.strip)
  iq.from = my_jid
  iq.add(IqQueryMUCOwner.new)

  fields = []

  @stream.send_with_id(iq) do |answer|
    raise "Configuration not possible for this room" unless answer.query && answer.query.x(Dataforms::XData)

    answer.query.x(Dataforms::XData).fields.each do |field|
      if (var = field.attributes['var'])
        fields << var
      end
    end
  end

  fields
end

#join(jid, password = nil) ⇒ Object

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)



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/gems/xmpp4r-0.4/lib/xmpp4r/muc/helper/mucclient.rb', line 72

def join(jid, password=nil)
  if active?
    raise "MUCClient already active"
  end

  @jid = (jid.kind_of?(JID) ? jid : JID.new(jid))
  activate

  # Joining
  pres = Presence.new
  pres.to = @jid
  pres.from = @my_jid
  xmuc = XMUC.new
  xmuc.password = password
  pres.add(xmuc)

  # We don't use Stream#send_with_id here as it's unknown
  # if the MUC component *always* uses our stanza id.
  error = nil
  @stream.send(pres) { |r|
    if from_room?(r.from) and r.kind_of?(Presence) and r.type == :error
      # Error from room
      error = r.error
      true
    # type='unavailable' may occur when the MUC kills our previous instance,
    # but all join-failures should be type='error'
    elsif r.from == jid and r.kind_of?(Presence) and r.type != :unavailable
      # Our own presence reflected back - success
      if r.x(XMUCUser) and (i = r.x(XMUCUser).items.first)
        @affiliation = i.affiliation  # we're interested in if it's :owner
        @role = i.role                # :moderator ?
      end

      handle_presence(r, false)
      true
    else
      # Everything else
      false
    end
  }

  if error
    deactivate
    raise ServerError.new(error)
  end

  self
end

#nickObject

The MUCClient’s own nick (= resource)

result
String

Nickname



169
170
171
# File 'lib/gems/xmpp4r-0.4/lib/xmpp4r/muc/helper/mucclient.rb', line 169

def nick
  @jid ? @jid.resource : nil
end

#nick=(new_nick) ⇒ Object

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.



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/gems/xmpp4r-0.4/lib/xmpp4r/muc/helper/mucclient.rb', line 182

def nick=(new_nick)
  unless active?
    raise "MUCClient not active"
  end

  new_jid = JID.new(@jid.node, @jid.domain, new_nick)

  # Joining
  pres = Presence.new
  pres.to = new_jid
  pres.from = @my_jid

  error = nil
  # Keeping track of the two stanzas enables us to process stanzas
  # which don't arrive in the order specified by JEP-0045
  presence_unavailable = false
  presence_available = false
  # We don't use Stream#send_with_id here as it's unknown
  # if the MUC component *always* uses our stanza id.
  @stream.send(pres) { |r|
    if from_room?(r.from) and r.kind_of?(Presence) and r.type == :error
      # Error from room
      error = r.error
    elsif r.from == @jid and r.kind_of?(Presence) and r.type == :unavailable and
          r.x and r.x.kind_of?(XMUCUser) and r.x.status_code == 303
      # Old JID is offline, but wait for the new JID and let stanza be handled
      # by the standard callback
      presence_unavailable = true
      handle_presence(r)
    elsif r.from == new_jid and r.kind_of?(Presence) and r.type != :unavailable
      # Our own presence reflected back - success
      presence_available = true
      handle_presence(r)
    end

    if error or (presence_available and presence_unavailable)
      true
    else
      false
    end
  }

  if error
    raise ServerError.new(error)
  end

  # Apply new JID
  @jid = new_jid
end

#owner?Boolean

Returns:

  • (Boolean)


390
391
392
# File 'lib/gems/xmpp4r-0.4/lib/xmpp4r/muc/helper/mucclient.rb', line 390

def owner?
  @affiliation == :owner
end

#roomObject

The room name (= node)

result
String

Room name



236
237
238
# File 'lib/gems/xmpp4r-0.4/lib/xmpp4r/muc/helper/mucclient.rb', line 236

def room
  @jid ? @jid.node : nil
end

#send(stanza, to = nil) ⇒ Object

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



248
249
250
251
252
253
254
255
# File 'lib/gems/xmpp4r-0.4/lib/xmpp4r/muc/helper/mucclient.rb', line 248

def send(stanza, to=nil)
  if stanza.kind_of? Message
    stanza.type = to ? :chat : :groupchat
  end
  stanza.from = @my_jid
  stanza.to = JID.new(jid.node, jid.domain, to)
  @stream.send(stanza)
end

#send_affiliations(items) ⇒ Object

Push a list of new affiliations to the room

items
Array

of, or single [IqQueryMUCAdminItem]



448
449
450
451
452
453
454
455
456
457
458
459
# File 'lib/gems/xmpp4r-0.4/lib/xmpp4r/muc/helper/mucclient.rb', line 448

def send_affiliations(items)
  iq = Iq.new(:set, jid.strip)
  iq.from = my_jid
  iq.add(IqQueryMUCAdmin.new)

  items = [item] unless items.kind_of? Array
  items.each { |item|
    iq.query.add(item)
  }

  @stream.send_with_id(iq)
end

#submit_room_configuration(options) ⇒ Object



426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
# File 'lib/gems/xmpp4r-0.4/lib/xmpp4r/muc/helper/mucclient.rb', line 426

def submit_room_configuration(options)
  # fill out the reply form
  iq = Iq.new(:set, jid.strip)
  iq.from = my_jid
  query = IqQueryMUCOwner.new
  form = Dataforms::XData.new
  form.type = :submit
  options.each do |var, values|
    field = Dataforms::XDataField.new
    values = [values] unless values.is_a?(Array)
    field.var, field.values = var, values
    form.add(field)
  end
  query.add(form)
  iq.add(query)

  @stream.send_with_id(iq)
end