Mercurial > libervia-backend
comparison sat/core/xmpp.py @ 2624:56f94936df1e
code style reformatting using black
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 27 Jun 2018 20:14:46 +0200 |
parents | 93d64ce7a429 |
children | 189e38fb11ff |
comparison
equal
deleted
inserted
replaced
2623:49533de4540b | 2624:56f94936df1e |
---|---|
29 from twisted.python import failure | 29 from twisted.python import failure |
30 from wokkel import client as wokkel_client, disco, xmppim, generic, iwokkel | 30 from wokkel import client as wokkel_client, disco, xmppim, generic, iwokkel |
31 from wokkel import component | 31 from wokkel import component |
32 from wokkel import delay | 32 from wokkel import delay |
33 from sat.core.log import getLogger | 33 from sat.core.log import getLogger |
34 | |
34 log = getLogger(__name__) | 35 log = getLogger(__name__) |
35 from sat.core import exceptions | 36 from sat.core import exceptions |
36 from zope.interface import implements | 37 from zope.interface import implements |
37 import time | 38 import time |
38 import calendar | 39 import calendar |
51 # else, it's a deferred which fire on disconnection | 52 # else, it's a deferred which fire on disconnection |
52 self._connected = None | 53 self._connected = None |
53 self.profile = profile | 54 self.profile = profile |
54 self.host_app = host_app | 55 self.host_app = host_app |
55 self.cache = cache.Cache(host_app, profile) | 56 self.cache = cache.Cache(host_app, profile) |
56 self._mess_id_uid = {} # map from message id to uid used in history. Key: (full_jid,message_id) Value: uid | 57 self._mess_id_uid = {} # map from message id to uid used in history. Key: (full_jid,message_id) Value: uid |
57 self.conn_deferred = defer.Deferred() | 58 self.conn_deferred = defer.Deferred() |
58 self._progress_cb = {} # callback called when a progress is requested (key = progress id) | 59 self._progress_cb = {} # callback called when a progress is requested (key = progress id) |
59 self.actions = {} # used to keep track of actions for retrieval (key = action_id) | 60 self.actions = {} # used to keep track of actions for retrieval (key = action_id) |
60 | 61 |
61 ## initialisation ## | 62 ## initialisation ## |
62 | 63 |
63 @defer.inlineCallbacks | 64 @defer.inlineCallbacks |
64 def _callConnectionTriggers(self): | 65 def _callConnectionTriggers(self): |
115 # FIXME: reconnection doesn't seems to be handled correclty (client is deleted then recreated from scrash | 116 # FIXME: reconnection doesn't seems to be handled correclty (client is deleted then recreated from scrash |
116 # most of methods called here should be called once on first connection (e.g. adding subprotocols) | 117 # most of methods called here should be called once on first connection (e.g. adding subprotocols) |
117 # but client should not be deleted except if session is finished (independently of connection/deconnection | 118 # but client should not be deleted except if session is finished (independently of connection/deconnection |
118 # | 119 # |
119 try: | 120 try: |
120 port = int(host.memory.getParamA(C.FORCE_PORT_PARAM, "Connection", profile_key=profile)) | 121 port = int( |
122 host.memory.getParamA( | |
123 C.FORCE_PORT_PARAM, "Connection", profile_key=profile | |
124 ) | |
125 ) | |
121 except ValueError: | 126 except ValueError: |
122 log.debug(_("Can't parse port value, using default value")) | 127 log.debug(_("Can't parse port value, using default value")) |
123 port = None # will use default value 5222 or be retrieved from a DNS SRV record | 128 port = ( |
124 | 129 None |
125 password = yield host.memory.asyncGetParamA("Password", "Connection", profile_key=profile) | 130 ) # will use default value 5222 or be retrieved from a DNS SRV record |
126 entity = host.profiles[profile] = cls(host, profile, | 131 |
132 password = yield host.memory.asyncGetParamA( | |
133 "Password", "Connection", profile_key=profile | |
134 ) | |
135 entity = host.profiles[profile] = cls( | |
136 host, | |
137 profile, | |
127 jid.JID(host.memory.getParamA("JabberID", "Connection", profile_key=profile)), | 138 jid.JID(host.memory.getParamA("JabberID", "Connection", profile_key=profile)), |
128 password, host.memory.getParamA(C.FORCE_SERVER_PARAM, "Connection", profile_key=profile) or None, | 139 password, |
129 port, max_retries) | 140 host.memory.getParamA(C.FORCE_SERVER_PARAM, "Connection", profile_key=profile) |
141 or None, | |
142 port, | |
143 max_retries, | |
144 ) | |
130 | 145 |
131 entity._createSubProtocols() | 146 entity._createSubProtocols() |
132 | 147 |
133 entity.fallBack = SatFallbackHandler(host) | 148 entity.fallBack = SatFallbackHandler(host) |
134 entity.fallBack.setHandlerParent(entity) | 149 entity.fallBack.setHandlerParent(entity) |
135 | 150 |
136 entity.versionHandler = SatVersionHandler(C.APP_NAME_FULL, | 151 entity.versionHandler = SatVersionHandler(C.APP_NAME_FULL, host.full_version) |
137 host.full_version) | |
138 entity.versionHandler.setHandlerParent(entity) | 152 entity.versionHandler.setHandlerParent(entity) |
139 | 153 |
140 entity.identityHandler = SatIdentityHandler() | 154 entity.identityHandler = SatIdentityHandler() |
141 entity.identityHandler.setHandlerParent(entity) | 155 entity.identityHandler.setHandlerParent(entity) |
142 | 156 |
160 all_succeed = all([success for success, result in results]) | 174 all_succeed = all([success for success, result in results]) |
161 if not all_succeed: | 175 if not all_succeed: |
162 log.error(_(u"Plugins initialisation error")) | 176 log.error(_(u"Plugins initialisation error")) |
163 for idx, (success, result) in enumerate(results): | 177 for idx, (success, result) in enumerate(results): |
164 if not success: | 178 if not success: |
165 log.error(u"error (plugin %(name)s): %(failure)s" % | 179 log.error( |
166 {'name': plugin_conn_cb[idx][0]._info['import_name'], 'failure': result}) | 180 u"error (plugin %(name)s): %(failure)s" |
167 | 181 % { |
168 yield list_d.addCallback(logPluginResults) # FIXME: we should have a timeout here, and a way to know if a plugin freeze | 182 "name": plugin_conn_cb[idx][0]._info["import_name"], |
183 "failure": result, | |
184 } | |
185 ) | |
186 | |
187 yield list_d.addCallback( | |
188 logPluginResults | |
189 ) # FIXME: we should have a timeout here, and a way to know if a plugin freeze | |
169 # TODO: mesure launch time of each plugin | 190 # TODO: mesure launch time of each plugin |
170 | 191 |
171 def getConnectionDeferred(self): | 192 def getConnectionDeferred(self): |
172 """Return a deferred which fire when the client is connected""" | 193 """Return a deferred which fire when the client is connected""" |
173 return self.conn_deferred | 194 return self.conn_deferred |
188 self._connected = defer.Deferred() | 209 self._connected = defer.Deferred() |
189 self._connected.addCallback(self._cleanConnection) | 210 self._connected.addCallback(self._cleanConnection) |
190 self._connected.addCallback(self._disconnectionCb) | 211 self._connected.addCallback(self._disconnectionCb) |
191 self._connected.addErrback(self._disconnectionEb) | 212 self._connected.addErrback(self._disconnectionEb) |
192 | 213 |
193 log.info(_(u"********** [{profile}] CONNECTED **********").format(profile=self.profile)) | 214 log.info( |
215 _(u"********** [{profile}] CONNECTED **********").format(profile=self.profile) | |
216 ) | |
194 self.streamInitialized() | 217 self.streamInitialized() |
195 self.host_app.bridge.connected(self.profile, unicode(self.jid)) # we send the signal to the clients | 218 self.host_app.bridge.connected( |
219 self.profile, unicode(self.jid) | |
220 ) # we send the signal to the clients | |
196 | 221 |
197 def _finish_connection(self, dummy): | 222 def _finish_connection(self, dummy): |
198 self.conn_deferred.callback(None) | 223 self.conn_deferred.callback(None) |
199 | 224 |
200 def streamInitialized(self): | 225 def streamInitialized(self): |
201 """Called after _authd""" | 226 """Called after _authd""" |
202 log.debug(_(u"XML stream is initialized")) | 227 log.debug(_(u"XML stream is initialized")) |
203 self.keep_alife = task.LoopingCall(self.xmlstream.send, " ") # Needed to avoid disconnection (specially with openfire) | 228 self.keep_alife = task.LoopingCall( |
229 self.xmlstream.send, " " | |
230 ) # Needed to avoid disconnection (specially with openfire) | |
204 self.keep_alife.start(C.XMPP_KEEP_ALIFE) | 231 self.keep_alife.start(C.XMPP_KEEP_ALIFE) |
205 | 232 |
206 self.disco = SatDiscoProtocol(self) | 233 self.disco = SatDiscoProtocol(self) |
207 self.disco.setHandlerParent(self) | 234 self.disco.setHandlerParent(self) |
208 self.discoHandler = disco.DiscoHandler() | 235 self.discoHandler = disco.DiscoHandler() |
213 return | 240 return |
214 | 241 |
215 disco_d.addCallback(self._finish_connection) | 242 disco_d.addCallback(self._finish_connection) |
216 | 243 |
217 def initializationFailed(self, reason): | 244 def initializationFailed(self, reason): |
218 log.error(_(u"ERROR: XMPP connection failed for profile '%(profile)s': %(reason)s" % {'profile': self.profile, 'reason': reason})) | 245 log.error( |
246 _( | |
247 u"ERROR: XMPP connection failed for profile '%(profile)s': %(reason)s" | |
248 % {"profile": self.profile, "reason": reason} | |
249 ) | |
250 ) | |
219 self.conn_deferred.errback(reason.value) | 251 self.conn_deferred.errback(reason.value) |
220 try: | 252 try: |
221 super(SatXMPPEntity, self).initializationFailed(reason) | 253 super(SatXMPPEntity, self).initializationFailed(reason) |
222 except: | 254 except: |
223 # we already chained an errback, no need to raise an exception | 255 # we already chained an errback, no need to raise an exception |
229 try: | 261 try: |
230 self.keep_alife.stop() | 262 self.keep_alife.stop() |
231 except AttributeError: | 263 except AttributeError: |
232 log.debug(_("No keep_alife")) | 264 log.debug(_("No keep_alife")) |
233 if self._connected is not None: | 265 if self._connected is not None: |
234 self.host_app.bridge.disconnected(self.profile) # we send the signal to the clients | 266 self.host_app.bridge.disconnected( |
267 self.profile | |
268 ) # we send the signal to the clients | |
235 self._connected.callback(None) | 269 self._connected.callback(None) |
236 self.host_app.purgeEntity(self.profile) # and we remove references to this client | 270 self.host_app.purgeEntity( |
237 log.info(_(u"********** [{profile}] DISCONNECTED **********").format(profile=self.profile)) | 271 self.profile |
272 ) # and we remove references to this client | |
273 log.info( | |
274 _(u"********** [{profile}] DISCONNECTED **********").format( | |
275 profile=self.profile | |
276 ) | |
277 ) | |
238 if not self.conn_deferred.called: | 278 if not self.conn_deferred.called: |
239 # FIXME: real error is not gotten here (e.g. if jid is not know by Prosody, | 279 # FIXME: real error is not gotten here (e.g. if jid is not know by Prosody, |
240 # we should have the real error) | 280 # we should have the real error) |
241 self.conn_deferred.errback(error.StreamError(u"Server unexpectedly closed the connection")) | 281 self.conn_deferred.errback( |
282 error.StreamError(u"Server unexpectedly closed the connection") | |
283 ) | |
242 | 284 |
243 @defer.inlineCallbacks | 285 @defer.inlineCallbacks |
244 def _cleanConnection(self, dummy): | 286 def _cleanConnection(self, dummy): |
245 """method called on disconnection | 287 """method called on disconnection |
246 | 288 |
263 else: | 305 else: |
264 return defer.succeed(None) | 306 return defer.succeed(None) |
265 | 307 |
266 ## sending ## | 308 ## sending ## |
267 | 309 |
268 def IQ(self, type_=u'set', timeout=60): | 310 def IQ(self, type_=u"set", timeout=60): |
269 """shortcut to create an IQ element managing deferred | 311 """shortcut to create an IQ element managing deferred |
270 | 312 |
271 @param type_(unicode): IQ type ('set' or 'get') | 313 @param type_(unicode): IQ type ('set' or 'get') |
272 @param timeout(None, int): timeout in seconds | 314 @param timeout(None, int): timeout in seconds |
273 @return((D)domish.Element: result stanza | 315 @return((D)domish.Element: result stanza |
299 - type | 341 - type |
300 - subject | 342 - subject |
301 - extra | 343 - extra |
302 @return (dict) message data | 344 @return (dict) message data |
303 """ | 345 """ |
304 data['xml'] = message_elt = domish.Element((None, 'message')) | 346 data["xml"] = message_elt = domish.Element((None, "message")) |
305 message_elt["to"] = data["to"].full() | 347 message_elt["to"] = data["to"].full() |
306 message_elt["from"] = data['from'].full() | 348 message_elt["from"] = data["from"].full() |
307 message_elt["type"] = data["type"] | 349 message_elt["type"] = data["type"] |
308 if data['uid']: # key must be present but can be set to '' | 350 if data["uid"]: # key must be present but can be set to '' |
309 # by a plugin to avoid id on purpose | 351 # by a plugin to avoid id on purpose |
310 message_elt['id'] = data['uid'] | 352 message_elt["id"] = data["uid"] |
311 for lang, subject in data["subject"].iteritems(): | 353 for lang, subject in data["subject"].iteritems(): |
312 subject_elt = message_elt.addElement("subject", content=subject) | 354 subject_elt = message_elt.addElement("subject", content=subject) |
313 if lang: | 355 if lang: |
314 subject_elt[(C.NS_XML, 'lang')] = lang | 356 subject_elt[(C.NS_XML, "lang")] = lang |
315 for lang, message in data["message"].iteritems(): | 357 for lang, message in data["message"].iteritems(): |
316 body_elt = message_elt.addElement("body", content=message) | 358 body_elt = message_elt.addElement("body", content=message) |
317 if lang: | 359 if lang: |
318 body_elt[(C.NS_XML, 'lang')] = lang | 360 body_elt[(C.NS_XML, "lang")] = lang |
319 try: | 361 try: |
320 thread = data['extra']['thread'] | 362 thread = data["extra"]["thread"] |
321 except KeyError: | 363 except KeyError: |
322 if 'thread_parent' in data['extra']: | 364 if "thread_parent" in data["extra"]: |
323 raise exceptions.InternalError(u"thread_parent found while there is not associated thread") | 365 raise exceptions.InternalError( |
366 u"thread_parent found while there is not associated thread" | |
367 ) | |
324 else: | 368 else: |
325 thread_elt = message_elt.addElement("thread", content=thread) | 369 thread_elt = message_elt.addElement("thread", content=thread) |
326 try: | 370 try: |
327 thread_elt["parent"] = data["extra"]["thread_parent"] | 371 thread_elt["parent"] = data["extra"]["thread_parent"] |
328 except KeyError: | 372 except KeyError: |
334 | 378 |
335 @param post_xml_treatments(D): the same Deferred as in sendMessage trigger | 379 @param post_xml_treatments(D): the same Deferred as in sendMessage trigger |
336 """ | 380 """ |
337 raise NotImplementedError | 381 raise NotImplementedError |
338 | 382 |
339 def sendMessage(self, to_jid, message, subject=None, mess_type='auto', extra=None, uid=None, no_trigger=False): | 383 def sendMessage( |
384 self, | |
385 to_jid, | |
386 message, | |
387 subject=None, | |
388 mess_type="auto", | |
389 extra=None, | |
390 uid=None, | |
391 no_trigger=False, | |
392 ): | |
340 """Send a message to an entity | 393 """Send a message to an entity |
341 | 394 |
342 @param to_jid(jid.JID): destinee of the message | 395 @param to_jid(jid.JID): destinee of the message |
343 @param message(dict): message body, key is the language (use '' when unknown) | 396 @param message(dict): message body, key is the language (use '' when unknown) |
344 @param subject(dict): message subject, key is the language (use '' when unknown) | 397 @param subject(dict): message subject, key is the language (use '' when unknown) |
369 "subject": subject, | 422 "subject": subject, |
370 "type": mess_type, | 423 "type": mess_type, |
371 "extra": extra, | 424 "extra": extra, |
372 "timestamp": time.time(), | 425 "timestamp": time.time(), |
373 } | 426 } |
374 pre_xml_treatments = defer.Deferred() # XXX: plugin can add their pre XML treatments to this deferred | 427 pre_xml_treatments = ( |
375 post_xml_treatments = defer.Deferred() # XXX: plugin can add their post XML treatments to this deferred | 428 defer.Deferred() |
429 ) # XXX: plugin can add their pre XML treatments to this deferred | |
430 post_xml_treatments = ( | |
431 defer.Deferred() | |
432 ) # XXX: plugin can add their post XML treatments to this deferred | |
376 | 433 |
377 if data["type"] == C.MESS_TYPE_AUTO: | 434 if data["type"] == C.MESS_TYPE_AUTO: |
378 # we try to guess the type | 435 # we try to guess the type |
379 if data["subject"]: | 436 if data["subject"]: |
380 data["type"] = C.MESS_TYPE_NORMAL | 437 data["type"] = C.MESS_TYPE_NORMAL |
381 elif not data["to"].resource: # if to JID has a resource, the type is not 'groupchat' | 438 elif not data[ |
439 "to" | |
440 ].resource: # if to JID has a resource, the type is not 'groupchat' | |
382 # we may have a groupchat message, we check if the we know this jid | 441 # we may have a groupchat message, we check if the we know this jid |
383 try: | 442 try: |
384 entity_type = self.host_app.memory.getEntityData(data["to"], ['type'], self.profile)["type"] | 443 entity_type = self.host_app.memory.getEntityData( |
385 #FIXME: should entity_type manage resources ? | 444 data["to"], ["type"], self.profile |
445 )["type"] | |
446 # FIXME: should entity_type manage resources ? | |
386 except (exceptions.UnknownEntityError, KeyError): | 447 except (exceptions.UnknownEntityError, KeyError): |
387 entity_type = "contact" | 448 entity_type = "contact" |
388 | 449 |
389 if entity_type == "chatroom": | 450 if entity_type == "chatroom": |
390 data["type"] = C.MESS_TYPE_GROUPCHAT | 451 data["type"] = C.MESS_TYPE_GROUPCHAT |
395 data["type"] == C.MESS_TYPE_CHAT if data["subject"] else C.MESS_TYPE_NORMAL | 456 data["type"] == C.MESS_TYPE_CHAT if data["subject"] else C.MESS_TYPE_NORMAL |
396 | 457 |
397 # FIXME: send_only is used by libervia's OTR plugin to avoid | 458 # FIXME: send_only is used by libervia's OTR plugin to avoid |
398 # the triggers from frontend, and no_trigger do the same | 459 # the triggers from frontend, and no_trigger do the same |
399 # thing internally, this could be unified | 460 # thing internally, this could be unified |
400 send_only = data['extra'].get('send_only', False) | 461 send_only = data["extra"].get("send_only", False) |
401 | 462 |
402 if not no_trigger and not send_only: | 463 if not no_trigger and not send_only: |
403 if not self.host_app.trigger.point("sendMessage" + self.trigger_suffix, self, data, pre_xml_treatments, post_xml_treatments): | 464 if not self.host_app.trigger.point( |
465 "sendMessage" + self.trigger_suffix, | |
466 self, | |
467 data, | |
468 pre_xml_treatments, | |
469 post_xml_treatments, | |
470 ): | |
404 return defer.succeed(None) | 471 return defer.succeed(None) |
405 | 472 |
406 log.debug(_(u"Sending message (type {type}, to {to})").format(type=data["type"], to=to_jid.full())) | 473 log.debug( |
474 _(u"Sending message (type {type}, to {to})").format( | |
475 type=data["type"], to=to_jid.full() | |
476 ) | |
477 ) | |
407 | 478 |
408 pre_xml_treatments.addCallback(lambda dummy: self.generateMessageXML(data)) | 479 pre_xml_treatments.addCallback(lambda dummy: self.generateMessageXML(data)) |
409 pre_xml_treatments.chainDeferred(post_xml_treatments) | 480 pre_xml_treatments.chainDeferred(post_xml_treatments) |
410 post_xml_treatments.addCallback(self.sendMessageData) | 481 post_xml_treatments.addCallback(self.sendMessageData) |
411 if send_only: | 482 if send_only: |
412 log.debug(_("Triggers, storage and echo have been inhibited by the 'send_only' parameter")) | 483 log.debug( |
484 _( | |
485 "Triggers, storage and echo have been inhibited by the 'send_only' parameter" | |
486 ) | |
487 ) | |
413 else: | 488 else: |
414 self.addPostXmlCallbacks(post_xml_treatments) | 489 self.addPostXmlCallbacks(post_xml_treatments) |
415 post_xml_treatments.addErrback(self._cancelErrorTrap) | 490 post_xml_treatments.addErrback(self._cancelErrorTrap) |
416 post_xml_treatments.addErrback(self.host_app.logErrback) | 491 post_xml_treatments.addErrback(self.host_app.logErrback) |
417 pre_xml_treatments.callback(data) | 492 pre_xml_treatments.callback(data) |
428 @param client: profile's client | 503 @param client: profile's client |
429 """ | 504 """ |
430 if data[u"type"] != C.MESS_TYPE_GROUPCHAT: | 505 if data[u"type"] != C.MESS_TYPE_GROUPCHAT: |
431 # we don't add groupchat message to history, as we get them back | 506 # we don't add groupchat message to history, as we get them back |
432 # and they will be added then | 507 # and they will be added then |
433 if data[u'message'] or data[u'subject']: # we need a message to store | 508 if data[u"message"] or data[u"subject"]: # we need a message to store |
434 self.host_app.memory.addToHistory(self, data) | 509 self.host_app.memory.addToHistory(self, data) |
435 else: | 510 else: |
436 log.warning(u"No message found") # empty body should be managed by plugins before this point | 511 log.warning( |
512 u"No message found" | |
513 ) # empty body should be managed by plugins before this point | |
437 return data | 514 return data |
438 | 515 |
439 def messageSendToBridge(self, data): | 516 def messageSendToBridge(self, data): |
440 """Send message to bridge, so frontends can display it | 517 """Send message to bridge, so frontends can display it |
441 | 518 |
443 @param client: profile's client | 520 @param client: profile's client |
444 """ | 521 """ |
445 if data[u"type"] != C.MESS_TYPE_GROUPCHAT: | 522 if data[u"type"] != C.MESS_TYPE_GROUPCHAT: |
446 # we don't send groupchat message to bridge, as we get them back | 523 # we don't send groupchat message to bridge, as we get them back |
447 # and they will be added the | 524 # and they will be added the |
448 if data[u'message'] or data[u'subject']: # we need a message to send something | 525 if ( |
526 data[u"message"] or data[u"subject"] | |
527 ): # we need a message to send something | |
449 # We send back the message, so all frontends are aware of it | 528 # We send back the message, so all frontends are aware of it |
450 self.host_app.bridge.messageNew(data[u'uid'], data[u'timestamp'], data[u'from'].full(), data[u'to'].full(), data[u'message'], data[u'subject'], data[u'type'], data[u'extra'], profile=self.profile) | 529 self.host_app.bridge.messageNew( |
530 data[u"uid"], | |
531 data[u"timestamp"], | |
532 data[u"from"].full(), | |
533 data[u"to"].full(), | |
534 data[u"message"], | |
535 data[u"subject"], | |
536 data[u"type"], | |
537 data[u"extra"], | |
538 profile=self.profile, | |
539 ) | |
451 else: | 540 else: |
452 log.warning(_(u"No message found")) | 541 log.warning(_(u"No message found")) |
453 return data | 542 return data |
454 | 543 |
455 | 544 |
456 class SatXMPPClient(SatXMPPEntity, wokkel_client.XMPPClient): | 545 class SatXMPPClient(SatXMPPEntity, wokkel_client.XMPPClient): |
457 implements(iwokkel.IDisco) | 546 implements(iwokkel.IDisco) |
458 trigger_suffix = "" | 547 trigger_suffix = "" |
459 is_component = False | 548 is_component = False |
460 | 549 |
461 def __init__(self, host_app, profile, user_jid, password, host=None, port=C.XMPP_C2S_PORT, max_retries=C.XMPP_MAX_RETRIES): | 550 def __init__( |
551 self, | |
552 host_app, | |
553 profile, | |
554 user_jid, | |
555 password, | |
556 host=None, | |
557 port=C.XMPP_C2S_PORT, | |
558 max_retries=C.XMPP_MAX_RETRIES, | |
559 ): | |
462 # XXX: DNS SRV records are checked when the host is not specified. | 560 # XXX: DNS SRV records are checked when the host is not specified. |
463 # If no SRV record is found, the host is directly extracted from the JID. | 561 # If no SRV record is found, the host is directly extracted from the JID. |
464 self.started = time.time() | 562 self.started = time.time() |
465 | 563 |
466 # Currently, we use "client/pc/Salut à Toi", but as | 564 # Currently, we use "client/pc/Salut à Toi", but as |
480 if host is None and user_jid.host in hosts_map: | 578 if host is None and user_jid.host in hosts_map: |
481 host_data = hosts_map[user_jid.host] | 579 host_data = hosts_map[user_jid.host] |
482 if isinstance(host_data, basestring): | 580 if isinstance(host_data, basestring): |
483 host = host_data | 581 host = host_data |
484 elif isinstance(host_data, dict): | 582 elif isinstance(host_data, dict): |
485 if u'host' in host_data: | 583 if u"host" in host_data: |
486 host = host_data[u'host'] | 584 host = host_data[u"host"] |
487 if u'port' in host_data: | 585 if u"port" in host_data: |
488 port = host_data[u'port'] | 586 port = host_data[u"port"] |
489 else: | 587 else: |
490 log.warning(_(u"invalid data used for host: {data}").format(data=host_data)) | 588 log.warning( |
589 _(u"invalid data used for host: {data}").format(data=host_data) | |
590 ) | |
491 host_data = None | 591 host_data = None |
492 if host_data is not None: | 592 if host_data is not None: |
493 log.info(u"using {host}:{port} for host {host_ori} as requested in config".format( | 593 log.info( |
494 host_ori = user_jid.host, | 594 u"using {host}:{port} for host {host_ori} as requested in config".format( |
495 host = host, | 595 host_ori=user_jid.host, host=host, port=port |
496 port = port)) | 596 ) |
497 | 597 ) |
498 wokkel_client.XMPPClient.__init__(self, user_jid, password, host or None, port or C.XMPP_C2S_PORT) | 598 |
599 wokkel_client.XMPPClient.__init__( | |
600 self, user_jid, password, host or None, port or C.XMPP_C2S_PORT | |
601 ) | |
499 SatXMPPEntity.__init__(self, host_app, profile, max_retries) | 602 SatXMPPEntity.__init__(self, host_app, profile, max_retries) |
500 | 603 |
501 def _getPluginsList(self): | 604 def _getPluginsList(self): |
502 for p in self.host_app.plugins.itervalues(): | 605 for p in self.host_app.plugins.itervalues(): |
503 if C.PLUG_MODE_CLIENT in p._info[u'modes']: | 606 if C.PLUG_MODE_CLIENT in p._info[u"modes"]: |
504 yield p | 607 yield p |
505 | 608 |
506 def _createSubProtocols(self): | 609 def _createSubProtocols(self): |
507 self.messageProt = SatMessageProtocol(self.host_app) | 610 self.messageProt = SatMessageProtocol(self.host_app) |
508 self.messageProt.setHandlerParent(self) | 611 self.messageProt.setHandlerParent(self) |
529 # it is intended for things like end 2 end encryption. | 632 # it is intended for things like end 2 end encryption. |
530 # *DO NOT* cancel (i.e. return False) without very good reason | 633 # *DO NOT* cancel (i.e. return False) without very good reason |
531 # (out of band transmission for instance). | 634 # (out of band transmission for instance). |
532 # e2e should have a priority of 0 here, and out of band transmission | 635 # e2e should have a priority of 0 here, and out of band transmission |
533 # a lower priority | 636 # a lower priority |
534 # FIXME: trigger not used yet, can be uncommented when e2e full stanza encryption is implemented | 637 # FIXME: trigger not used yet, can be uncommented when e2e full stanza encryption is implemented |
535 # if not self.host_app.trigger.point("send", self, obj): | 638 # if not self.host_app.trigger.point("send", self, obj): |
536 # return | 639 # return |
537 super(SatXMPPClient, self).send(obj) | 640 super(SatXMPPClient, self).send(obj) |
538 | 641 |
539 def sendMessageData(self, mess_data): | 642 def sendMessageData(self, mess_data): |
540 """Convenient method to send message data to stream | 643 """Convenient method to send message data to stream |
541 | 644 |
547 """ | 650 """ |
548 # XXX: This is the last trigger before u"send" (last but one globally) for sending message. | 651 # XXX: This is the last trigger before u"send" (last but one globally) for sending message. |
549 # This is intented for e2e encryption which doesn't do full stanza encryption (e.g. OTR) | 652 # This is intented for e2e encryption which doesn't do full stanza encryption (e.g. OTR) |
550 # This trigger point can't cancel the method | 653 # This trigger point can't cancel the method |
551 self.host_app.trigger.point("sendMessageData", self, mess_data) | 654 self.host_app.trigger.point("sendMessageData", self, mess_data) |
552 self.send(mess_data[u'xml']) | 655 self.send(mess_data[u"xml"]) |
553 return mess_data | 656 return mess_data |
554 | 657 |
555 def feedback(self, to_jid, message): | 658 def feedback(self, to_jid, message): |
556 """Send message to frontends | 659 """Send message to frontends |
557 | 660 |
558 This message will be an info message, not recorded in history. | 661 This message will be an info message, not recorded in history. |
559 It can be used to give feedback of a command | 662 It can be used to give feedback of a command |
560 @param to_jid(jid.JID): destinee jid | 663 @param to_jid(jid.JID): destinee jid |
561 @param message(unicode): message to send to frontends | 664 @param message(unicode): message to send to frontends |
562 """ | 665 """ |
563 self.host_app.bridge.messageNew(uid=unicode(uuid.uuid4()), | 666 self.host_app.bridge.messageNew( |
564 timestamp=time.time(), | 667 uid=unicode(uuid.uuid4()), |
565 from_jid=self.jid.full(), | 668 timestamp=time.time(), |
566 to_jid=to_jid.full(), | 669 from_jid=self.jid.full(), |
567 message={u'': message}, | 670 to_jid=to_jid.full(), |
568 subject={}, | 671 message={u"": message}, |
569 mess_type=C.MESS_TYPE_INFO, | 672 subject={}, |
570 extra={}, | 673 mess_type=C.MESS_TYPE_INFO, |
571 profile=self.profile) | 674 extra={}, |
675 profile=self.profile, | |
676 ) | |
572 | 677 |
573 def _finish_connection(self, dummy): | 678 def _finish_connection(self, dummy): |
574 self.roster.requestRoster() | 679 self.roster.requestRoster() |
575 self.presence.available() | 680 self.presence.available() |
576 super(SatXMPPClient, self)._finish_connection(dummy) | 681 super(SatXMPPClient, self)._finish_connection(dummy) |
581 | 686 |
582 This component are similar but not identical to clients. | 687 This component are similar but not identical to clients. |
583 An entry point plugin is launched after component is connected. | 688 An entry point plugin is launched after component is connected. |
584 Component need to instantiate MessageProtocol itself | 689 Component need to instantiate MessageProtocol itself |
585 """ | 690 """ |
691 | |
586 implements(iwokkel.IDisco) | 692 implements(iwokkel.IDisco) |
587 trigger_suffix = "Component" # used for to distinguish some trigger points set in SatXMPPEntity | 693 trigger_suffix = ( |
694 "Component" | |
695 ) # used for to distinguish some trigger points set in SatXMPPEntity | |
588 is_component = True | 696 is_component = True |
589 sendHistory = False # XXX: set to True from entry plugin to keep messages in history for received messages | 697 sendHistory = ( |
590 | 698 False |
591 def __init__(self, host_app, profile, component_jid, password, host=None, port=None, max_retries=C.XMPP_MAX_RETRIES): | 699 ) # XXX: set to True from entry plugin to keep messages in history for received messages |
700 | |
701 def __init__( | |
702 self, | |
703 host_app, | |
704 profile, | |
705 component_jid, | |
706 password, | |
707 host=None, | |
708 port=None, | |
709 max_retries=C.XMPP_MAX_RETRIES, | |
710 ): | |
592 self.started = time.time() | 711 self.started = time.time() |
593 if port is None: | 712 if port is None: |
594 port = C.XMPP_COMPONENT_PORT | 713 port = C.XMPP_COMPONENT_PORT |
595 | 714 |
596 ## entry point ## | 715 ## entry point ## |
597 entry_point = host_app.memory.getEntryPoint(profile) | 716 entry_point = host_app.memory.getEntryPoint(profile) |
598 try: | 717 try: |
599 self.entry_plugin = host_app.plugins[entry_point] | 718 self.entry_plugin = host_app.plugins[entry_point] |
600 except KeyError: | 719 except KeyError: |
601 raise exceptions.NotFound(_(u"The requested entry point ({entry_point}) is not available").format( | 720 raise exceptions.NotFound( |
602 entry_point = entry_point)) | 721 _(u"The requested entry point ({entry_point}) is not available").format( |
722 entry_point=entry_point | |
723 ) | |
724 ) | |
603 | 725 |
604 self.identities = [disco.DiscoIdentity(u"component", u"generic", C.APP_NAME)] | 726 self.identities = [disco.DiscoIdentity(u"component", u"generic", C.APP_NAME)] |
605 # jid is set automatically on bind by Twisted for Client, but not for Component | 727 # jid is set automatically on bind by Twisted for Client, but not for Component |
606 self.jid = component_jid | 728 self.jid = component_jid |
607 if host is None: | 729 if host is None: |
608 try: | 730 try: |
609 host = component_jid.host.split(u'.', 1)[1] | 731 host = component_jid.host.split(u".", 1)[1] |
610 except IndexError: | 732 except IndexError: |
611 raise ValueError(u"Can't guess host from jid, please specify a host") | 733 raise ValueError(u"Can't guess host from jid, please specify a host") |
612 # XXX: component.Component expect unicode jid, while Client expect jid.JID. | 734 # XXX: component.Component expect unicode jid, while Client expect jid.JID. |
613 # this is not consistent, so we use jid.JID for SatXMPP* | 735 # this is not consistent, so we use jid.JID for SatXMPP* |
614 component.Component.__init__(self, host, port, component_jid.full(), password) | 736 component.Component.__init__(self, host, port, component_jid.full(), password) |
626 @param required(bool): True if plugin is mandatory | 748 @param required(bool): True if plugin is mandatory |
627 for recursive calls only, should not be modified by inital caller | 749 for recursive calls only, should not be modified by inital caller |
628 @raise InternalError: one of the plugin is not handling components | 750 @raise InternalError: one of the plugin is not handling components |
629 @raise KeyError: one plugin should be present in self.host_app.plugins but it is not | 751 @raise KeyError: one plugin should be present in self.host_app.plugins but it is not |
630 """ | 752 """ |
631 if C.PLUG_MODE_COMPONENT not in current._info[u'modes']: | 753 if C.PLUG_MODE_COMPONENT not in current._info[u"modes"]: |
632 if not required: | 754 if not required: |
633 return | 755 return |
634 else: | 756 else: |
635 log.error(_(u"Plugin {current_name} is needed for {entry_name}, but it doesn't handle component mode").format( | 757 log.error( |
636 current_name = current._info[u'import_name'], | 758 _( |
637 entry_name = self.entry_plugin._info[u'import_name'] | 759 u"Plugin {current_name} is needed for {entry_name}, but it doesn't handle component mode" |
638 )) | 760 ).format( |
761 current_name=current._info[u"import_name"], | |
762 entry_name=self.entry_plugin._info[u"import_name"], | |
763 ) | |
764 ) | |
639 raise exceptions.InternalError(_(u"invalid plugin mode")) | 765 raise exceptions.InternalError(_(u"invalid plugin mode")) |
640 | 766 |
641 for import_name in current._info.get(C.PI_DEPENDENCIES, []): | 767 for import_name in current._info.get(C.PI_DEPENDENCIES, []): |
642 # plugins are already loaded as dependencies | 768 # plugins are already loaded as dependencies |
643 # so we know they are in self.host_app.plugins | 769 # so we know they are in self.host_app.plugins |
649 # so they may not exist in self.host_app.plugins | 775 # so they may not exist in self.host_app.plugins |
650 try: | 776 try: |
651 dep = self.host_app.plugins[import_name] | 777 dep = self.host_app.plugins[import_name] |
652 except KeyError: | 778 except KeyError: |
653 continue | 779 continue |
654 self._buildDependencies(dep, plugins, required = False) | 780 self._buildDependencies(dep, plugins, required=False) |
655 | 781 |
656 if current not in plugins: | 782 if current not in plugins: |
657 # current can be required for several plugins and so | 783 # current can be required for several plugins and so |
658 # it can already be present in the list | 784 # it can already be present in the list |
659 plugins.append(current) | 785 plugins.append(current) |
678 if self.sendHistory: | 804 if self.sendHistory: |
679 post_xml_treatments.addCallback(self.messageAddToHistory) | 805 post_xml_treatments.addCallback(self.messageAddToHistory) |
680 | 806 |
681 | 807 |
682 class SatMessageProtocol(xmppim.MessageProtocol): | 808 class SatMessageProtocol(xmppim.MessageProtocol): |
683 | |
684 def __init__(self, host): | 809 def __init__(self, host): |
685 xmppim.MessageProtocol.__init__(self) | 810 xmppim.MessageProtocol.__init__(self) |
686 self.host = host | 811 self.host = host |
687 | 812 |
688 @staticmethod | 813 @staticmethod |
695 @return(dict): message data | 820 @return(dict): message data |
696 """ | 821 """ |
697 message = {} | 822 message = {} |
698 subject = {} | 823 subject = {} |
699 extra = {} | 824 extra = {} |
700 data = {"from": jid.JID(message_elt['from']), | 825 data = { |
701 "to": jid.JID(message_elt['to']), | 826 "from": jid.JID(message_elt["from"]), |
702 "uid": message_elt.getAttribute('uid', unicode(uuid.uuid4())), # XXX: uid is not a standard attribute but may be added by plugins | 827 "to": jid.JID(message_elt["to"]), |
703 "message": message, | 828 "uid": message_elt.getAttribute( |
704 "subject": subject, | 829 "uid", unicode(uuid.uuid4()) |
705 "type": message_elt.getAttribute('type', 'normal'), | 830 ), # XXX: uid is not a standard attribute but may be added by plugins |
706 "extra": extra} | 831 "message": message, |
832 "subject": subject, | |
833 "type": message_elt.getAttribute("type", "normal"), | |
834 "extra": extra, | |
835 } | |
707 | 836 |
708 if client is not None: | 837 if client is not None: |
709 try: | 838 try: |
710 data['stanza_id'] = message_elt['id'] | 839 data["stanza_id"] = message_elt["id"] |
711 except KeyError: | 840 except KeyError: |
712 pass | 841 pass |
713 else: | 842 else: |
714 client._mess_id_uid[(data['from'], data['stanza_id'])] = data['uid'] | 843 client._mess_id_uid[(data["from"], data["stanza_id"])] = data["uid"] |
715 | 844 |
716 # message | 845 # message |
717 for e in message_elt.elements(C.NS_CLIENT, 'body'): | 846 for e in message_elt.elements(C.NS_CLIENT, "body"): |
718 message[e.getAttribute((C.NS_XML,'lang'),'')] = unicode(e) | 847 message[e.getAttribute((C.NS_XML, "lang"), "")] = unicode(e) |
719 | 848 |
720 # subject | 849 # subject |
721 for e in message_elt.elements(C.NS_CLIENT, 'subject'): | 850 for e in message_elt.elements(C.NS_CLIENT, "subject"): |
722 subject[e.getAttribute((C.NS_XML, 'lang'),'')] = unicode(e) | 851 subject[e.getAttribute((C.NS_XML, "lang"), "")] = unicode(e) |
723 | 852 |
724 # delay and timestamp | 853 # delay and timestamp |
725 try: | 854 try: |
726 delay_elt = message_elt.elements(delay.NS_DELAY, 'delay').next() | 855 delay_elt = message_elt.elements(delay.NS_DELAY, "delay").next() |
727 except StopIteration: | 856 except StopIteration: |
728 data['timestamp'] = time.time() | 857 data["timestamp"] = time.time() |
729 else: | 858 else: |
730 parsed_delay = delay.Delay.fromElement(delay_elt) | 859 parsed_delay = delay.Delay.fromElement(delay_elt) |
731 data['timestamp'] = calendar.timegm(parsed_delay.stamp.utctimetuple()) | 860 data["timestamp"] = calendar.timegm(parsed_delay.stamp.utctimetuple()) |
732 data['received_timestamp'] = unicode(time.time()) | 861 data["received_timestamp"] = unicode(time.time()) |
733 if parsed_delay.sender: | 862 if parsed_delay.sender: |
734 data['delay_sender'] = parsed_delay.sender.full() | 863 data["delay_sender"] = parsed_delay.sender.full() |
735 return data | 864 return data |
736 | 865 |
737 def onMessage(self, message_elt): | 866 def onMessage(self, message_elt): |
738 # TODO: handle threads | 867 # TODO: handle threads |
739 client = self.parent | 868 client = self.parent |
740 if not 'from' in message_elt.attributes: | 869 if not "from" in message_elt.attributes: |
741 message_elt['from'] = client.jid.host | 870 message_elt["from"] = client.jid.host |
742 log.debug(_(u"got message from: {from_}").format(from_=message_elt['from'])) | 871 log.debug(_(u"got message from: {from_}").format(from_=message_elt["from"])) |
743 post_treat = defer.Deferred() # XXX: plugin can add their treatments to this deferred | 872 post_treat = ( |
744 | 873 defer.Deferred() |
745 if not self.host.trigger.point("MessageReceived", client, message_elt, post_treat): | 874 ) # XXX: plugin can add their treatments to this deferred |
875 | |
876 if not self.host.trigger.point( | |
877 "MessageReceived", client, message_elt, post_treat | |
878 ): | |
746 return | 879 return |
747 | 880 |
748 data = self.parseMessage(message_elt, client) | 881 data = self.parseMessage(message_elt, client) |
749 | 882 |
750 post_treat.addCallback(self.skipEmptyMessage) | 883 post_treat.addCallback(self.skipEmptyMessage) |
752 post_treat.addCallback(self.bridgeSignal, client, data) | 885 post_treat.addCallback(self.bridgeSignal, client, data) |
753 post_treat.addErrback(self.cancelErrorTrap) | 886 post_treat.addErrback(self.cancelErrorTrap) |
754 post_treat.callback(data) | 887 post_treat.callback(data) |
755 | 888 |
756 def skipEmptyMessage(self, data): | 889 def skipEmptyMessage(self, data): |
757 if not data['message'] and not data['extra'] and not data['subject']: | 890 if not data["message"] and not data["extra"] and not data["subject"]: |
758 raise failure.Failure(exceptions.CancelError("Cancelled empty message")) | 891 raise failure.Failure(exceptions.CancelError("Cancelled empty message")) |
759 return data | 892 return data |
760 | 893 |
761 def addToHistory(self, data, client): | 894 def addToHistory(self, data, client): |
762 if data.pop(u'history', None) == C.HISTORY_SKIP: | 895 if data.pop(u"history", None) == C.HISTORY_SKIP: |
763 log.info(u'history is skipped as requested') | 896 log.info(u"history is skipped as requested") |
764 data[u'extra'][u'history'] = C.HISTORY_SKIP | 897 data[u"extra"][u"history"] = C.HISTORY_SKIP |
765 else: | 898 else: |
766 return self.host.memory.addToHistory(client, data) | 899 return self.host.memory.addToHistory(client, data) |
767 | 900 |
768 def bridgeSignal(self, dummy, client, data): | 901 def bridgeSignal(self, dummy, client, data): |
769 try: | 902 try: |
770 data['extra']['received_timestamp'] = data['received_timestamp'] | 903 data["extra"]["received_timestamp"] = data["received_timestamp"] |
771 data['extra']['delay_sender'] = data['delay_sender'] | 904 data["extra"]["delay_sender"] = data["delay_sender"] |
772 except KeyError: | 905 except KeyError: |
773 pass | 906 pass |
774 if data is not None: | 907 if data is not None: |
775 self.host.bridge.messageNew(data['uid'], data['timestamp'], data['from'].full(), data['to'].full(), data['message'], data['subject'], data['type'], data['extra'], profile=client.profile) | 908 self.host.bridge.messageNew( |
909 data["uid"], | |
910 data["timestamp"], | |
911 data["from"].full(), | |
912 data["to"].full(), | |
913 data["message"], | |
914 data["subject"], | |
915 data["type"], | |
916 data["extra"], | |
917 profile=client.profile, | |
918 ) | |
776 return data | 919 return data |
777 | 920 |
778 def cancelErrorTrap(self, failure_): | 921 def cancelErrorTrap(self, failure_): |
779 """A message sending can be cancelled by a plugin treatment""" | 922 """A message sending can be cancelled by a plugin treatment""" |
780 failure_.trap(exceptions.CancelError) | 923 failure_.trap(exceptions.CancelError) |
781 | 924 |
782 | 925 |
783 class SatRosterProtocol(xmppim.RosterClientProtocol): | 926 class SatRosterProtocol(xmppim.RosterClientProtocol): |
784 | |
785 def __init__(self, host): | 927 def __init__(self, host): |
786 xmppim.RosterClientProtocol.__init__(self) | 928 xmppim.RosterClientProtocol.__init__(self) |
787 self.host = host | 929 self.host = host |
788 self.got_roster = defer.Deferred() # called when roster is received and ready | 930 self.got_roster = defer.Deferred() # called when roster is received and ready |
789 #XXX: the two following dicts keep a local copy of the roster | 931 # XXX: the two following dicts keep a local copy of the roster |
790 self._groups = {} # map from groups to jids: key=group value=set of jids | 932 self._groups = {} # map from groups to jids: key=group value=set of jids |
791 self._jids = None # map from jids to RosterItem: key=jid value=RosterItem | 933 self._jids = None # map from jids to RosterItem: key=jid value=RosterItem |
792 | 934 |
793 def rosterCb(self, roster): | 935 def rosterCb(self, roster): |
794 assert roster is not None # FIXME: must be managed with roster versioning | 936 assert roster is not None # FIXME: must be managed with roster versioning |
795 self._groups.clear() | 937 self._groups.clear() |
796 self._jids = roster | 938 self._jids = roster |
797 for item in roster.itervalues(): | 939 for item in roster.itervalues(): |
798 if not item.subscriptionTo and not item.subscriptionFrom and not item.ask: | 940 if not item.subscriptionTo and not item.subscriptionFrom and not item.ask: |
799 #XXX: current behaviour: we don't want contact in our roster list | 941 # XXX: current behaviour: we don't want contact in our roster list |
800 # if there is no presence subscription | 942 # if there is no presence subscription |
801 # may change in the future | 943 # may change in the future |
802 log.info(u"Removing contact {} from roster because there is no presence subscription".format(item.jid)) | 944 log.info( |
803 self.removeItem(item.entity) # FIXME: to be checked | 945 u"Removing contact {} from roster because there is no presence subscription".format( |
946 item.jid | |
947 ) | |
948 ) | |
949 self.removeItem(item.entity) # FIXME: to be checked | |
804 else: | 950 else: |
805 self._registerItem(item) | 951 self._registerItem(item) |
806 | 952 |
807 def _registerItem(self, item): | 953 def _registerItem(self, item): |
808 """Register item in local cache | 954 """Register item in local cache |
810 item must be already registered in self._jids before this method is called | 956 item must be already registered in self._jids before this method is called |
811 @param item (RosterIem): item added | 957 @param item (RosterIem): item added |
812 """ | 958 """ |
813 log.debug(u"registering item: {}".format(item.entity.full())) | 959 log.debug(u"registering item: {}".format(item.entity.full())) |
814 if item.entity.resource: | 960 if item.entity.resource: |
815 log.warning(u"Received a roster item with a resource, this is not common but not restricted by RFC 6121, this case may be not well tested.") | 961 log.warning( |
962 u"Received a roster item with a resource, this is not common but not restricted by RFC 6121, this case may be not well tested." | |
963 ) | |
816 if not item.subscriptionTo: | 964 if not item.subscriptionTo: |
817 if not item.subscriptionFrom: | 965 if not item.subscriptionFrom: |
818 log.info(_(u"There's no subscription between you and [{}]!").format(item.entity.full())) | 966 log.info( |
967 _(u"There's no subscription between you and [{}]!").format( | |
968 item.entity.full() | |
969 ) | |
970 ) | |
819 else: | 971 else: |
820 log.info(_(u"You are not subscribed to [{}]!").format(item.entity.full())) | 972 log.info(_(u"You are not subscribed to [{}]!").format(item.entity.full())) |
821 if not item.subscriptionFrom: | 973 if not item.subscriptionFrom: |
822 log.info(_(u"[{}] is not subscribed to you!").format(item.entity.full())) | 974 log.info(_(u"[{}] is not subscribed to you!").format(item.entity.full())) |
823 | 975 |
841 """Return dictionary of attributes as used in bridge from a RosterItem | 993 """Return dictionary of attributes as used in bridge from a RosterItem |
842 | 994 |
843 @param item: RosterItem | 995 @param item: RosterItem |
844 @return: dictionary of attributes | 996 @return: dictionary of attributes |
845 """ | 997 """ |
846 item_attr = {'to': unicode(item.subscriptionTo), | 998 item_attr = { |
847 'from': unicode(item.subscriptionFrom), | 999 "to": unicode(item.subscriptionTo), |
848 'ask': unicode(item.ask) | 1000 "from": unicode(item.subscriptionFrom), |
849 } | 1001 "ask": unicode(item.ask), |
1002 } | |
850 if item.name: | 1003 if item.name: |
851 item_attr['name'] = item.name | 1004 item_attr["name"] = item.name |
852 return item_attr | 1005 return item_attr |
853 | 1006 |
854 def setReceived(self, request): | 1007 def setReceived(self, request): |
855 #TODO: implement roster versioning (cf RFC 6121 §2.6) | 1008 # TODO: implement roster versioning (cf RFC 6121 §2.6) |
856 item = request.item | 1009 item = request.item |
857 try: # update the cache for the groups the contact has been removed from | 1010 try: # update the cache for the groups the contact has been removed from |
858 left_groups = set(self._jids[item.entity].groups).difference(item.groups) | 1011 left_groups = set(self._jids[item.entity].groups).difference(item.groups) |
859 for group in left_groups: | 1012 for group in left_groups: |
860 jids_set = self._groups[group] | 1013 jids_set = self._groups[group] |
863 del self._groups[group] | 1016 del self._groups[group] |
864 except KeyError: | 1017 except KeyError: |
865 pass # no previous item registration (or it's been cleared) | 1018 pass # no previous item registration (or it's been cleared) |
866 self._jids[item.entity] = item | 1019 self._jids[item.entity] = item |
867 self._registerItem(item) | 1020 self._registerItem(item) |
868 self.host.bridge.newContact(item.entity.full(), self.getAttributes(item), item.groups, self.parent.profile) | 1021 self.host.bridge.newContact( |
1022 item.entity.full(), self.getAttributes(item), item.groups, self.parent.profile | |
1023 ) | |
869 | 1024 |
870 def removeReceived(self, request): | 1025 def removeReceived(self, request): |
871 entity = request.item.entity | 1026 entity = request.item.entity |
872 log.info(u"removing %s from roster list" % entity.full()) | 1027 log.info(u"removing %s from roster list" % entity.full()) |
873 | 1028 |
874 # we first remove item from local cache (self._groups and self._jids) | 1029 # we first remove item from local cache (self._groups and self._jids) |
875 try: | 1030 try: |
876 item = self._jids.pop(entity) | 1031 item = self._jids.pop(entity) |
877 except KeyError: | 1032 except KeyError: |
878 log.error(u"Received a roster remove event for an item not in cache ({})".format(entity)) | 1033 log.error( |
1034 u"Received a roster remove event for an item not in cache ({})".format( | |
1035 entity | |
1036 ) | |
1037 ) | |
879 return | 1038 return |
880 for group in item.groups: | 1039 for group in item.groups: |
881 try: | 1040 try: |
882 jids_set = self._groups[group] | 1041 jids_set = self._groups[group] |
883 jids_set.remove(entity) | 1042 jids_set.remove(entity) |
884 if not jids_set: | 1043 if not jids_set: |
885 del self._groups[group] | 1044 del self._groups[group] |
886 except KeyError: | 1045 except KeyError: |
887 log.warning(u"there is no cache for the group [%(group)s] of the removed roster item [%(jid)s]" % | 1046 log.warning( |
888 {"group": group, "jid": entity}) | 1047 u"there is no cache for the group [%(group)s] of the removed roster item [%(jid)s]" |
1048 % {"group": group, "jid": entity} | |
1049 ) | |
889 | 1050 |
890 # then we send the bridge signal | 1051 # then we send the bridge signal |
891 self.host.bridge.contactDeleted(entity.full(), self.parent.profile) | 1052 self.host.bridge.contactDeleted(entity.full(), self.parent.profile) |
892 | 1053 |
893 def getGroups(self): | 1054 def getGroups(self): |
937 C.GROUP: get jids from groups (listed in "groups") | 1098 C.GROUP: get jids from groups (listed in "groups") |
938 @groups(list[unicode]): list of groups used if type_==C.GROUP | 1099 @groups(list[unicode]): list of groups used if type_==C.GROUP |
939 @return (set(jid.JID)): set of selected jids | 1100 @return (set(jid.JID)): set of selected jids |
940 """ | 1101 """ |
941 if type_ == C.ALL and groups is not None: | 1102 if type_ == C.ALL and groups is not None: |
942 raise ValueError('groups must not be set for {} type'.format(C.ALL)) | 1103 raise ValueError("groups must not be set for {} type".format(C.ALL)) |
943 | 1104 |
944 if type_ == C.ALL: | 1105 if type_ == C.ALL: |
945 return set(self.getJids()) | 1106 return set(self.getJids()) |
946 elif type_ == C.GROUP: | 1107 elif type_ == C.GROUP: |
947 jids = set() | 1108 jids = set() |
948 for group in groups: | 1109 for group in groups: |
949 jids.update(self.getJidsFromGroup(group)) | 1110 jids.update(self.getJidsFromGroup(group)) |
950 return jids | 1111 return jids |
951 else: | 1112 else: |
952 raise ValueError(u'Unexpected type_ {}'.format(type_)) | 1113 raise ValueError(u"Unexpected type_ {}".format(type_)) |
953 | 1114 |
954 def getNick(self, entity_jid): | 1115 def getNick(self, entity_jid): |
955 """Return a nick name for an entity | 1116 """Return a nick name for an entity |
956 | 1117 |
957 return nick choosed by user if available | 1118 return nick choosed by user if available |
963 else: | 1124 else: |
964 return item.name or entity_jid.user | 1125 return item.name or entity_jid.user |
965 | 1126 |
966 | 1127 |
967 class SatPresenceProtocol(xmppim.PresenceClientProtocol): | 1128 class SatPresenceProtocol(xmppim.PresenceClientProtocol): |
968 | |
969 def __init__(self, host): | 1129 def __init__(self, host): |
970 xmppim.PresenceClientProtocol.__init__(self) | 1130 xmppim.PresenceClientProtocol.__init__(self) |
971 self.host = host | 1131 self.host = host |
972 | 1132 |
973 def send(self, obj): | 1133 def send(self, obj): |
975 if not self.host.trigger.point("Presence send", self.parent, obj, presence_d): | 1135 if not self.host.trigger.point("Presence send", self.parent, obj, presence_d): |
976 return | 1136 return |
977 presence_d.addCallback(lambda __: super(SatPresenceProtocol, self).send(obj)) | 1137 presence_d.addCallback(lambda __: super(SatPresenceProtocol, self).send(obj)) |
978 | 1138 |
979 def availableReceived(self, entity, show=None, statuses=None, priority=0): | 1139 def availableReceived(self, entity, show=None, statuses=None, priority=0): |
980 log.debug(_(u"presence update for [%(entity)s] (available, show=%(show)s statuses=%(statuses)s priority=%(priority)d)") % {'entity': entity, C.PRESENCE_SHOW: show, C.PRESENCE_STATUSES: statuses, C.PRESENCE_PRIORITY: priority}) | 1140 log.debug( |
1141 _( | |
1142 u"presence update for [%(entity)s] (available, show=%(show)s statuses=%(statuses)s priority=%(priority)d)" | |
1143 ) | |
1144 % { | |
1145 "entity": entity, | |
1146 C.PRESENCE_SHOW: show, | |
1147 C.PRESENCE_STATUSES: statuses, | |
1148 C.PRESENCE_PRIORITY: priority, | |
1149 } | |
1150 ) | |
981 | 1151 |
982 if not statuses: | 1152 if not statuses: |
983 statuses = {} | 1153 statuses = {} |
984 | 1154 |
985 if None in statuses: # we only want string keys | 1155 if None in statuses: # we only want string keys |
986 statuses[C.PRESENCE_STATUSES_DEFAULT] = statuses.pop(None) | 1156 statuses[C.PRESENCE_STATUSES_DEFAULT] = statuses.pop(None) |
987 | 1157 |
988 if not self.host.trigger.point("presenceReceived", entity, show, priority, statuses, self.parent.profile): | 1158 if not self.host.trigger.point( |
1159 "presenceReceived", entity, show, priority, statuses, self.parent.profile | |
1160 ): | |
989 return | 1161 return |
990 | 1162 |
991 self.host.memory.setPresenceStatus(entity, show or "", | 1163 self.host.memory.setPresenceStatus( |
992 int(priority), statuses, | 1164 entity, show or "", int(priority), statuses, self.parent.profile |
993 self.parent.profile) | 1165 ) |
994 | 1166 |
995 # now it's time to notify frontends | 1167 # now it's time to notify frontends |
996 self.host.bridge.presenceUpdate(entity.full(), show or "", | 1168 self.host.bridge.presenceUpdate( |
997 int(priority), statuses, | 1169 entity.full(), show or "", int(priority), statuses, self.parent.profile |
998 self.parent.profile) | 1170 ) |
999 | 1171 |
1000 def unavailableReceived(self, entity, statuses=None): | 1172 def unavailableReceived(self, entity, statuses=None): |
1001 log.debug(_(u"presence update for [%(entity)s] (unavailable, statuses=%(statuses)s)") % {'entity': entity, C.PRESENCE_STATUSES: statuses}) | 1173 log.debug( |
1174 _(u"presence update for [%(entity)s] (unavailable, statuses=%(statuses)s)") | |
1175 % {"entity": entity, C.PRESENCE_STATUSES: statuses} | |
1176 ) | |
1002 | 1177 |
1003 if not statuses: | 1178 if not statuses: |
1004 statuses = {} | 1179 statuses = {} |
1005 | 1180 |
1006 if None in statuses: # we only want string keys | 1181 if None in statuses: # we only want string keys |
1007 statuses[C.PRESENCE_STATUSES_DEFAULT] = statuses.pop(None) | 1182 statuses[C.PRESENCE_STATUSES_DEFAULT] = statuses.pop(None) |
1008 | 1183 |
1009 if not self.host.trigger.point("presenceReceived", entity, "unavailable", 0, statuses, self.parent.profile): | 1184 if not self.host.trigger.point( |
1185 "presenceReceived", entity, "unavailable", 0, statuses, self.parent.profile | |
1186 ): | |
1010 return | 1187 return |
1011 | 1188 |
1012 # now it's time to notify frontends | 1189 # now it's time to notify frontends |
1013 # if the entity is not known yet in this session or is already unavailable, there is no need to send an unavailable signal | 1190 # if the entity is not known yet in this session or is already unavailable, there is no need to send an unavailable signal |
1014 try: | 1191 try: |
1015 presence = self.host.memory.getEntityDatum(entity, "presence", self.parent.profile) | 1192 presence = self.host.memory.getEntityDatum( |
1193 entity, "presence", self.parent.profile | |
1194 ) | |
1016 except (KeyError, exceptions.UnknownEntityError): | 1195 except (KeyError, exceptions.UnknownEntityError): |
1017 # the entity has not been seen yet in this session | 1196 # the entity has not been seen yet in this session |
1018 pass | 1197 pass |
1019 else: | 1198 else: |
1020 if presence.show != C.PRESENCE_UNAVAILABLE: | 1199 if presence.show != C.PRESENCE_UNAVAILABLE: |
1021 self.host.bridge.presenceUpdate(entity.full(), C.PRESENCE_UNAVAILABLE, 0, statuses, self.parent.profile) | 1200 self.host.bridge.presenceUpdate( |
1022 | 1201 entity.full(), |
1023 self.host.memory.setPresenceStatus(entity, C.PRESENCE_UNAVAILABLE, 0, statuses, self.parent.profile) | 1202 C.PRESENCE_UNAVAILABLE, |
1203 0, | |
1204 statuses, | |
1205 self.parent.profile, | |
1206 ) | |
1207 | |
1208 self.host.memory.setPresenceStatus( | |
1209 entity, C.PRESENCE_UNAVAILABLE, 0, statuses, self.parent.profile | |
1210 ) | |
1024 | 1211 |
1025 def available(self, entity=None, show=None, statuses=None, priority=None): | 1212 def available(self, entity=None, show=None, statuses=None, priority=None): |
1026 """Set a presence and statuses. | 1213 """Set a presence and statuses. |
1027 | 1214 |
1028 @param entity (jid.JID): entity | 1215 @param entity (jid.JID): entity |
1030 @param statuses (dict{unicode: unicode}): multilingual statuses with | 1217 @param statuses (dict{unicode: unicode}): multilingual statuses with |
1031 the entry key beeing a language code on 2 characters or "default". | 1218 the entry key beeing a language code on 2 characters or "default". |
1032 """ | 1219 """ |
1033 if priority is None: | 1220 if priority is None: |
1034 try: | 1221 try: |
1035 priority = int(self.host.memory.getParamA("Priority", "Connection", profile_key=self.parent.profile)) | 1222 priority = int( |
1223 self.host.memory.getParamA( | |
1224 "Priority", "Connection", profile_key=self.parent.profile | |
1225 ) | |
1226 ) | |
1036 except ValueError: | 1227 except ValueError: |
1037 priority = 0 | 1228 priority = 0 |
1038 | 1229 |
1039 if statuses is None: | 1230 if statuses is None: |
1040 statuses = {} | 1231 statuses = {} |
1046 | 1237 |
1047 presence_elt = xmppim.AvailablePresence(entity, show, statuses, priority) | 1238 presence_elt = xmppim.AvailablePresence(entity, show, statuses, priority) |
1048 | 1239 |
1049 # ... before switching back | 1240 # ... before switching back |
1050 if None in statuses: | 1241 if None in statuses: |
1051 statuses['default'] = statuses.pop(None) | 1242 statuses["default"] = statuses.pop(None) |
1052 | 1243 |
1053 if not self.host.trigger.point("presence_available", presence_elt, self.parent): | 1244 if not self.host.trigger.point("presence_available", presence_elt, self.parent): |
1054 return | 1245 return |
1055 self.send(presence_elt) | 1246 self.send(presence_elt) |
1056 | 1247 |
1058 def subscribed(self, entity): | 1249 def subscribed(self, entity): |
1059 yield self.parent.roster.got_roster | 1250 yield self.parent.roster.got_roster |
1060 xmppim.PresenceClientProtocol.subscribed(self, entity) | 1251 xmppim.PresenceClientProtocol.subscribed(self, entity) |
1061 self.host.memory.delWaitingSub(entity.userhost(), self.parent.profile) | 1252 self.host.memory.delWaitingSub(entity.userhost(), self.parent.profile) |
1062 item = self.parent.roster.getItem(entity) | 1253 item = self.parent.roster.getItem(entity) |
1063 if not item or not item.subscriptionTo: # we automatically subscribe to 'to' presence | 1254 if ( |
1255 not item or not item.subscriptionTo | |
1256 ): # we automatically subscribe to 'to' presence | |
1064 log.debug(_('sending automatic "from" subscription request')) | 1257 log.debug(_('sending automatic "from" subscription request')) |
1065 self.subscribe(entity) | 1258 self.subscribe(entity) |
1066 | 1259 |
1067 def unsubscribed(self, entity): | 1260 def unsubscribed(self, entity): |
1068 xmppim.PresenceClientProtocol.unsubscribed(self, entity) | 1261 xmppim.PresenceClientProtocol.unsubscribed(self, entity) |
1069 self.host.memory.delWaitingSub(entity.userhost(), self.parent.profile) | 1262 self.host.memory.delWaitingSub(entity.userhost(), self.parent.profile) |
1070 | 1263 |
1071 def subscribedReceived(self, entity): | 1264 def subscribedReceived(self, entity): |
1072 log.debug(_(u"subscription approved for [%s]") % entity.userhost()) | 1265 log.debug(_(u"subscription approved for [%s]") % entity.userhost()) |
1073 self.host.bridge.subscribe('subscribed', entity.userhost(), self.parent.profile) | 1266 self.host.bridge.subscribe("subscribed", entity.userhost(), self.parent.profile) |
1074 | 1267 |
1075 def unsubscribedReceived(self, entity): | 1268 def unsubscribedReceived(self, entity): |
1076 log.debug(_(u"unsubscription confirmed for [%s]") % entity.userhost()) | 1269 log.debug(_(u"unsubscription confirmed for [%s]") % entity.userhost()) |
1077 self.host.bridge.subscribe('unsubscribed', entity.userhost(), self.parent.profile) | 1270 self.host.bridge.subscribe("unsubscribed", entity.userhost(), self.parent.profile) |
1078 | 1271 |
1079 @defer.inlineCallbacks | 1272 @defer.inlineCallbacks |
1080 def subscribeReceived(self, entity): | 1273 def subscribeReceived(self, entity): |
1081 log.debug(_(u"subscription request from [%s]") % entity.userhost()) | 1274 log.debug(_(u"subscription request from [%s]") % entity.userhost()) |
1082 yield self.parent.roster.got_roster | 1275 yield self.parent.roster.got_roster |
1083 item = self.parent.roster.getItem(entity) | 1276 item = self.parent.roster.getItem(entity) |
1084 if item and item.subscriptionTo: | 1277 if item and item.subscriptionTo: |
1085 # We automatically accept subscription if we are already subscribed to contact presence | 1278 # We automatically accept subscription if we are already subscribed to contact presence |
1086 log.debug(_('sending automatic subscription acceptance')) | 1279 log.debug(_("sending automatic subscription acceptance")) |
1087 self.subscribed(entity) | 1280 self.subscribed(entity) |
1088 else: | 1281 else: |
1089 self.host.memory.addWaitingSub('subscribe', entity.userhost(), self.parent.profile) | 1282 self.host.memory.addWaitingSub( |
1090 self.host.bridge.subscribe('subscribe', entity.userhost(), self.parent.profile) | 1283 "subscribe", entity.userhost(), self.parent.profile |
1284 ) | |
1285 self.host.bridge.subscribe( | |
1286 "subscribe", entity.userhost(), self.parent.profile | |
1287 ) | |
1091 | 1288 |
1092 @defer.inlineCallbacks | 1289 @defer.inlineCallbacks |
1093 def unsubscribeReceived(self, entity): | 1290 def unsubscribeReceived(self, entity): |
1094 log.debug(_(u"unsubscription asked for [%s]") % entity.userhost()) | 1291 log.debug(_(u"unsubscription asked for [%s]") % entity.userhost()) |
1095 yield self.parent.roster.got_roster | 1292 yield self.parent.roster.got_roster |
1096 item = self.parent.roster.getItem(entity) | 1293 item = self.parent.roster.getItem(entity) |
1097 if item and item.subscriptionFrom: # we automatically remove contact | 1294 if item and item.subscriptionFrom: # we automatically remove contact |
1098 log.debug(_('automatic contact deletion')) | 1295 log.debug(_("automatic contact deletion")) |
1099 self.host.delContact(entity, self.parent.profile) | 1296 self.host.delContact(entity, self.parent.profile) |
1100 self.host.bridge.subscribe('unsubscribe', entity.userhost(), self.parent.profile) | 1297 self.host.bridge.subscribe("unsubscribe", entity.userhost(), self.parent.profile) |
1101 | 1298 |
1102 | 1299 |
1103 class SatDiscoProtocol(disco.DiscoClientProtocol): | 1300 class SatDiscoProtocol(disco.DiscoClientProtocol): |
1104 def __init__(self, host): | 1301 def __init__(self, host): |
1105 disco.DiscoClientProtocol.__init__(self) | 1302 disco.DiscoClientProtocol.__init__(self) |
1115 log.debug(u"iqFallback: xml = [%s]" % (iq.toXml())) | 1312 log.debug(u"iqFallback: xml = [%s]" % (iq.toXml())) |
1116 generic.FallbackHandler.iqFallback(self, iq) | 1313 generic.FallbackHandler.iqFallback(self, iq) |
1117 | 1314 |
1118 | 1315 |
1119 class SatVersionHandler(generic.VersionHandler): | 1316 class SatVersionHandler(generic.VersionHandler): |
1120 | |
1121 def getDiscoInfo(self, requestor, target, node): | 1317 def getDiscoInfo(self, requestor, target, node): |
1122 #XXX: We need to work around wokkel's behaviour (namespace not added if there is a | 1318 # XXX: We need to work around wokkel's behaviour (namespace not added if there is a |
1123 # node) as it cause issues with XEP-0115 & PEP (XEP-0163): there is a node when server | 1319 # node) as it cause issues with XEP-0115 & PEP (XEP-0163): there is a node when server |
1124 # ask for disco info, and not when we generate the key, so the hash is used with different | 1320 # ask for disco info, and not when we generate the key, so the hash is used with different |
1125 # disco features, and when the server (seen on ejabberd) generate its own hash for security check | 1321 # disco features, and when the server (seen on ejabberd) generate its own hash for security check |
1126 # it reject our features (resulting in e.g. no notification on PEP) | 1322 # it reject our features (resulting in e.g. no notification on PEP) |
1127 return generic.VersionHandler.getDiscoInfo(self, requestor, target, None) | 1323 return generic.VersionHandler.getDiscoInfo(self, requestor, target, None) |
1129 | 1325 |
1130 class SatIdentityHandler(XMPPHandler): | 1326 class SatIdentityHandler(XMPPHandler): |
1131 """ Manage disco Identity of SàT. | 1327 """ Manage disco Identity of SàT. |
1132 | 1328 |
1133 """ | 1329 """ |
1134 #TODO: dynamic identity update (see docstring). Note that a XMPP entity can have several identities | 1330 |
1331 # TODO: dynamic identity update (see docstring). Note that a XMPP entity can have several identities | |
1135 implements(iwokkel.IDisco) | 1332 implements(iwokkel.IDisco) |
1136 | 1333 |
1137 def getDiscoInfo(self, requestor, target, nodeIdentifier=''): | 1334 def getDiscoInfo(self, requestor, target, nodeIdentifier=""): |
1138 return self.parent.identities | 1335 return self.parent.identities |
1139 | 1336 |
1140 def getDiscoItems(self, requestor, target, nodeIdentifier=''): | 1337 def getDiscoItems(self, requestor, target, nodeIdentifier=""): |
1141 return [] | 1338 return [] |