Mercurial > libervia-backend
comparison src/plugins/plugin_xep_0054.py @ 2252:cffa50c9f26b
plugin XEP-0054: nick handling + don't remove data on avatar set
- new getNick and setNick allow to manipulate nickname
- setAvatar was removing any data previously in vCard, this is not the case anymore
author | Goffi <goffi@goffi.org> |
---|---|
date | Sun, 21 May 2017 20:01:24 +0200 |
parents | 33c8c4973743 |
children | 074c2f157dc9 |
comparison
equal
deleted
inserted
replaced
2251:83bcd9ec4782 | 2252:cffa50c9f26b |
---|---|
21 from sat.core.i18n import _ | 21 from sat.core.i18n import _ |
22 from sat.core.constants import Const as C | 22 from sat.core.constants import Const as C |
23 from sat.core.log import getLogger | 23 from sat.core.log import getLogger |
24 log = getLogger(__name__) | 24 log = getLogger(__name__) |
25 from twisted.internet import threads, defer | 25 from twisted.internet import threads, defer |
26 from twisted.words.protocols.jabber import jid | 26 from twisted.words.protocols.jabber import jid, error |
27 from twisted.words.xish import domish | 27 from twisted.words.xish import domish |
28 from twisted.python.failure import Failure | 28 from twisted.python.failure import Failure |
29 | 29 |
30 from zope.interface import implements | 30 from zope.interface import implements |
31 | 31 |
185 client._cache_0054.force(jid_s) | 185 client._cache_0054.force(jid_s) |
186 | 186 |
187 def getCache(self, client, entity_jid, name): | 187 def getCache(self, client, entity_jid, name): |
188 """return cached value for jid | 188 """return cached value for jid |
189 | 189 |
190 @param entity_jid: target contact | 190 @param entity_jid(jid.JID): target contact |
191 @param name: name of the value ('nick' or 'avatar') | 191 @param name(unicode): name of the value ('nick' or 'avatar') |
192 @return: wanted value or None""" | 192 @return(unicode, None): wanted value or None""" |
193 entity_jid = self.getBareOrFull(client, entity_jid) | 193 entity_jid = self.getBareOrFull(client, entity_jid) |
194 try: | 194 try: |
195 data = self.host.memory.getEntityData(entity_jid, [name], client.profile) | 195 data = self.host.memory.getEntityData(entity_jid, [name], client.profile) |
196 except exceptions.UnknownEntityError: | 196 except exceptions.UnknownEntityError: |
197 return None | 197 return None |
287 log.debug(u"reseting vcard datum [{datum}] for {entity}".format(datum=datum, entity=entity_jid.full())) | 287 log.debug(u"reseting vcard datum [{datum}] for {entity}".format(datum=datum, entity=entity_jid.full())) |
288 self.updateCache(client, entity_jid, datum, None) | 288 self.updateCache(client, entity_jid, datum, None) |
289 | 289 |
290 defer.returnValue(vcard_dict) | 290 defer.returnValue(vcard_dict) |
291 | 291 |
292 def _vCardCb(self, iq_elt, to_jid, client): | 292 def _vCardCb(self, vcard_elt, to_jid, client): |
293 """Called after the first get IQ""" | 293 """Called after the first get IQ""" |
294 log.debug(_("VCard found")) | 294 log.debug(_("VCard found")) |
295 | 295 iq_elt = vcard_elt.parent |
296 try: | |
297 vcard_elt = iq_elt.elements(NS_VCARD, "vCard").next() | |
298 except StopIteration: | |
299 log.warning(u"Can't find vCard element in answer for jid {jid}", jid=to_jid.full()) | |
300 return | |
301 try: | 296 try: |
302 from_jid = jid.JID(iq_elt["from"]) | 297 from_jid = jid.JID(iq_elt["from"]) |
303 except KeyError: | 298 except KeyError: |
304 from_jid = client.jid.userhostJID() | 299 from_jid = client.jid.userhostJID() |
305 d = self.vCard2Dict(client, vcard_elt, from_jid) | 300 d = self.vCard2Dict(client, vcard_elt, from_jid) |
308 def _vCardEb(self, failure_, to_jid, client): | 303 def _vCardEb(self, failure_, to_jid, client): |
309 """Called when something is wrong with registration""" | 304 """Called when something is wrong with registration""" |
310 log.warning(u"Can't get vCard for {jid}: {failure}".format(jid=to_jid.full, failure=failure_)) | 305 log.warning(u"Can't get vCard for {jid}: {failure}".format(jid=to_jid.full, failure=failure_)) |
311 self.updateCache(client, to_jid, "avatar", None) | 306 self.updateCache(client, to_jid, "avatar", None) |
312 | 307 |
313 def getCard(self, client, entity_jid): | 308 def _getVcardElt(self, iq_elt): |
314 """Ask server for VCard | 309 return iq_elt.elements(NS_VCARD, "vCard").next() |
315 | 310 |
316 @param entity_jid(jid.JID): jid from which we want the VCard | 311 def getCardRaw(self, client, entity_jid): |
317 @result: id to retrieve the profile | 312 """get raw vCard XML |
313 | |
314 params are as in [getCard] | |
318 """ | 315 """ |
319 entity_jid = self.getBareOrFull(client, entity_jid) | 316 entity_jid = self.getBareOrFull(client, entity_jid) |
320 log.debug(u"Asking for {}'s VCard".format(entity_jid.full())) | 317 log.debug(u"Asking for {}'s VCard".format(entity_jid.full())) |
321 reg_request = client.IQ('get') | 318 reg_request = client.IQ('get') |
322 reg_request["from"] = client.jid.full() | 319 reg_request["from"] = client.jid.full() |
323 reg_request["to"] = entity_jid.full() | 320 reg_request["to"] = entity_jid.full() |
324 reg_request.addElement('vCard', NS_VCARD) | 321 reg_request.addElement('vCard', NS_VCARD) |
325 d = reg_request.send(entity_jid.full()).addCallbacks(self._vCardCb, self._vCardEb, callbackArgs=[entity_jid, client], errbackArgs=[entity_jid, client]) | 322 d = reg_request.send(entity_jid.full()) |
323 d.addCallback(self._getVcardElt) | |
324 return d | |
325 | |
326 def getCard(self, client, entity_jid): | |
327 """Ask server for VCard | |
328 | |
329 @param entity_jid(jid.JID): jid from which we want the VCard | |
330 @result: id to retrieve the profile | |
331 """ | |
332 d = self.getCardRaw(client, entity_jid) | |
333 d.addCallbacks(self._vCardCb, self._vCardEb, callbackArgs=[entity_jid, client], errbackArgs=[entity_jid, client]) | |
326 return d | 334 return d |
327 | 335 |
328 def _getCardCb(self, dummy, client, entity): | 336 def _getCardCb(self, dummy, client, entity): |
329 try: | 337 try: |
330 return client._cache_0054[entity.full()]['avatar'] | 338 return client._cache_0054[entity.full()]['avatar'] |
380 d.addCallback(client.cache.getFilePath) | 388 d.addCallback(client.cache.getFilePath) |
381 else: | 389 else: |
382 d.addCallback(lambda dummy: full_path) | 390 d.addCallback(lambda dummy: full_path) |
383 return d | 391 return d |
384 | 392 |
385 def _buildSetAvatar(self, client, vcard_set, file_path): | 393 @defer.inlineCallbacks |
394 def getNick(self, client, entity): | |
395 """get nick from cache, or check vCard | |
396 | |
397 @param entity(jid.JID): entity to get nick from | |
398 @return(unicode, None): nick or None if not found | |
399 """ | |
400 nick = self.getCache(client, entity, u'nick') | |
401 if nick is not None: | |
402 defer.returnValue(nick) | |
403 yield self.getCard(client, entity) | |
404 defer.returnValue(self.getCache(client, entity, u'nick')) | |
405 | |
406 @defer.inlineCallbacks | |
407 def setNick(self, client, nick): | |
408 """update our vCard and set a nickname | |
409 | |
410 @param nick(unicode): new nickname to use | |
411 """ | |
412 jid_ = client.jid.userhostJID() | |
413 try: | |
414 vcard_elt = yield self.getCardRaw(client, jid_) | |
415 except error.StanzaError as e: | |
416 if e.condition == 'item-not-found': | |
417 vcard_elt = domish.Element((NS_VCARD, 'vCard')) | |
418 else: | |
419 raise e | |
420 try: | |
421 nickname_elt = next(vcard_elt.elements(NS_VCARD, u'NICKNAME')) | |
422 except StopIteration: | |
423 pass | |
424 else: | |
425 vcard_elt.children.remove(nickname_elt) | |
426 | |
427 nickname_elt = vcard_elt.addElement((NS_VCARD, u'NICKNAME'), content=nick) | |
428 iq_elt = client.IQ() | |
429 vcard_elt = iq_elt.addChild(vcard_elt) | |
430 yield iq_elt.send() | |
431 self.updateCache(client, jid_, u'nick', unicode(nick)) | |
432 | |
433 def _buildSetAvatar(self, client, vcard_elt, file_path): | |
386 # XXX: this method is executed in a separate thread | 434 # XXX: this method is executed in a separate thread |
387 try: | 435 try: |
388 img = Image.open(file_path) | 436 img = Image.open(file_path) |
389 except IOError: | 437 except IOError: |
390 return Failure(exceptions.DataError(u"Can't open image")) | 438 return Failure(exceptions.DataError(u"Can't open image")) |
403 right -= offset | 451 right -= offset |
404 img = img.crop((left, upper, right, lower)) | 452 img = img.crop((left, upper, right, lower)) |
405 img_buf = StringIO() | 453 img_buf = StringIO() |
406 img.save(img_buf, 'PNG') | 454 img.save(img_buf, 'PNG') |
407 | 455 |
408 vcard_elt = vcard_set.addElement('vCard', NS_VCARD) | |
409 photo_elt = vcard_elt.addElement('PHOTO') | 456 photo_elt = vcard_elt.addElement('PHOTO') |
410 photo_elt.addElement('TYPE', content='image/png') | 457 photo_elt.addElement('TYPE', content='image/png') |
411 photo_elt.addElement('BINVAL', content=b64encode(img_buf.getvalue())) | 458 photo_elt.addElement('BINVAL', content=b64encode(img_buf.getvalue())) |
412 image_hash = sha1(img_buf.getvalue()).hexdigest() | 459 image_hash = sha1(img_buf.getvalue()).hexdigest() |
413 with client.cache.cacheData( | 460 with client.cache.cacheData( |
415 image_hash, | 462 image_hash, |
416 "image/png", | 463 "image/png", |
417 MAX_AGE | 464 MAX_AGE |
418 ) as f: | 465 ) as f: |
419 f.write(img_buf.getvalue()) | 466 f.write(img_buf.getvalue()) |
420 return vcard_set, image_hash | 467 return image_hash |
421 | 468 |
422 def _setAvatar(self, file_path, profile_key=C.PROF_KEY_NONE): | 469 def _setAvatar(self, file_path, profile_key=C.PROF_KEY_NONE): |
423 client = self.host.getClient(profile_key) | 470 client = self.host.getClient(profile_key) |
424 return self.setAvatar(client, file_path) | 471 return self.setAvatar(client, file_path) |
425 | 472 |
473 @defer.inlineCallbacks | |
426 def setAvatar(self, client, file_path): | 474 def setAvatar(self, client, file_path): |
427 """Set avatar of the profile | 475 """Set avatar of the profile |
428 | 476 |
429 @param file_path: path of the image of the avatar | 477 @param file_path: path of the image of the avatar |
430 """ | 478 """ |
431 #TODO: This is a temporary way of setting the avatar, as other VCard information is not managed. | 479 try: |
432 # A proper full VCard management should be done (and more generaly a public/private profile) | 480 # we first check if a vcard already exists, to keep data |
433 vcard_set = client.IQ() | 481 vcard_elt = yield self.getCardRaw(client, client.jid.userhostJID()) |
434 d = threads.deferToThread(self._buildSetAvatar, client, vcard_set, file_path) | 482 except error.StanzaError as e: |
435 | 483 if e.condition == 'item-not-found': |
436 def elementBuilt(result): | 484 vcard_elt = domish.Element((NS_VCARD, 'vCard')) |
437 """Called once the image is at the right size/format, and the vcard set element is build""" | 485 else: |
438 set_avatar_elt, image_hash = result | 486 raise e |
439 self.updateCache(client, client.jid.userhostJID(), 'avatar', image_hash) | 487 else: |
440 return set_avatar_elt.send().addCallback(lambda ignore: client.presence.available()) # FIXME: should send the current presence, not always "available" ! | 488 # the vcard exists, we need to remove PHOTO element as we'll make a new one |
441 | 489 try: |
442 d.addCallback(elementBuilt) | 490 photo_elt = next(vcard_elt.elements(NS_VCARD, u'PHOTO')) |
443 | 491 except StopIteration: |
444 return d | 492 pass |
493 else: | |
494 vcard_elt.children.remove(photo_elt) | |
495 | |
496 iq_elt = client.IQ() | |
497 iq_elt.addChild(vcard_elt) | |
498 image_hash = yield threads.deferToThread(self._buildSetAvatar, client, vcard_elt, file_path) | |
499 # image is now at the right size/format | |
500 | |
501 self.updateCache(client, client.jid.userhostJID(), 'avatar', image_hash) | |
502 yield iq_elt.send() | |
503 client.presence.available() # FIXME: should send the current presence, not always "available" ! | |
445 | 504 |
446 | 505 |
447 class XEP_0054_handler(XMPPHandler): | 506 class XEP_0054_handler(XMPPHandler): |
448 implements(iwokkel.IDisco) | 507 implements(iwokkel.IDisco) |
449 | 508 |