summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--itests/test_blocking.py32
-rw-r--r--itests/test_last_activity.py33
-rw-r--r--itests/test_user_avatar.py61
-rw-r--r--itests/test_vcard.py49
-rw-r--r--itests/test_vcard_avatar.py49
-rw-r--r--itests/test_version.py37
-rw-r--r--slixmpp/plugins/xep_0012/last_activity.py38
-rw-r--r--slixmpp/plugins/xep_0049/private_storage.py42
-rw-r--r--slixmpp/plugins/xep_0054/vcard_temp.py50
-rw-r--r--slixmpp/plugins/xep_0084/avatar.py115
-rw-r--r--slixmpp/plugins/xep_0084/stanza.py29
-rw-r--r--slixmpp/plugins/xep_0092/version.py25
-rw-r--r--slixmpp/plugins/xep_0152/reachability.py39
-rw-r--r--slixmpp/plugins/xep_0153/vcard_avatar.py125
-rw-r--r--slixmpp/plugins/xep_0191/blocking.py57
-rw-r--r--slixmpp/test/integration.py11
16 files changed, 580 insertions, 212 deletions
diff --git a/itests/test_blocking.py b/itests/test_blocking.py
new file mode 100644
index 00000000..7954c1dc
--- /dev/null
+++ b/itests/test_blocking.py
@@ -0,0 +1,32 @@
+import unittest
+from slixmpp import JID
+from slixmpp.test.integration import SlixIntegration
+
+
+class TestBlocking(SlixIntegration):
+ async def asyncSetUp(self):
+ await super().asyncSetUp()
+ self.add_client(
+ self.envjid('CI_ACCOUNT1'),
+ self.envstr('CI_ACCOUNT1_PASSWORD'),
+ )
+ self.register_plugins(['xep_0191'])
+ await self.connect_clients()
+
+ async def test_blocking(self):
+ """Check we can block, unblock, and list blocked"""
+ await self.clients[0]['xep_0191'].block(
+ [JID('toto@example.com'), JID('titi@example.com')]
+ )
+ blocked = {JID('toto@example.com'), JID('titi@example.com')}
+ iq = await self.clients[0]['xep_0191'].get_blocked()
+ self.assertEqual(iq['blocklist']['items'], blocked)
+
+ info = await self.clients[0]['xep_0191'].unblock(
+ blocked,
+ )
+ iq = await self.clients[0]['xep_0191'].get_blocked()
+ self.assertEqual(len(iq['blocklist']['items']), 0)
+
+
+suite = unittest.TestLoader().loadTestsFromTestCase(TestBlocking)
diff --git a/itests/test_last_activity.py b/itests/test_last_activity.py
new file mode 100644
index 00000000..3d36b4b8
--- /dev/null
+++ b/itests/test_last_activity.py
@@ -0,0 +1,33 @@
+import unittest
+from slixmpp.test.integration import SlixIntegration
+
+
+class TestLastActivity(SlixIntegration):
+ async def asyncSetUp(self):
+ await super().asyncSetUp()
+ self.add_client(
+ self.envjid('CI_ACCOUNT1'),
+ self.envstr('CI_ACCOUNT1_PASSWORD'),
+ )
+ self.add_client(
+ self.envjid('CI_ACCOUNT2'),
+ self.envstr('CI_ACCOUNT2_PASSWORD'),
+ )
+ self.register_plugins(['xep_0012'])
+ await self.connect_clients()
+
+ async def test_activity(self):
+ """Check we can set and get last activity"""
+ self.clients[0]['xep_0012'].set_last_activity(
+ status='coucou',
+ seconds=4242,
+ )
+ act = await self.clients[1]['xep_0012'].get_last_activity(
+ self.clients[0].boundjid.full
+ )
+ self.assertEqual(act['last_activity']['status'], 'coucou')
+ self.assertGreater(act['last_activity']['seconds'], 4241)
+ self.assertGreater(4250, act['last_activity']['seconds'])
+
+
+suite = unittest.TestLoader().loadTestsFromTestCase(TestLastActivity)
diff --git a/itests/test_user_avatar.py b/itests/test_user_avatar.py
new file mode 100644
index 00000000..193bbe72
--- /dev/null
+++ b/itests/test_user_avatar.py
@@ -0,0 +1,61 @@
+import asyncio
+import unittest
+from slixmpp import JID
+from slixmpp.test.integration import SlixIntegration
+
+
+class TestUserAvatar(SlixIntegration):
+ async def asyncSetUp(self):
+ await super().asyncSetUp()
+ self.add_client(
+ self.envjid('CI_ACCOUNT1'),
+ self.envstr('CI_ACCOUNT1_PASSWORD'),
+ )
+ self.register_plugins(['xep_0084'])
+ self.data = b'coucou coucou'
+ await self.connect_clients()
+
+ async def _clear_avatar(self):
+ """Utility for purging remote state"""
+ await self.clients[0]['xep_0084'].stop()
+ await self.clients[0]['xep_0084'].publish_avatar(b'')
+
+ async def test_set_avatar(self):
+ """Check we can set and get a PEP avatar and metadata"""
+ await self._clear_avatar()
+
+ await self.clients[0]['xep_0084'].publish_avatar(
+ self.data
+ )
+ metadata = {
+ 'id': self.clients[0]['xep_0084'].generate_id(self.data),
+ 'bytes': 13,
+ 'type': 'image/jpeg',
+ }
+ # Wait for metadata publish event
+ event = self.clients[0].wait_until('avatar_metadata_publish')
+ publish = self.clients[0]['xep_0084'].publish_avatar_metadata(
+ metadata,
+ )
+ res = await asyncio.gather(
+ event,
+ publish,
+ )
+ message = res[0]
+ recv_meta = message['pubsub_event']['items']['item']['avatar_metadata']
+ info = recv_meta['info']
+ self.assertEqual(info['bytes'], metadata['bytes'])
+ self.assertEqual(info['type'], metadata['type'])
+ self.assertEqual(info['id'], metadata['id'])
+
+ recv = await self.clients[0]['xep_0084'].retrieve_avatar(
+ JID(self.clients[0].boundjid.bare),
+ info['id']
+ )
+ avatar = recv['pubsub']['items']['item']['avatar_data']['value']
+ self.assertEqual(avatar, self.data)
+
+ await self._clear_avatar()
+
+
+suite = unittest.TestLoader().loadTestsFromTestCase(TestUserAvatar)
diff --git a/itests/test_vcard.py b/itests/test_vcard.py
new file mode 100644
index 00000000..800d5a5b
--- /dev/null
+++ b/itests/test_vcard.py
@@ -0,0 +1,49 @@
+import unittest
+from slixmpp.test.integration import SlixIntegration
+
+
+class TestVcardTemp(SlixIntegration):
+ async def asyncSetUp(self):
+ await super().asyncSetUp()
+ self.add_client(
+ self.envjid('CI_ACCOUNT1'),
+ self.envstr('CI_ACCOUNT1_PASSWORD'),
+ )
+ self.add_client(
+ self.envjid('CI_ACCOUNT2'),
+ self.envstr('CI_ACCOUNT2_PASSWORD'),
+ )
+ self.register_plugins(['xep_0054'])
+ await self.connect_clients()
+
+ async def _clear_vcard(self):
+ # cleanup
+ await self.clients[0]['xep_0054'].publish_vcard(
+ self.clients[0]['xep_0054'].make_vcard()
+ )
+
+ async def test_vcard(self):
+ """Check we can set and get a vcard"""
+ await self._clear_vcard()
+
+ # Check that vcard is empty
+ recv = await self.clients[1]['xep_0054'].get_vcard(
+ self.clients[0].boundjid.bare
+ )
+ self.assertEqual(recv['vcard_temp']['TITLE'], None)
+
+ vcard = self.clients[0]['xep_0054'].make_vcard()
+ vcard['TITLE'] = 'Coucou coucou'
+ await self.clients[0]['xep_0054'].publish_vcard(
+ vcard,
+ )
+ #
+ recv = await self.clients[1]['xep_0054'].get_vcard(
+ self.clients[0].boundjid.bare
+ )
+ self.assertEqual(recv['vcard_temp']['TITLE'], 'Coucou coucou')
+
+ await self._clear_vcard()
+
+
+suite = unittest.TestLoader().loadTestsFromTestCase(TestVcardTemp)
diff --git a/itests/test_vcard_avatar.py b/itests/test_vcard_avatar.py
new file mode 100644
index 00000000..284c08e1
--- /dev/null
+++ b/itests/test_vcard_avatar.py
@@ -0,0 +1,49 @@
+import asyncio
+import unittest
+from slixmpp import JID
+from slixmpp.test.integration import SlixIntegration
+from hashlib import sha1
+
+
+class TestVcardAvatar(SlixIntegration):
+ async def asyncSetUp(self):
+ await super().asyncSetUp()
+ self.add_client(
+ self.envjid('CI_ACCOUNT1'),
+ self.envstr('CI_ACCOUNT1_PASSWORD'),
+ )
+ self.register_plugins(['xep_0153'])
+ self.data = b'coucou coucou'
+ self.hashed_data = sha1(self.data).hexdigest()
+ await self.connect_clients()
+
+ async def _clear_avatar(self):
+ """Utility for purging remote state"""
+ await self.clients[0]['xep_0153'].set_avatar(avatar=b'')
+
+ async def test_set_avatar(self):
+ """Check we can set and get a PEP avatar and metadata"""
+ await self._clear_avatar()
+
+ event = self.clients[0].wait_until('vcard_avatar_update')
+ update = self.clients[0]['xep_0153'].set_avatar(
+ avatar=self.data
+ )
+ result = await asyncio.gather(
+ event,
+ update,
+ )
+ presence = result[0]
+ hash = presence['vcard_temp_update']['photo']
+ self.assertEqual(hash, self.hashed_data)
+
+ iq = await self.clients[0]['xep_0054'].get_vcard(
+ JID(self.clients[0].boundjid.bare)
+ )
+ photo = iq['vcard_temp']['PHOTO']['BINVAL']
+ self.assertEqual(photo, self.data)
+
+ await self._clear_avatar()
+
+
+suite = unittest.TestLoader().loadTestsFromTestCase(TestVcardAvatar)
diff --git a/itests/test_version.py b/itests/test_version.py
new file mode 100644
index 00000000..5b8e42fd
--- /dev/null
+++ b/itests/test_version.py
@@ -0,0 +1,37 @@
+import unittest
+from slixmpp.test.integration import SlixIntegration
+
+
+class TestVersion(SlixIntegration):
+ async def asyncSetUp(self):
+ await super().asyncSetUp()
+ self.add_client(
+ self.envjid('CI_ACCOUNT1'),
+ self.envstr('CI_ACCOUNT1_PASSWORD'),
+ )
+ self.add_client(
+ self.envjid('CI_ACCOUNT2'),
+ self.envstr('CI_ACCOUNT2_PASSWORD'),
+ )
+ self.register_plugins(
+ ['xep_0092'],
+ configs=[{
+ 'software_name': 'Slix Test',
+ 'version': '1.2.3.4',
+ 'os': 'I use arch btw',
+ }]
+ )
+ await self.connect_clients()
+
+ async def test_version(self):
+ """Check we can set and query software version info"""
+ iq = await self.clients[1]['xep_0092'].get_version(
+ self.clients[0].boundjid.full
+ )
+ version = iq['software_version']
+ self.assertEqual(version['name'], 'Slix Test')
+ self.assertEqual(version['version'], '1.2.3.4')
+ self.assertEqual(version['os'], 'I use arch btw')
+
+
+suite = unittest.TestLoader().loadTestsFromTestCase(TestVersion)
diff --git a/slixmpp/plugins/xep_0012/last_activity.py b/slixmpp/plugins/xep_0012/last_activity.py
index 807c0e05..bd21252b 100644
--- a/slixmpp/plugins/xep_0012/last_activity.py
+++ b/slixmpp/plugins/xep_0012/last_activity.py
@@ -7,10 +7,16 @@
"""
import logging
+from asyncio import Future
from datetime import datetime, timedelta
+from typing import (
+ Dict,
+ Optional
+)
from slixmpp.plugins import BasePlugin, register_plugin
-from slixmpp import future_wrapper, Iq
+from slixmpp import future_wrapper, JID
+from slixmpp.stanza import Iq
from slixmpp.exceptions import XMPPError
from slixmpp.xmlstream import JID, register_stanza_plugin
from slixmpp.xmlstream.handler import Callback
@@ -59,7 +65,11 @@ class XEP_0012(BasePlugin):
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature('jabber:iq:last')
- def begin_idle(self, jid=None, status=None):
+ def begin_idle(self, jid: Optional[JID] = None, status: str = None):
+ """Reset the last activity for the given JID.
+
+ :param status: Optional status.
+ """
self.set_last_activity(jid, 0, status)
def end_idle(self, jid=None):
@@ -77,8 +87,12 @@ class XEP_0012(BasePlugin):
self.api['del_last_activity'](jid)
@future_wrapper
- def get_last_activity(self, jid, local=False, ifrom=None, timeout=None,
- callback=None, timeout_callback=None):
+ def get_last_activity(self, jid: JID, local: bool = False,
+ ifrom: Optional[JID] = None, **iqkwargs) -> Future:
+ """Get last activity for a specific JID.
+
+ :param local: Fetch the value from the local cache.
+ """
if jid is not None and not isinstance(jid, JID):
jid = JID(jid)
@@ -94,15 +108,11 @@ class XEP_0012(BasePlugin):
log.debug("Looking up local last activity data for %s", jid)
return self.api['get_last_activity'](jid, None, ifrom, None)
- iq = self.xmpp.Iq()
- iq['from'] = ifrom
- iq['to'] = jid
- iq['type'] = 'get'
+ iq = self.xmpp.make_iq_get(ito=jid, ifrom=ifrom)
iq.enable('last_activity')
- return iq.send(timeout=timeout, callback=callback,
- timeout_callback=timeout_callback)
+ return iq.send(**iqkwargs)
- def _handle_get_last_activity(self, iq):
+ def _handle_get_last_activity(self, iq: Iq):
log.debug("Received last activity query from " + \
"<%s> to <%s>.", iq['from'], iq['to'])
reply = self.api['get_last_activity'](iq['to'], None, iq['from'], iq)
@@ -112,7 +122,7 @@ class XEP_0012(BasePlugin):
# Default in-memory implementations for storing last activity data.
# =================================================================
- def _default_set_last_activity(self, jid, node, ifrom, data):
+ def _default_set_last_activity(self, jid: JID, node: str, ifrom: JID, data: Dict):
seconds = data.get('seconds', None)
if seconds is None:
seconds = 0
@@ -125,11 +135,11 @@ class XEP_0012(BasePlugin):
'seconds': datetime.now() - timedelta(seconds=seconds),
'status': status}
- def _default_del_last_activity(self, jid, node, ifrom, data):
+ def _default_del_last_activity(self, jid: JID, node: str, ifrom: JID, data: Dict):
if jid in self._last_activities:
del self._last_activities[jid]
- def _default_get_last_activity(self, jid, node, ifrom, iq):
+ def _default_get_last_activity(self, jid: JID, node: str, ifrom: JID, iq: Iq) -> Iq:
if not isinstance(iq, Iq):
reply = self.xmpp.Iq()
else:
diff --git a/slixmpp/plugins/xep_0049/private_storage.py b/slixmpp/plugins/xep_0049/private_storage.py
index cb92db51..22286f92 100644
--- a/slixmpp/plugins/xep_0049/private_storage.py
+++ b/slixmpp/plugins/xep_0049/private_storage.py
@@ -7,12 +7,19 @@
"""
import logging
-
-from slixmpp import Iq
+from typing import (
+ List,
+ Optional,
+ Union,
+)
+from asyncio import Future
+
+from slixmpp import JID
+from slixmpp.stanza import Iq
from slixmpp.plugins import BasePlugin
from slixmpp.xmlstream.handler import Callback
from slixmpp.xmlstream.matcher import StanzaPath
-from slixmpp.xmlstream import register_stanza_plugin
+from slixmpp.xmlstream import register_stanza_plugin, ElementBase
from slixmpp.plugins.xep_0049 import stanza, PrivateXML
@@ -32,26 +39,25 @@ class XEP_0049(BasePlugin):
def register(self, stanza):
register_stanza_plugin(PrivateXML, stanza, iterable=True)
- def store(self, data, ifrom=None, timeout=None, callback=None,
- timeout_callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['from'] = ifrom
+ def store(self, data: Union[List[ElementBase], ElementBase], ifrom: Optional[JID] = None, **iqkwargs) -> Future:
+ """Store data in Private XML Storage.
+
+ :param data: An XML element or list of xml element to store.
+ """
+ iq = self.xmpp.make_iq_set(ifrom=ifrom)
if not isinstance(data, list):
data = [data]
-
for elem in data:
iq['private'].append(elem)
- return iq.send(timeout=timeout, callback=callback,
- timeout_callback=timeout_callback)
+ return iq.send(**iqkwargs)
+
+ def retrieve(self, name: str, ifrom: Optional[JID] = None, **iqkwargs) -> Future:
+ """Get previously stored data from Private XML Storage.
- def retrieve(self, name, ifrom=None, timeout=None, callback=None,
- timeout_callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'get'
- iq['from'] = ifrom
+ :param name: Name of the payload to retrieve (slixmpp plugin attribute)
+ """
+ iq = self.xmpp.make_iq_get(ifrom=ifrom)
iq['private'].enable(name)
- return iq.send(timeout=timeout, callback=callback,
- timeout_callback=timeout_callback)
+ return iq.send(**iqkwargs)
diff --git a/slixmpp/plugins/xep_0054/vcard_temp.py b/slixmpp/plugins/xep_0054/vcard_temp.py
index 214746e9..12840052 100644
--- a/slixmpp/plugins/xep_0054/vcard_temp.py
+++ b/slixmpp/plugins/xep_0054/vcard_temp.py
@@ -7,8 +7,11 @@
"""
import logging
+from asyncio import Future
+from typing import Optional
-from slixmpp import JID, Iq
+from slixmpp import JID
+from slixmpp.stanza import Iq
from slixmpp.exceptions import XMPPError
from slixmpp.xmlstream import register_stanza_plugin
from slixmpp.xmlstream.handler import Callback
@@ -57,12 +60,22 @@ class XEP_0054(BasePlugin):
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature('vcard-temp')
- def make_vcard(self):
+ def make_vcard(self) -> VCardTemp:
+ """Return an empty vcard element."""
return VCardTemp()
@future_wrapper
- def get_vcard(self, jid=None, ifrom=None, local=None, cached=False,
- callback=None, timeout=None, timeout_callback=None):
+ def get_vcard(self, jid: Optional[JID] = None, *,
+ local: Optional[bool] = None, cached: bool = False,
+ ifrom: Optional[JID] = None,
+ **iqkwargs) -> Future:
+ """Retrieve a VCard.
+
+ :param jid: JID of the entity to fetch the VCard from.
+ :param local: Only check internally for a vcard.
+ :param cached: Whether to check in the local cache before
+ sending a query.
+ """
if local is None:
if jid is not None and not isinstance(jid, JID):
jid = JID(jid)
@@ -95,31 +108,28 @@ class XEP_0054(BasePlugin):
return iq
return vcard
- iq = self.xmpp.Iq()
- iq['to'] = jid
- iq['from'] = ifrom
- iq['type'] = 'get'
+ iq = self.xmpp.make_iq_get(ito=jid, ifrom=ifrom)
iq.enable('vcard_temp')
-
- return iq.send(callback=callback, timeout=timeout,
- timeout_callback=timeout_callback)
+ return iq.send(**iqkwargs)
@future_wrapper
- def publish_vcard(self, vcard=None, jid=None, ifrom=None,
- callback=None, timeout=None, timeout_callback=None):
+ def publish_vcard(self, vcard: Optional[VCardTemp] = None,
+ jid: Optional[JID] = None,
+ ifrom: Optional[JID] = None, **iqkwargs) -> Future:
+ """Publish a vcard.
+
+ :param vcard: The VCard to publish.
+ :param jid: The JID to publish the VCard to.
+ """
self.api['set_vcard'](jid, None, ifrom, vcard)
if self.xmpp.is_component:
return
- iq = self.xmpp.Iq()
- iq['to'] = jid
- iq['from'] = ifrom
- iq['type'] = 'set'
+ iq = self.xmpp.make_iq_set(ito=jid, ifrom=ifrom)
iq.append(vcard)
- return iq.send(callback=callback, timeout=timeout,
- timeout_callback=timeout_callback)
+ return iq.send(**iqkwargs)
- def _handle_get_vcard(self, iq):
+ def _handle_get_vcard(self, iq: Iq):
if iq['type'] == 'result':
self.api['set_vcard'](jid=iq['from'], args=iq['vcard_temp'])
return
diff --git a/slixmpp/plugins/xep_0084/avatar.py b/slixmpp/plugins/xep_0084/avatar.py
index e434d39c..536c4c24 100644
--- a/slixmpp/plugins/xep_0084/avatar.py
+++ b/slixmpp/plugins/xep_0084/avatar.py
@@ -6,15 +6,49 @@
See the file LICENSE for copying permission.
"""
+from __future__ import annotations
+
import hashlib
import logging
-from slixmpp import Iq
+from asyncio import Future
+from typing import (
+ Dict,
+ Iterable,
+ List,
+ Optional,
+ Set,
+ Union,
+ TYPE_CHECKING,
+)
+
+from slixmpp.stanza import Iq
from slixmpp.plugins import BasePlugin
from slixmpp.xmlstream.handler import Callback
from slixmpp.xmlstream.matcher import StanzaPath
from slixmpp.xmlstream import register_stanza_plugin, JID
-from slixmpp.plugins.xep_0084 import stanza, Data, MetaData
+from slixmpp.plugins.xep_0084.stanza import Data, MetaData, Pointer
+from slixmpp.plugins.xep_0084 import stanza
+
+try:
+ from typing import TypedDict
+except ImportError:
+ from typing_extensions import TypedDict
+
+
+class AvatarMetadataItem(TypedDict, total=False):
+ bytes: int
+ id: str
+ type: str
+ height: int
+ width: int
+ url: str
+
+MetadataItems = Union[
+ AvatarMetadataItem,
+ List[AvatarMetadataItem],
+ Set[AvatarMetadataItem]
+]
log = logging.getLogger(__name__)
@@ -41,32 +75,43 @@ class XEP_0084(BasePlugin):
def session_bind(self, jid):
self.xmpp['xep_0163'].register_pep('avatar_metadata', MetaData)
- def generate_id(self, data):
+ def generate_id(self, data) -> str:
return hashlib.sha1(data).hexdigest()
- def retrieve_avatar(self, jid, id, url=None, ifrom=None,
- callback=None, timeout=None, timeout_callback=None):
- return self.xmpp['xep_0060'].get_item(jid, Data.namespace, id,
- ifrom=ifrom,
- callback=callback,
- timeout=timeout,
- timeout_callback=timeout_callback)
+ def retrieve_avatar(self, jid: JID, id: str, **pubsubkwargs) -> Future:
+ """Retrieve an avatar.
- def publish_avatar(self, data, ifrom=None, callback=None,
- timeout=None, timeout_callback=None):
+ :param jid: JID of the entity to get the avatar from.
+ :param id: Identifier of the item containing the avatar.
+ """
+ return self.xmpp['xep_0060'].get_item(
+ jid,
+ Data.namespace,
+ id,
+ **pubsubkwargs
+ )
+
+ def publish_avatar(self, data: bytes, **pubsubkwargs) -> Future:
+ """Publish an avatar.
+
+ :param data: The avatar, in bytes representation.
+ """
payload = Data()
payload['value'] = data
- return self.xmpp['xep_0163'].publish(payload,
- id=self.generate_id(data),
- ifrom=ifrom,
- callback=callback,
- timeout=timeout,
- timeout_callback=timeout_callback)
-
- def publish_avatar_metadata(self, items=None, pointers=None,
- ifrom=None,
- callback=None, timeout=None,
- timeout_callback=None):
+ return self.xmpp['xep_0163'].publish(
+ payload,
+ id=self.generate_id(data),
+ **pubsubkwargs
+ )
+
+ def publish_avatar_metadata(self, items: Optional[MetadataItems] = None,
+ pointers: Optional[Iterable[Pointer]] = None,
+ **pubsubkwargs) -> Future:
+ """Publish avatar metadata.
+
+ :param items: Metadata items to store
+ :param pointers: Optional pointers
+ """
metadata = MetaData()
if items is None:
items = []
@@ -82,21 +127,19 @@ class XEP_0084(BasePlugin):
for pointer in pointers:
metadata.add_pointer(pointer)
- return self.xmpp['xep_0163'].publish(metadata,
- id=info['id'],
- ifrom=ifrom,
- callback=callback,
- timeout=timeout,
- timeout_callback=timeout_callback)
+ return self.xmpp['xep_0163'].publish(
+ metadata,
+ id=info['id'],
+ **pubsubkwargs
+ )
- def stop(self, ifrom=None, callback=None, timeout=None, timeout_callback=None):
+ def stop(self, **pubsubkwargs) -> Future:
"""
Clear existing avatar metadata information to stop notifications.
"""
metadata = MetaData()
- return self.xmpp['xep_0163'].publish(metadata,
- node=MetaData.namespace,
- ifrom=ifrom,
- callback=callback,
- timeout=timeout,
- timeout_callback=timeout_callback)
+ return self.xmpp['xep_0163'].publish(
+ metadata,
+ node=MetaData.namespace,
+ **pubsubkwargs
+ )
diff --git a/slixmpp/plugins/xep_0084/stanza.py b/slixmpp/plugins/xep_0084/stanza.py
index 4b6370ce..57754265 100644
--- a/slixmpp/plugins/xep_0084/stanza.py
+++ b/slixmpp/plugins/xep_0084/stanza.py
@@ -65,6 +65,35 @@ class Info(ElementBase):
plugin_multi_attrib = 'items'
interfaces = {'bytes', 'height', 'id', 'type', 'url', 'width'}
+ def _get_int(self, name: str) -> int:
+ try:
+ return int(self._get_attr(name))
+ except ValueError:
+ return 0
+
+ def _set_int(self, name: str, value: int):
+ if value not in ('', None):
+ int(value)
+ self._set_attr(name, value)
+
+ def get_bytes(self) -> int:
+ return self._get_int('bytes')
+
+ def _set_bytes(self, value: int):
+ self._set_int('bytes', value)
+
+ def get_height(self) -> int:
+ self._get_int('height')
+
+ def set_height(self, value: int):
+ self._set_int('height', value)
+
+ def get_width(self) -> int:
+ self._get_int(self, 'width')
+
+ def set_width(self, value: int):
+ self._set_int('with', value)
+
class Pointer(ElementBase):
name = 'pointer'
diff --git a/slixmpp/plugins/xep_0092/version.py b/slixmpp/plugins/xep_0092/version.py
index a60acd99..3a7b949f 100644
--- a/slixmpp/plugins/xep_0092/version.py
+++ b/slixmpp/plugins/xep_0092/version.py
@@ -8,8 +8,12 @@
import logging
+from asyncio import Future
+from typing import Optional
+
import slixmpp
-from slixmpp import Iq
+from slixmpp import JID
+from slixmpp.stanza import Iq
from slixmpp.xmlstream import register_stanza_plugin
from slixmpp.xmlstream.handler import Callback
from slixmpp.xmlstream.matcher import StanzaPath
@@ -57,12 +61,11 @@ class XEP_0092(BasePlugin):
def session_bind(self, jid):
self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:version')
- def _handle_version(self, iq):
+ def _handle_version(self, iq: Iq):
"""
Respond to a software version query.
- Arguments:
- iq -- The Iq stanza containing the software version query.
+ :param iq: The Iq stanza containing the software version query.
"""
iq = iq.reply()
if self.software_name:
@@ -75,18 +78,12 @@ class XEP_0092(BasePlugin):
iq['error']['condition'] = 'service-unavailable'
iq.send()
- def get_version(self, jid, ifrom=None, timeout=None, callback=None,
- timeout_callback=None):
+ def get_version(self, jid: JID, ifrom: Optional[JID] = None, **iqkwargs) -> Future:
"""
Retrieve the software version of a remote agent.
- Arguments:
- jid -- The JID of the entity to query.
+ :param jid: The JID of the entity to query.
"""
- iq = self.xmpp.Iq()
- iq['to'] = jid
- iq['from'] = ifrom
- iq['type'] = 'get'
+ iq = self.xmpp.make_iq_get(ito=jid, ifrom=ifrom)
iq['query'] = Version.namespace
- return iq.send(timeout=timeout, callback=callback,
- timeout_callback=timeout_callback)
+ return iq.send(**iqkwargs)
diff --git a/slixmpp/plugins/xep_0152/reachability.py b/slixmpp/plugins/xep_0152/reachability.py
index 0ca0ae68..3c1a23aa 100644
--- a/slixmpp/plugins/xep_0152/reachability.py
+++ b/slixmpp/plugins/xep_0152/reachability.py
@@ -8,6 +8,8 @@
import logging
+from asyncio import Future
+
from slixmpp import JID
from typing import Dict, List, Optional, Callable
from slixmpp.plugins.base import BasePlugin
@@ -37,17 +39,12 @@ class XEP_0152(BasePlugin):
self.xmpp['xep_0163'].register_pep('reachability', Reachability)
def publish_reachability(self, addresses: List[Dict[str, str]],
- options: Optional[Form] = None,
- ifrom: Optional[JID] = None,
- callback: Optional[Callable] = None,
- timeout: Optional[int] = None,
- timeout_callback: Optional[Callable] = None):
+ **pubsubkwargs) -> Future:
"""
Publish alternative addresses where the user can be reached.
:param addresses: A list of dictionaries containing the URI and
optional description for each address.
- :param options: Optional form of publish options.
"""
if not isinstance(addresses, (list, tuple)):
addresses = [addresses]
@@ -60,25 +57,19 @@ class XEP_0152(BasePlugin):
for key, val in address.items():
addr[key] = val
reach.append(addr)
- return self.xmpp['xep_0163'].publish(reach,
- node=Reachability.namespace,
- options=options,
- ifrom=ifrom,
- callback=callback,
- timeout=timeout,
- timeout_callback=timeout_callback)
-
- def stop(self, ifrom: Optional[JID] = None,
- callback: Optional[Callable] = None,
- timeout: Optional[int] = None,
- timeout_callback: Optional[Callable] = None):
+ return self.xmpp['xep_0163'].publish(
+ reach,
+ node=Reachability.namespace,
+ **pubsubkwargs
+ )
+
+ def stop(self, **pubsubkwargs) -> Future:
"""
Clear existing user activity information to stop notifications.
"""
reach = Reachability()
- return self.xmpp['xep_0163'].publish(reach,
- node=Reachability.namespace,
- ifrom=ifrom,
- callback=callback,
- timeout=timeout,
- timeout_callback=timeout_callback)
+ return self.xmpp['xep_0163'].publish(
+ reach,
+ node=Reachability.namespace,
+ **pubsubkwargs
+ )
diff --git a/slixmpp/plugins/xep_0153/vcard_avatar.py b/slixmpp/plugins/xep_0153/vcard_avatar.py
index cf10283a..ef328acc 100644
--- a/slixmpp/plugins/xep_0153/vcard_avatar.py
+++ b/slixmpp/plugins/xep_0153/vcard_avatar.py
@@ -8,13 +8,19 @@
import hashlib
import logging
+from asyncio import Future, ensure_future
+from typing import (
+ Dict,
+ Optional,
+)
+from slixmpp import JID
from slixmpp.stanza import Presence
from slixmpp.exceptions import XMPPError, IqTimeout
-from slixmpp.xmlstream import register_stanza_plugin
+from slixmpp.xmlstream import register_stanza_plugin, ElementBase
from slixmpp.plugins.base import BasePlugin
from slixmpp.plugins.xep_0153 import stanza, VCardTempUpdate
-from slixmpp import asyncio, future_wrapper
+from slixmpp import future_wrapper
log = logging.getLogger(__name__)
@@ -35,7 +41,6 @@ class XEP_0153(BasePlugin):
self.xmpp.add_filter('out', self._update_presence)
self.xmpp.add_event_handler('session_start', self._start)
- self.xmpp.add_event_handler('session_end', self._end)
self.xmpp.add_event_handler('presence_available', self._recv_presence)
self.xmpp.add_event_handler('presence_dnd', self._recv_presence)
@@ -58,45 +63,47 @@ class XEP_0153(BasePlugin):
self.xmpp.del_event_handler('presence_away', self._recv_presence)
@future_wrapper
- def set_avatar(self, jid=None, avatar=None, mtype=None, timeout=None,
- callback=None, timeout_callback=None):
+ def set_avatar(self, jid: Optional[JID] = None,
+ avatar: Optional[bytes] = None,
+ mtype: Optional[str] = None, **iqkwargs) -> Future:
+ """Set a VCard avatar.
+
+ :param jid: The JID to set the avatar for.
+ :param avatar: Avatar content.
+ :param mtype: Avatar file type (e.g. image/jpeg).
+ """
if jid is None:
jid = self.xmpp.boundjid.bare
-
- future = asyncio.Future()
-
- def propagate_timeout_exception(fut):
+ async def get_and_set_avatar():
+ timeout = iqkwargs.get('timeout', None)
+ timeout_cb = iqkwargs.get('timeout_callback', None)
try:
- fut.done()
- except IqTimeout as e:
- future.set_exception(e)
-
- def custom_callback(result):
+ result = await self.xmpp['xep_0054'].get_vcard(
+ jid,
+ cached=False,
+ timeout=timeout
+ )
+ except IqTimeout as exc:
+ if timeout_cb is not None:
+ timeout_cb(exc)
+ raise
vcard = result['vcard_temp']
vcard['PHOTO']['TYPE'] = mtype
vcard['PHOTO']['BINVAL'] = avatar
- new_future = self.xmpp['xep_0054'].publish_vcard(jid=jid,
- vcard=vcard,
- timeout=timeout,
- callback=next_callback,
- timeout_callback=timeout_callback)
- new_future.add_done_callback(propagate_timeout_exception)
-
- def next_callback(result):
- if result['type'] == 'error':
- future.set_exception(result)
- else:
- self.api['reset_hash'](jid)
- self.xmpp.roster[jid].send_last_presence()
-
- future.set_result(result)
+ try:
+ result = await self.xmpp['xep_0054'].publish_vcard(
+ jid=jid,
+ vcard=vcard,
+ **iqkwargs
+ )
+ except IqTimeout as exc:
+ timeout_cb(exc)
+ raise
+ self.api['reset_hash'](jid)
+ self.xmpp.roster[jid].send_last_presence()
- first_future = self.xmpp['xep_0054'].get_vcard(jid, cached=False, timeout=timeout,
- callback=custom_callback,
- timeout_callback=timeout_callback)
- first_future.add_done_callback(propagate_timeout_exception)
- return future
+ return ensure_future(get_and_set_avatar(), loop=self.xmpp.loop)
async def _start(self, event):
try:
@@ -110,10 +117,7 @@ class XEP_0153(BasePlugin):
except XMPPError:
log.debug('Could not retrieve vCard for %s', self.xmpp.boundjid.bare)
- def _end(self, event):
- pass
-
- def _update_presence(self, stanza):
+ def _update_presence(self, stanza: ElementBase) -> ElementBase:
if not isinstance(stanza, Presence):
return stanza
@@ -124,7 +128,27 @@ class XEP_0153(BasePlugin):
stanza['vcard_temp_update']['photo'] = current_hash
return stanza
- def _reset_hash(self, jid, node, ifrom, args):
+ def _recv_presence(self, pres: Presence):
+ try:
+ if pres.get_plugin('muc', check=True):
+ # Don't process vCard avatars for MUC occupants
+ # since they all share the same bare JID.
+ return
+ except:
+ pass
+
+ if not pres.match('presence/vcard_temp_update'):
+ self.api['set_hash'](pres['from'], args=None)
+ return
+
+ data = pres['vcard_temp_update']['photo']
+ if data is None:
+ return
+ self.xmpp.event('vcard_avatar_update', pres)
+
+ # =================================================================
+
+ def _reset_hash(self, jid: JID, node: str, ifrom: JID, args: Dict):
own_jid = (jid.bare == self.xmpp.boundjid.bare)
if self.xmpp.is_component:
own_jid = (jid.domain == self.xmpp.boundjid.domain)
@@ -152,27 +176,8 @@ class XEP_0153(BasePlugin):
self.xmpp['xep_0054'].get_vcard(jid=jid.bare, ifrom=ifrom,
callback=callback)
- def _recv_presence(self, pres):
- try:
- if pres['muc']['affiliation']:
- # Don't process vCard avatars for MUC occupants
- # since they all share the same bare JID.
- return
- except: pass
-
- if not pres.match('presence/vcard_temp_update'):
- self.api['set_hash'](pres['from'], args=None)
- return
-
- data = pres['vcard_temp_update']['photo']
- if data is None:
- return
- self.xmpp.event('vcard_avatar_update', pres)
-
- # =================================================================
-
- def _get_hash(self, jid, node, ifrom, args):
+ def _get_hash(self, jid: JID, node: str, ifrom: JID, args: Dict):
return self._hashes.get(jid.bare, None)
- def _set_hash(self, jid, node, ifrom, args):
+ def _set_hash(self, jid: JID, node: str, ifrom: JID, args: Dict):
self._hashes[jid.bare] = args
diff --git a/slixmpp/plugins/xep_0191/blocking.py b/slixmpp/plugins/xep_0191/blocking.py
index 5425544d..53c279df 100644
--- a/slixmpp/plugins/xep_0191/blocking.py
+++ b/slixmpp/plugins/xep_0191/blocking.py
@@ -8,7 +8,15 @@
import logging
-from slixmpp import Iq
+from asyncio import Future
+from typing import (
+ List,
+ Optional,
+ Set,
+ Union,
+)
+
+from slixmpp.stanza import Iq
from slixmpp.plugins import BasePlugin
from slixmpp.xmlstream.handler import Callback
from slixmpp.xmlstream.matcher import StanzaPath
@@ -18,6 +26,12 @@ from slixmpp.plugins.xep_0191 import stanza, Block, Unblock, BlockList
log = logging.getLogger(__name__)
+BlockedJIDs = Union[
+ JID,
+ Set[JID],
+ List[JID]
+]
+
class XEP_0191(BasePlugin):
@@ -45,42 +59,39 @@ class XEP_0191(BasePlugin):
self.xmpp.remove_handler('Blocked Contact')
self.xmpp.remove_handler('Unblocked Contact')
- def get_blocked(self, ifrom=None, timeout=None, callback=None,
- timeout_callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'get'
- iq['from'] = ifrom
+ def get_blocked(self, ifrom: Optional[JID] = None, **iqkwargs) -> Future:
+ """Get the list of blocked JIDs."""
+ iq = self.xmpp.make_iq_get(ifrom=ifrom)
iq.enable('blocklist')
- return iq.send(timeout=timeout, callback=callback,
- timeout_callback=timeout_callback)
+ return iq.send(**iqkwargs)
- def block(self, jids, ifrom=None, timeout=None, callback=None,
- timeout_callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['from'] = ifrom
+ def block(self, jids: BlockedJIDs,
+ ifrom: Optional[JID] = None, **iqkwargs) -> Future:
+ """Block a JID or a list of JIDs.
+ :param jids: JID(s) to block.
+ """
+ iq = self.xmpp.make_iq_set(ifrom=ifrom)
if not isinstance(jids, (set, list)):
jids = [jids]
iq['block']['items'] = jids
- return iq.send(timeout=timeout, callback=callback,
- timeout_callback=timeout_callback)
+ return iq.send(**iqkwargs)
- def unblock(self, jids=None, ifrom=None, timeout=None, callback=None,
- timeout_callback=None):
- iq = self.xmpp.Iq()
- iq['type'] = 'set'
- iq['from'] = ifrom
+ def unblock(self, jids: BlockedJIDs, ifrom: Optional[JID] = None, **iqkwargs) -> Future:
+ """Unblock a JID or a list of JIDs.
+ :param jids: JID(s) to unblock.
+ """
if jids is None:
- jids = []
+ raise ValueError("jids cannot be empty.")
+ iq = self.xmpp.make_iq_set(ifrom=ifrom)
+
if not isinstance(jids, (set, list)):
jids = [jids]
iq['unblock']['items'] = jids
- return iq.send(timeout=timeout, callback=callback,
- timeout_callback=timeout_callback)
+ return iq.send(**iqkwargs)
def _handle_blocked(self, iq):
self.xmpp.event('blocked', iq)
diff --git a/slixmpp/test/integration.py b/slixmpp/test/integration.py
index 758ad239..7da3f86a 100644
--- a/slixmpp/test/integration.py
+++ b/slixmpp/test/integration.py
@@ -16,7 +16,9 @@ except ImportError:
# not usable.
from unittest import TestCase as IsolatedAsyncioTestCase
from typing import (
+ Dict,
List,
+ Optional,
)
from slixmpp import JID
@@ -39,11 +41,14 @@ class SlixIntegration(IsolatedAsyncioTestCase):
"""get a str from an env var"""
return os.getenv(name)
- def register_plugins(self, plugins: List[str]):
+ def register_plugins(self, plugins: List[str], configs: Optional[List[Dict]] = None):
"""Register plugins on all known clients"""
- for plugin in plugins:
+ for index, plugin in enumerate(plugins):
for client in self.clients:
- client.register_plugin(plugin)
+ if configs is not None:
+ client.register_plugin(plugin, pconfig=configs[index])
+ else:
+ client.register_plugin(plugin)
def add_client(self, jid: JID, password: str):
"""Register a new client"""