# This file is part of Gajim. # # Gajim is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published # by the Free Software Foundation; version 3 only. # # Gajim is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Gajim. If not, see . # XEP-0054: vcard-temp import hashlib import binascii import base64 import nbxmpp from gajim.common import app from gajim.common.const import RequestAvatar from gajim.common.nec import NetworkEvent from gajim.common.nec import NetworkIncomingEvent from gajim.common.modules.base import BaseModule from gajim.common.connection_handlers_events import InformationEvent class VCardTemp(BaseModule): def __init__(self, con): BaseModule.__init__(self, con) self._own_vcard = None self.own_vcard_received = False self.room_jids = [] self.supported = False def pass_disco(self, from_, _identities, features, _data, _node): if nbxmpp.NS_VCARD not in features: return self.supported = True self._log.info('Discovered vcard-temp: %s', from_) app.nec.push_incoming_event(NetworkEvent('feature-discovered', account=self._account, feature=nbxmpp.NS_VCARD)) @staticmethod def _node_to_dict(node): dict_ = {} for info in node.getChildren(): name = info.getName() if name in ('ADR', 'TEL', 'EMAIL'): # we can have several dict_.setdefault(name, []) entry = {} for child in info.getChildren(): entry[child.getName()] = child.getData() dict_[name].append(entry) elif info.getChildren() == []: dict_[name] = info.getData() else: dict_[name] = {} for child in info.getChildren(): dict_[name][child.getName()] = child.getData() return dict_ def request_vcard(self, callback=RequestAvatar.SELF, jid=None, room=False, sha=None): if not app.account_is_connected(self._account): return if isinstance(callback, RequestAvatar): if callback == RequestAvatar.SELF: if not self.supported: return callback = self._on_own_avatar_received elif callback == RequestAvatar.ROOM: callback = self._on_room_avatar_received elif callback == RequestAvatar.USER: callback = self._on_avatar_received if room: room_jid = app.get_room_from_fjid(jid) if room_jid not in self.room_jids: self.room_jids.append(room_jid) iq = nbxmpp.Iq(typ='get') if jid: iq.setTo(jid) iq.setQuery('vCard').setNamespace(nbxmpp.NS_VCARD) own_jid = self._con.get_own_jid().getStripped() self._log.info('Request: %s, expected sha: %s', jid or own_jid, sha) self._con.connection.SendAndCallForResponse( iq, self._parse_vcard, {'callback': callback, 'expected_sha': sha}) def send_vcard(self, vcard, sha): if not app.account_is_connected(self._account): return iq = nbxmpp.Iq(typ='set') iq2 = iq.setTag(nbxmpp.NS_VCARD + ' vCard') for i in vcard: if i == 'jid': continue if isinstance(vcard[i], dict): iq3 = iq2.addChild(i) for j in vcard[i]: iq3.addChild(j).setData(vcard[i][j]) elif isinstance(vcard[i], list): for j in vcard[i]: iq3 = iq2.addChild(i) for k in j: iq3.addChild(k).setData(j[k]) else: iq2.addChild(i).setData(vcard[i]) self._log.info('Upload avatar: %s %s', self._account, sha) self._con.connection.SendAndCallForResponse( iq, self._avatar_publish_result, {'sha': sha}) def upload_room_avatar(self, room_jid, data): iq = nbxmpp.Iq(typ='set', to=room_jid) vcard = iq.addChild('vCard', namespace=nbxmpp.NS_VCARD) photo = vcard.addChild('PHOTO') photo.addChild('TYPE', payload='image/png') photo.addChild('BINVAL', payload=data) self._log.info('Upload avatar: %s', room_jid) self._con.connection.SendAndCallForResponse( iq, self._upload_room_avatar_result) @staticmethod def _upload_room_avatar_result(stanza): if not nbxmpp.isResultNode(stanza): reason = stanza.getErrorMsg() or stanza.getError() app.nec.push_incoming_event(InformationEvent( None, dialog_name='avatar-upload-error', args=reason)) def _avatar_publish_result(self, _con, stanza, sha): if stanza.getType() == 'result': current_sha = app.config.get_per( 'accounts', self._account, 'avatar_sha') if (current_sha != sha and not app.is_invisible(self._account)): if not app.account_is_connected(self._account): return app.config.set_per( 'accounts', self._account, 'avatar_sha', sha or '') own_jid = self._con.get_own_jid().getStripped() app.contacts.set_avatar(self._account, own_jid, sha) app.interface.update_avatar( self._account, self._con.get_own_jid().getStripped()) self._con.get_module('VCardAvatars').send_avatar_presence( after_publish=True) self._log.info('%s: Published: %s', self._account, sha) app.nec.push_incoming_event( VcardPublishedEvent(None, conn=self._con)) elif stanza.getType() == 'error': app.nec.push_incoming_event( VcardNotPublishedEvent(None, conn=self._con)) def _get_vcard_photo(self, vcard, jid): try: photo = vcard['PHOTO']['BINVAL'] except (KeyError, AttributeError, TypeError): avatar_sha = None photo_decoded = None else: if photo == '': avatar_sha = None photo_decoded = None else: try: photo_decoded = base64.b64decode(photo.encode('utf-8')) except binascii.Error as error: self._log.warning('Invalid avatar for %s: %s', jid, error) return None, None avatar_sha = hashlib.sha1(photo_decoded).hexdigest() return avatar_sha, photo_decoded def _parse_vcard(self, _con, stanza, callback, expected_sha): frm_jid = stanza.getFrom() room = False if frm_jid is None: frm_jid = self._con.get_own_jid() elif frm_jid.getStripped() in self.room_jids: room = True resource = frm_jid.getResource() jid = frm_jid.getStripped() stanza_error = stanza.getError() if stanza_error in ('service-unavailable', 'item-not-found', 'not-allowed'): self._log.info('vCard not available: %s %s', frm_jid, stanza_error) callback(jid, resource, room, {}, expected_sha) return vcard_node = stanza.getTag('vCard', namespace=nbxmpp.NS_VCARD) if vcard_node is None: self._log.info('vCard not available: %s', frm_jid) self._log.debug(stanza) return vcard = self._node_to_dict(vcard_node) if self._con.get_own_jid().bareMatch(jid): if 'NICKNAME' in vcard: app.nicks[self._account] = vcard['NICKNAME'] elif 'FN' in vcard: app.nicks[self._account] = vcard['FN'] app.nec.push_incoming_event( VcardReceivedEvent(None, conn=self._con, vcard_dict=vcard, jid=jid)) callback(jid, resource, room, vcard, expected_sha) def _on_own_avatar_received(self, jid, _resource, _room, vcard, *args): avatar_sha, photo_decoded = self._get_vcard_photo(vcard, jid) self._log.info('Received own vcard, avatar sha is: %s', avatar_sha) self._own_vcard = vcard self.own_vcard_received = True if avatar_sha is None: # No avatar found in vcard self._log.info('No avatar found') app.config.set_per('accounts', self._account, 'avatar_sha', '') app.contacts.set_avatar(self._account, jid, avatar_sha) self._con.get_module('VCardAvatars').send_avatar_presence( force=True) return # Avatar found in vcard current_sha = app.config.get_per( 'accounts', self._account, 'avatar_sha') if current_sha == avatar_sha: self._con.get_module('VCardAvatars').send_avatar_presence() else: app.interface.save_avatar(photo_decoded) app.contacts.set_avatar(self._account, jid, avatar_sha) app.config.set_per( 'accounts', self._account, 'avatar_sha', avatar_sha) self._con.get_module('VCardAvatars').send_avatar_presence( force=True) app.interface.update_avatar(self._account, jid) def _on_room_avatar_received(self, jid, _resource, _room, vcard, expected_sha): avatar_sha, photo_decoded = self._get_vcard_photo(vcard, jid) if expected_sha != avatar_sha: self._log.warning('Avatar mismatch: %s %s', jid, avatar_sha) return app.interface.save_avatar(photo_decoded) self._log.info('Received: %s %s', jid, avatar_sha) app.contacts.set_avatar(self._account, jid, avatar_sha) app.interface.update_avatar(self._account, jid, room_avatar=True) def _on_avatar_received(self, jid, resource, room, vcard, expected_sha): request_jid = jid if room: request_jid = '%s/%s' % (jid, resource) avatar_sha, photo_decoded = self._get_vcard_photo(vcard, request_jid) if expected_sha != avatar_sha: self._log.warning('Received: avatar mismatch: %s %s', request_jid, avatar_sha) return app.interface.save_avatar(photo_decoded) # Received vCard from a contact if room: self._log.info('Received: %s %s', resource, avatar_sha) contact = app.contacts.get_gc_contact(self._account, jid, resource) if contact is not None: contact.avatar_sha = avatar_sha app.interface.update_avatar(contact=contact) else: self._log.info('Received: %s %s', jid, avatar_sha) own_jid = self._con.get_own_jid().getStripped() app.logger.set_avatar_sha(own_jid, jid, avatar_sha) app.contacts.set_avatar(self._account, jid, avatar_sha) app.interface.update_avatar(self._account, jid) def get_vard_name(self): name = '' vcard = self._own_vcard if not vcard: return name if 'N' in vcard: if 'GIVEN' in vcard['N'] and 'FAMILY' in vcard['N']: name = vcard['N']['GIVEN'] + ' ' + vcard['N']['FAMILY'] if not name and 'FN' in vcard: name = vcard['FN'] return name class VcardPublishedEvent(NetworkIncomingEvent): name = 'vcard-published' class VcardNotPublishedEvent(NetworkIncomingEvent): name = 'vcard-not-published' class VcardReceivedEvent(NetworkIncomingEvent): name = 'vcard-received' def get_instance(*args, **kwargs): return VCardTemp(*args, **kwargs), 'VCardTemp'