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
This commit is contained in:
Goffi 2017-05-21 20:01:24 +02:00
commit 7ab6802b2c

View file

@ -23,7 +23,7 @@ from sat.core.constants import Const as C
from sat.core.log import getLogger
log = getLogger(__name__)
from twisted.internet import threads, defer
from twisted.words.protocols.jabber import jid
from twisted.words.protocols.jabber import jid, error
from twisted.words.xish import domish
from twisted.python.failure import Failure
@ -187,9 +187,9 @@ class XEP_0054(object):
def getCache(self, client, entity_jid, name):
"""return cached value for jid
@param entity_jid: target contact
@param name: name of the value ('nick' or 'avatar')
@return: wanted value or None"""
@param entity_jid(jid.JID): target contact
@param name(unicode): name of the value ('nick' or 'avatar')
@return(unicode, None): wanted value or None"""
entity_jid = self.getBareOrFull(client, entity_jid)
try:
data = self.host.memory.getEntityData(entity_jid, [name], client.profile)
@ -289,15 +289,10 @@ class XEP_0054(object):
defer.returnValue(vcard_dict)
def _vCardCb(self, iq_elt, to_jid, client):
def _vCardCb(self, vcard_elt, to_jid, client):
"""Called after the first get IQ"""
log.debug(_("VCard found"))
try:
vcard_elt = iq_elt.elements(NS_VCARD, "vCard").next()
except StopIteration:
log.warning(u"Can't find vCard element in answer for jid {jid}", jid=to_jid.full())
return
iq_elt = vcard_elt.parent
try:
from_jid = jid.JID(iq_elt["from"])
except KeyError:
@ -310,11 +305,13 @@ class XEP_0054(object):
log.warning(u"Can't get vCard for {jid}: {failure}".format(jid=to_jid.full, failure=failure_))
self.updateCache(client, to_jid, "avatar", None)
def getCard(self, client, entity_jid):
"""Ask server for VCard
def _getVcardElt(self, iq_elt):
return iq_elt.elements(NS_VCARD, "vCard").next()
@param entity_jid(jid.JID): jid from which we want the VCard
@result: id to retrieve the profile
def getCardRaw(self, client, entity_jid):
"""get raw vCard XML
params are as in [getCard]
"""
entity_jid = self.getBareOrFull(client, entity_jid)
log.debug(u"Asking for {}'s VCard".format(entity_jid.full()))
@ -322,7 +319,18 @@ class XEP_0054(object):
reg_request["from"] = client.jid.full()
reg_request["to"] = entity_jid.full()
reg_request.addElement('vCard', NS_VCARD)
d = reg_request.send(entity_jid.full()).addCallbacks(self._vCardCb, self._vCardEb, callbackArgs=[entity_jid, client], errbackArgs=[entity_jid, client])
d = reg_request.send(entity_jid.full())
d.addCallback(self._getVcardElt)
return d
def getCard(self, client, entity_jid):
"""Ask server for VCard
@param entity_jid(jid.JID): jid from which we want the VCard
@result: id to retrieve the profile
"""
d = self.getCardRaw(client, entity_jid)
d.addCallbacks(self._vCardCb, self._vCardEb, callbackArgs=[entity_jid, client], errbackArgs=[entity_jid, client])
return d
def _getCardCb(self, dummy, client, entity):
@ -382,7 +390,47 @@ class XEP_0054(object):
d.addCallback(lambda dummy: full_path)
return d
def _buildSetAvatar(self, client, vcard_set, file_path):
@defer.inlineCallbacks
def getNick(self, client, entity):
"""get nick from cache, or check vCard
@param entity(jid.JID): entity to get nick from
@return(unicode, None): nick or None if not found
"""
nick = self.getCache(client, entity, u'nick')
if nick is not None:
defer.returnValue(nick)
yield self.getCard(client, entity)
defer.returnValue(self.getCache(client, entity, u'nick'))
@defer.inlineCallbacks
def setNick(self, client, nick):
"""update our vCard and set a nickname
@param nick(unicode): new nickname to use
"""
jid_ = client.jid.userhostJID()
try:
vcard_elt = yield self.getCardRaw(client, jid_)
except error.StanzaError as e:
if e.condition == 'item-not-found':
vcard_elt = domish.Element((NS_VCARD, 'vCard'))
else:
raise e
try:
nickname_elt = next(vcard_elt.elements(NS_VCARD, u'NICKNAME'))
except StopIteration:
pass
else:
vcard_elt.children.remove(nickname_elt)
nickname_elt = vcard_elt.addElement((NS_VCARD, u'NICKNAME'), content=nick)
iq_elt = client.IQ()
vcard_elt = iq_elt.addChild(vcard_elt)
yield iq_elt.send()
self.updateCache(client, jid_, u'nick', unicode(nick))
def _buildSetAvatar(self, client, vcard_elt, file_path):
# XXX: this method is executed in a separate thread
try:
img = Image.open(file_path)
@ -405,7 +453,6 @@ class XEP_0054(object):
img_buf = StringIO()
img.save(img_buf, 'PNG')
vcard_elt = vcard_set.addElement('vCard', NS_VCARD)
photo_elt = vcard_elt.addElement('PHOTO')
photo_elt.addElement('TYPE', content='image/png')
photo_elt.addElement('BINVAL', content=b64encode(img_buf.getvalue()))
@ -417,31 +464,43 @@ class XEP_0054(object):
MAX_AGE
) as f:
f.write(img_buf.getvalue())
return vcard_set, image_hash
return image_hash
def _setAvatar(self, file_path, profile_key=C.PROF_KEY_NONE):
client = self.host.getClient(profile_key)
return self.setAvatar(client, file_path)
@defer.inlineCallbacks
def setAvatar(self, client, file_path):
"""Set avatar of the profile
@param file_path: path of the image of the avatar
"""
#TODO: This is a temporary way of setting the avatar, as other VCard information is not managed.
# A proper full VCard management should be done (and more generaly a public/private profile)
vcard_set = client.IQ()
d = threads.deferToThread(self._buildSetAvatar, client, vcard_set, file_path)
try:
# we first check if a vcard already exists, to keep data
vcard_elt = yield self.getCardRaw(client, client.jid.userhostJID())
except error.StanzaError as e:
if e.condition == 'item-not-found':
vcard_elt = domish.Element((NS_VCARD, 'vCard'))
else:
raise e
else:
# the vcard exists, we need to remove PHOTO element as we'll make a new one
try:
photo_elt = next(vcard_elt.elements(NS_VCARD, u'PHOTO'))
except StopIteration:
pass
else:
vcard_elt.children.remove(photo_elt)
def elementBuilt(result):
"""Called once the image is at the right size/format, and the vcard set element is build"""
set_avatar_elt, image_hash = result
self.updateCache(client, client.jid.userhostJID(), 'avatar', image_hash)
return set_avatar_elt.send().addCallback(lambda ignore: client.presence.available()) # FIXME: should send the current presence, not always "available" !
iq_elt = client.IQ()
iq_elt.addChild(vcard_elt)
image_hash = yield threads.deferToThread(self._buildSetAvatar, client, vcard_elt, file_path)
# image is now at the right size/format
d.addCallback(elementBuilt)
return d
self.updateCache(client, client.jid.userhostJID(), 'avatar', image_hash)
yield iq_elt.send()
client.presence.available() # FIXME: should send the current presence, not always "available" !
class XEP_0054_handler(XMPPHandler):