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

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

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 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

Public Instance methods

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 153
153:       def active?
154:         @active
155:       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.

[Source]

     # File lib/xmpp4r/muc/helper/mucclient.rb, line 260
260:       def add_join_callback(prio = 0, ref = nil, &block)
261:         @join_cbs.add(prio, ref, block)
262:       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.

[Source]

     # File lib/xmpp4r/muc/helper/mucclient.rb, line 276
276:       def add_leave_callback(prio = 0, ref = nil, &block)
277:         @leave_cbs.add(prio, ref, block)
278:       end

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 293
293:       def add_message_callback(prio = 0, ref = nil, &block)
294:         @message_cbs.add(prio, ref, block)
295:       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.

[Source]

     # File lib/xmpp4r/muc/helper/mucclient.rb, line 284
284:       def add_presence_callback(prio = 0, ref = nil, &block)
285:         @presence_cbs.add(prio, ref, block)
286:       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.

[Source]

     # File lib/xmpp4r/muc/helper/mucclient.rb, line 302
302:       def add_private_message_callback(prio = 0, ref = nil, &block)
303:         @private_message_cbs.add(prio, ref, block)
304:       end

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 122
122:       def exit(reason=nil)
123:         unless active?
124:           raise "MUCClient hasn't yet joined"
125:         end
126: 
127:         pres = Presence.new
128:         pres.type = :unavailable
129:         pres.to = jid
130:         pres.from = @my_jid
131:         pres.status = reason if reason
132:         @stream.send(pres) { |r|
133:           Jabber::debuglog "exit: #{r.to_s.inspect}"
134:           if r.kind_of?(Presence) and r.type == :unavailable and r.from == jid
135:             @leave_cbs.process(r)
136:             true
137:           else
138:             false
139:           end
140:         }
141: 
142:         deactivate
143: 
144:         self
145:       end

Does this JID belong to that room?

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

[Source]

     # File lib/xmpp4r/muc/helper/mucclient.rb, line 310
310:       def from_room?(jid)
311:         @jid.strip == jid.strip
312:       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)

[Source]

     # 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:             handle_presence(r, false)
 98:             true
 99:           else
100:             # Everything else
101:             false
102:           end
103:         }
104: 
105:         if error
106:           deactivate
107:           raise ErrorException.new(error)
108:         end
109: 
110:         self
111:       end

The MUCClient‘s own nick (= resource)

result:[String] Nickname

[Source]

     # File lib/xmpp4r/muc/helper/mucclient.rb, line 161
161:       def nick
162:         @jid ? @jid.resource : nil
163:       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.

[Source]

     # File lib/xmpp4r/muc/helper/mucclient.rb, line 174
174:       def nick=(new_nick)
175:         unless active?
176:           raise "MUCClient not active"
177:         end
178:         
179:         new_jid = JID.new(@jid.node, @jid.domain, new_nick)
180: 
181:         # Joining
182:         pres = Presence.new
183:         pres.to = new_jid
184:         pres.from = @my_jid
185: 
186:         error = nil
187:         # Keeping track of the two stanzas enables us to process stanzas
188:         # which don't arrive in the order specified by JEP-0045
189:         presence_unavailable = false
190:         presence_available = false
191:         # We don't use Stream#send_with_id here as it's unknown
192:         # if the MUC component *always* uses our stanza id.
193:         @stream.send(pres) { |r|
194:           if from_room?(r.from) and r.kind_of?(Presence) and r.type == :error
195:             # Error from room
196:             error = r.error
197:           elsif r.from == @jid and r.kind_of?(Presence) and r.type == :unavailable and
198:                 r.x and r.x.kind_of?(XMUCUser) and r.x.status_code == 303
199:             # Old JID is offline, but wait for the new JID and let stanza be handled
200:             # by the standard callback
201:             presence_unavailable = true
202:             handle_presence(r)
203:           elsif r.from == new_jid and r.kind_of?(Presence) and r.type != :unavailable
204:             # Our own presence reflected back - success
205:             presence_available = true
206:             handle_presence(r)
207:           end
208: 
209:           if error or (presence_available and presence_unavailable)
210:             true
211:           else
212:             false
213:           end
214:         }
215: 
216:         if error
217:           raise ErrorException.new(error)
218:         end
219: 
220:         # Apply new JID
221:         @jid = new_jid
222:       end

The room name (= node)

result:[String] Room name

[Source]

     # File lib/xmpp4r/muc/helper/mucclient.rb, line 228
228:       def room
229:         @jid ? @jid.node : nil
230:       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:[XMLStanza] to send
to:[String] Stanza destination recipient, or room if nil

[Source]

     # File lib/xmpp4r/muc/helper/mucclient.rb, line 240
240:       def send(stanza, to=nil)
241:         if stanza.kind_of? Message
242:           stanza.type = to ? :chat : :groupchat
243:         end
244:         stanza.from = @my_jid
245:         stanza.to = JID::new(jid.node, jid.domain, to)
246:         @stream.send(stanza)
247:       end

[Validate]