Class: Jabber::MUC::MUCClient

Inherits:
Object
  • Object
show all
Defined in:
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


42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/xmpp4r/muc/helper/mucclient.rb', line 42

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

[email protected]/nick


33
34
35
# File 'lib/xmpp4r/muc/helper/mucclient.rb', line 33

def jid
  @jid
end

#my_jidObject

Sender JID, set this to use MUCClient from Components

my_jid
JID

Defaults to nil


23
24
25
# File 'lib/xmpp4r/muc/helper/mucclient.rb', line 23

def my_jid
  @my_jid
end

#rosterObject (readonly)

MUC room roster

roster
Hash

of [String] Nick => [Presence]


28
29
30
# File 'lib/xmpp4r/muc/helper/mucclient.rb', line 28

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)

153
154
155
# File 'lib/xmpp4r/muc/helper/mucclient.rb', line 153

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.


260
261
262
# File 'lib/xmpp4r/muc/helper/mucclient.rb', line 260

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.


276
277
278
# File 'lib/xmpp4r/muc/helper/mucclient.rb', line 276

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.


293
294
295
# File 'lib/xmpp4r/muc/helper/mucclient.rb', line 293

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.


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

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.


302
303
304
# File 'lib/xmpp4r/muc/helper/mucclient.rb', line 302

def add_private_message_callback(prio = 0, ref = nil, &block)
  @private_message_cbs.add(prio, ref, block)
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


122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/xmpp4r/muc/helper/mucclient.rb', line 122

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)

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

def from_room?(jid)
  @jid.strip == jid.strip
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 ErrorException if joining fails.

jid
JID

[email protected]/nick

password
String

Optional password

return
MUCClient

self (chain-able)


69
70
71
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
# File 'lib/xmpp4r/muc/helper/mucclient.rb', line 69

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
      handle_presence(r, false)
      true
    else
      # Everything else
      false
    end
  }

  if error
    deactivate
    raise ErrorException.new(error)
  end

  self
end

#nickObject

The MUCClient's own nick (= resource)

result
String

Nickname


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

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, ErrorException will be raisen.


174
175
176
177
178
179
180
181
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
# File 'lib/xmpp4r/muc/helper/mucclient.rb', line 174

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 ErrorException.new(error)
  end

  # Apply new JID
  @jid = new_jid
end

#roomObject

The room name (= node)

result
String

Room name


228
229
230
# File 'lib/xmpp4r/muc/helper/mucclient.rb', line 228

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
XMLStanza

to send

to
String

Stanza destination recipient, or room if nil


240
241
242
243
244
245
246
247
# File 'lib/xmpp4r/muc/helper/mucclient.rb', line 240

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