Philipp Hörist pushed to branch master at gajim / python-nbxmpp
Commits:
-
4a7ecc26
by Philipp Hörist at 2019-02-02T23:06:33Z
4 changed files:
Changes:
| ... | ... | @@ -61,6 +61,7 @@ from nbxmpp.modules.activity import Activity |
| 61 | 61 |
from nbxmpp.modules.tune import Tune
|
| 62 | 62 |
from nbxmpp.modules.mood import Mood
|
| 63 | 63 |
from nbxmpp.modules.location import Location
|
| 64 |
+from nbxmpp.modules.user_avatar import UserAvatar
|
|
| 64 | 65 |
from nbxmpp.modules.misc import unwrap_carbon
|
| 65 | 66 |
from nbxmpp.modules.misc import unwrap_mam
|
| 66 | 67 |
from nbxmpp.util import get_properties_struct
|
| ... | ... | @@ -189,6 +190,7 @@ class XMPPDispatcher(PlugIn): |
| 189 | 190 |
self._modules['Activity'] = Activity(self._owner)
|
| 190 | 191 |
self._modules['Tune'] = Tune(self._owner)
|
| 191 | 192 |
self._modules['Location'] = Location(self._owner)
|
| 193 |
+ self._modules['UserAvatar'] = UserAvatar(self._owner)
|
|
| 192 | 194 |
|
| 193 | 195 |
for instance in self._modules.values():
|
| 194 | 196 |
for handler in instance.handlers:
|
| ... | ... | @@ -76,3 +76,14 @@ class PubSub: |
| 76 | 76 |
return CommonResult(jid=stanza.getFrom(),
|
| 77 | 77 |
error=stanza.getError())
|
| 78 | 78 |
return CommonResult(jid=stanza.getFrom())
|
| 79 |
+ |
|
| 80 |
+ |
|
| 81 |
+def get_pubsub_request(jid, node, id_=None, max_items=None):
|
|
| 82 |
+ query = Iq('get', to=jid)
|
|
| 83 |
+ pubsub = query.addChild('pubsub', namespace=NS_PUBSUB)
|
|
| 84 |
+ items = pubsub.addChild('items', {'node': node})
|
|
| 85 |
+ if max_items is not None:
|
|
| 86 |
+ items.setAttr('max_items', max_items)
|
|
| 87 |
+ if id_ is not None:
|
|
| 88 |
+ items.addChild('item', {'id': id_})
|
|
| 89 |
+ return query
|
| 1 |
+# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com>
|
|
| 2 |
+#
|
|
| 3 |
+# This file is part of nbxmpp.
|
|
| 4 |
+#
|
|
| 5 |
+# This program is free software; you can redistribute it and/or
|
|
| 6 |
+# modify it under the terms of the GNU General Public License
|
|
| 7 |
+# as published by the Free Software Foundation; either version 3
|
|
| 8 |
+# of the License, or (at your option) any later version.
|
|
| 9 |
+#
|
|
| 10 |
+# This program is distributed in the hope that it will be useful,
|
|
| 11 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
| 12 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
| 13 |
+# GNU General Public License for more details.
|
|
| 14 |
+#
|
|
| 15 |
+# You should have received a copy of the GNU General Public License
|
|
| 16 |
+# along with this program; If not, see <http://www.gnu.org/licenses/>.
|
|
| 17 |
+ |
|
| 18 |
+import logging
|
|
| 19 |
+import base64
|
|
| 20 |
+ |
|
| 21 |
+from nbxmpp.protocol import NS_AVATAR_DATA
|
|
| 22 |
+from nbxmpp.protocol import NS_AVATAR_METADATA
|
|
| 23 |
+from nbxmpp.protocol import NS_PUBSUB_EVENT
|
|
| 24 |
+from nbxmpp.protocol import NodeProcessed
|
|
| 25 |
+from nbxmpp.protocol import isResultNode
|
|
| 26 |
+from nbxmpp.protocol import JID
|
|
| 27 |
+from nbxmpp.structs import StanzaHandler
|
|
| 28 |
+from nbxmpp.structs import AvatarMetaData
|
|
| 29 |
+from nbxmpp.structs import AvatarData
|
|
| 30 |
+from nbxmpp.util import call_on_response
|
|
| 31 |
+from nbxmpp.util import callback
|
|
| 32 |
+from nbxmpp.modules.pubsub import get_pubsub_request
|
|
| 33 |
+ |
|
| 34 |
+log = logging.getLogger('nbxmpp.m.user_avatar')
|
|
| 35 |
+ |
|
| 36 |
+ |
|
| 37 |
+class UserAvatar:
|
|
| 38 |
+ def __init__(self, client):
|
|
| 39 |
+ self._client = client
|
|
| 40 |
+ self.handlers = [
|
|
| 41 |
+ StanzaHandler(name='message',
|
|
| 42 |
+ callback=self._process_pubsub_avatar,
|
|
| 43 |
+ ns=NS_PUBSUB_EVENT,
|
|
| 44 |
+ priority=16),
|
|
| 45 |
+ ]
|
|
| 46 |
+ |
|
| 47 |
+ def _process_pubsub_avatar(self, _con, stanza, properties):
|
|
| 48 |
+ if not properties.is_pubsub_event:
|
|
| 49 |
+ return
|
|
| 50 |
+ |
|
| 51 |
+ if properties.pubsub_event.node != NS_AVATAR_METADATA:
|
|
| 52 |
+ return
|
|
| 53 |
+ |
|
| 54 |
+ item = properties.pubsub_event.item
|
|
| 55 |
+ |
|
| 56 |
+ metadata = item.getTag('metadata', namespace=NS_AVATAR_METADATA)
|
|
| 57 |
+ if not metadata.getChildren():
|
|
| 58 |
+ pubsub_event = properties.pubsub_event._replace(empty=True)
|
|
| 59 |
+ log.info('Received avatar metadata: %s - no avatar set',
|
|
| 60 |
+ properties.jid)
|
|
| 61 |
+ else:
|
|
| 62 |
+ info = metadata.getTags('info', one=True)
|
|
| 63 |
+ try:
|
|
| 64 |
+ data = AvatarMetaData(**info.getAttrs())
|
|
| 65 |
+ except Exception:
|
|
| 66 |
+ log.warning('Malformed user avatar data')
|
|
| 67 |
+ log.warning(stanza)
|
|
| 68 |
+ raise NodeProcessed
|
|
| 69 |
+ |
|
| 70 |
+ pubsub_event = properties.pubsub_event._replace(data=data)
|
|
| 71 |
+ log.info('Received avatar metadata: %s - %s', properties.jid, data)
|
|
| 72 |
+ |
|
| 73 |
+ properties.pubsub_event = pubsub_event
|
|
| 74 |
+ |
|
| 75 |
+ @call_on_response('_avatar_data_received')
|
|
| 76 |
+ def request_avatar(self, jid, id_):
|
|
| 77 |
+ return get_pubsub_request(jid, NS_AVATAR_DATA, id_=id_)
|
|
| 78 |
+ |
|
| 79 |
+ @callback
|
|
| 80 |
+ def _avatar_data_received(self, stanza):
|
|
| 81 |
+ jid = stanza.getFrom()
|
|
| 82 |
+ if jid is None:
|
|
| 83 |
+ jid = JID(self._client.get_bound_jid().getBare())
|
|
| 84 |
+ |
|
| 85 |
+ if not isResultNode(stanza):
|
|
| 86 |
+ log.info('Error: %s', stanza.getError())
|
|
| 87 |
+ raise NodeProcessed
|
|
| 88 |
+ |
|
| 89 |
+ pubsub_node = stanza.getTag('pubsub')
|
|
| 90 |
+ items_node = pubsub_node.getTag('items')
|
|
| 91 |
+ item = items_node.getTag('item')
|
|
| 92 |
+ |
|
| 93 |
+ sha = item.getAttr('id')
|
|
| 94 |
+ data_node = item.getTag('data', namespace=NS_AVATAR_DATA)
|
|
| 95 |
+ if data_node is None:
|
|
| 96 |
+ log.warning('No data node found')
|
|
| 97 |
+ log.warning(stanza)
|
|
| 98 |
+ raise NodeProcessed
|
|
| 99 |
+ |
|
| 100 |
+ data = data_node.getData()
|
|
| 101 |
+ if data is None:
|
|
| 102 |
+ log.warning('Data node empty')
|
|
| 103 |
+ log.warning(stanza)
|
|
| 104 |
+ raise NodeProcessed
|
|
| 105 |
+ |
|
| 106 |
+ data = base64.b64decode(data.encode('utf-8'))
|
|
| 107 |
+ log.info('Received avatar data: %s %s', jid, sha)
|
|
| 108 |
+ return AvatarData(jid, sha, data)
|
| ... | ... | @@ -68,6 +68,12 @@ ActivityData = namedtuple('ActivityData', 'activity subactivity text') |
| 68 | 68 |
LocationData = namedtuple('LocationData', LOCATION_DATA)
|
| 69 | 69 |
LocationData.__new__.__defaults__ = (None,) * len(LocationData._fields)
|
| 70 | 70 |
|
| 71 |
+AvatarMetaData = namedtuple('AvatarMetaData', 'bytes height width id type url')
|
|
| 72 |
+AvatarMetaData.__new__.__defaults__ = (None,) * len(AvatarMetaData._fields)
|
|
| 73 |
+ |
|
| 74 |
+AvatarData = namedtuple('AvatarData', 'jid sha data')
|
|
| 75 |
+AvatarData.__new__.__defaults__ = (None,) * len(AvatarData._fields)
|
|
| 76 |
+ |
|
| 71 | 77 |
|
| 72 | 78 |
class TuneData(namedtuple('TuneData', 'artist length rating source title track uri')):
|
| 73 | 79 |
|