# 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-0153: vCard-Based Avatars import os from pathlib import Path import nbxmpp from nbxmpp.structs import StanzaHandler from nbxmpp.const import AvatarState from gajim.common import app from gajim.common import helpers from gajim.common import configpaths from gajim.common.const import RequestAvatar from gajim.common.modules.base import BaseModule class VCardAvatars(BaseModule): def __init__(self, con): BaseModule.__init__(self, con) self._requested_shas = [] self.handlers = [ StanzaHandler(name='presence', callback=self._presence_received, ns=nbxmpp.NS_VCARD_UPDATE, priority=51), ] self.avatar_advertised = False self._find_own_avatar() def _find_own_avatar(self): sha = app.config.get_per('accounts', self._account, 'avatar_sha') if not sha: return path = Path(configpaths.get('AVATAR')) / sha if not path.exists(): self._log.info('Missing own avatar, reset sha') app.config.set_per('accounts', self._account, 'avatar_sha', '') def _presence_received(self, _con, _stanza, properties): if not properties.type.is_available: return if properties.avatar_state in (AvatarState.IGNORE, AvatarState.NOT_READY): return if self._con.get_own_jid().bareMatch(properties.jid): if self._con.get_own_jid() == properties.jid: # Initial presence reflection if self._con.avatar_conversion: # XEP-0398: Tells us the current avatar sha on the # inital presence reflection self._self_update_received(properties) else: # Presence from another resource of ours self._self_update_received(properties) return if properties.from_muc: self._gc_update_received(properties) else: # Check if presence is from a MUC service contact = app.contacts.get_groupchat_contact(self._account, str(properties.jid)) self._update_received(properties, room=contact is not None) def _self_update_received(self, properties): jid = properties.jid.getBare() if properties.avatar_state == AvatarState.EMPTY: # Empty tag, means no avatar is advertised self._log.info('%s has no avatar published', properties.jid) app.config.set_per('accounts', self._account, 'avatar_sha', '') app.contacts.set_avatar(self._account, jid, None) app.interface.update_avatar(self._account, jid) return self._log.info('Update: %s %s', jid, properties.avatar_sha) current_sha = app.config.get_per( 'accounts', self._account, 'avatar_sha') if properties.avatar_sha != current_sha: path = Path(configpaths.get('AVATAR')) / properties.avatar_sha if path.exists(): app.config.set_per('accounts', self._account, 'avatar_sha', properties.avatar_sha) app.contacts.set_avatar(self._account, jid, properties.avatar_sha) app.interface.update_avatar(self._account, jid) else: self._log.info('Request : %s', jid) self._con.get_module('VCardTemp').request_vcard( RequestAvatar.SELF) else: self._log.info('Avatar already known: %s %s', jid, properties.avatar_sha) def _update_received(self, properties, room=False): jid = properties.jid.getBare() if properties.avatar_state == AvatarState.EMPTY: # Empty tag, means no avatar is advertised self._log.info('%s has no avatar published', properties.jid) # Remove avatar self._log.debug('Remove: %s', jid) app.contacts.set_avatar(self._account, jid, None) acc_jid = self._con.get_own_jid().getStripped() if not room: app.logger.set_avatar_sha(acc_jid, jid, None) app.interface.update_avatar( self._account, jid, room_avatar=room) else: self._log.info('Update: %s %s', properties.jid, properties.avatar_sha) current_sha = app.contacts.get_avatar_sha(self._account, jid) if properties.avatar_sha == current_sha: self._log.info('Avatar already known: %s %s', jid, properties.avatar_sha) return if room: # We dont save the room avatar hash in our DB, so check # if we previously downloaded it if app.interface.avatar_exists(properties.avatar_sha): app.contacts.set_avatar(self._account, jid, properties.avatar_sha) app.interface.update_avatar( self._account, jid, room_avatar=room) return if properties.avatar_sha not in self._requested_shas: self._requested_shas.append(properties.avatar_sha) if room: self._con.get_module('VCardTemp').request_vcard( RequestAvatar.ROOM, jid, sha=properties.avatar_sha) else: self._con.get_module('VCardTemp').request_vcard( RequestAvatar.USER, jid, sha=properties.avatar_sha) def _gc_update_received(self, properties): nick = properties.jid.getResource() gc_contact = app.contacts.get_gc_contact( self._account, properties.jid.getBare(), nick) if gc_contact is None: self._log.error('no gc contact found: %s', nick) return if properties.avatar_state == AvatarState.EMPTY: # Empty tag, means no avatar is advertised, remove avatar self._log.info('%s has no avatar published', nick) self._log.debug('Remove: %s', nick) gc_contact.avatar_sha = None app.interface.update_avatar(contact=gc_contact) else: self._log.info('Update: %s %s', nick, properties.avatar_sha) path = os.path.join(configpaths.get('AVATAR'), properties.avatar_sha) if not os.path.isfile(path): if properties.avatar_sha not in self._requested_shas: app.log('avatar').info('Request: %s', nick) self._requested_shas.append(properties.avatar_sha) self._con.get_module('VCardTemp').request_vcard( RequestAvatar.USER, str(properties.jid), room=True, sha=properties.avatar_sha) return if gc_contact.avatar_sha != properties.avatar_sha: self._log.info('%s changed their Avatar: %s', nick, properties.avatar_sha) gc_contact.avatar_sha = properties.avatar_sha app.interface.update_avatar(contact=gc_contact) else: self._log.info('Avatar already known: %s', nick) def send_avatar_presence(self, force=False, after_publish=False): if self._con.avatar_conversion: if not after_publish: # XEP-0398: We only resend presence after we publish a # new avatar return else: if self.avatar_advertised and not force: self._log.debug('Avatar already advertised') return show = helpers.get_xmpp_show(app.SHOW_LIST[self._con.connected]) self._con.get_module('Presence').send_presence( priority=self._con.priority, show=show, status=self._con.status) self.avatar_advertised = True def add_update_node(self, node): update = node.setTag('x', namespace=nbxmpp.NS_VCARD_UPDATE) if self._con.get_module('VCardTemp').own_vcard_received: sha = app.config.get_per('accounts', self._account, 'avatar_sha') own_jid = self._con.get_own_jid() self._log.info('Send avatar presence to: %s %s', node.getTo() or own_jid, sha or 'no sha advertised') update.setTagData('photo', sha) def get_instance(*args, **kwargs): return VCardAvatars(*args, **kwargs), 'VCardAvatars'