Philipp Hörist pushed to branch master at gajim / python-nbxmpp
Commits: 4a7ecc26 by Philipp Hörist at 2019-02-02T23:06:33Z Add UserAvatar (XEP-0084) module
- - - - -
4 changed files:
- nbxmpp/dispatcher_nb.py - nbxmpp/modules/pubsub.py - + nbxmpp/modules/user_avatar.py - nbxmpp/structs.py
Changes:
===================================== nbxmpp/dispatcher_nb.py ===================================== @@ -61,6 +61,7 @@ from nbxmpp.modules.activity import Activity from nbxmpp.modules.tune import Tune from nbxmpp.modules.mood import Mood from nbxmpp.modules.location import Location +from nbxmpp.modules.user_avatar import UserAvatar from nbxmpp.modules.misc import unwrap_carbon from nbxmpp.modules.misc import unwrap_mam from nbxmpp.util import get_properties_struct @@ -189,6 +190,7 @@ class XMPPDispatcher(PlugIn): self._modules['Activity'] = Activity(self._owner) self._modules['Tune'] = Tune(self._owner) self._modules['Location'] = Location(self._owner) + self._modules['UserAvatar'] = UserAvatar(self._owner)
for instance in self._modules.values(): for handler in instance.handlers:
===================================== nbxmpp/modules/pubsub.py ===================================== @@ -76,3 +76,14 @@ class PubSub: return CommonResult(jid=stanza.getFrom(), error=stanza.getError()) return CommonResult(jid=stanza.getFrom()) + + +def get_pubsub_request(jid, node, id_=None, max_items=None): + query = Iq('get', to=jid) + pubsub = query.addChild('pubsub', namespace=NS_PUBSUB) + items = pubsub.addChild('items', {'node': node}) + if max_items is not None: + items.setAttr('max_items', max_items) + if id_ is not None: + items.addChild('item', {'id': id_}) + return query
===================================== nbxmpp/modules/user_avatar.py ===================================== @@ -0,0 +1,108 @@ +# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com> +# +# This file is part of nbxmpp. +# +# This program 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; either version 3 +# of the License, or (at your option) any later version. +# +# This program 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 this program; If not, see http://www.gnu.org/licenses/. + +import logging +import base64 + +from nbxmpp.protocol import NS_AVATAR_DATA +from nbxmpp.protocol import NS_AVATAR_METADATA +from nbxmpp.protocol import NS_PUBSUB_EVENT +from nbxmpp.protocol import NodeProcessed +from nbxmpp.protocol import isResultNode +from nbxmpp.protocol import JID +from nbxmpp.structs import StanzaHandler +from nbxmpp.structs import AvatarMetaData +from nbxmpp.structs import AvatarData +from nbxmpp.util import call_on_response +from nbxmpp.util import callback +from nbxmpp.modules.pubsub import get_pubsub_request + +log = logging.getLogger('nbxmpp.m.user_avatar') + + +class UserAvatar: + def __init__(self, client): + self._client = client + self.handlers = [ + StanzaHandler(name='message', + callback=self._process_pubsub_avatar, + ns=NS_PUBSUB_EVENT, + priority=16), + ] + + def _process_pubsub_avatar(self, _con, stanza, properties): + if not properties.is_pubsub_event: + return + + if properties.pubsub_event.node != NS_AVATAR_METADATA: + return + + item = properties.pubsub_event.item + + metadata = item.getTag('metadata', namespace=NS_AVATAR_METADATA) + if not metadata.getChildren(): + pubsub_event = properties.pubsub_event._replace(empty=True) + log.info('Received avatar metadata: %s - no avatar set', + properties.jid) + else: + info = metadata.getTags('info', one=True) + try: + data = AvatarMetaData(**info.getAttrs()) + except Exception: + log.warning('Malformed user avatar data') + log.warning(stanza) + raise NodeProcessed + + pubsub_event = properties.pubsub_event._replace(data=data) + log.info('Received avatar metadata: %s - %s', properties.jid, data) + + properties.pubsub_event = pubsub_event + + @call_on_response('_avatar_data_received') + def request_avatar(self, jid, id_): + return get_pubsub_request(jid, NS_AVATAR_DATA, id_=id_) + + @callback + def _avatar_data_received(self, stanza): + jid = stanza.getFrom() + if jid is None: + jid = JID(self._client.get_bound_jid().getBare()) + + if not isResultNode(stanza): + log.info('Error: %s', stanza.getError()) + raise NodeProcessed + + pubsub_node = stanza.getTag('pubsub') + items_node = pubsub_node.getTag('items') + item = items_node.getTag('item') + + sha = item.getAttr('id') + data_node = item.getTag('data', namespace=NS_AVATAR_DATA) + if data_node is None: + log.warning('No data node found') + log.warning(stanza) + raise NodeProcessed + + data = data_node.getData() + if data is None: + log.warning('Data node empty') + log.warning(stanza) + raise NodeProcessed + + data = base64.b64decode(data.encode('utf-8')) + log.info('Received avatar data: %s %s', jid, sha) + return AvatarData(jid, sha, data)
===================================== nbxmpp/structs.py ===================================== @@ -68,6 +68,12 @@ ActivityData = namedtuple('ActivityData', 'activity subactivity text') LocationData = namedtuple('LocationData', LOCATION_DATA) LocationData.__new__.__defaults__ = (None,) * len(LocationData._fields)
+AvatarMetaData = namedtuple('AvatarMetaData', 'bytes height width id type url') +AvatarMetaData.__new__.__defaults__ = (None,) * len(AvatarMetaData._fields) + +AvatarData = namedtuple('AvatarData', 'jid sha data') +AvatarData.__new__.__defaults__ = (None,) * len(AvatarData._fields) +
class TuneData(namedtuple('TuneData', 'artist length rating source title track uri')):
View it on GitLab: https://dev.gajim.org/gajim/python-nbxmpp/commit/4a7ecc26df815af67f0fc7987be...