comparison src/plugins/plugin_xep_0045.py @ 1970:200cd707a46d

plugin XEP-0045, quick_frontend + primitivus (chat): cleaning of XEP-0045 (first pass): - bridge methods/signals now all start with "muc" to follow new convention - internal method use client instead of profile to follow new convention - removed excetpions from plugin XEP-0045 in favor of core.exceptions, NotReady added - cleaned/simplified several part of the code. checkClient removed as it is not needed anymore - self.clients map removed, muc data are now stored directly in client - getRoomEntityNick and getRoomNicksOfUsers are removed as they don't look sane. /!\ This break all room game plugins for the moment - use of uuid4 instead of uuid1 for getUniqueName, as host ID and current time are used for uuid1
author Goffi <goffi@goffi.org>
date Mon, 27 Jun 2016 21:45:11 +0200
parents a2bc5089c2eb
children bdc6a5b07922
comparison
equal deleted inserted replaced
1969:5fbe09b9b568 1970:200cd707a46d
61 CONFIG_SECTION = u'plugin muc' 61 CONFIG_SECTION = u'plugin muc'
62 62
63 default_conf = {"default_muc": u'sat@chat.jabberfr.org'} 63 default_conf = {"default_muc": u'sat@chat.jabberfr.org'}
64 64
65 65
66 class UnknownRoom(Exception):
67 pass
68
69
70 class AlreadyJoinedRoom(Exception):
71 pass
72
73 class NotReadyYet(Exception):
74 pass
75
76
77 class XEP_0045(object): 66 class XEP_0045(object):
78 # TODO: this plugin is messy, need a big cleanup/refactoring
79 67
80 def __init__(self, host): 68 def __init__(self, host):
81 log.info(_("Plugin XEP_0045 initialization")) 69 log.info(_("Plugin XEP_0045 initialization"))
82 self.host = host 70 self.host = host
83 self.clients = {} # FIXME: should be moved to profile's client
84 self._sessions = memory.Sessions() 71 self._sessions = memory.Sessions()
85 host.bridge.addMethod("mucJoin", ".plugin", in_sign='ssa{ss}s', out_sign='s', method=self._join, async=True) 72 host.bridge.addMethod("mucJoin", ".plugin", in_sign='ssa{ss}s', out_sign='s', method=self._join, async=True)
86 host.bridge.addMethod("mucNick", ".plugin", in_sign='sss', out_sign='', method=self.mucNick) 73 host.bridge.addMethod("mucNick", ".plugin", in_sign='sss', out_sign='', method=self._nick)
87 host.bridge.addMethod("mucLeave", ".plugin", in_sign='ss', out_sign='', method=self.mucLeave, async=True) 74 host.bridge.addMethod("mucLeave", ".plugin", in_sign='ss', out_sign='', method=self._leave, async=True)
88 host.bridge.addMethod("getRoomsJoined", ".plugin", in_sign='s', out_sign='a(sa{sa{ss}}ss)', method=self.getRoomsJoined) 75 host.bridge.addMethod("mucGetRoomsJoined", ".plugin", in_sign='s', out_sign='a(sa{sa{ss}}ss)', method=self._getRoomsJoined)
89 host.bridge.addMethod("getRoomsSubjects", ".plugin", in_sign='s', out_sign='a(ss)', method=self.getRoomsSubjects) 76 host.bridge.addMethod("mucGetUniqueRoomName", ".plugin", in_sign='ss', out_sign='s', method=self._getUniqueName)
90 host.bridge.addMethod("getUniqueRoomName", ".plugin", in_sign='ss', out_sign='s', method=self._getUniqueName) 77 host.bridge.addMethod("mucConfigureRoom", ".plugin", in_sign='ss', out_sign='s', method=self._configureRoom, async=True)
91 host.bridge.addMethod("configureRoom", ".plugin", in_sign='ss', out_sign='s', method=self._configureRoom, async=True) 78 host.bridge.addMethod("mucGetDefaultService", ".plugin", in_sign='', out_sign='s', method=self.getDefaultMUC)
92 host.bridge.addMethod("getDefaultMUC", ".plugin", in_sign='', out_sign='s', method=self.getDefaultMUC) 79 host.bridge.addSignal("mucRoomJoined", ".plugin", signature='sa{sa{ss}}sss') # args: room_jid, occupants, user_nick, subject, profile
93 host.bridge.addSignal("roomJoined", ".plugin", signature='sa{sa{ss}}sss') # args: room_jid, occupants, user_nick, subject, profile 80 host.bridge.addSignal("mucRoomLeft", ".plugin", signature='ss') # args: room_jid, profile
94 host.bridge.addSignal("roomLeft", ".plugin", signature='ss') # args: room_jid, profile 81 host.bridge.addSignal("mucRoomUserChangedNick", ".plugin", signature='ssss') # args: room_jid, old_nick, new_nick, profile
95 host.bridge.addSignal("roomUserChangedNick", ".plugin", signature='ssss') # args: room_jid, old_nick, new_nick, profile 82 host.bridge.addSignal("mucRoomNewSubject", ".plugin", signature='sss') # args: room_jid, subject, profile
96 host.bridge.addSignal("roomNewSubject", ".plugin", signature='sss') # args: room_jid, subject, profile
97 self.__submit_conf_id = host.registerCallback(self._submitConfiguration, with_data=True) 83 self.__submit_conf_id = host.registerCallback(self._submitConfiguration, with_data=True)
98 host.importMenu((D_("MUC"), D_("configure")), self._configureRoomMenu, security_limit=0, help_string=D_("Configure Multi-User Chat room"), type_=C.MENU_ROOM) 84 host.importMenu((D_("MUC"), D_("configure")), self._configureRoomMenu, security_limit=0, help_string=D_("Configure Multi-User Chat room"), type_=C.MENU_ROOM)
99 try: 85 try:
100 self.host.plugins[C.TEXT_CMDS].registerTextCommands(self) 86 txt_cmds = self.host.plugins[C.TEXT_CMDS]
101 self.host.plugins[C.TEXT_CMDS].addWhoIsCb(self._whois, 100)
102 except KeyError: 87 except KeyError:
103 log.info(_("Text commands not available")) 88 log.info(_(u"Text commands not available"))
89 else:
90 txt_cmds.registerTextCommands(self)
91 txt_cmds.addWhoIsCb(self._whois, 100)
104 92
105 host.trigger.add("presence_available", self.presenceTrigger) 93 host.trigger.add("presence_available", self.presenceTrigger)
106 host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=1000000) 94 host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=1000000)
107 95
108 def profileConnected(self, profile): 96 def profileConnected(self, profile):
115 if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT: 103 if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT:
116 if message_elt.subject or message_elt.delay: 104 if message_elt.subject or message_elt.delay:
117 return False 105 return False
118 from_jid = jid.JID(message_elt['from']) 106 from_jid = jid.JID(message_elt['from'])
119 room_jid = from_jid.userhostJID() 107 room_jid = from_jid.userhostJID()
120 if room_jid in self.clients[client.profile].joined_rooms: 108 if room_jid in client._muc_client.joined_rooms:
121 room = self.clients[client.profile].joined_rooms[room_jid] 109 room = client._muc_client.joined_rooms[room_jid]
122 if not room._room_ok: 110 if not room._room_ok:
123 log.warning(u"Received non delayed message in a room before its initialisation: {}".format(message_elt.toXml())) 111 log.warning(u"Received non delayed message in a room before its initialisation: {}".format(message_elt.toXml()))
124 room._cache.append(message_elt) 112 room._cache.append(message_elt)
125 return False 113 return False
126 else: 114 else:
127 log.warning(u"Received groupchat message for a room which has not been joined, ignoring it: {}".format(message_elt.toXml())) 115 log.warning(u"Received groupchat message for a room which has not been joined, ignoring it: {}".format(message_elt.toXml()))
128 return False 116 return False
129 return True 117 return True
130 118
131 def checkClient(self, profile): 119 def checkRoomJoined(self, client, room_jid):
132 """Check if the profile is connected and has used the MUC feature. 120 """Check that given room has been joined in current session
133 121
134 If profile was using MUC feature but is now disconnected, remove it from the client list. 122 @param room_jid (JID): room JID
135 @param profile: profile to check 123 """
136 @return: True if the profile is connected and has used the MUC feature, else False""" 124 if room_jid not in client._muc_client.joined_rooms:
137 if not profile or profile not in self.clients or not self.host.isConnected(profile): 125 raise exceptions.NotFound(_(u"This room has not been joined"))
138 log.error(_(u'Unknown or disconnected profile (%s)') % profile) 126
139 if profile in self.clients: 127 def isJoinedRoom(self, client, room_jid):
140 del self.clients[profile] 128 """Tell if a jid is a known and joined room
129
130 @room_jid(jid.JID): jid of the room
131 """
132 try:
133 self.checkRoomJoined(client, room_jid)
134 except exceptions.NotFound:
141 return False 135 return False
142 return True 136 else:
143 137 return True
144 def getProfileAssertInRoom(self, room_jid, profile_key): 138
145 """Retrieve the profile name, assert that it's connected and participating in the given room. 139 def _joinCb(self, room, client):
146
147 @param room_jid (JID): room JID
148 @param profile_key (str): %(doc_profile_key)
149 @return: the profile name
150 """
151 profile = self.host.memory.getProfileName(profile_key)
152 if not self.checkClient(profile):
153 raise exceptions.ProfileUnknownError("Unknown or disconnected profile")
154 if room_jid not in self.clients[profile].joined_rooms:
155 raise UnknownRoom("This room has not been joined")
156 return profile
157
158 def _joinCb(self, room, profile):
159 """Called when the user is in the requested room""" 140 """Called when the user is in the requested room"""
160 if room.locked: 141 if room.locked:
161 # FIXME: the current behaviour is to create an instant room 142 # FIXME: the current behaviour is to create an instant room
162 # and send the signal only when the room is unlocked 143 # and send the signal only when the room is unlocked
163 # a proper configuration management should be done 144 # a proper configuration management should be done
164 print "room locked !" 145 log.debug(_(u"room locked !"))
165 d = self.clients[profile].configure(room.roomJID, {}) 146 d = client._muc_client.configure(room.roomJID, {})
166 d.addErrback(lambda dummy: log.error(_(u'Error while configuring the room'))) 147 d.addErrback(lambda dummy: log.error(_(u'Error while configuring the room')))
167 return room 148 return room
168 149
169 def _joinEb(self, failure, room_jid, nick, password, profile): 150 def _joinEb(self, failure, client, room_jid, nick, password):
170 """Called when something is going wrong when joining the room""" 151 """Called when something is going wrong when joining the room"""
171 if hasattr(failure.value, "condition") and failure.value.condition == 'conflict':
172 # we have a nickname conflict, we try again with "_" suffixed to current nickname
173 nick += '_'
174 return self.clients[profile].join(room_jid, nick, password).addCallbacks(self._joinCb, self._joinEb, callbackKeywords={'profile': profile}, errbackArgs=[room_jid, nick, password, profile])
175 mess = D_("Error while joining the room %s" % room_jid.userhost())
176 try: 152 try:
177 mess += " with condition '%s'" % failure.value.condition 153 condition = failure.value.condition
178 except AttributeError: 154 except AttributeError:
179 pass 155 msg_suffix = ''
156 else:
157 if condition == 'conflict':
158 # we have a nickname conflict, we try again with "_" suffixed to current nickname
159 nick += '_'
160 return client.join(room_jid, nick, password).addCallbacks(self._joinCb, self._joinEb, callbackKeywords={client: client}, errbackArgs=[client, room_jid, nick, password])
161 msg_suffix = ' with condition "{}"'.format(failure.value.condition)
162
163 mess = D_(u"Error while joining the room {room}{suffix}".format(
164 room = room_jid.userhost(), suffix = msg_suffix))
180 log.error(mess) 165 log.error(mess)
181 self.host.bridge.newAlert(mess, D_("Group chat error"), "ERROR", profile) 166 xmlui = xml_tools.note(mess, D_(u"Group chat error"), level=C.XMLUI_DATA_LVL_ERROR)
182 raise failure 167 self.host.actionNew({'xmlui': xmlui.toXml()}, profile=client.profile)
183 168
184 @staticmethod 169 @staticmethod
185 def _getOccupants(room): 170 def _getOccupants(room):
186 """Get occupants of a room in a form suitable for bridge""" 171 """Get occupants of a room in a form suitable for bridge"""
187 return {u.nick: {k:unicode(getattr(u,k) or '') for k in OCCUPANT_KEYS} for u in room.roster.values()} 172 return {u.nick: {k:unicode(getattr(u,k) or '') for k in OCCUPANT_KEYS} for u in room.roster.values()}
188 173
189 def isRoom(self, entity_bare, profile_key): 174 def _getRoomsJoined(self, profile_key=C.PROF_KEY_NONE):
190 """Tell if a bare entity is a MUC room. 175 client = self.host.getClient(profile_key)
191 176 return self.getRoomsJoined(client)
192 @param entity_bare (jid.JID): bare entity 177
193 @param profile_key (unicode): %(doc_profile_key)s 178 def getRoomsJoined(self, client):
194 @return bool 179 """Return rooms where user is"""
195 """
196 profile = self.host.memory.getProfileName(profile_key)
197 return entity_bare in self.clients[profile].joined_rooms
198
199 def getRoomsJoined(self, profile_key=C.PROF_KEY_NONE):
200 """Return room where user is"""
201 profile = self.host.memory.getProfileName(profile_key)
202 result = [] 180 result = []
203 if not self.checkClient(profile): 181 for room in client._muc_client.joined_rooms.values():
204 return result
205 for room in self.clients[profile].joined_rooms.values():
206 if room._room_ok: 182 if room._room_ok:
207 result.append((room.roomJID.userhost(), self._getOccupants(room), room.nick, room.subject)) 183 result.append((room.roomJID.userhost(), self._getOccupants(room), room.nick, room.subject))
208 return result 184 return result
209 185
210 def getRoomNick(self, room_jid, profile_key=C.PROF_KEY_NONE): 186 def getRoomNick(self, client, room_jid):
211 """return nick used in room by user 187 """return nick used in room by user
212 188
213 @param room_jid (jid.JID): JID of the room 189 @param room_jid (jid.JID): JID of the room
214 @profile_key: profile 190 @profile_key: profile
215 @return: nick or empty string in case of error""" 191 @return: nick or empty string in case of error
216 profile = self.host.memory.getProfileName(profile_key) 192 """
217 if not self.checkClient(profile) or room_jid not in self.clients[profile].joined_rooms: 193 try:
218 return '' 194 self.checkRoomJoined(client, room_jid)
219 return self.clients[profile].joined_rooms[room_jid].nick 195 except exceptions.NotFound:
220 196 return '' # FIXME: should not return empty string but raise the error
221 def getRoomNickOfUser(self, room, user_jid, secure=True): 197 return client._muc_client.joined_rooms[room_jid].nick
222 """Returns the nick of the given user in the room. 198
223 199 # FIXME: broken, to be removed !
224 @param room (wokkel.muc.Room): the room 200 # def getRoomEntityNick(self, client, room_jid, entity_jid, =True):
225 @param user (jid.JID): bare JID of the user 201 # """Returns the nick of the given user in the room.
226 @param secure (bool): set to True for a secure check 202
227 @return: unicode or None if the user didn't join the room. 203 # @param room (wokkel.muc.Room): the room
228 """ 204 # @param user (jid.JID): bare JID of the user
229 for user in room.roster.values(): 205 # @param secure (bool): set to True for a secure check
230 if user.entity is not None: 206 # @return: unicode or None if the user didn't join the room.
231 if user.entity.userhostJID() == user_jid.userhostJID(): 207 # """
232 return user.nick 208 # for user in room.roster.values():
233 elif not secure: 209 # if user.entity is not None:
234 # FIXME: this is NOT ENOUGH to check an identity!! 210 # if user.entity.userhostJID() == user_jid.userhostJID():
235 # See in which conditions user.entity could be None. 211 # return user.nick
236 if user.nick == user_jid.user: 212 # elif not secure:
237 return user.nick 213 # # FIXME: this is NOT ENOUGH to check an identity!!
238 return None 214 # # See in which conditions user.entity could be None.
239 215 # if user.nick == user_jid.user:
240 def getRoomNicksOfUsers(self, room, users=[], secure=True): 216 # return user.nick
241 """Returns the nicks of the given users in the room. 217 # return None
242 218
243 @param room (wokkel.muc.Room): the room 219 # def getRoomNicksOfUsers(self, room, users=[], secure=True):
244 @param users (list[jid.JID]): list of users 220 # """Returns the nicks of the given users in the room.
245 @param secure (True): set to True for a secure check 221
246 @return: a couple (x, y) with: 222 # @param room (wokkel.muc.Room): the room
247 - x (list[unicode]): nicks of the users who are in the room 223 # @param users (list[jid.JID]): list of users
248 - y (list[jid.JID]): JID of the missing users. 224 # @param secure (True): set to True for a secure check
249 """ 225 # @return: a couple (x, y) with:
250 nicks = [] 226 # - x (list[unicode]): nicks of the users who are in the room
251 missing = [] 227 # - y (list[jid.JID]): JID of the missing users.
252 for user in users: 228 # """
253 nick = self.getRoomNickOfUser(room, user, secure) 229 # nicks = []
254 if nick is None: 230 # missing = []
255 missing.append(user) 231 # for user in users:
256 else: 232 # nick = self.getRoomNickOfUser(room, user, secure)
257 nicks.append(nick) 233 # if nick is None:
258 return nicks, missing 234 # missing.append(user)
235 # else:
236 # nicks.append(nick)
237 # return nicks, missing
259 238
260 def _configureRoom(self, room_jid_s, profile_key=C.PROF_KEY_NONE): 239 def _configureRoom(self, room_jid_s, profile_key=C.PROF_KEY_NONE):
261 d = self.configureRoom(jid.JID(room_jid_s), profile_key) 240 client = self.host.getClient(profile_key)
241 d = self.configureRoom(client, jid.JID(room_jid_s))
262 d.addCallback(lambda xmlui: xmlui.toXml()) 242 d.addCallback(lambda xmlui: xmlui.toXml())
263 return d 243 return d
264 244
265 def _configureRoomMenu(self, menu_data, profile): 245 def _configureRoomMenu(self, menu_data, profile):
266 """Return room configuration form 246 """Return room configuration form
267 247
268 @param menu_data: %(menu_data)s 248 @param menu_data: %(menu_data)s
269 @param profile: %(doc_profile)s 249 @param profile: %(doc_profile)s
270 """ 250 """
251 client = self.host.getClient(profile)
271 try: 252 try:
272 room_jid = jid.JID(menu_data['room_jid']) 253 room_jid = jid.JID(menu_data['room_jid'])
273 except KeyError: 254 except KeyError:
274 log.error(_("room_jid key is not present !")) 255 log.error(_("room_jid key is not present !"))
275 return defer.fail(exceptions.DataError) 256 return defer.fail(exceptions.DataError)
276 257
277 def xmluiReceived(xmlui): 258 def xmluiReceived(xmlui):
278 return {"xmlui": xmlui.toXml()} 259 return {"xmlui": xmlui.toXml()}
279 return self.configureRoom(room_jid, profile).addCallback(xmluiReceived) 260 return self.configureRoom(client, room_jid).addCallback(xmluiReceived)
280 261
281 def configureRoom(self, room_jid, profile_key=C.PROF_KEY_NONE): 262 def configureRoom(self, client, room_jid):
282 """return the room configuration form 263 """return the room configuration form
283 264
284 @param room: jid of the room to configure 265 @param room: jid of the room to configure
285 @param profile_key: %(doc_profile_key)s
286 @return: configuration form as XMLUI 266 @return: configuration form as XMLUI
287 """ 267 """
288 profile = self.getProfileAssertInRoom(room_jid, profile_key) 268 self.checkRoomJoined(client, room_jid)
289 269
290 def config2XMLUI(result): 270 def config2XMLUI(result):
291 if not result: 271 if not result:
292 return "" 272 return ""
293 session_id, session_data = self._sessions.newSession(profile=profile) 273 session_id, session_data = self._sessions.newSession(profile=client.profile)
294 session_data["room_jid"] = room_jid 274 session_data["room_jid"] = room_jid
295 xmlui = xml_tools.dataForm2XMLUI(result, submit_id=self.__submit_conf_id) 275 xmlui = xml_tools.dataForm2XMLUI(result, submit_id=self.__submit_conf_id)
296 xmlui.session_id = session_id 276 xmlui.session_id = session_id
297 return xmlui 277 return xmlui
298 278
299 d = self.clients[profile].getConfiguration(room_jid) 279 d = client._muc_client.getConfiguration(room_jid)
300 d.addCallback(config2XMLUI) 280 d.addCallback(config2XMLUI)
301 return d 281 return d
302 282
303 def _submitConfiguration(self, raw_data, profile): 283 def _submitConfiguration(self, raw_data, profile):
284 client = self.host.getClient(profile)
304 try: 285 try:
305 session_data = self._sessions.profileGet(raw_data["session_id"], profile) 286 session_data = self._sessions.profileGet(raw_data["session_id"], profile)
306 except KeyError: 287 except KeyError:
307 log.warning(D_("Session ID doesn't exist, session has probably expired.")) 288 log.warning(D_("Session ID doesn't exist, session has probably expired."))
308 _dialog = xml_tools.XMLUI('popup', title=D_('Room configuration failed')) 289 _dialog = xml_tools.XMLUI('popup', title=D_('Room configuration failed'))
309 _dialog.addText(D_("Session ID doesn't exist, session has probably expired.")) 290 _dialog.addText(D_("Session ID doesn't exist, session has probably expired."))
310 return defer.succeed({'xmlui': _dialog.toXml()}) 291 return defer.succeed({'xmlui': _dialog.toXml()})
311 292
312 data = xml_tools.XMLUIResult2DataFormResult(raw_data) 293 data = xml_tools.XMLUIResult2DataFormResult(raw_data)
313 d = self.clients[profile].configure(session_data['room_jid'], data) 294 d = client._muc_client.configure(session_data['room_jid'], data)
314 _dialog = xml_tools.XMLUI('popup', title=D_('Room configuration succeed')) 295 _dialog = xml_tools.XMLUI('popup', title=D_('Room configuration succeed'))
315 _dialog.addText(D_("The new settings have been saved.")) 296 _dialog.addText(D_("The new settings have been saved."))
316 d.addCallback(lambda ignore: {'xmlui': _dialog.toXml()}) 297 d.addCallback(lambda ignore: {'xmlui': _dialog.toXml()})
317 del self._sessions[raw_data["session_id"]] 298 del self._sessions[raw_data["session_id"]]
318 return d 299 return d
319 300
320 def isNickInRoom(self, room_jid, nick, profile): 301 def isNickInRoom(self, client, room_jid, nick):
321 """Tell if a nick is currently present in a room""" 302 """Tell if a nick is currently present in a room"""
322 profile = self.getProfileAssertInRoom(room_jid, profile) 303 self.checkRoomJoined(client, room_jid)
323 return self.clients[profile].joined_rooms[room_jid].inRoster(muc.User(nick)) 304 return client._muc_client.joined_rooms[room_jid].inRoster(muc.User(nick))
324
325 def getRoomsSubjects(self, profile_key=C.PROF_KEY_NONE):
326 """Return received subjects of rooms"""
327 # FIXME: to be removed
328 profile = self.host.memory.getProfileName(profile_key)
329 if not self.checkClient(profile):
330 return []
331 return self.clients[profile].rec_subjects.values()
332 305
333 @defer.inlineCallbacks 306 @defer.inlineCallbacks
334 def getMUCService(self, jid_=None, profile=C.PROF_KEY_NONE): 307 def getMUCService(self, jid_=None, profile=C.PROF_KEY_NONE):
335 """Return first found MUC service of an entity 308 """Return first found MUC service of an entity
336 309
347 muc_service = service 320 muc_service = service
348 break 321 break
349 defer.returnValue(muc_service) 322 defer.returnValue(muc_service)
350 323
351 def _getUniqueName(self, muc_service="", profile_key=C.PROF_KEY_NONE): 324 def _getUniqueName(self, muc_service="", profile_key=C.PROF_KEY_NONE):
352 return self.getUniqueName(muc_service or None, profile_key).full() 325 client = self.host.getClient(profile_key)
353 326 return self.getUniqueName(client, muc_service or None).full()
354 def getUniqueName(self, muc_service=None, profile_key=C.PROF_KEY_NONE): 327
328 def getUniqueName(self, client, muc_service=None):
355 """Return unique name for a room, avoiding collision 329 """Return unique name for a room, avoiding collision
356 330
357 @param muc_service (jid.JID) : leave empty string to use the default service 331 @param muc_service (jid.JID) : leave empty string to use the default service
358 @return: jid.JID (unique room bare JID) 332 @return: jid.JID (unique room bare JID)
359 """ 333 """
360 # TODO: we should use #RFC-0045 10.1.4 when available here 334 # TODO: we should use #RFC-0045 10.1.4 when available here
361 client = self.host.getClient(profile_key) 335 room_name = unicode(uuid.uuid4())
362 room_name = uuid.uuid1()
363 if muc_service is None: 336 if muc_service is None:
364 try: 337 try:
365 muc_service = client.muc_service 338 muc_service = client.muc_service
366 except AttributeError: 339 except AttributeError:
367 raise NotReadyYet("Main server MUC service has not been checked yet") 340 raise exceptions.NotReady(u"Main server MUC service has not been checked yet")
368 if muc_service is None: 341 if muc_service is None:
369 log.warning(_("No MUC service found on main server")) 342 log.warning(_("No MUC service found on main server"))
370 raise exceptions.FeatureNotFound 343 raise exceptions.FeatureNotFound
371 344
372 muc_service = muc_service.userhost() 345 muc_service = muc_service.userhost()
373 return jid.JID("%s@%s" % (room_name, muc_service)) 346 return jid.JID(u"{}@{}".format(room_name, muc_service))
374 347
375 def getDefaultMUC(self): 348 def getDefaultMUC(self):
376 """Return the default MUC. 349 """Return the default MUC.
377 350
378 @return: unicode 351 @return: unicode
379 """ 352 """
380 return self.host.memory.getConfig(CONFIG_SECTION, 'default_muc', default_conf['default_muc']) 353 return self.host.memory.getConfig(CONFIG_SECTION, 'default_muc', default_conf['default_muc'])
381 354
382 def join(self, client, room_jid, nick, options): 355 def join(self, client, room_jid, nick, options):
383 def _errDeferred(exc_obj=Exception, txt='Error while joining room'): 356 def _errDeferred(exc_obj=Exception, txt=u'Error while joining room'):
384 d = defer.Deferred() 357 d = defer.Deferred()
385 d.errback(exc_obj(txt)) 358 d.errback(exc_obj(txt))
386 return d 359 return d
387 360
388 if room_jid in self.clients[client.profile].joined_rooms: 361 if room_jid in client._muc_client.joined_rooms:
389 log.warning(_(u'%(profile)s is already in room %(room_jid)s') % {'profile': client.profile, 'room_jid': room_jid.userhost()}) 362 log.warning(_(u'{profile} is already in room {room_jid}').format(profile=client.profile, room_jid = room_jid.userhost()))
390 return _errDeferred(AlreadyJoinedRoom, D_(u"The room has already been joined")) 363 return defer.fail(exceptions.ConflictError(_(u"The room has already been joined")))
391 log.info(_(u"[%(profile)s] is joining room %(room)s with nick %(nick)s") % {'profile': client.profile, 'room': room_jid.userhost(), 'nick': nick}) 364 log.info(_(u"[{profile}] is joining room {room} with nick {nick}").format(profile=client.profile, room=room_jid.userhost(), nick=nick))
392 365
393 password = options["password"] if "password" in options else None 366 password = options["password"] if "password" in options else None
394 367
395 return self.clients[client.profile].join(room_jid, nick, password).addCallbacks(self._joinCb, self._joinEb, callbackKeywords={'profile': client.profile}, errbackArgs=[room_jid, nick, password, client.profile]) 368 return client._muc_client.join(room_jid, nick, password).addCallbacks(self._joinCb, self._joinEb, callbackKeywords={'client': client}, errbackArgs=[client, room_jid, nick, password])
396 369
397 def _join(self, room_jid_s, nick, options=None, profile_key=C.PROF_KEY_NONE): 370 def _join(self, room_jid_s, nick, options=None, profile_key=C.PROF_KEY_NONE):
398 """join method used by bridge 371 """join method used by bridge
399 372
400 @return: unicode (the room bare) 373 @return: unicode (the room bare)
414 room_jid = self.getUniqueName(profile_key=client.profile) 387 room_jid = self.getUniqueName(profile_key=client.profile)
415 # TODO: error management + signal in bridge 388 # TODO: error management + signal in bridge
416 d = self.join(client, room_jid, nick, options) 389 d = self.join(client, room_jid, nick, options)
417 return d.addCallback(lambda room: room.roomJID.userhost()) 390 return d.addCallback(lambda room: room.roomJID.userhost())
418 391
419 def nick(self, room_jid, nick, profile_key): 392 def _nick(self, room_jid_s, nick, profile_key=C.PROF_KEY_NONE):
420 profile = self.getProfileAssertInRoom(room_jid, profile_key) 393 client = self.host.getClient(profile_key)
421 return self.clients[profile].nick(room_jid, nick) 394 return self.nick(client, jid.JID(room_jid_s), nick)
422 395
423 def leave(self, room_jid, profile_key): 396 def nick(self, client, room_jid, nick):
424 profile = self.getProfileAssertInRoom(room_jid, profile_key)
425 return self.clients[profile].leave(room_jid)
426
427 def subject(self, room_jid, subject, profile_key):
428 profile = self.getProfileAssertInRoom(room_jid, profile_key)
429 return self.clients[profile].subject(room_jid, subject)
430
431 def mucNick(self, room_jid_s, nick, profile_key=C.PROF_KEY_NONE):
432 """Change nickname in a room""" 397 """Change nickname in a room"""
433 return self.nick(jid.JID(room_jid_s), nick, profile_key) 398 self.checkRoomJoined(client, room_jid)
434 399 return client._muc_client.nick(room_jid, nick)
435 def mucLeave(self, room_jid_s, profile_key=C.PROF_KEY_NONE): 400
436 """Leave a room""" 401 def _leave(self, room_jid, profile_key):
437 return self.leave(jid.JID(room_jid_s), profile_key) 402 client = self.host.getClient(profile_key)
403 return self.leave(client, jid.JID(room_jid))
404
405 def leave(self, client, room_jid):
406 self.checkRoomJoined(client, room_jid)
407 return client._muc_client.leave(room_jid)
408
409 def subject(self, client, room_jid, subject):
410 self.checkRoomJoined(client, room_jid)
411 return client._muc_client.subject(room_jid, subject)
438 412
439 def getHandler(self, profile): 413 def getHandler(self, profile):
440 self.clients[profile] = SatMUCClient(self) 414 # create a MUC client and associate it with profile' session
441 return self.clients[profile] 415 client = self.host.getClient(profile)
442 416 muc_client = client._muc_client = SatMUCClient(self)
443 def profileDisconnected(self, profile): 417 return muc_client
444 try: 418
445 del self.clients[profile] 419 def kick(self, client, nick, room_jid, options=None):
446 except KeyError:
447 pass
448
449 def kick(self, nick, room_jid, options={}, profile_key=C.PROF_KEY_NONE):
450 """ 420 """
451 Kick a participant from the room 421 Kick a participant from the room
452 @param nick (str): nick of the user to kick 422 @param nick (str): nick of the user to kick
453 @param room_jid_s (JID): jid of the room 423 @param room_jid_s (JID): jid of the room
454 @param options (dict): attribute with extra info (reason, password) as in #XEP-0045 424 @param options (dict): attribute with extra info (reason, password) as in #XEP-0045
455 @param profile_key (str): %(doc_profile_key)s 425 """
456 """ 426 if options is None:
457 profile = self.getProfileAssertInRoom(room_jid, profile_key) 427 options = {}
458 return self.clients[profile].kick(room_jid, nick, reason=options.get('reason', None)) 428 self.checkRoomJoined(client, room_jid)
459 429 return client._muc_client.kick(room_jid, nick, reason=options.get('reason', None))
460 def ban(self, entity_jid, room_jid, options={}, profile_key=C.PROF_KEY_NONE): 430
461 """ 431 def ban(self, client, entity_jid, room_jid, options=None):
462 Ban an entity from the room 432 """Ban an entity from the room
433
463 @param entity_jid (JID): bare jid of the entity to be banned 434 @param entity_jid (JID): bare jid of the entity to be banned
464 @param room_jid_s (JID): jid of the room 435 @param room_jid (JID): jid of the room
465 @param options: attribute with extra info (reason, password) as in #XEP-0045 436 @param options: attribute with extra info (reason, password) as in #XEP-0045
466 @param profile_key (str): %(doc_profile_key)s 437 """
467 """ 438 self.checkRoomJoined(client, room_jid)
468 assert(not entity_jid.resource) 439 if options is None:
469 assert(not room_jid.resource) 440 options = {}
470 profile = self.getProfileAssertInRoom(room_jid, profile_key) 441 assert not entity_jid.resource
471 return self.clients[profile].ban(room_jid, entity_jid, reason=options.get('reason', None)) 442 assert not room_jid.resource
472 443 return client._muc_client.ban(room_jid, entity_jid, reason=options.get('reason', None))
473 def affiliate(self, entity_jid, room_jid, options=None, profile_key=C.PROF_KEY_NONE): 444
474 """ 445 def affiliate(self, client, entity_jid, room_jid, options):
475 Change the affiliation of an entity 446 """Change the affiliation of an entity
447
476 @param entity_jid (JID): bare jid of the entity 448 @param entity_jid (JID): bare jid of the entity
477 @param room_jid_s (JID): jid of the room 449 @param room_jid_s (JID): jid of the room
478 @param options: attribute with extra info (reason, nick) as in #XEP-0045 450 @param options: attribute with extra info (reason, nick) as in #XEP-0045
479 @param profile_key (str): %(doc_profile_key)s 451 """
480 """ 452 self.checkRoomJoined(client, room_jid)
481 assert(not entity_jid.resource) 453 assert not entity_jid.resource
482 assert(not room_jid.resource) 454 assert not room_jid.resource
483 assert('affiliation' in options) 455 assert 'affiliation' in options
484 profile = self.getProfileAssertInRoom(room_jid, profile_key)
485 # TODO: handles reason and nick 456 # TODO: handles reason and nick
486 return self.clients[profile].modifyAffiliationList(room_jid, [entity_jid], options['affiliation']) 457 return client._muc_client.modifyAffiliationList(room_jid, [entity_jid], options['affiliation'])
487 458
488 # Text commands # 459 # Text commands #
489 460
490 def cmd_nick(self, client, mess_data): 461 def cmd_nick(self, client, mess_data):
491 """change nickname 462 """change nickname
506 @command (all): JID 477 @command (all): JID
507 - JID: room to join (on the same service if full jid is not specified) 478 - JID: room to join (on the same service if full jid is not specified)
508 """ 479 """
509 if mess_data["unparsed"].strip(): 480 if mess_data["unparsed"].strip():
510 room_jid = self.host.plugins[C.TEXT_CMDS].getRoomJID(mess_data["unparsed"].strip(), mess_data["to"].host) 481 room_jid = self.host.plugins[C.TEXT_CMDS].getRoomJID(mess_data["unparsed"].strip(), mess_data["to"].host)
511 nick = (self.getRoomNick(room_jid, client.profile) or 482 nick = (self.getRoomNick(client, room_jid) or
512 self.host.getClient(client.profile).jid.user) 483 self.host.getClient(client.profile).jid.user)
513 self.join(client, room_jid, nick, {}) 484 self.join(client, room_jid, nick, {})
514 485
515 return False 486 return False
516 487
544 - ROOM_NICK: the nick of the person to kick 515 - ROOM_NICK: the nick of the person to kick
545 """ 516 """
546 options = mess_data["unparsed"].strip().split() 517 options = mess_data["unparsed"].strip().split()
547 try: 518 try:
548 nick = options[0] 519 nick = options[0]
549 assert(self.isNickInRoom(mess_data["to"], nick, client.profile)) 520 assert self.isNickInRoom(client, mess_data["to"], nick)
550 except (IndexError, AssertionError): 521 except (IndexError, AssertionError):
551 feedback = _(u"You must provide a member's nick to kick.") 522 feedback = _(u"You must provide a member's nick to kick.")
552 self.host.plugins[C.TEXT_CMDS].feedBack(client, feedback, mess_data) 523 self.host.plugins[C.TEXT_CMDS].feedBack(client, feedback, mess_data)
553 return False 524 return False
554 525
637 """ 608 """
638 subject = mess_data["unparsed"].strip() 609 subject = mess_data["unparsed"].strip()
639 610
640 if subject: 611 if subject:
641 room = mess_data["to"] 612 room = mess_data["to"]
642 self.subject(room, subject, client.profile) 613 self.subject(client, room, subject)
643 614
644 return False 615 return False
645 616
646 def cmd_topic(self, client, mess_data): 617 def cmd_topic(self, client, mess_data):
647 """just a synonym of /title 618 """just a synonym of /title
653 624
654 def _whois(self, client, whois_msg, mess_data, target_jid): 625 def _whois(self, client, whois_msg, mess_data, target_jid):
655 """ Add MUC user information to whois """ 626 """ Add MUC user information to whois """
656 if mess_data['type'] != "groupchat": 627 if mess_data['type'] != "groupchat":
657 return 628 return
658 if target_jid.userhostJID() not in self.clients[client.profile].joined_rooms: 629 if target_jid.userhostJID() not in client._muc_client.joined_rooms:
659 log.warning(_("This room has not been joined")) 630 log.warning(_("This room has not been joined"))
660 return 631 return
661 if not target_jid.resource: 632 if not target_jid.resource:
662 return 633 return
663 user = self.clients[client.profile].joined_rooms[target_jid.userhostJID()].getUser(target_jid.resource) 634 user = client._muc_client.joined_rooms[target_jid.userhostJID()].getUser(target_jid.resource)
664 whois_msg.append(_("Nickname: %s") % user.nick) 635 whois_msg.append(_("Nickname: %s") % user.nick)
665 if user.entity: 636 if user.entity:
666 whois_msg.append(_("Entity: %s") % user.entity) 637 whois_msg.append(_("Entity: %s") % user.entity)
667 if user.affiliation != 'none': 638 if user.affiliation != 'none':
668 whois_msg.append(_("Affiliation: %s") % user.affiliation) 639 whois_msg.append(_("Affiliation: %s") % user.affiliation)
673 if user.show: 644 if user.show:
674 whois_msg.append(_("Show: %s") % user.show) 645 whois_msg.append(_("Show: %s") % user.show)
675 646
676 def presenceTrigger(self, presence_elt, client): 647 def presenceTrigger(self, presence_elt, client):
677 # XXX: shouldn't it be done by the server ?!! 648 # XXX: shouldn't it be done by the server ?!!
678 muc_client = self.clients[client.profile] 649 muc_client = client._muc_client
679 for room_jid, room in muc_client.joined_rooms.iteritems(): 650 for room_jid, room in muc_client.joined_rooms.iteritems():
680 elt = copy.deepcopy(presence_elt) 651 elt = copy.deepcopy(presence_elt)
681 elt['to'] = room_jid.userhost() + '/' + room.nick 652 elt['to'] = room_jid.userhost() + '/' + room.nick
682 client.presence.send(elt) 653 client.presence.send(elt)
683 return True 654 return True
684 655
685 656
686 class SatMUCClient (muc.MUCClient): 657 class SatMUCClient(muc.MUCClient):
687 implements(iwokkel.IDisco) 658 implements(iwokkel.IDisco)
688 659
689 def __init__(self, plugin_parent): 660 def __init__(self, plugin_parent):
690 self.plugin_parent = plugin_parent 661 self.plugin_parent = plugin_parent
691 self.host = plugin_parent.host 662 self.host = plugin_parent.host
821 # we left the room 792 # we left the room
822 room_jid_s = room.roomJID.userhost() 793 room_jid_s = room.roomJID.userhost()
823 log.info(_(u"Room ({room}) left ({profile})").format( 794 log.info(_(u"Room ({room}) left ({profile})").format(
824 room = room_jid_s, profile = self.parent.profile)) 795 room = room_jid_s, profile = self.parent.profile))
825 self.host.memory.delEntityCache(room.roomJID, profile_key=self.parent.profile) 796 self.host.memory.delEntityCache(room.roomJID, profile_key=self.parent.profile)
826 self.host.bridge.roomLeft(room.roomJID.userhost(), self.parent.profile) 797 self.host.bridge.mucRoomLeft(room.roomJID.userhost(), self.parent.profile)
827 else: 798 else:
828 log.debug(_(u"user {nick} left room {room_id}").format(nick=user.nick, room_id=room.occupantJID.userhost())) 799 log.debug(_(u"user {nick} left room {room_id}").format(nick=user.nick, room_id=room.occupantJID.userhost()))
829 extra = {'info_type': ROOM_USER_LEFT, 800 extra = {'info_type': ROOM_USER_LEFT,
830 'user_affiliation': user.affiliation, 801 'user_affiliation': user.affiliation,
831 'user_role': user.role, 802 'user_role': user.role,
845 } 816 }
846 self.host.messageAddToHistory(mess_data, self.parent) 817 self.host.messageAddToHistory(mess_data, self.parent)
847 self.host.messageSendToBridge(mess_data, self.parent) 818 self.host.messageSendToBridge(mess_data, self.parent)
848 819
849 def userChangedNick(self, room, user, new_nick): 820 def userChangedNick(self, room, user, new_nick):
850 self.host.bridge.roomUserChangedNick(room.roomJID.userhost(), user.nick, new_nick, self.parent.profile) 821 self.host.bridge.mucRoomUserChangedNick(room.roomJID.userhost(), user.nick, new_nick, self.parent.profile)
851 822
852 def userUpdatedStatus(self, room, user, show, status): 823 def userUpdatedStatus(self, room, user, show, status):
853 self.host.bridge.presenceUpdate(room.roomJID.userhost() + '/' + user.nick, show or '', 0, {C.PRESENCE_STATUSES_DEFAULT: status or ''}, self.parent.profile) 824 self.host.bridge.presenceUpdate(room.roomJID.userhost() + '/' + user.nick, show or '', 0, {C.PRESENCE_STATUSES_DEFAULT: status or ''}, self.parent.profile)
854 825
855 ## messages ## 826 ## messages ##
921 892
922 def subject(self, room, subject): 893 def subject(self, room, subject):
923 return muc.MUCClientProtocol.subject(self, room, subject) 894 return muc.MUCClientProtocol.subject(self, room, subject)
924 895
925 def _historyCb(self, dummy, room): 896 def _historyCb(self, dummy, room):
926 self.host.bridge.roomJoined( 897 self.host.bridge.mucRoomJoined(
927 room.roomJID.userhost(), 898 room.roomJID.userhost(),
928 XEP_0045._getOccupants(room), 899 XEP_0045._getOccupants(room),
929 room.nick, 900 room.nick,
930 room.subject, 901 room.subject,
931 self.parent.profile) 902 self.parent.profile)
952 room._room_ok = False 923 room._room_ok = False
953 room._history_d.addCallbacks(self._historyCb, self._historyEb, [room], errbackArgs=[room]) 924 room._history_d.addCallbacks(self._historyCb, self._historyEb, [room], errbackArgs=[room])
954 else: 925 else:
955 # the subject has been changed 926 # the subject has been changed
956 log.debug(_(u"New subject for room ({room_id}): {subject}").format(room_id = room.roomJID.full(), subject = subject)) 927 log.debug(_(u"New subject for room ({room_id}): {subject}").format(room_id = room.roomJID.full(), subject = subject))
957 self.host.bridge.roomNewSubject(room.roomJID.userhost(), subject, self.parent.profile) 928 self.host.bridge.mucRoomNewSubject(room.roomJID.userhost(), subject, self.parent.profile)
958 929
959 ## disco ## 930 ## disco ##
960 931
961 def getDiscoInfo(self, requestor, target, nodeIdentifier=''): 932 def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
962 return [disco.DiscoFeature(NS_MUC)] 933 return [disco.DiscoFeature(NS_MUC)]