comparison sat/plugins/plugin_xep_0054.py @ 3709:09f5ac48ffe3

merge bookmark @
author Goffi <goffi@goffi.org>
date Fri, 12 Nov 2021 17:21:24 +0100
parents 36849fb5c854
children 213e83a4ed10
comparison
equal deleted inserted replaced
3684:8353cc3b8db9 3709:09f5ac48ffe3
43 except: 43 except:
44 raise exceptions.MissingModule( 44 raise exceptions.MissingModule(
45 "Missing module pillow, please download/install it from https://python-pillow.github.io" 45 "Missing module pillow, please download/install it from https://python-pillow.github.io"
46 ) 46 )
47 47
48 try: 48 from twisted.words.protocols.jabber.xmlstream import XMPPHandler
49 from twisted.words.protocols.xmlstream import XMPPHandler
50 except ImportError:
51 from wokkel.subprotocols import XMPPHandler
52 49
53 IMPORT_NAME = "XEP-0054" 50 IMPORT_NAME = "XEP-0054"
54 51
55 PLUGIN_INFO = { 52 PLUGIN_INFO = {
56 C.PI_NAME: "XEP 0054 Plugin", 53 C.PI_NAME: "XEP 0054 Plugin",
107 async def profileConnecting(self, client): 104 async def profileConnecting(self, client):
108 client._xep_0054_avatar_hashes = persistent.PersistentDict( 105 client._xep_0054_avatar_hashes = persistent.PersistentDict(
109 NS_VCARD, client.profile) 106 NS_VCARD, client.profile)
110 await client._xep_0054_avatar_hashes.load() 107 await client._xep_0054_avatar_hashes.load()
111 108
112 def savePhoto(self, client, photo_elt, entity_jid): 109 def savePhoto(self, client, photo_elt, entity):
113 """Parse a <PHOTO> photo_elt and save the picture""" 110 """Parse a <PHOTO> photo_elt and save the picture"""
114 # XXX: this method is launched in a separate thread 111 # XXX: this method is launched in a separate thread
115 try: 112 try:
116 mime_type = str(next(photo_elt.elements(NS_VCARD, "TYPE"))) 113 mime_type = str(next(photo_elt.elements(NS_VCARD, "TYPE")))
117 except StopIteration: 114 except StopIteration:
129 except StopIteration: 126 except StopIteration:
130 log.warning("BINVAL element not found") 127 log.warning("BINVAL element not found")
131 raise Failure(exceptions.NotFound()) 128 raise Failure(exceptions.NotFound())
132 129
133 if not buf: 130 if not buf:
134 log.warning("empty avatar for {jid}".format(jid=entity_jid.full())) 131 log.warning("empty avatar for {jid}".format(jid=entity.full()))
135 raise Failure(exceptions.NotFound()) 132 raise Failure(exceptions.NotFound())
136 133
137 log.debug(_("Decoding binary")) 134 log.debug(_("Decoding binary"))
138 decoded = b64decode(buf) 135 decoded = b64decode(buf)
139 del buf 136 del buf
140 137
141 if mime_type is None: 138 if mime_type is None:
142 log.debug( 139 log.debug(
143 f"no media type found specified for {entity_jid}'s avatar, trying to " 140 f"no media type found specified for {entity}'s avatar, trying to "
144 f"guess") 141 f"guess")
145 142
146 try: 143 try:
147 mime_type = image.guess_type(io.BytesIO(decoded)) 144 mime_type = image.guess_type(io.BytesIO(decoded))
148 except IOError as e: 145 except IOError as e:
149 log.warning(f"Can't open avatar buffer: {e}") 146 log.warning(f"Can't open avatar buffer: {e}")
150 147
151 if mime_type is None: 148 if mime_type is None:
152 msg = f"Can't find media type for {entity_jid}'s avatar" 149 msg = f"Can't find media type for {entity}'s avatar"
153 log.warning(msg) 150 log.warning(msg)
154 raise Failure(exceptions.DataError(msg)) 151 raise Failure(exceptions.DataError(msg))
155 152
156 image_hash = sha1(decoded).hexdigest() 153 image_hash = sha1(decoded).hexdigest()
157 with self.host.common_cache.cacheData( 154 with self.host.common_cache.cacheData(
433 return [disco.DiscoFeature(NS_VCARD)] 430 return [disco.DiscoFeature(NS_VCARD)]
434 431
435 def getDiscoItems(self, requestor, target, nodeIdentifier=""): 432 def getDiscoItems(self, requestor, target, nodeIdentifier=""):
436 return [] 433 return []
437 434
438 def _checkAvatarHash(self, client, entity, given_hash):
439 """Check that hash in cache (i.e. computed hash) is the same as given one"""
440 # XXX: if they differ, the avatar will be requested on each connection
441 # TODO: try to avoid re-requesting avatar in this case
442 computed_hash = client._xep_0054_avatar_hashes[entity.full()]
443 if computed_hash != given_hash:
444 log.warning(
445 "computed hash differs from given hash for {entity}:\n"
446 "computed: {computed}\ngiven: {given}".format(
447 entity=entity, computed=computed_hash, given=given_hash
448 )
449 )
450
451 async def update(self, presence): 435 async def update(self, presence):
452 """Called on <presence/> stanza with vcard data 436 """Called on <presence/> stanza with vcard data
453 437
454 Check for avatar information, and get VCard if needed 438 Check for avatar information, and get VCard if needed
455 @param presence(domish.Element): <presence/> stanza 439 @param presence(domish.Element): <presence/> stanza
466 try: 450 try:
467 photo_elt = next(x_elt.elements(NS_VCARD_UPDATE, "photo")) 451 photo_elt = next(x_elt.elements(NS_VCARD_UPDATE, "photo"))
468 except StopIteration: 452 except StopIteration:
469 return 453 return
470 454
471 new_hash = str(photo_elt).strip() 455 given_hash = str(photo_elt).strip()
472 if new_hash == HASH_SHA1_EMPTY: 456 if given_hash == HASH_SHA1_EMPTY:
473 new_hash = "" 457 given_hash = ""
474 458
475 hashes_cache = client._xep_0054_avatar_hashes 459 hashes_cache = client._xep_0054_avatar_hashes
476 460
477 old_hash = hashes_cache.get(entity_jid.full()) 461 old_hash = hashes_cache.get(entity_jid.full())
478 462
479 if old_hash == new_hash: 463 if old_hash == given_hash:
480 # no change, we can return… 464 # no change, we can return…
481 if new_hash: 465 if given_hash:
482 # …but we double check that avatar is in cache 466 # …but we double check that avatar is in cache
483 avatar_cache = self.host.common_cache.getMetadata(new_hash) 467 avatar_cache = self.host.common_cache.getMetadata(given_hash)
484 if avatar_cache is None: 468 if avatar_cache is None:
485 log.debug( 469 log.debug(
486 f"Avatar for [{entity_jid}] is known but not in cache, we get " 470 f"Avatar for [{entity_jid}] is known but not in cache, we get "
487 f"it" 471 f"it"
488 ) 472 )
490 await self.plugin_parent.getCard(client, entity_jid) 474 await self.plugin_parent.getCard(client, entity_jid)
491 else: 475 else:
492 log.debug(f"avatar for {entity_jid} is already in cache") 476 log.debug(f"avatar for {entity_jid} is already in cache")
493 return 477 return
494 478
495 if new_hash is None: 479 if given_hash is None:
496 # XXX: we use empty string to indicate that there is no avatar 480 # XXX: we use empty string to indicate that there is no avatar
497 new_hash = "" 481 given_hash = ""
498 482
499 await hashes_cache.aset(entity_jid.full(), new_hash) 483 await hashes_cache.aset(entity_jid.full(), given_hash)
500 484
501 if not new_hash: 485 if not given_hash:
502 await self.plugin_parent._i.update( 486 await self.plugin_parent._i.update(
503 client, IMPORT_NAME, "avatar", None, entity_jid) 487 client, IMPORT_NAME, "avatar", None, entity_jid)
504 # the avatar has been removed, no need to go further 488 # the avatar has been removed, no need to go further
505 return 489 return
506 490
507 avatar_cache = self.host.common_cache.getMetadata(new_hash) 491 avatar_cache = self.host.common_cache.getMetadata(given_hash)
508 if avatar_cache is not None: 492 if avatar_cache is not None:
509 log.debug( 493 log.debug(
510 f"New avatar found for [{entity_jid}], it's already in cache, we use it" 494 f"New avatar found for [{entity_jid}], it's already in cache, we use it"
511 ) 495 )
512 await self.plugin_parent._i.update( 496 await self.plugin_parent._i.update(
514 IMPORT_NAME, "avatar", 498 IMPORT_NAME, "avatar",
515 { 499 {
516 'path': avatar_cache['path'], 500 'path': avatar_cache['path'],
517 'filename': avatar_cache['filename'], 501 'filename': avatar_cache['filename'],
518 'media_type': avatar_cache['mime_type'], 502 'media_type': avatar_cache['mime_type'],
519 'cache_uid': new_hash, 503 'cache_uid': given_hash,
520 }, 504 },
521 entity_jid 505 entity_jid
522 ) 506 )
523 else: 507 else:
524 log.debug( 508 log.debug(
526 ) 510 )
527 vcard = await self.plugin_parent.getCard(client, entity_jid) 511 vcard = await self.plugin_parent.getCard(client, entity_jid)
528 if vcard is None: 512 if vcard is None:
529 log.warning(f"Unexpected empty vCard for {entity_jid}") 513 log.warning(f"Unexpected empty vCard for {entity_jid}")
530 return 514 return
531 await self._checkAvatarHash(client, entity_jid, new_hash) 515 computed_hash = client._xep_0054_avatar_hashes[entity_jid.full()]
516 if computed_hash != given_hash:
517 log.warning(
518 "computed hash differs from given hash for {entity}:\n"
519 "computed: {computed}\ngiven: {given}".format(
520 entity=entity_jid, computed=computed_hash, given=given_hash
521 )
522 )
532 523
533 def _update(self, presence): 524 def _update(self, presence):
534 defer.ensureDeferred(self.update(presence)) 525 defer.ensureDeferred(self.update(presence))