comparison sat/plugins/plugin_xep_0054.py @ 3816:213e83a4ed10

plugin identity, XEP-0054: move avatar resizing and caching method to identity plugin: resizing and caching is now done in identity plugin, to prepare for the implementation of other XEP to handle avatars. rel 368
author Goffi <goffi@goffi.org>
date Wed, 29 Jun 2022 11:47:48 +0200
parents 36849fb5c854
children 524856bd7b19
comparison
equal deleted inserted replaced
3815:853cbaf56e9e 3816:213e83a4ed10
51 51
52 PLUGIN_INFO = { 52 PLUGIN_INFO = {
53 C.PI_NAME: "XEP 0054 Plugin", 53 C.PI_NAME: "XEP 0054 Plugin",
54 C.PI_IMPORT_NAME: IMPORT_NAME, 54 C.PI_IMPORT_NAME: IMPORT_NAME,
55 C.PI_TYPE: "XEP", 55 C.PI_TYPE: "XEP",
56 C.PI_MODES: C.PLUG_MODE_BOTH,
56 C.PI_PROTOCOLS: ["XEP-0054", "XEP-0153"], 57 C.PI_PROTOCOLS: ["XEP-0054", "XEP-0153"],
57 C.PI_DEPENDENCIES: ["IDENTITY"], 58 C.PI_DEPENDENCIES: ["IDENTITY"],
58 C.PI_RECOMMENDATIONS: [], 59 C.PI_RECOMMENDATIONS: [],
59 C.PI_MAIN: "XEP_0054", 60 C.PI_MAIN: "XEP_0054",
60 C.PI_HANDLER: "yes", 61 C.PI_HANDLER: "yes",
61 C.PI_DESCRIPTION: _("""Implementation of vcard-temp"""), 62 C.PI_DESCRIPTION: _("""Implementation of vcard-temp"""),
62 } 63 }
63
64 AVATAR_PATH = "avatars"
65 AVATAR_DIM = (128, 128)
66 64
67 IQ_GET = '/iq[@type="get"]' 65 IQ_GET = '/iq[@type="get"]'
68 NS_VCARD = "vcard-temp" 66 NS_VCARD = "vcard-temp"
69 VCARD_REQUEST = IQ_GET + '/vCard[@xmlns="' + NS_VCARD + '"]' # TODO: manage requests 67 VCARD_REQUEST = IQ_GET + '/vCard[@xmlns="' + NS_VCARD + '"]' # TODO: manage requests
70 68
316 314
317 avatar_cache = self.host.common_cache.getMetadata(avatar_hash) 315 avatar_cache = self.host.common_cache.getMetadata(avatar_hash)
318 return self._i.avatarBuildMetadata( 316 return self._i.avatarBuildMetadata(
319 avatar_cache['path'], avatar_cache['mime_type'], avatar_hash) 317 avatar_cache['path'], avatar_cache['mime_type'], avatar_hash)
320 318
321 def _buildSetAvatar(self, client, vcard_elt, avatar_data):
322 # XXX: this method is executed in a separate thread
323 if avatar_data["media_type"] == "image/svg+xml":
324 # for vector image, we save directly
325 img_buf = open(avatar_data["path"], "rb")
326 else:
327 # for bitmap image, we check size and resize if necessary
328 try:
329 img = Image.open(avatar_data["path"])
330 except IOError as e:
331 raise exceptions.DataError(f"Can't open image: {e}")
332
333 if img.size != AVATAR_DIM:
334 img.thumbnail(AVATAR_DIM)
335 if img.size[0] != img.size[1]: # we need to crop first
336 left, upper = (0, 0)
337 right, lower = img.size
338 offset = abs(right - lower) / 2
339 if right == min(img.size):
340 upper += offset
341 lower -= offset
342 else:
343 left += offset
344 right -= offset
345 img = img.crop((left, upper, right, lower))
346 img_buf = io.BytesIO()
347 # PNG is well supported among clients, so we convert to this format
348 img.save(img_buf, "PNG")
349 img_buf.seek(0)
350 avatar_data["media_type"] = "image/png"
351
352 media_type = avatar_data["media_type"]
353 photo_elt = vcard_elt.addElement("PHOTO")
354 photo_elt.addElement("TYPE", content=media_type)
355 image_b64 = b64encode(img_buf.read()).decode()
356 img_buf.seek(0)
357 photo_elt.addElement("BINVAL", content=image_b64)
358 image_hash = sha1(img_buf.read()).hexdigest()
359 img_buf.seek(0)
360 with self.host.common_cache.cacheData(
361 PLUGIN_INFO["import_name"], image_hash, media_type
362 ) as f:
363 f.write(img_buf.read())
364 avatar_data['path'] = Path(f.name)
365 avatar_data['filename'] = avatar_data['path'].name
366 avatar_data['cache_uid'] = image_hash
367 return image_hash
368
369 async def setAvatar(self, client, avatar_data, entity): 319 async def setAvatar(self, client, avatar_data, entity):
370 """Set avatar of the profile 320 """Set avatar of the profile
371 321
372 @param avatar_data(dict): data of the image to use as avatar, as built by 322 @param avatar_data(dict): data of the image to use as avatar, as built by
373 IDENTITY plugin. 323 IDENTITY plugin.
375 """ 325 """
376 vcard_elt = await self.updateVCardElt(client, entity, ['PHOTO']) 326 vcard_elt = await self.updateVCardElt(client, entity, ['PHOTO'])
377 327
378 iq_elt = client.IQ() 328 iq_elt = client.IQ()
379 iq_elt.addChild(vcard_elt) 329 iq_elt.addChild(vcard_elt)
380 await threads.deferToThread( 330 # metadata with encoded image are now filled at the right size/format
381 self._buildSetAvatar, client, vcard_elt, avatar_data 331 photo_elt = vcard_elt.addElement("PHOTO")
382 ) 332 photo_elt.addElement("TYPE", content=avatar_data["media_type"])
383 # image is now at the right size/format 333 photo_elt.addElement("BINVAL", content=avatar_data["base64"])
384 334
385 await iq_elt.send() 335 await iq_elt.send()
386 336
387 # FIXME: should send the current presence, not always "available" ! 337 # FIXME: should send the current presence, not always "available" !
388 await client.presence.available() 338 await client.presence.available()