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