Mercurial > libervia-backend
comparison sat/plugins/plugin_xep_0045.py @ 3028:ab2696e34d29
Python 3 port:
/!\ this is a huge commit
/!\ starting from this commit, SàT is needs Python 3.6+
/!\ SàT maybe be instable or some feature may not work anymore, this will improve with time
This patch port backend, bridge and frontends to Python 3.
Roughly this has been done this way:
- 2to3 tools has been applied (with python 3.7)
- all references to python2 have been replaced with python3 (notably shebangs)
- fixed files not handled by 2to3 (notably the shell script)
- several manual fixes
- fixed issues reported by Python 3 that where not handled in Python 2
- replaced "async" with "async_" when needed (it's a reserved word from Python 3.7)
- replaced zope's "implements" with @implementer decorator
- temporary hack to handle data pickled in database, as str or bytes may be returned,
to be checked later
- fixed hash comparison for password
- removed some code which is not needed anymore with Python 3
- deactivated some code which needs to be checked (notably certificate validation)
- tested with jp, fixed reported issues until some basic commands worked
- ported Primitivus (after porting dependencies like urwid satext)
- more manual fixes
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 13 Aug 2019 19:08:41 +0200 |
parents | 7b8d40b17451 |
children | 730bbed77a89 |
comparison
equal
deleted
inserted
replaced
3027:ff5bcb12ae60 | 3028:ab2696e34d29 |
---|---|
1 #!/usr/bin/env python2 | 1 #!/usr/bin/env python3 |
2 # -*- coding: utf-8 -*- | 2 # -*- coding: utf-8 -*- |
3 | 3 |
4 # SAT plugin for managing xep-0045 | 4 # SAT plugin for managing xep-0045 |
5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) | 5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) |
6 | 6 |
32 import uuid | 32 import uuid |
33 | 33 |
34 from wokkel import muc, disco, iwokkel | 34 from wokkel import muc, disco, iwokkel |
35 from sat.tools import xml_tools | 35 from sat.tools import xml_tools |
36 | 36 |
37 from zope.interface import implements | 37 from zope.interface import implementer |
38 | 38 |
39 # XXX: mam and rsm come from sat_tmp.wokkel | 39 # XXX: mam and rsm come from sat_tmp.wokkel |
40 from wokkel import rsm | 40 from wokkel import rsm |
41 from wokkel import mam | 41 from wokkel import mam |
42 | 42 |
45 C.PI_NAME: "XEP-0045 Plugin", | 45 C.PI_NAME: "XEP-0045 Plugin", |
46 C.PI_IMPORT_NAME: "XEP-0045", | 46 C.PI_IMPORT_NAME: "XEP-0045", |
47 C.PI_TYPE: "XEP", | 47 C.PI_TYPE: "XEP", |
48 C.PI_PROTOCOLS: ["XEP-0045"], | 48 C.PI_PROTOCOLS: ["XEP-0045"], |
49 C.PI_DEPENDENCIES: ["XEP-0359"], | 49 C.PI_DEPENDENCIES: ["XEP-0359"], |
50 C.PI_RECOMMENDATIONS: [C.TEXT_CMDS, u"XEP-0313"], | 50 C.PI_RECOMMENDATIONS: [C.TEXT_CMDS, "XEP-0313"], |
51 C.PI_MAIN: "XEP_0045", | 51 C.PI_MAIN: "XEP_0045", |
52 C.PI_HANDLER: "yes", | 52 C.PI_HANDLER: "yes", |
53 C.PI_DESCRIPTION: _("""Implementation of Multi-User Chat""") | 53 C.PI_DESCRIPTION: _("""Implementation of Multi-User Chat""") |
54 } | 54 } |
55 | 55 |
60 OCCUPANT_KEYS = ('nick', 'entity', 'affiliation', 'role') | 60 OCCUPANT_KEYS = ('nick', 'entity', 'affiliation', 'role') |
61 ROOM_STATE_OCCUPANTS = "occupants" | 61 ROOM_STATE_OCCUPANTS = "occupants" |
62 ROOM_STATE_SELF_PRESENCE = "self-presence" | 62 ROOM_STATE_SELF_PRESENCE = "self-presence" |
63 ROOM_STATE_LIVE = "live" | 63 ROOM_STATE_LIVE = "live" |
64 ROOM_STATES = (ROOM_STATE_OCCUPANTS, ROOM_STATE_SELF_PRESENCE, ROOM_STATE_LIVE) | 64 ROOM_STATES = (ROOM_STATE_OCCUPANTS, ROOM_STATE_SELF_PRESENCE, ROOM_STATE_LIVE) |
65 HISTORY_LEGACY = u"legacy" | 65 HISTORY_LEGACY = "legacy" |
66 HISTORY_MAM = u"mam" | 66 HISTORY_MAM = "mam" |
67 | 67 |
68 | 68 |
69 CONFIG_SECTION = u'plugin muc' | 69 CONFIG_SECTION = 'plugin muc' |
70 | 70 |
71 default_conf = {"default_muc": u'sat@chat.jabberfr.org'} | 71 default_conf = {"default_muc": 'sat@chat.jabberfr.org'} |
72 | 72 |
73 | 73 |
74 class AlreadyJoined(exceptions.ConflictError): | 74 class AlreadyJoined(exceptions.ConflictError): |
75 | 75 |
76 def __init__(self, room): | 76 def __init__(self, room): |
84 | 84 |
85 def __init__(self, host): | 85 def __init__(self, host): |
86 log.info(_("Plugin XEP_0045 initialization")) | 86 log.info(_("Plugin XEP_0045 initialization")) |
87 self.host = host | 87 self.host = host |
88 self._sessions = memory.Sessions() | 88 self._sessions = memory.Sessions() |
89 host.bridge.addMethod("mucJoin", ".plugin", in_sign='ssa{ss}s', out_sign='(bsa{sa{ss}}sss)', method=self._join, async=True) # return same arguments as mucRoomJoined + a boolean set to True is the room was already joined (first argument) | 89 host.bridge.addMethod("mucJoin", ".plugin", in_sign='ssa{ss}s', out_sign='(bsa{sa{ss}}sss)', method=self._join, async_=True) # return same arguments as mucRoomJoined + a boolean set to True is the room was already joined (first argument) |
90 host.bridge.addMethod("mucNick", ".plugin", in_sign='sss', out_sign='', method=self._nick) | 90 host.bridge.addMethod("mucNick", ".plugin", in_sign='sss', out_sign='', method=self._nick) |
91 host.bridge.addMethod("mucNickGet", ".plugin", in_sign='ss', out_sign='s', method=self._getRoomNick) | 91 host.bridge.addMethod("mucNickGet", ".plugin", in_sign='ss', out_sign='s', method=self._getRoomNick) |
92 host.bridge.addMethod("mucLeave", ".plugin", in_sign='ss', out_sign='', method=self._leave, async=True) | 92 host.bridge.addMethod("mucLeave", ".plugin", in_sign='ss', out_sign='', method=self._leave, async_=True) |
93 host.bridge.addMethod("mucOccupantsGet", ".plugin", in_sign='ss', out_sign='a{sa{ss}}', method=self._getRoomOccupants) | 93 host.bridge.addMethod("mucOccupantsGet", ".plugin", in_sign='ss', out_sign='a{sa{ss}}', method=self._getRoomOccupants) |
94 host.bridge.addMethod("mucSubject", ".plugin", in_sign='sss', out_sign='', method=self._subject) | 94 host.bridge.addMethod("mucSubject", ".plugin", in_sign='sss', out_sign='', method=self._subject) |
95 host.bridge.addMethod("mucGetRoomsJoined", ".plugin", in_sign='s', out_sign='a(sa{sa{ss}}ss)', method=self._getRoomsJoined) | 95 host.bridge.addMethod("mucGetRoomsJoined", ".plugin", in_sign='s', out_sign='a(sa{sa{ss}}ss)', method=self._getRoomsJoined) |
96 host.bridge.addMethod("mucGetUniqueRoomName", ".plugin", in_sign='ss', out_sign='s', method=self._getUniqueName) | 96 host.bridge.addMethod("mucGetUniqueRoomName", ".plugin", in_sign='ss', out_sign='s', method=self._getUniqueName) |
97 host.bridge.addMethod("mucConfigureRoom", ".plugin", in_sign='ss', out_sign='s', method=self._configureRoom, async=True) | 97 host.bridge.addMethod("mucConfigureRoom", ".plugin", in_sign='ss', out_sign='s', method=self._configureRoom, async_=True) |
98 host.bridge.addMethod("mucGetDefaultService", ".plugin", in_sign='', out_sign='s', method=self.getDefaultMUC) | 98 host.bridge.addMethod("mucGetDefaultService", ".plugin", in_sign='', out_sign='s', method=self.getDefaultMUC) |
99 host.bridge.addMethod("mucGetService", ".plugin", in_sign='ss', out_sign='s', method=self._getMUCService, async=True) | 99 host.bridge.addMethod("mucGetService", ".plugin", in_sign='ss', out_sign='s', method=self._getMUCService, async_=True) |
100 host.bridge.addSignal("mucRoomJoined", ".plugin", signature='sa{sa{ss}}sss') # args: room_jid, occupants, user_nick, subject, profile | 100 host.bridge.addSignal("mucRoomJoined", ".plugin", signature='sa{sa{ss}}sss') # args: room_jid, occupants, user_nick, subject, profile |
101 host.bridge.addSignal("mucRoomLeft", ".plugin", signature='ss') # args: room_jid, profile | 101 host.bridge.addSignal("mucRoomLeft", ".plugin", signature='ss') # args: room_jid, profile |
102 host.bridge.addSignal("mucRoomUserChangedNick", ".plugin", signature='ssss') # args: room_jid, old_nick, new_nick, profile | 102 host.bridge.addSignal("mucRoomUserChangedNick", ".plugin", signature='ssss') # args: room_jid, old_nick, new_nick, profile |
103 host.bridge.addSignal("mucRoomNewSubject", ".plugin", signature='sss') # args: room_jid, subject, profile | 103 host.bridge.addSignal("mucRoomNewSubject", ".plugin", signature='sss') # args: room_jid, subject, profile |
104 self.__submit_conf_id = host.registerCallback(self._submitConfiguration, with_data=True) | 104 self.__submit_conf_id = host.registerCallback(self._submitConfiguration, with_data=True) |
105 self._room_join_id = host.registerCallback(self._UIRoomJoinCb, with_data=True) | 105 self._room_join_id = host.registerCallback(self._UIRoomJoinCb, with_data=True) |
106 host.importMenu((D_("MUC"), D_("configure")), self._configureRoomMenu, security_limit=0, help_string=D_("Configure Multi-User Chat room"), type_=C.MENU_ROOM) | 106 host.importMenu((D_("MUC"), D_("configure")), self._configureRoomMenu, security_limit=0, help_string=D_("Configure Multi-User Chat room"), type_=C.MENU_ROOM) |
107 try: | 107 try: |
108 self.text_cmds = self.host.plugins[C.TEXT_CMDS] | 108 self.text_cmds = self.host.plugins[C.TEXT_CMDS] |
109 except KeyError: | 109 except KeyError: |
110 log.info(_(u"Text commands not available")) | 110 log.info(_("Text commands not available")) |
111 else: | 111 else: |
112 self.text_cmds.registerTextCommands(self) | 112 self.text_cmds.registerTextCommands(self) |
113 self.text_cmds.addWhoIsCb(self._whois, 100) | 113 self.text_cmds.addWhoIsCb(self._whois, 100) |
114 | 114 |
115 self._mam = self.host.plugins.get(u"XEP-0313") | 115 self._mam = self.host.plugins.get("XEP-0313") |
116 self._si = self.host.plugins[u"XEP-0359"] | 116 self._si = self.host.plugins["XEP-0359"] |
117 | 117 |
118 host.trigger.add("presence_available", self.presenceTrigger) | 118 host.trigger.add("presence_available", self.presenceTrigger) |
119 host.trigger.add("presence_received", self.presenceReceivedTrigger) | 119 host.trigger.add("presence_received", self.presenceReceivedTrigger) |
120 host.trigger.add("MessageReceived", self.messageReceivedTrigger, priority=1000000) | 120 host.trigger.add("MessageReceived", self.messageReceivedTrigger, priority=1000000) |
121 host.trigger.add("message_parse", self._message_parseTrigger) | 121 host.trigger.add("message_parse", self._message_parseTrigger) |
125 client.muc_service = service | 125 client.muc_service = service |
126 return self.getMUCService(client).addCallback(assign_service) | 126 return self.getMUCService(client).addCallback(assign_service) |
127 | 127 |
128 def _message_parseTrigger(self, client, message_elt, data): | 128 def _message_parseTrigger(self, client, message_elt, data): |
129 """Add stanza-id from the room if present""" | 129 """Add stanza-id from the room if present""" |
130 if message_elt.getAttribute(u"type") != C.MESS_TYPE_GROUPCHAT: | 130 if message_elt.getAttribute("type") != C.MESS_TYPE_GROUPCHAT: |
131 return True | 131 return True |
132 | 132 |
133 # stanza_id will not be filled by parseMessage because the emitter | 133 # stanza_id will not be filled by parseMessage because the emitter |
134 # is the room and not our server, so we have to parse it here | 134 # is the room and not our server, so we have to parse it here |
135 room_jid = data[u"from"].userhostJID() | 135 room_jid = data["from"].userhostJID() |
136 stanza_id = self._si.getStanzaId(message_elt, room_jid) | 136 stanza_id = self._si.getStanzaId(message_elt, room_jid) |
137 if stanza_id: | 137 if stanza_id: |
138 data[u"extra"][u"stanza_id"] = stanza_id | 138 data["extra"]["stanza_id"] = stanza_id |
139 | 139 |
140 def messageReceivedTrigger(self, client, message_elt, post_treat): | 140 def messageReceivedTrigger(self, client, message_elt, post_treat): |
141 if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT: | 141 if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT: |
142 if message_elt.subject or message_elt.delay: | 142 if message_elt.subject or message_elt.delay: |
143 return False | 143 return False |
150 # With MAM history, order is different, and we can get live | 150 # With MAM history, order is different, and we can get live |
151 # messages before history is complete, so this is not a warning | 151 # messages before history is complete, so this is not a warning |
152 # but an expected case. | 152 # but an expected case. |
153 # On the other hand, with legacy history, it's not normal. | 153 # On the other hand, with legacy history, it's not normal. |
154 log.warning(_( | 154 log.warning(_( |
155 u"Received non delayed message in a room before its " | 155 "Received non delayed message in a room before its " |
156 u"initialisation: state={state}, msg={msg}").format( | 156 "initialisation: state={state}, msg={msg}").format( |
157 state=room.state, | 157 state=room.state, |
158 msg=message_elt.toXml())) | 158 msg=message_elt.toXml())) |
159 room._cache.append(message_elt) | 159 room._cache.append(message_elt) |
160 return False | 160 return False |
161 else: | 161 else: |
162 log.warning(u"Received groupchat message for a room which has not been " | 162 log.warning("Received groupchat message for a room which has not been " |
163 u"joined, ignoring it: {}".format(message_elt.toXml())) | 163 "joined, ignoring it: {}".format(message_elt.toXml())) |
164 return False | 164 return False |
165 return True | 165 return True |
166 | 166 |
167 def getRoom(self, client, room_jid): | 167 def getRoom(self, client, room_jid): |
168 """Retrieve Room instance from its jid | 168 """Retrieve Room instance from its jid |
171 @raise exceptions.NotFound: the room has not been joined | 171 @raise exceptions.NotFound: the room has not been joined |
172 """ | 172 """ |
173 try: | 173 try: |
174 return client._muc_client.joined_rooms[room_jid] | 174 return client._muc_client.joined_rooms[room_jid] |
175 except KeyError: | 175 except KeyError: |
176 raise exceptions.NotFound(_(u"This room has not been joined")) | 176 raise exceptions.NotFound(_("This room has not been joined")) |
177 | 177 |
178 def checkRoomJoined(self, client, room_jid): | 178 def checkRoomJoined(self, client, room_jid): |
179 """Check that given room has been joined in current session | 179 """Check that given room has been joined in current session |
180 | 180 |
181 @param room_jid (JID): room JID | 181 @param room_jid (JID): room JID |
182 """ | 182 """ |
183 if room_jid not in client._muc_client.joined_rooms: | 183 if room_jid not in client._muc_client.joined_rooms: |
184 raise exceptions.NotFound(_(u"This room has not been joined")) | 184 raise exceptions.NotFound(_("This room has not been joined")) |
185 | 185 |
186 def isJoinedRoom(self, client, room_jid): | 186 def isJoinedRoom(self, client, room_jid): |
187 """Tell if a jid is a known and joined room | 187 """Tell if a jid is a known and joined room |
188 | 188 |
189 @room_jid(jid.JID): jid of the room | 189 @room_jid(jid.JID): jid of the room |
211 return {} | 211 return {} |
212 | 212 |
213 def _passwordUICb(self, data, client, room_jid, nick): | 213 def _passwordUICb(self, data, client, room_jid, nick): |
214 """Called when the user has given room password (or cancelled)""" | 214 """Called when the user has given room password (or cancelled)""" |
215 if C.bool(data.get(C.XMLUI_DATA_CANCELLED, "false")): | 215 if C.bool(data.get(C.XMLUI_DATA_CANCELLED, "false")): |
216 log.info(u"room join for {} is cancelled".format(room_jid.userhost())) | 216 log.info("room join for {} is cancelled".format(room_jid.userhost())) |
217 raise failure.Failure(exceptions.CancelError(D_(u"Room joining cancelled by user"))) | 217 raise failure.Failure(exceptions.CancelError(D_("Room joining cancelled by user"))) |
218 password = data[xml_tools.formEscape('password')] | 218 password = data[xml_tools.formEscape('password')] |
219 return client._muc_client.join(room_jid, nick, password).addCallbacks(self._joinCb, self._joinEb, (client, room_jid, nick), errbackArgs=(client, room_jid, nick, password)) | 219 return client._muc_client.join(room_jid, nick, password).addCallbacks(self._joinCb, self._joinEb, (client, room_jid, nick), errbackArgs=(client, room_jid, nick, password)) |
220 | 220 |
221 def _showListUI(self, items, client, service): | 221 def _showListUI(self, items, client, service): |
222 xmlui = xml_tools.XMLUI(title=D_('Rooms in {}'.format(service.full()))) | 222 xmlui = xml_tools.XMLUI(title=D_('Rooms in {}'.format(service.full()))) |
232 """Called when the user is in the requested room""" | 232 """Called when the user is in the requested room""" |
233 if room.locked: | 233 if room.locked: |
234 # FIXME: the current behaviour is to create an instant room | 234 # FIXME: the current behaviour is to create an instant room |
235 # and send the signal only when the room is unlocked | 235 # and send the signal only when the room is unlocked |
236 # a proper configuration management should be done | 236 # a proper configuration management should be done |
237 log.debug(_(u"room locked !")) | 237 log.debug(_("room locked !")) |
238 d = client._muc_client.configure(room.roomJID, {}) | 238 d = client._muc_client.configure(room.roomJID, {}) |
239 d.addErrback(self.host.logErrback, | 239 d.addErrback(self.host.logErrback, |
240 msg=_(u'Error while configuring the room: {failure_}')) | 240 msg=_('Error while configuring the room: {failure_}')) |
241 return room.fully_joined | 241 return room.fully_joined |
242 | 242 |
243 def _joinEb(self, failure, client, room_jid, nick, password): | 243 def _joinEb(self, failure, client, room_jid, nick, password): |
244 """Called when something is going wrong when joining the room""" | 244 """Called when something is going wrong when joining the room""" |
245 try: | 245 try: |
251 # we have a nickname conflict, we try again with "_" suffixed to current nickname | 251 # we have a nickname conflict, we try again with "_" suffixed to current nickname |
252 nick += '_' | 252 nick += '_' |
253 return client._muc_client.join(room_jid, nick, password).addCallbacks(self._joinCb, self._joinEb, (client, room_jid, nick), errbackArgs=(client, room_jid, nick, password)) | 253 return client._muc_client.join(room_jid, nick, password).addCallbacks(self._joinCb, self._joinEb, (client, room_jid, nick), errbackArgs=(client, room_jid, nick, password)) |
254 elif condition == 'not-allowed': | 254 elif condition == 'not-allowed': |
255 # room is restricted, we need a password | 255 # room is restricted, we need a password |
256 password_ui = xml_tools.XMLUI("form", title=D_(u'Room {} is restricted').format(room_jid.userhost()), submit_id='') | 256 password_ui = xml_tools.XMLUI("form", title=D_('Room {} is restricted').format(room_jid.userhost()), submit_id='') |
257 password_ui.addText(D_("This room is restricted, please enter the password")) | 257 password_ui.addText(D_("This room is restricted, please enter the password")) |
258 password_ui.addPassword('password') | 258 password_ui.addPassword('password') |
259 d = xml_tools.deferXMLUI(self.host, password_ui, profile=client.profile) | 259 d = xml_tools.deferXMLUI(self.host, password_ui, profile=client.profile) |
260 d.addCallback(self._passwordUICb, client, room_jid, nick) | 260 d.addCallback(self._passwordUICb, client, room_jid, nick) |
261 return d | 261 return d |
262 | 262 |
263 msg_suffix = ' with condition "{}"'.format(failure.value.condition) | 263 msg_suffix = ' with condition "{}"'.format(failure.value.condition) |
264 | 264 |
265 mess = D_(u"Error while joining the room {room}{suffix}".format( | 265 mess = D_("Error while joining the room {room}{suffix}".format( |
266 room = room_jid.userhost(), suffix = msg_suffix)) | 266 room = room_jid.userhost(), suffix = msg_suffix)) |
267 log.error(mess) | 267 log.error(mess) |
268 xmlui = xml_tools.note(mess, D_(u"Group chat error"), level=C.XMLUI_DATA_LVL_ERROR) | 268 xmlui = xml_tools.note(mess, D_("Group chat error"), level=C.XMLUI_DATA_LVL_ERROR) |
269 self.host.actionNew({'xmlui': xmlui.toXml()}, profile=client.profile) | 269 self.host.actionNew({'xmlui': xmlui.toXml()}, profile=client.profile) |
270 | 270 |
271 @staticmethod | 271 @staticmethod |
272 def _getOccupants(room): | 272 def _getOccupants(room): |
273 """Get occupants of a room in a form suitable for bridge""" | 273 """Get occupants of a room in a form suitable for bridge""" |
274 return {u.nick: {k:unicode(getattr(u,k) or '') for k in OCCUPANT_KEYS} for u in room.roster.values()} | 274 return {u.nick: {k:str(getattr(u,k) or '') for k in OCCUPANT_KEYS} for u in list(room.roster.values())} |
275 | 275 |
276 def _getRoomOccupants(self, room_jid_s, profile_key): | 276 def _getRoomOccupants(self, room_jid_s, profile_key): |
277 client = self.host.getClient(profile_key) | 277 client = self.host.getClient(profile_key) |
278 room_jid = jid.JID(room_jid_s) | 278 room_jid = jid.JID(room_jid_s) |
279 return self.getRoomOccupants(client, room_jid) | 279 return self.getRoomOccupants(client, room_jid) |
287 return self.getRoomsJoined(client) | 287 return self.getRoomsJoined(client) |
288 | 288 |
289 def getRoomsJoined(self, client): | 289 def getRoomsJoined(self, client): |
290 """Return rooms where user is""" | 290 """Return rooms where user is""" |
291 result = [] | 291 result = [] |
292 for room in client._muc_client.joined_rooms.values(): | 292 for room in list(client._muc_client.joined_rooms.values()): |
293 if room.state == ROOM_STATE_LIVE: | 293 if room.state == ROOM_STATE_LIVE: |
294 result.append((room.roomJID.userhost(), self._getOccupants(room), room.nick, room.subject)) | 294 result.append((room.roomJID.userhost(), self._getOccupants(room), room.nick, room.subject)) |
295 return result | 295 return result |
296 | 296 |
297 def _getRoomNick(self, room_jid_s, profile_key=C.PROF_KEY_NONE): | 297 def _getRoomNick(self, room_jid_s, profile_key=C.PROF_KEY_NONE): |
328 log.error(_("room_jid key is not present !")) | 328 log.error(_("room_jid key is not present !")) |
329 return defer.fail(exceptions.DataError) | 329 return defer.fail(exceptions.DataError) |
330 | 330 |
331 def xmluiReceived(xmlui): | 331 def xmluiReceived(xmlui): |
332 if not xmlui: | 332 if not xmlui: |
333 msg = D_(u"No configuration available for this room") | 333 msg = D_("No configuration available for this room") |
334 return {"xmlui": xml_tools.note(msg).toXml()} | 334 return {"xmlui": xml_tools.note(msg).toXml()} |
335 return {"xmlui": xmlui.toXml()} | 335 return {"xmlui": xmlui.toXml()} |
336 return self.configureRoom(client, room_jid).addCallback(xmluiReceived) | 336 return self.configureRoom(client, room_jid).addCallback(xmluiReceived) |
337 | 337 |
338 def configureRoom(self, client, room_jid): | 338 def configureRoom(self, client, room_jid): |
383 return client._muc_client.joined_rooms[room_jid].inRoster(muc.User(nick)) | 383 return client._muc_client.joined_rooms[room_jid].inRoster(muc.User(nick)) |
384 | 384 |
385 def _getMUCService(self, jid_=None, profile=C.PROF_KEY_NONE): | 385 def _getMUCService(self, jid_=None, profile=C.PROF_KEY_NONE): |
386 client = self.host.getClient(profile) | 386 client = self.host.getClient(profile) |
387 d = self.getMUCService(client, jid_ or None) | 387 d = self.getMUCService(client, jid_ or None) |
388 d.addCallback(lambda service_jid: service_jid.full() if service_jid is not None else u'') | 388 d.addCallback(lambda service_jid: service_jid.full() if service_jid is not None else '') |
389 return d | 389 return d |
390 | 390 |
391 @defer.inlineCallbacks | 391 @defer.inlineCallbacks |
392 def getMUCService(self, client, jid_=None): | 392 def getMUCService(self, client, jid_=None): |
393 """Return first found MUC service of an entity | 393 """Return first found MUC service of an entity |
424 | 424 |
425 @param muc_service (jid.JID) : leave empty string to use the default service | 425 @param muc_service (jid.JID) : leave empty string to use the default service |
426 @return: jid.JID (unique room bare JID) | 426 @return: jid.JID (unique room bare JID) |
427 """ | 427 """ |
428 # TODO: we should use #RFC-0045 10.1.4 when available here | 428 # TODO: we should use #RFC-0045 10.1.4 when available here |
429 room_name = unicode(uuid.uuid4()) | 429 room_name = str(uuid.uuid4()) |
430 if muc_service is None: | 430 if muc_service is None: |
431 try: | 431 try: |
432 muc_service = client.muc_service | 432 muc_service = client.muc_service |
433 except AttributeError: | 433 except AttributeError: |
434 raise exceptions.NotReady(u"Main server MUC service has not been checked yet") | 434 raise exceptions.NotReady("Main server MUC service has not been checked yet") |
435 if muc_service is None: | 435 if muc_service is None: |
436 log.warning(_("No MUC service found on main server")) | 436 log.warning(_("No MUC service found on main server")) |
437 raise exceptions.FeatureNotFound | 437 raise exceptions.FeatureNotFound |
438 | 438 |
439 muc_service = muc_service.userhost() | 439 muc_service = muc_service.userhost() |
440 return jid.JID(u"{}@{}".format(room_name, muc_service)) | 440 return jid.JID("{}@{}".format(room_name, muc_service)) |
441 | 441 |
442 def getDefaultMUC(self): | 442 def getDefaultMUC(self): |
443 """Return the default MUC. | 443 """Return the default MUC. |
444 | 444 |
445 @return: unicode | 445 @return: unicode |
460 if room_jid_s: | 460 if room_jid_s: |
461 muc_service = client.muc_service | 461 muc_service = client.muc_service |
462 try: | 462 try: |
463 room_jid = jid.JID(room_jid_s) | 463 room_jid = jid.JID(room_jid_s) |
464 except (RuntimeError, jid.InvalidFormat, AttributeError): | 464 except (RuntimeError, jid.InvalidFormat, AttributeError): |
465 return defer.fail(jid.InvalidFormat(_(u"Invalid room identifier: {room_id}'. Please give a room short or full identifier like 'room' or 'room@{muc_service}'.").format( | 465 return defer.fail(jid.InvalidFormat(_("Invalid room identifier: {room_id}'. Please give a room short or full identifier like 'room' or 'room@{muc_service}'.").format( |
466 room_id=room_jid_s, | 466 room_id=room_jid_s, |
467 muc_service=unicode(muc_service)))) | 467 muc_service=str(muc_service)))) |
468 if not room_jid.user: | 468 if not room_jid.user: |
469 room_jid.user, room_jid.host = room_jid.host, muc_service | 469 room_jid.user, room_jid.host = room_jid.host, muc_service |
470 else: | 470 else: |
471 room_jid = self.getUniqueName(profile_key=client.profile) | 471 room_jid = self.getUniqueName(profile_key=client.profile) |
472 # TODO: error management + signal in bridge | 472 # TODO: error management + signal in bridge |
480 nick = client.jid.user | 480 nick = client.jid.user |
481 if options is None: | 481 if options is None: |
482 options = {} | 482 options = {} |
483 if room_jid in client._muc_client.joined_rooms: | 483 if room_jid in client._muc_client.joined_rooms: |
484 room = client._muc_client.joined_rooms[room_jid] | 484 room = client._muc_client.joined_rooms[room_jid] |
485 log.info(_(u'{profile} is already in room {room_jid}').format( | 485 log.info(_('{profile} is already in room {room_jid}').format( |
486 profile=client.profile, room_jid = room_jid.userhost())) | 486 profile=client.profile, room_jid = room_jid.userhost())) |
487 return defer.fail(AlreadyJoined(room)) | 487 return defer.fail(AlreadyJoined(room)) |
488 log.info(_(u"[{profile}] is joining room {room} with nick {nick}").format( | 488 log.info(_("[{profile}] is joining room {room} with nick {nick}").format( |
489 profile=client.profile, room=room_jid.userhost(), nick=nick)) | 489 profile=client.profile, room=room_jid.userhost(), nick=nick)) |
490 | 490 |
491 password = options.get("password") | 491 password = options.get("password") |
492 | 492 |
493 d = client._muc_client.join(room_jid, nick, password) | 493 d = client._muc_client.join(room_jid, nick, password) |
502 This methods is to be called before a hot reconnection | 502 This methods is to be called before a hot reconnection |
503 @return (list[(jid.JID, unicode)]): arguments needed to re-join the rooms | 503 @return (list[(jid.JID, unicode)]): arguments needed to re-join the rooms |
504 This list can be used directly (unpacked) with self.join | 504 This list can be used directly (unpacked) with self.join |
505 """ | 505 """ |
506 args_list = [] | 506 args_list = [] |
507 for room in client._muc_client.joined_rooms.values(): | 507 for room in list(client._muc_client.joined_rooms.values()): |
508 client._muc_client._removeRoom(room.roomJID) | 508 client._muc_client._removeRoom(room.roomJID) |
509 args_list.append((client, room.roomJID, room.nick)) | 509 args_list.append((client, room.roomJID, room.nick)) |
510 return args_list | 510 return args_list |
511 | 511 |
512 def _nick(self, room_jid_s, nick, profile_key=C.PROF_KEY_NONE): | 512 def _nick(self, room_jid_s, nick, profile_key=C.PROF_KEY_NONE): |
640 options = mess_data["unparsed"].strip().split() | 640 options = mess_data["unparsed"].strip().split() |
641 try: | 641 try: |
642 nick = options[0] | 642 nick = options[0] |
643 assert self.isNickInRoom(client, mess_data["to"], nick) | 643 assert self.isNickInRoom(client, mess_data["to"], nick) |
644 except (IndexError, AssertionError): | 644 except (IndexError, AssertionError): |
645 feedback = _(u"You must provide a member's nick to kick.") | 645 feedback = _("You must provide a member's nick to kick.") |
646 self.text_cmds.feedBack(client, feedback, mess_data) | 646 self.text_cmds.feedBack(client, feedback, mess_data) |
647 return False | 647 return False |
648 | 648 |
649 d = self.kick(client, nick, mess_data["to"], {} if len(options) == 1 else {'reason': options[1]}) | 649 d = self.kick(client, nick, mess_data["to"], {} if len(options) == 1 else {'reason': options[1]}) |
650 | 650 |
651 def cb(__): | 651 def cb(__): |
652 feedback_msg = _(u'You have kicked {}').format(nick) | 652 feedback_msg = _('You have kicked {}').format(nick) |
653 if len(options) > 1: | 653 if len(options) > 1: |
654 feedback_msg += _(u' for the following reason: {}').format(options[1]) | 654 feedback_msg += _(' for the following reason: {}').format(options[1]) |
655 self.text_cmds.feedBack(client, feedback_msg, mess_data) | 655 self.text_cmds.feedBack(client, feedback_msg, mess_data) |
656 return True | 656 return True |
657 d.addCallback(cb) | 657 d.addCallback(cb) |
658 return d | 658 return d |
659 | 659 |
669 jid_s = options[0] | 669 jid_s = options[0] |
670 entity_jid = jid.JID(jid_s).userhostJID() | 670 entity_jid = jid.JID(jid_s).userhostJID() |
671 assert(entity_jid.user) | 671 assert(entity_jid.user) |
672 assert(entity_jid.host) | 672 assert(entity_jid.host) |
673 except (RuntimeError, jid.InvalidFormat, AttributeError, IndexError, AssertionError): | 673 except (RuntimeError, jid.InvalidFormat, AttributeError, IndexError, AssertionError): |
674 feedback = _(u"You must provide a valid JID to ban, like in '/ban contact@example.net'") | 674 feedback = _("You must provide a valid JID to ban, like in '/ban contact@example.net'") |
675 self.text_cmds.feedBack(client, feedback, mess_data) | 675 self.text_cmds.feedBack(client, feedback, mess_data) |
676 return False | 676 return False |
677 | 677 |
678 d = self.ban(client, entity_jid, mess_data["to"], {} if len(options) == 1 else {'reason': options[1]}) | 678 d = self.ban(client, entity_jid, mess_data["to"], {} if len(options) == 1 else {'reason': options[1]}) |
679 | 679 |
680 def cb(__): | 680 def cb(__): |
681 feedback_msg = _(u'You have banned {}').format(entity_jid) | 681 feedback_msg = _('You have banned {}').format(entity_jid) |
682 if len(options) > 1: | 682 if len(options) > 1: |
683 feedback_msg += _(u' for the following reason: {}').format(options[1]) | 683 feedback_msg += _(' for the following reason: {}').format(options[1]) |
684 self.text_cmds.feedBack(client, feedback_msg, mess_data) | 684 self.text_cmds.feedBack(client, feedback_msg, mess_data) |
685 return True | 685 return True |
686 d.addCallback(cb) | 686 d.addCallback(cb) |
687 return d | 687 return d |
688 | 688 |
702 jid_s = options[0] | 702 jid_s = options[0] |
703 entity_jid = jid.JID(jid_s).userhostJID() | 703 entity_jid = jid.JID(jid_s).userhostJID() |
704 assert(entity_jid.user) | 704 assert(entity_jid.user) |
705 assert(entity_jid.host) | 705 assert(entity_jid.host) |
706 except (RuntimeError, jid.InvalidFormat, AttributeError, IndexError, AssertionError): | 706 except (RuntimeError, jid.InvalidFormat, AttributeError, IndexError, AssertionError): |
707 feedback = _(u"You must provide a valid JID to affiliate, like in '/affiliate contact@example.net member'") | 707 feedback = _("You must provide a valid JID to affiliate, like in '/affiliate contact@example.net member'") |
708 self.text_cmds.feedBack(client, feedback, mess_data) | 708 self.text_cmds.feedBack(client, feedback, mess_data) |
709 return False | 709 return False |
710 | 710 |
711 affiliation = options[1] if len(options) > 1 else 'none' | 711 affiliation = options[1] if len(options) > 1 else 'none' |
712 if affiliation not in AFFILIATIONS: | 712 if affiliation not in AFFILIATIONS: |
713 feedback = _(u"You must provide a valid affiliation: %s") % ' '.join(AFFILIATIONS) | 713 feedback = _("You must provide a valid affiliation: %s") % ' '.join(AFFILIATIONS) |
714 self.text_cmds.feedBack(client, feedback, mess_data) | 714 self.text_cmds.feedBack(client, feedback, mess_data) |
715 return False | 715 return False |
716 | 716 |
717 d = self.affiliate(client, entity_jid, mess_data["to"], {'affiliation': affiliation}) | 717 d = self.affiliate(client, entity_jid, mess_data["to"], {'affiliation': affiliation}) |
718 | 718 |
719 def cb(__): | 719 def cb(__): |
720 feedback_msg = _(u'New affiliation for %(entity)s: %(affiliation)s').format(entity=entity_jid, affiliation=affiliation) | 720 feedback_msg = _('New affiliation for %(entity)s: %(affiliation)s').format(entity=entity_jid, affiliation=affiliation) |
721 self.text_cmds.feedBack(client, feedback_msg, mess_data) | 721 self.text_cmds.feedBack(client, feedback_msg, mess_data) |
722 return True | 722 return True |
723 d.addCallback(cb) | 723 d.addCallback(cb) |
724 return d | 724 return d |
725 | 725 |
761 room_jid = mess_data["to"] | 761 room_jid = mess_data["to"] |
762 service = jid.JID(room_jid.host) | 762 service = jid.JID(room_jid.host) |
763 elif client.muc_service is not None: | 763 elif client.muc_service is not None: |
764 service = client.muc_service | 764 service = client.muc_service |
765 else: | 765 else: |
766 msg = D_(u"No known default MUC service".format(unparsed)) | 766 msg = D_("No known default MUC service".format(unparsed)) |
767 self.text_cmds.feedBack(client, msg, mess_data) | 767 self.text_cmds.feedBack(client, msg, mess_data) |
768 return False | 768 return False |
769 except jid.InvalidFormat: | 769 except jid.InvalidFormat: |
770 msg = D_(u"{} is not a valid JID!".format(unparsed)) | 770 msg = D_("{} is not a valid JID!".format(unparsed)) |
771 self.text_cmds.feedBack(client, msg, mess_data) | 771 self.text_cmds.feedBack(client, msg, mess_data) |
772 return False | 772 return False |
773 d = self.host.getDiscoItems(client, service) | 773 d = self.host.getDiscoItems(client, service) |
774 d.addCallback(self._showListUI, client, service) | 774 d.addCallback(self._showListUI, client, service) |
775 | 775 |
799 | 799 |
800 def presenceTrigger(self, presence_elt, client): | 800 def presenceTrigger(self, presence_elt, client): |
801 # FIXME: should we add a privacy parameters in settings to activate before | 801 # FIXME: should we add a privacy parameters in settings to activate before |
802 # broadcasting the presence to all MUC rooms ? | 802 # broadcasting the presence to all MUC rooms ? |
803 muc_client = client._muc_client | 803 muc_client = client._muc_client |
804 for room_jid, room in muc_client.joined_rooms.iteritems(): | 804 for room_jid, room in muc_client.joined_rooms.items(): |
805 elt = xml_tools.elementCopy(presence_elt) | 805 elt = xml_tools.elementCopy(presence_elt) |
806 elt['to'] = room_jid.userhost() + '/' + room.nick | 806 elt['to'] = room_jid.userhost() + '/' + room.nick |
807 client.presence.send(elt) | 807 client.presence.send(elt) |
808 return True | 808 return True |
809 | 809 |
814 # presence is already handled in (un)availableReceived | 814 # presence is already handled in (un)availableReceived |
815 return False | 815 return False |
816 return True | 816 return True |
817 | 817 |
818 | 818 |
819 @implementer(iwokkel.IDisco) | |
819 class SatMUCClient(muc.MUCClient): | 820 class SatMUCClient(muc.MUCClient): |
820 implements(iwokkel.IDisco) | |
821 | 821 |
822 def __init__(self, plugin_parent): | 822 def __init__(self, plugin_parent): |
823 self.plugin_parent = plugin_parent | 823 self.plugin_parent = plugin_parent |
824 muc.MUCClient.__init__(self) | 824 muc.MUCClient.__init__(self) |
825 self._changing_nicks = set() # used to keep trace of who is changing nick, | 825 self._changing_nicks = set() # used to keep trace of who is changing nick, |
826 # and to discard userJoinedRoom signal in this case | 826 # and to discard userJoinedRoom signal in this case |
827 print "init SatMUCClient OK" | 827 print("init SatMUCClient OK") |
828 | 828 |
829 @property | 829 @property |
830 def joined_rooms(self): | 830 def joined_rooms(self): |
831 return self._rooms | 831 return self._rooms |
832 | 832 |
859 room=room.userhost(), | 859 room=room.userhost(), |
860 state=new_state)) | 860 state=new_state)) |
861 expected_state = ROOM_STATES[new_state_idx-1] | 861 expected_state = ROOM_STATES[new_state_idx-1] |
862 if room.state != expected_state: | 862 if room.state != expected_state: |
863 log.error(_( | 863 log.error(_( |
864 u"room {room} is not in expected state: room is in state {current_state} " | 864 "room {room} is not in expected state: room is in state {current_state} " |
865 u"while we were expecting {expected_state}").format( | 865 "while we were expecting {expected_state}").format( |
866 room=room.roomJID.userhost(), | 866 room=room.roomJID.userhost(), |
867 current_state=room.state, | 867 current_state=room.state, |
868 expected_state=expected_state)) | 868 expected_state=expected_state)) |
869 room.state = new_state | 869 room.state = new_state |
870 | 870 |
917 room_jid, | 917 room_jid, |
918 None, | 918 None, |
919 limit=1, | 919 limit=1, |
920 between=False, | 920 between=False, |
921 filters={ | 921 filters={ |
922 u'types': C.MESS_TYPE_GROUPCHAT, | 922 'types': C.MESS_TYPE_GROUPCHAT, |
923 u'last_stanza_id': True}, | 923 'last_stanza_id': True}, |
924 profile=client.profile) | 924 profile=client.profile) |
925 if last_mess: | 925 if last_mess: |
926 stanza_id = last_mess[0][-1][u'stanza_id'] | 926 stanza_id = last_mess[0][-1]['stanza_id'] |
927 rsm_req = rsm.RSMRequest(max_=100, after=stanza_id) | 927 rsm_req = rsm.RSMRequest(max_=100, after=stanza_id) |
928 no_loop=False | 928 no_loop=False |
929 else: | 929 else: |
930 log.info(u"We have no MAM archive for room {room_jid}.".format( | 930 log.info("We have no MAM archive for room {room_jid}.".format( |
931 room_jid=room_jid)) | 931 room_jid=room_jid)) |
932 # we don't want the whole archive if we have no archive yet | 932 # we don't want the whole archive if we have no archive yet |
933 # as it can be huge | 933 # as it can be huge |
934 rsm_req = rsm.RSMRequest(max_=50, before=u'') | 934 rsm_req = rsm.RSMRequest(max_=50, before='') |
935 no_loop=True | 935 no_loop=True |
936 | 936 |
937 mam_req = mam.MAMRequest(rsm_=rsm_req) | 937 mam_req = mam.MAMRequest(rsm_=rsm_req) |
938 complete = False | 938 complete = False |
939 count = 0 | 939 count = 0 |
940 while not complete: | 940 while not complete: |
941 mam_data = yield self._mam.getArchives(client, mam_req, | 941 mam_data = yield self._mam.getArchives(client, mam_req, |
942 service=room_jid) | 942 service=room_jid) |
943 elt_list, rsm_response, mam_response = mam_data | 943 elt_list, rsm_response, mam_response = mam_data |
944 complete = True if no_loop else mam_response[u"complete"] | 944 complete = True if no_loop else mam_response["complete"] |
945 # we update MAM request for next iteration | 945 # we update MAM request for next iteration |
946 mam_req.rsm.after = rsm_response.last | 946 mam_req.rsm.after = rsm_response.last |
947 | 947 |
948 if not elt_list: | 948 if not elt_list: |
949 break | 949 break |
954 try: | 954 try: |
955 fwd_message_elt = self._mam.getMessageFromResult( | 955 fwd_message_elt = self._mam.getMessageFromResult( |
956 client, mess_elt, mam_req, service=room_jid) | 956 client, mess_elt, mam_req, service=room_jid) |
957 except exceptions.DataError: | 957 except exceptions.DataError: |
958 continue | 958 continue |
959 if fwd_message_elt.getAttribute(u"to"): | 959 if fwd_message_elt.getAttribute("to"): |
960 log.warning( | 960 log.warning( |
961 u'Forwarded message element has a "to" attribute while it is ' | 961 'Forwarded message element has a "to" attribute while it is ' |
962 u'forbidden by specifications') | 962 'forbidden by specifications') |
963 fwd_message_elt[u"to"] = client.jid.full() | 963 fwd_message_elt["to"] = client.jid.full() |
964 mess_data = client.messageProt.parseMessage(fwd_message_elt) | 964 mess_data = client.messageProt.parseMessage(fwd_message_elt) |
965 # we attache parsed message data to element, to avoid parsing | 965 # we attache parsed message data to element, to avoid parsing |
966 # again in _addToHistory | 966 # again in _addToHistory |
967 fwd_message_elt._mess_data = mess_data | 967 fwd_message_elt._mess_data = mess_data |
968 # and we inject to MUC workflow | 968 # and we inject to MUC workflow |
969 client._muc_client._onGroupChat(fwd_message_elt) | 969 client._muc_client._onGroupChat(fwd_message_elt) |
970 | 970 |
971 if not count: | 971 if not count: |
972 log.info(_(u"No message received while offline in {room_jid}".format( | 972 log.info(_("No message received while offline in {room_jid}".format( |
973 room_jid=room_jid))) | 973 room_jid=room_jid))) |
974 else: | 974 else: |
975 log.info( | 975 log.info( |
976 _(u"We have received {num_mess} message(s) in {room_jid} while " | 976 _("We have received {num_mess} message(s) in {room_jid} while " |
977 u"offline.") | 977 "offline.") |
978 .format(num_mess=count, room_jid=room_jid)) | 978 .format(num_mess=count, room_jid=room_jid)) |
979 | 979 |
980 # for legacy history, the following steps are done in receivedSubject but for MAM | 980 # for legacy history, the following steps are done in receivedSubject but for MAM |
981 # the order is different (we have to join then get MAM archive, so subject | 981 # the order is different (we have to join then get MAM archive, so subject |
982 # is received before archive), so we change state and add the callbacks here. | 982 # is received before archive), so we change state and add the callbacks here. |
1015 return | 1015 return |
1016 | 1016 |
1017 if user is None: | 1017 if user is None: |
1018 nick = presence.sender.resource | 1018 nick = presence.sender.resource |
1019 if not nick: | 1019 if not nick: |
1020 log.warning(_(u"missing nick in presence: {xml}").format( | 1020 log.warning(_("missing nick in presence: {xml}").format( |
1021 xml = presence.toElement().toXml())) | 1021 xml = presence.toElement().toXml())) |
1022 return | 1022 return |
1023 user = muc.User(nick, presence.entity) | 1023 user = muc.User(nick, presence.entity) |
1024 | 1024 |
1025 # Update user data | 1025 # Update user data |
1059 def userJoinedRoom(self, room, user): | 1059 def userJoinedRoom(self, room, user): |
1060 if user.nick == room.nick: | 1060 if user.nick == room.nick: |
1061 # we have received our own nick, | 1061 # we have received our own nick, |
1062 # this mean that the full room roster was received | 1062 # this mean that the full room roster was received |
1063 self.changeRoomState(room, ROOM_STATE_SELF_PRESENCE) | 1063 self.changeRoomState(room, ROOM_STATE_SELF_PRESENCE) |
1064 log.debug(u"room {room} joined with nick {nick}".format( | 1064 log.debug("room {room} joined with nick {nick}".format( |
1065 room=room.occupantJID.userhost(), nick=user.nick)) | 1065 room=room.occupantJID.userhost(), nick=user.nick)) |
1066 # we set type so we don't have to use a deferred | 1066 # we set type so we don't have to use a deferred |
1067 # with disco to check entity type | 1067 # with disco to check entity type |
1068 self.host.memory.updateEntityData( | 1068 self.host.memory.updateEntityData( |
1069 room.roomJID, C.ENTITY_TYPE, C.ENTITY_TYPE_MUC, | 1069 room.roomJID, C.ENTITY_TYPE, C.ENTITY_TYPE_MUC, |
1070 profile_key=self.client.profile) | 1070 profile_key=self.client.profile) |
1071 elif room.state not in (ROOM_STATE_OCCUPANTS, ROOM_STATE_LIVE): | 1071 elif room.state not in (ROOM_STATE_OCCUPANTS, ROOM_STATE_LIVE): |
1072 log.warning( | 1072 log.warning( |
1073 u"Received user presence data in a room before its initialisation " | 1073 "Received user presence data in a room before its initialisation " |
1074 u"(current state: {state})," | 1074 "(current state: {state})," |
1075 u"this is not standard! Ignoring it: {room} ({nick})".format( | 1075 "this is not standard! Ignoring it: {room} ({nick})".format( |
1076 state=room.state, | 1076 state=room.state, |
1077 room=room.roomJID.userhost(), | 1077 room=room.roomJID.userhost(), |
1078 nick=user.nick)) | 1078 nick=user.nick)) |
1079 return | 1079 return |
1080 else: | 1080 else: |
1082 return | 1082 return |
1083 try: | 1083 try: |
1084 self._changing_nicks.remove(user.nick) | 1084 self._changing_nicks.remove(user.nick) |
1085 except KeyError: | 1085 except KeyError: |
1086 # this is a new user | 1086 # this is a new user |
1087 log.debug(_(u"user {nick} has joined room {room_id}").format( | 1087 log.debug(_("user {nick} has joined room {room_id}").format( |
1088 nick=user.nick, room_id=room.occupantJID.userhost())) | 1088 nick=user.nick, room_id=room.occupantJID.userhost())) |
1089 if not self.host.trigger.point( | 1089 if not self.host.trigger.point( |
1090 "MUC user joined", room, user, self.client.profile): | 1090 "MUC user joined", room, user, self.client.profile): |
1091 return | 1091 return |
1092 | 1092 |
1098 if user.entity is not None: | 1098 if user.entity is not None: |
1099 extra['user_entity'] = user.entity.full() | 1099 extra['user_entity'] = user.entity.full() |
1100 mess_data = { # dict is similar to the one used in client.onMessage | 1100 mess_data = { # dict is similar to the one used in client.onMessage |
1101 "from": room.roomJID, | 1101 "from": room.roomJID, |
1102 "to": self.client.jid, | 1102 "to": self.client.jid, |
1103 "uid": unicode(uuid.uuid4()), | 1103 "uid": str(uuid.uuid4()), |
1104 "message": {'': D_(u"=> {} has joined the room").format(user.nick)}, | 1104 "message": {'': D_("=> {} has joined the room").format(user.nick)}, |
1105 "subject": {}, | 1105 "subject": {}, |
1106 "type": C.MESS_TYPE_INFO, | 1106 "type": C.MESS_TYPE_INFO, |
1107 "extra": extra, | 1107 "extra": extra, |
1108 "timestamp": time.time(), | 1108 "timestamp": time.time(), |
1109 } | 1109 } |
1118 if not self.host.trigger.point("MUC user left", room, user, self.client.profile): | 1118 if not self.host.trigger.point("MUC user left", room, user, self.client.profile): |
1119 return | 1119 return |
1120 if user.nick == room.nick: | 1120 if user.nick == room.nick: |
1121 # we left the room | 1121 # we left the room |
1122 room_jid_s = room.roomJID.userhost() | 1122 room_jid_s = room.roomJID.userhost() |
1123 log.info(_(u"Room ({room}) left ({profile})").format( | 1123 log.info(_("Room ({room}) left ({profile})").format( |
1124 room = room_jid_s, profile = self.client.profile)) | 1124 room = room_jid_s, profile = self.client.profile)) |
1125 self.host.memory.delEntityCache(room.roomJID, profile_key=self.client.profile) | 1125 self.host.memory.delEntityCache(room.roomJID, profile_key=self.client.profile) |
1126 self.host.bridge.mucRoomLeft(room.roomJID.userhost(), self.client.profile) | 1126 self.host.bridge.mucRoomLeft(room.roomJID.userhost(), self.client.profile) |
1127 elif room.state != ROOM_STATE_LIVE: | 1127 elif room.state != ROOM_STATE_LIVE: |
1128 log.warning(u"Received user presence data in a room before its initialisation (current state: {state})," | 1128 log.warning("Received user presence data in a room before its initialisation (current state: {state})," |
1129 "this is not standard! Ignoring it: {room} ({nick})".format( | 1129 "this is not standard! Ignoring it: {room} ({nick})".format( |
1130 state=room.state, | 1130 state=room.state, |
1131 room=room.roomJID.userhost(), | 1131 room=room.roomJID.userhost(), |
1132 nick=user.nick)) | 1132 nick=user.nick)) |
1133 return | 1133 return |
1134 else: | 1134 else: |
1135 if not room.fully_joined.called: | 1135 if not room.fully_joined.called: |
1136 return | 1136 return |
1137 log.debug(_(u"user {nick} left room {room_id}").format(nick=user.nick, room_id=room.occupantJID.userhost())) | 1137 log.debug(_("user {nick} left room {room_id}").format(nick=user.nick, room_id=room.occupantJID.userhost())) |
1138 extra = {'info_type': ROOM_USER_LEFT, | 1138 extra = {'info_type': ROOM_USER_LEFT, |
1139 'user_affiliation': user.affiliation, | 1139 'user_affiliation': user.affiliation, |
1140 'user_role': user.role, | 1140 'user_role': user.role, |
1141 'user_nick': user.nick | 1141 'user_nick': user.nick |
1142 } | 1142 } |
1143 if user.entity is not None: | 1143 if user.entity is not None: |
1144 extra['user_entity'] = user.entity.full() | 1144 extra['user_entity'] = user.entity.full() |
1145 mess_data = { # dict is similar to the one used in client.onMessage | 1145 mess_data = { # dict is similar to the one used in client.onMessage |
1146 "from": room.roomJID, | 1146 "from": room.roomJID, |
1147 "to": self.client.jid, | 1147 "to": self.client.jid, |
1148 "uid": unicode(uuid.uuid4()), | 1148 "uid": str(uuid.uuid4()), |
1149 "message": {'': D_(u"<= {} has left the room").format(user.nick)}, | 1149 "message": {'': D_("<= {} has left the room").format(user.nick)}, |
1150 "subject": {}, | 1150 "subject": {}, |
1151 "type": C.MESS_TYPE_INFO, | 1151 "type": C.MESS_TYPE_INFO, |
1152 "extra": extra, | 1152 "extra": extra, |
1153 "timestamp": time.time(), | 1153 "timestamp": time.time(), |
1154 } | 1154 } |
1179 entity.full(), show or '', 0, statuses, self.client.profile) | 1179 entity.full(), show or '', 0, statuses, self.client.profile) |
1180 | 1180 |
1181 ## messages ## | 1181 ## messages ## |
1182 | 1182 |
1183 def receivedGroupChat(self, room, user, body): | 1183 def receivedGroupChat(self, room, user, body): |
1184 log.debug(u'receivedGroupChat: room=%s user=%s body=%s' % (room.roomJID.full(), user, body)) | 1184 log.debug('receivedGroupChat: room=%s user=%s body=%s' % (room.roomJID.full(), user, body)) |
1185 | 1185 |
1186 def _addToHistory(self, __, user, message): | 1186 def _addToHistory(self, __, user, message): |
1187 try: | 1187 try: |
1188 # message can be already parsed (with MAM), in this case mess_data | 1188 # message can be already parsed (with MAM), in this case mess_data |
1189 # it attached to the element | 1189 # it attached to the element |
1190 mess_data = message.element._mess_data | 1190 mess_data = message.element._mess_data |
1191 except AttributeError: | 1191 except AttributeError: |
1192 mess_data = self.client.messageProt.parseMessage(message.element) | 1192 mess_data = self.client.messageProt.parseMessage(message.element) |
1193 if mess_data[u'message'] or mess_data[u'subject']: | 1193 if mess_data['message'] or mess_data['subject']: |
1194 return self.host.memory.addToHistory(self.client, mess_data) | 1194 return self.host.memory.addToHistory(self.client, mess_data) |
1195 else: | 1195 else: |
1196 return defer.succeed(None) | 1196 return defer.succeed(None) |
1197 | 1197 |
1198 def _addToHistoryEb(self, failure): | 1198 def _addToHistoryEb(self, failure): |
1208 None if the message come from the room | 1208 None if the message come from the room |
1209 @param message(muc.GroupChat): the parsed message | 1209 @param message(muc.GroupChat): the parsed message |
1210 """ | 1210 """ |
1211 if room.state != ROOM_STATE_SELF_PRESENCE: | 1211 if room.state != ROOM_STATE_SELF_PRESENCE: |
1212 log.warning(_( | 1212 log.warning(_( |
1213 u"received history in unexpected state in room {room} (state: " | 1213 "received history in unexpected state in room {room} (state: " |
1214 u"{state})").format(room = room.roomJID.userhost(), | 1214 "{state})").format(room = room.roomJID.userhost(), |
1215 state = room.state)) | 1215 state = room.state)) |
1216 if not hasattr(room, "_history_d"): | 1216 if not hasattr(room, "_history_d"): |
1217 # XXX: this hack is due to buggy behaviour seen in the wild because of the | 1217 # XXX: this hack is due to buggy behaviour seen in the wild because of the |
1218 # "mod_delay" prosody module being activated. This module add an | 1218 # "mod_delay" prosody module being activated. This module add an |
1219 # unexpected <delay> elements which break our workflow. | 1219 # unexpected <delay> elements which break our workflow. |
1220 log.warning(_(u"storing the unexpected message anyway, to avoid loss")) | 1220 log.warning(_("storing the unexpected message anyway, to avoid loss")) |
1221 # we have to restore URI which are stripped by wokkel parsing | 1221 # we have to restore URI which are stripped by wokkel parsing |
1222 for c in message.element.elements(): | 1222 for c in message.element.elements(): |
1223 if c.uri is None: | 1223 if c.uri is None: |
1224 c.uri = C.NS_CLIENT | 1224 c.uri = C.NS_CLIENT |
1225 mess_data = self.client.messageProt.parseMessage(message.element) | 1225 mess_data = self.client.messageProt.parseMessage(message.element) |
1226 message.element._mess_data = mess_data | 1226 message.element._mess_data = mess_data |
1227 self._addToHistory(None, user, message) | 1227 self._addToHistory(None, user, message) |
1228 if mess_data[u'message'] or mess_data[u'subject']: | 1228 if mess_data['message'] or mess_data['subject']: |
1229 self.host.bridge.messageNew( | 1229 self.host.bridge.messageNew( |
1230 *self.client.messageGetBridgeArgs(mess_data), | 1230 *self.client.messageGetBridgeArgs(mess_data), |
1231 profile=self.client.profile | 1231 profile=self.client.profile |
1232 ) | 1232 ) |
1233 return | 1233 return |
1246 # We override this method to fix subject handling | 1246 # We override this method to fix subject handling |
1247 # FIXME: remove this merge fixed upstream | 1247 # FIXME: remove this merge fixed upstream |
1248 room, user = self._getRoomUser(message) | 1248 room, user = self._getRoomUser(message) |
1249 | 1249 |
1250 if room is None: | 1250 if room is None: |
1251 log.warning(u"No room found for message: {message}" | 1251 log.warning("No room found for message: {message}" |
1252 .format(message=message.toElement().toXml())) | 1252 .format(message=message.toElement().toXml())) |
1253 return | 1253 return |
1254 | 1254 |
1255 if message.subject is not None: | 1255 if message.subject is not None: |
1256 self.receivedSubject(room, user, message.subject) | 1256 self.receivedSubject(room, user, message.subject) |
1280 del room._cache | 1280 del room._cache |
1281 cache_presence = room._cache_presence | 1281 cache_presence = room._cache_presence |
1282 del room._cache_presence | 1282 del room._cache_presence |
1283 for elem in cache: | 1283 for elem in cache: |
1284 self.client.xmlstream.dispatch(elem) | 1284 self.client.xmlstream.dispatch(elem) |
1285 for presence_data in cache_presence.itervalues(): | 1285 for presence_data in cache_presence.values(): |
1286 if not presence_data[u'show'] and not presence_data[u'status']: | 1286 if not presence_data['show'] and not presence_data['status']: |
1287 # occupants are already sent in mucRoomJoined, so if we don't have | 1287 # occupants are already sent in mucRoomJoined, so if we don't have |
1288 # extra information like show or statuses, we can discard the signal | 1288 # extra information like show or statuses, we can discard the signal |
1289 continue | 1289 continue |
1290 else: | 1290 else: |
1291 self.userUpdatedStatus(**presence_data) | 1291 self.userUpdatedStatus(**presence_data) |
1292 | 1292 |
1293 def _historyEb(self, failure_, room): | 1293 def _historyEb(self, failure_, room): |
1294 log.error(u"Error while managing history: {}".format(failure_)) | 1294 log.error("Error while managing history: {}".format(failure_)) |
1295 self._historyCb(None, room) | 1295 self._historyCb(None, room) |
1296 | 1296 |
1297 def receivedSubject(self, room, user, subject): | 1297 def receivedSubject(self, room, user, subject): |
1298 # when subject is received, we know that we have whole roster and history | 1298 # when subject is received, we know that we have whole roster and history |
1299 # cf. http://xmpp.org/extensions/xep-0045.html#enter-subject | 1299 # cf. http://xmpp.org/extensions/xep-0045.html#enter-subject |
1302 if room._history_type == HISTORY_LEGACY: | 1302 if room._history_type == HISTORY_LEGACY: |
1303 self.changeRoomState(room, ROOM_STATE_LIVE) | 1303 self.changeRoomState(room, ROOM_STATE_LIVE) |
1304 room._history_d.addCallbacks(self._historyCb, self._historyEb, [room], errbackArgs=[room]) | 1304 room._history_d.addCallbacks(self._historyCb, self._historyEb, [room], errbackArgs=[room]) |
1305 else: | 1305 else: |
1306 # the subject has been changed | 1306 # the subject has been changed |
1307 log.debug(_(u"New subject for room ({room_id}): {subject}").format(room_id = room.roomJID.full(), subject = subject)) | 1307 log.debug(_("New subject for room ({room_id}): {subject}").format(room_id = room.roomJID.full(), subject = subject)) |
1308 self.host.bridge.mucRoomNewSubject(room.roomJID.userhost(), subject, self.client.profile) | 1308 self.host.bridge.mucRoomNewSubject(room.roomJID.userhost(), subject, self.client.profile) |
1309 | 1309 |
1310 ## disco ## | 1310 ## disco ## |
1311 | 1311 |
1312 def getDiscoInfo(self, requestor, target, nodeIdentifier=''): | 1312 def getDiscoInfo(self, requestor, target, nodeIdentifier=''): |