summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xsetup.py4
-rw-r--r--sleekxmpp/basexmpp.py11
-rw-r--r--sleekxmpp/plugins/__init__.py4
-rw-r--r--sleekxmpp/plugins/xep_0013/__init__.py15
-rw-r--r--sleekxmpp/plugins/xep_0013/offline.py134
-rw-r--r--sleekxmpp/plugins/xep_0013/stanza.py53
-rw-r--r--sleekxmpp/plugins/xep_0030/disco.py27
-rw-r--r--sleekxmpp/plugins/xep_0059/rsm.py14
-rw-r--r--sleekxmpp/plugins/xep_0091/__init__.py16
-rw-r--r--sleekxmpp/plugins/xep_0091/legacy_delay.py29
-rw-r--r--sleekxmpp/plugins/xep_0091/stanza.py46
-rw-r--r--sleekxmpp/plugins/xep_0203/stanza.py9
-rw-r--r--sleekxmpp/plugins/xep_0280/__init__.py2
-rw-r--r--sleekxmpp/plugins/xep_0280/carbons.py2
-rw-r--r--sleekxmpp/plugins/xep_0297/forwarded.py11
-rw-r--r--sleekxmpp/plugins/xep_0297/stanza.py20
-rw-r--r--sleekxmpp/plugins/xep_0308/__init__.py15
-rw-r--r--sleekxmpp/plugins/xep_0308/correction.py52
-rw-r--r--sleekxmpp/plugins/xep_0308/stanza.py16
-rw-r--r--sleekxmpp/plugins/xep_0313/__init__.py15
-rw-r--r--sleekxmpp/plugins/xep_0313/mam.py92
-rw-r--r--sleekxmpp/plugins/xep_0313/stanza.py131
-rw-r--r--sleekxmpp/stanza/message.py11
-rw-r--r--sleekxmpp/stanza/presence.py11
-rw-r--r--sleekxmpp/test/sleektest.py5
-rw-r--r--sleekxmpp/xmlstream/handler/__init__.py1
-rw-r--r--sleekxmpp/xmlstream/handler/collector.py66
-rw-r--r--tests/test_stream_xep_0059.py2
28 files changed, 779 insertions, 35 deletions
diff --git a/setup.py b/setup.py
index d0845e34..31d842d1 100755
--- a/setup.py
+++ b/setup.py
@@ -60,6 +60,7 @@ packages = [ 'sleekxmpp',
'sleekxmpp/plugins/xep_0009',
'sleekxmpp/plugins/xep_0009/stanza',
'sleekxmpp/plugins/xep_0012',
+ 'sleekxmpp/plugins/xep_0013',
'sleekxmpp/plugins/xep_0016',
'sleekxmpp/plugins/xep_0027',
'sleekxmpp/plugins/xep_0030',
@@ -80,6 +81,7 @@ packages = [ 'sleekxmpp',
'sleekxmpp/plugins/xep_0084',
'sleekxmpp/plugins/xep_0085',
'sleekxmpp/plugins/xep_0086',
+ 'sleekxmpp/plugins/xep_0091',
'sleekxmpp/plugins/xep_0092',
'sleekxmpp/plugins/xep_0107',
'sleekxmpp/plugins/xep_0108',
@@ -105,6 +107,8 @@ packages = [ 'sleekxmpp',
'sleekxmpp/plugins/xep_0279',
'sleekxmpp/plugins/xep_0280',
'sleekxmpp/plugins/xep_0297',
+ 'sleekxmpp/plugins/xep_0308',
+ 'sleekxmpp/plugins/xep_0313',
'sleekxmpp/features',
'sleekxmpp/features/feature_mechanisms',
'sleekxmpp/features/feature_mechanisms/stanza',
diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py
index 244d19c5..55250751 100644
--- a/sleekxmpp/basexmpp.py
+++ b/sleekxmpp/basexmpp.py
@@ -114,6 +114,17 @@ class BaseXMPP(XMLStream):
#: ``'to'`` and ``'from'`` JIDs of stanzas.
self.is_component = False
+ #: Messages may optionally be tagged with ID values. Setting
+ #: :attr:`use_message_ids` to `True` will assign all outgoing
+ #: messages an ID. Some plugin features require enabling
+ #: this option.
+ self.use_message_ids = False
+
+ #: Presence updates may optionally be tagged with ID values.
+ #: Setting :attr:`use_message_ids` to `True` will assign all
+ #: outgoing messages an ID.
+ self.use_presence_ids = False
+
#: The API registry is a way to process callbacks based on
#: JID+node combinations. Each callback in the registry is
#: marked with:
diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py
index 18b618ac..77e19e61 100644
--- a/sleekxmpp/plugins/__init__.py
+++ b/sleekxmpp/plugins/__init__.py
@@ -18,6 +18,7 @@ __all__ = [
'xep_0004', # Data Forms
'xep_0009', # Jabber-RPC
'xep_0012', # Last Activity
+ 'xep_0013', # Flexible Offline Message Retrieval
'xep_0016', # Privacy Lists
'xep_0027', # Current Jabber OpenPGP Usage
'xep_0030', # Service Discovery
@@ -38,6 +39,7 @@ __all__ = [
'xep_0084', # User Avatar
'xep_0085', # Chat State Notifications
'xep_0086', # Legacy Error Codes
+ 'xep_0091', # Legacy Delayed Delivery
'xep_0092', # Software Version
'xep_0106', # JID Escaping
'xep_0107', # User Mood
@@ -72,4 +74,6 @@ __all__ = [
'xep_0280', # Message Carbons
'xep_0297', # Stanza Forwarding
'xep_0302', # XMPP Compliance Suites 2012
+ 'xep_0308', # Last Message Correction
+ 'xep_0313', # Message Archive Management
]
diff --git a/sleekxmpp/plugins/xep_0013/__init__.py b/sleekxmpp/plugins/xep_0013/__init__.py
new file mode 100644
index 00000000..ad400949
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0013/__init__.py
@@ -0,0 +1,15 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permissio
+"""
+
+from sleekxmpp.plugins.base import register_plugin
+
+from sleekxmpp.plugins.xep_0013.stanza import Offline
+from sleekxmpp.plugins.xep_0013.offline import XEP_0013
+
+
+register_plugin(XEP_0013)
diff --git a/sleekxmpp/plugins/xep_0013/offline.py b/sleekxmpp/plugins/xep_0013/offline.py
new file mode 100644
index 00000000..a0d992a7
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0013/offline.py
@@ -0,0 +1,134 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permissio
+"""
+
+import logging
+
+import sleekxmpp
+from sleekxmpp.stanza import Message, Iq
+from sleekxmpp.exceptions import XMPPError
+from sleekxmpp.xmlstream.handler import Collector
+from sleekxmpp.xmlstream.matcher import StanzaPath
+from sleekxmpp.xmlstream import register_stanza_plugin
+from sleekxmpp.plugins import BasePlugin
+from sleekxmpp.plugins.xep_0013 import stanza
+
+
+log = logging.getLogger(__name__)
+
+
+class XEP_0013(BasePlugin):
+
+ """
+ XEP-0013 Flexible Offline Message Retrieval
+ """
+
+ name = 'xep_0013'
+ description = 'XEP-0013: Flexible Offline Message Retrieval'
+ dependencies = set(['xep_0030'])
+ stanza = stanza
+
+ def plugin_init(self):
+ register_stanza_plugin(Iq, stanza.Offline)
+ register_stanza_plugin(Message, stanza.Offline)
+
+ def get_count(self, **kwargs):
+ return self.xmpp['xep_0030'].get_info(
+ node='http://jabber.org/protocol/offline',
+ local=False,
+ **kwargs)
+
+ def get_headers(self, **kwargs):
+ return self.xmpp['xep_0030'].get_items(
+ node='http://jabber.org/protocol/offline',
+ local=False,
+ **kwargs)
+
+ def view(self, nodes, ifrom=None, block=True, timeout=None, callback=None):
+ if not isinstance(nodes, (list, set)):
+ nodes = [nodes]
+
+ iq = self.xmpp.Iq()
+ iq['type'] = 'get'
+ iq['from'] = ifrom
+ offline = iq['offline']
+ for node in nodes:
+ item = stanza.Item()
+ item['node'] = node
+ item['action'] = 'view'
+ offline.append(item)
+
+ collector = Collector(
+ 'Offline_Results_%s' % iq['id'],
+ StanzaPath('message/offline'))
+ self.xmpp.register_handler(collector)
+
+ if not block and callback is not None:
+ def wrapped_cb(iq):
+ results = collector.stop()
+ if iq['type'] == 'result':
+ iq['offline']['results'] = results
+ callback(iq)
+ return iq.send(block=block, timeout=timeout, callback=wrapped_cb)
+ else:
+ try:
+ resp = iq.send(block=block, timeout=timeout, callback=callback)
+ resp['offline']['results'] = collector.stop()
+ return resp
+ except XMPPError as e:
+ collector.stop()
+ raise e
+
+ def remove(self, nodes, ifrom=None, block=True, timeout=None, callback=None):
+ if not isinstance(nodes, (list, set)):
+ nodes = [nodes]
+
+ iq = self.xmpp.Iq()
+ iq['type'] = 'set'
+ iq['from'] = ifrom
+ offline = iq['offline']
+ for node in nodes:
+ item = stanza.Item()
+ item['node'] = node
+ item['action'] = 'remove'
+ offline.append(item)
+
+ return iq.send(block=block, timeout=timeout, callback=callback)
+
+ def fetch(self, ifrom=None, block=True, timeout=None, callback=None):
+ iq = self.xmpp.Iq()
+ iq['type'] = 'set'
+ iq['from'] = ifrom
+ iq['offline']['fetch'] = True
+
+ collector = Collector(
+ 'Offline_Results_%s' % iq['id'],
+ StanzaPath('message/offline'))
+ self.xmpp.register_handler(collector)
+
+ if not block and callback is not None:
+ def wrapped_cb(iq):
+ results = collector.stop()
+ if iq['type'] == 'result':
+ iq['offline']['results'] = results
+ callback(iq)
+ return iq.send(block=block, timeout=timeout, callback=wrapped_cb)
+ else:
+ try:
+ resp = iq.send(block=block, timeout=timeout, callback=callback)
+ resp['offline']['results'] = collector.stop()
+ return resp
+ except XMPPError as e:
+ collector.stop()
+ raise e
+
+ def purge(self, ifrom=None, block=True, timeout=None, callback=None):
+ iq = self.xmpp.Iq()
+ iq['type'] = 'set'
+ iq['from'] = ifrom
+ iq['offline']['purge'] = True
+ return iq.send(block=block, timeout=timeout, callback=callback)
diff --git a/sleekxmpp/plugins/xep_0013/stanza.py b/sleekxmpp/plugins/xep_0013/stanza.py
new file mode 100644
index 00000000..c9c69786
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0013/stanza.py
@@ -0,0 +1,53 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permissio
+"""
+
+from sleekxmpp.jid import JID
+from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
+
+
+class Offline(ElementBase):
+ name = 'offline'
+ namespace = 'http://jabber.org/protocol/offline'
+ plugin_attrib = 'offline'
+ interfaces = set(['fetch', 'purge', 'results'])
+ bool_interfaces = interfaces
+
+ def setup(self, xml=None):
+ ElementBase.setup(self, xml)
+ self._results = []
+
+ # The results interface is meant only as an easy
+ # way to access the set of collected message responses
+ # from the query.
+
+ def get_results(self):
+ return self._results
+
+ def set_results(self, values):
+ self._results = values
+
+ def del_results(self):
+ self._results = []
+
+
+class Item(ElementBase):
+ name = 'item'
+ namespace = 'http://jabber.org/protocol/offline'
+ plugin_attrib = 'item'
+ interfaces = set(['action', 'node', 'jid'])
+
+ actions = set(['view', 'remove'])
+
+ def get_jid(self):
+ return JID(self._get_attr('jid'))
+
+ def set_jid(self, value):
+ self._set_attr('jid', str(value))
+
+
+register_stanza_plugin(Offline, Item, iterable=True)
diff --git a/sleekxmpp/plugins/xep_0030/disco.py b/sleekxmpp/plugins/xep_0030/disco.py
index be66b6fd..278b4a34 100644
--- a/sleekxmpp/plugins/xep_0030/disco.py
+++ b/sleekxmpp/plugins/xep_0030/disco.py
@@ -288,7 +288,7 @@ class XEP_0030(BasePlugin):
'cached': cached}
return self.api['has_identity'](jid, node, ifrom, data)
- def get_info(self, jid=None, node=None, local=False,
+ def get_info(self, jid=None, node=None, local=None,
cached=None, **kwargs):
"""
Retrieve the disco#info results from a given JID/node combination.
@@ -325,17 +325,18 @@ class XEP_0030(BasePlugin):
received instead of blocking and waiting for
the reply.
"""
- if jid is not None and not isinstance(jid, JID):
- jid = JID(jid)
- if self.xmpp.is_component:
- if jid.domain == self.xmpp.boundjid.domain:
- local = True
- else:
- if str(jid) == str(self.xmpp.boundjid):
- local = True
- jid = jid.full
- elif jid in (None, ''):
- local = True
+ if local is None:
+ if jid is not None and not isinstance(jid, JID):
+ jid = JID(jid)
+ if self.xmpp.is_component:
+ if jid.domain == self.xmpp.boundjid.domain:
+ local = True
+ else:
+ if str(jid) == str(self.xmpp.boundjid):
+ local = True
+ jid = jid.full
+ elif jid in (None, ''):
+ local = True
if local:
log.debug("Looking up local disco#info data " + \
@@ -405,7 +406,7 @@ class XEP_0030(BasePlugin):
the XEP-0059 plugin, if the plugin is loaded.
Otherwise the parameter is ignored.
"""
- if local or jid is None:
+ if local or local is None and jid is None:
items = self.api['get_items'](jid, node,
kwargs.get('ifrom', None),
kwargs)
diff --git a/sleekxmpp/plugins/xep_0059/rsm.py b/sleekxmpp/plugins/xep_0059/rsm.py
index 59cfc10b..d73b45bc 100644
--- a/sleekxmpp/plugins/xep_0059/rsm.py
+++ b/sleekxmpp/plugins/xep_0059/rsm.py
@@ -25,11 +25,14 @@ class ResultIterator():
An iterator for Result Set Managment
"""
- def __init__(self, query, interface, amount=10, start=None, reverse=False):
+ def __init__(self, query, interface, results='substanzas', amount=10,
+ start=None, reverse=False):
"""
Arguments:
query -- The template query
interface -- The substanza of the query, for example disco_items
+ results -- The query stanza's interface which provides a
+ countable list of query results.
amount -- The max amounts of items to request per iteration
start -- From which item id to start
reverse -- If True, page backwards through the results
@@ -46,6 +49,7 @@ class ResultIterator():
self.amount = amount
self.start = start
self.interface = interface
+ self.results = results
self.reverse = reverse
self._stop = False
@@ -85,7 +89,7 @@ class ResultIterator():
r[self.interface]['rsm']['first_index']:
count = int(r[self.interface]['rsm']['count'])
first = int(r[self.interface]['rsm']['first_index'])
- num_items = len(r[self.interface]['substanzas'])
+ num_items = len(r[self.interface][self.results])
if first + num_items == count:
self._stop = True
@@ -123,7 +127,7 @@ class XEP_0059(BasePlugin):
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(Set.namespace)
- def iterate(self, stanza, interface):
+ def iterate(self, stanza, interface, results='substanzas'):
"""
Create a new result set iterator for a given stanza query.
@@ -135,5 +139,7 @@ class XEP_0059(BasePlugin):
result set management stanza should be
appended. For example, for disco#items queries
the interface 'disco_items' should be used.
+ results -- The name of the interface containing the
+ query results (typically just 'substanzas').
"""
- return ResultIterator(stanza, interface)
+ return ResultIterator(stanza, interface, results)
diff --git a/sleekxmpp/plugins/xep_0091/__init__.py b/sleekxmpp/plugins/xep_0091/__init__.py
new file mode 100644
index 00000000..04f21ef5
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0091/__init__.py
@@ -0,0 +1,16 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.plugins.base import register_plugin
+
+from sleekxmpp.plugins.xep_0091 import stanza
+from sleekxmpp.plugins.xep_0091.stanza import LegacyDelay
+from sleekxmpp.plugins.xep_0091.legacy_delay import XEP_0091
+
+
+register_plugin(XEP_0091)
diff --git a/sleekxmpp/plugins/xep_0091/legacy_delay.py b/sleekxmpp/plugins/xep_0091/legacy_delay.py
new file mode 100644
index 00000000..7323d468
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0091/legacy_delay.py
@@ -0,0 +1,29 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+
+from sleekxmpp.stanza import Message, Presence
+from sleekxmpp.xmlstream import register_stanza_plugin
+from sleekxmpp.plugins import BasePlugin
+from sleekxmpp.plugins.xep_0091 import stanza
+
+
+class XEP_0091(BasePlugin):
+
+ """
+ XEP-0091: Legacy Delayed Delivery
+ """
+
+ name = 'xep_0091'
+ description = 'XEP-0091: Legacy Delayed Delivery'
+ dependencies = set()
+ stanza = stanza
+
+ def plugin_init(self):
+ register_stanza_plugin(Message, stanza.LegacyDelay)
+ register_stanza_plugin(Presence, stanza.LegacyDelay)
diff --git a/sleekxmpp/plugins/xep_0091/stanza.py b/sleekxmpp/plugins/xep_0091/stanza.py
new file mode 100644
index 00000000..0b70ff63
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0091/stanza.py
@@ -0,0 +1,46 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+import datetime as dt
+
+from sleekxmpp.jid import JID
+from sleekxmpp.xmlstream import ElementBase
+from sleekxmpp.plugins import xep_0082
+
+
+class LegacyDelay(ElementBase):
+
+ name = 'x'
+ namespace = 'jabber:x:delay'
+ plugin_attrib = 'legacy_delay'
+ interfaces = set(('from', 'stamp', 'text'))
+
+ def get_from(self):
+ return JID(self._get_attr('from'))
+
+ def set_from(self, value):
+ self._set_attr('from', str(value))
+
+ def get_stamp(self):
+ timestamp = self._get_attr('stamp')
+ return xep_0082.parse('%sZ' % timestamp)
+
+ def set_stamp(self, value):
+ if isinstance(value, dt.datetime):
+ value = value.astimezone(xep_0082.tzutc)
+ value = xep_0082.format_datetime(value)
+ self._set_attr('stamp', value[0:19].replace('-', ''))
+
+ def get_text(self):
+ return self.xml.text
+
+ def set_text(self, value):
+ self.xml.text = value
+
+ def del_text(self):
+ self.xml.text = ''
diff --git a/sleekxmpp/plugins/xep_0203/stanza.py b/sleekxmpp/plugins/xep_0203/stanza.py
index baae4cd3..9a11cae9 100644
--- a/sleekxmpp/plugins/xep_0203/stanza.py
+++ b/sleekxmpp/plugins/xep_0203/stanza.py
@@ -14,14 +14,17 @@ from sleekxmpp.plugins import xep_0082
class Delay(ElementBase):
- """
- """
-
name = 'delay'
namespace = 'urn:xmpp:delay'
plugin_attrib = 'delay'
interfaces = set(('from', 'stamp', 'text'))
+ def get_from(self):
+ return JID(self._get_attr('from'))
+
+ def set_from(self, value):
+ self._set_attr('from', str(value))
+
def get_stamp(self):
timestamp = self._get_attr('stamp')
return xep_0082.parse(timestamp)
diff --git a/sleekxmpp/plugins/xep_0280/__init__.py b/sleekxmpp/plugins/xep_0280/__init__.py
index 8ed65346..929321af 100644
--- a/sleekxmpp/plugins/xep_0280/__init__.py
+++ b/sleekxmpp/plugins/xep_0280/__init__.py
@@ -1,6 +1,6 @@
"""
SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
+ Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permissio
diff --git a/sleekxmpp/plugins/xep_0280/carbons.py b/sleekxmpp/plugins/xep_0280/carbons.py
index 7eec7acd..fe2cdbb2 100644
--- a/sleekxmpp/plugins/xep_0280/carbons.py
+++ b/sleekxmpp/plugins/xep_0280/carbons.py
@@ -1,6 +1,6 @@
"""
SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
+ Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permissio
diff --git a/sleekxmpp/plugins/xep_0297/forwarded.py b/sleekxmpp/plugins/xep_0297/forwarded.py
index 7876967c..95703a2d 100644
--- a/sleekxmpp/plugins/xep_0297/forwarded.py
+++ b/sleekxmpp/plugins/xep_0297/forwarded.py
@@ -26,9 +26,14 @@ class XEP_0297(BasePlugin):
def plugin_init(self):
register_stanza_plugin(Message, Forwarded)
- register_stanza_plugin(Forwarded, Message)
- register_stanza_plugin(Forwarded, Presence)
- register_stanza_plugin(Forwarded, Iq)
+
+ # While these are marked as iterable, that is just for
+ # making it easier to extract the forwarded stanza. There
+ # still can be only a single forwarded stanza.
+ register_stanza_plugin(Forwarded, Message, iterable=True)
+ register_stanza_plugin(Forwarded, Presence, iterable=True)
+ register_stanza_plugin(Forwarded, Iq, iterable=True)
+
register_stanza_plugin(Forwarded, self.xmpp['xep_0203'].stanza.Delay)
self.xmpp.register_handler(
diff --git a/sleekxmpp/plugins/xep_0297/stanza.py b/sleekxmpp/plugins/xep_0297/stanza.py
index 1cf02f74..8b97accc 100644
--- a/sleekxmpp/plugins/xep_0297/stanza.py
+++ b/sleekxmpp/plugins/xep_0297/stanza.py
@@ -6,6 +6,7 @@
See the file LICENSE for copying permission.
"""
+from sleekxmpp.stanza import Message, Presence, Iq
from sleekxmpp.xmlstream import ElementBase
@@ -16,12 +17,9 @@ class Forwarded(ElementBase):
interfaces = set(['stanza'])
def get_stanza(self):
- if self.xml.find('{jabber:client}message') is not None:
- return self['message']
- elif self.xml.find('{jabber:client}presence') is not None:
- return self['presence']
- elif self.xml.find('{jabber:client}iq') is not None:
- return self['iq']
+ for stanza in self:
+ if isinstance(stanza, (Message, Presence, Iq)):
+ return stanza
return ''
def set_stanza(self, value):
@@ -29,6 +27,10 @@ class Forwarded(ElementBase):
self.append(value)
def del_stanza(self):
- del self['message']
- del self['presence']
- del self['iq']
+ found_stanzas = []
+ for stanza in self:
+ if isinstance(stanza, (Message, Presence, Iq)):
+ found_stanzas.append(stanza)
+ for stanza in found_stanzas:
+ self.iterables.remove(stanza)
+ self.xml.remove(stanza.xml)
diff --git a/sleekxmpp/plugins/xep_0308/__init__.py b/sleekxmpp/plugins/xep_0308/__init__.py
new file mode 100644
index 00000000..a6a100ee
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0308/__init__.py
@@ -0,0 +1,15 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permissio
+"""
+
+from sleekxmpp.plugins.base import register_plugin
+
+from sleekxmpp.plugins.xep_0308.stanza import Replace
+from sleekxmpp.plugins.xep_0308.correction import XEP_0308
+
+
+register_plugin(XEP_0308)
diff --git a/sleekxmpp/plugins/xep_0308/correction.py b/sleekxmpp/plugins/xep_0308/correction.py
new file mode 100644
index 00000000..d32b4bc4
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0308/correction.py
@@ -0,0 +1,52 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permissio
+"""
+
+import logging
+
+import sleekxmpp
+from sleekxmpp.stanza import Message
+from sleekxmpp.xmlstream.handler import Callback
+from sleekxmpp.xmlstream.matcher import StanzaPath
+from sleekxmpp.xmlstream import register_stanza_plugin
+from sleekxmpp.plugins import BasePlugin
+from sleekxmpp.plugins.xep_0308 import stanza, Replace
+
+
+log = logging.getLogger(__name__)
+
+
+class XEP_0308(BasePlugin):
+
+ """
+ XEP-0308 Last Message Correction
+ """
+
+ name = 'xep_0308'
+ description = 'XEP-0308: Last Message Correction'
+ dependencies = set(['xep_0030'])
+ stanza = stanza
+
+ def plugin_init(self):
+ self.xmpp.register_handler(
+ Callback('Message Correction',
+ StanzaPath('message/replace'),
+ self._handle_correction))
+
+ register_stanza_plugin(Message, Replace)
+
+ self.xmpp.use_message_ids = True
+
+ def plugin_end(self):
+ self.xmpp.remove_handler('Message Correction')
+ self.xmpp.plugin['xep_0030'].del_feature(feature=Replace.namespace)
+
+ def session_bind(self, jid):
+ self.xmpp.plugin['xep_0030'].add_feature(Replace.namespace)
+
+ def _handle_correction(self, msg):
+ self.xmpp.event('message_correction', msg)
diff --git a/sleekxmpp/plugins/xep_0308/stanza.py b/sleekxmpp/plugins/xep_0308/stanza.py
new file mode 100644
index 00000000..8f88cbc0
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0308/stanza.py
@@ -0,0 +1,16 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permissio
+"""
+
+from sleekxmpp.xmlstream import ElementBase
+
+
+class Replace(ElementBase):
+ name = 'replace'
+ namespace = 'urn:xmpp:message-correct:0'
+ plugin_attrib = 'replace'
+ interfaces = set(['id'])
diff --git a/sleekxmpp/plugins/xep_0313/__init__.py b/sleekxmpp/plugins/xep_0313/__init__.py
new file mode 100644
index 00000000..8b6ed97d
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0313/__init__.py
@@ -0,0 +1,15 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permissio
+"""
+
+from sleekxmpp.plugins.base import register_plugin
+
+from sleekxmpp.plugins.xep_0313.stanza import Result, MAM, Preferences
+from sleekxmpp.plugins.xep_0313.mam import XEP_0313
+
+
+register_plugin(XEP_0313)
diff --git a/sleekxmpp/plugins/xep_0313/mam.py b/sleekxmpp/plugins/xep_0313/mam.py
new file mode 100644
index 00000000..15aee828
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0313/mam.py
@@ -0,0 +1,92 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permissio
+"""
+
+import logging
+
+import sleekxmpp
+from sleekxmpp.stanza import Message, Iq
+from sleekxmpp.exceptions import XMPPError
+from sleekxmpp.xmlstream.handler import Collector
+from sleekxmpp.xmlstream.matcher import StanzaPath
+from sleekxmpp.xmlstream import register_stanza_plugin
+from sleekxmpp.plugins import BasePlugin
+from sleekxmpp.plugins.xep_0313 import stanza
+
+
+log = logging.getLogger(__name__)
+
+
+class XEP_0313(BasePlugin):
+
+ """
+ XEP-0313 Message Archive Management
+ """
+
+ name = 'xep_0313'
+ description = 'XEP-0313: Message Archive Management'
+ dependencies = set(['xep_0030', 'xep_0050', 'xep_0059', 'xep_0297'])
+ stanza = stanza
+
+ def plugin_init(self):
+ register_stanza_plugin(Iq, stanza.MAM)
+ register_stanza_plugin(Iq, stanza.Preferences)
+ register_stanza_plugin(Message, stanza.Result)
+ register_stanza_plugin(stanza.MAM, self.xmpp['xep_0059'].stanza.Set)
+
+ def retrieve(self, jid=None, start=None, end=None, with_jid=None, ifrom=None,
+ block=True, timeout=None, callback=None, iterator=False):
+ iq = self.xmpp.Iq()
+ query_id = iq['id']
+
+ iq['to'] = jid
+ iq['from'] = ifrom
+ iq['type'] = 'get'
+ iq['mam']['queryid'] = query_id
+ iq['mam']['start'] = start
+ iq['mam']['end'] = end
+ iq['mam']['with'] = with_jid
+
+ collector = Collector(
+ 'MAM_Results_%s' % query_id,
+ StanzaPath('message/mam_result@queryid=%s' % query_id))
+ self.xmpp.register_handler(collector)
+
+ if iterator:
+ return self.xmpp['xep_0059'].iterate(iq, 'mam', 'results')
+ elif not block and callback is not None:
+ def wrapped_cb(iq):
+ results = collector.stop()
+ if iq['type'] == 'result':
+ iq['mam']['results'] = results
+ callback(iq)
+ return iq.send(block=block, timeout=timeout, callback=wrapped_cb)
+ else:
+ try:
+ resp = iq.send(block=block, timeout=timeout, callback=callback)
+ resp['mam']['results'] = collector.stop()
+ return resp
+ except XMPPError as e:
+ collector.stop()
+ raise e
+
+ def set_preferences(self, jid=None, default=None, always=None, never=None,
+ ifrom=None, block=True, timeout=None, callback=None):
+ iq = self.xmpp.Iq()
+ iq['type'] = 'set'
+ iq['to'] = jid
+ iq['from'] = ifrom
+ iq['mam_prefs']['default'] = default
+ iq['mam_prefs']['always'] = always
+ iq['mam_prefs']['never'] = never
+ return iq.send(block=block, timeout=timeout, callback=callback)
+
+ def get_configuration_commands(self, jid, **kwargs):
+ return self.xmpp['xep_0030'].get_items(
+ jid=jid,
+ node='urn:xmpp:mam#configure',
+ **kwargs)
diff --git a/sleekxmpp/plugins/xep_0313/stanza.py b/sleekxmpp/plugins/xep_0313/stanza.py
new file mode 100644
index 00000000..a33c2e35
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0313/stanza.py
@@ -0,0 +1,131 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permissio
+"""
+
+import datetime as dt
+
+from sleekxmpp.jid import JID
+from sleekxmpp.xmlstream import ElementBase, ET
+from sleekxmpp.plugins import xep_0082
+
+
+class MAM(ElementBase):
+ name = 'query'
+ namespace = 'urn:xmpp:mam:tmp'
+ plugin_attrib = 'mam'
+ interfaces = set(['queryid', 'start', 'end', 'with', 'results'])
+ sub_interfaces = set(['start', 'end', 'with'])
+
+ def setup(self, xml=None):
+ ElementBase.setup(self, xml)
+ self._results = []
+
+ def get_start(self):
+ timestamp = self._get_attr('start')
+ return xep_0082.parse(timestamp)
+
+ def set_start(self, value):
+ if isinstance(value, dt.datetime):
+ value = xep_0082.format_datetime(value)
+ self._set_attr('start', value)
+
+ def get_end(self):
+ timestamp = self._get_sub_text('end')
+ return xep_0082.parse(timestamp)
+
+ def set_end(self, value):
+ if isinstance(value, dt.datetime):
+ value = xep_0082.format_datetime(value)
+ self._set_sub_text('end', value)
+
+ def get_with(self):
+ return JID(self._get_sub_text('with'))
+
+ def set_with(self, value):
+ self._set_sub_text('with', str(value))
+
+ # The results interface is meant only as an easy
+ # way to access the set of collected message responses
+ # from the query.
+
+ def get_results(self):
+ return self._results
+
+ def set_results(self, values):
+ self._results = values
+
+ def del_results(self):
+ self._results = []
+
+
+class Preferences(ElementBase):
+ name = 'prefs'
+ namespace = 'urn:xmpp:mam:tmp'
+ plugin_attrib = 'mam_prefs'
+ interfaces = set(['default', 'always', 'never'])
+ sub_interfaces = set(['always', 'never'])
+
+ def get_always(self):
+ results = set()
+
+ jids = self.xml.findall('{%s}always/{%s}jid' % (
+ self.namespace, self.namespace))
+
+ for jid in jids:
+ results.add(JID(jid.text))
+
+ return results
+
+ def set_always(self, value):
+ self._set_sub_text('always', '', keep=True)
+ always = self.xml.find('{%s}always' % self.namespace)
+ always.clear()
+
+ if not isinstance(value, (list, set)):
+ value = [value]
+
+ for jid in value:
+ jid_xml = ET.Element('{%s}jid' % self.namespace)
+ jid_xml.text = str(jid)
+ always.append(jid_xml)
+
+ def get_never(self):
+ results = set()
+
+ jids = self.xml.findall('{%s}never/{%s}jid' % (
+ self.namespace, self.namespace))
+
+ for jid in jids:
+ results.add(JID(jid.text))
+
+ return results
+
+ def set_never(self, value):
+ self._set_sub_text('never', '', keep=True)
+ never = self.xml.find('{%s}never' % self.namespace)
+ never.clear()
+
+ if not isinstance(value, (list, set)):
+ value = [value]
+
+ for jid in value:
+ jid_xml = ET.Element('{%s}jid' % self.namespace)
+ jid_xml.text = str(jid)
+ never.append(jid_xml)
+
+
+class Result(ElementBase):
+ name = 'result'
+ namespace = 'urn:xmpp:mam:tmp'
+ plugin_attrib = 'mam_result'
+ interfaces = set(['forwarded', 'queryid', 'id'])
+
+ def get_forwarded(self):
+ return self.parent()['forwarded']
+
+ def del_forwarded(self):
+ del self.parent()['forwarded']
diff --git a/sleekxmpp/stanza/message.py b/sleekxmpp/stanza/message.py
index 02133682..0bb6e587 100644
--- a/sleekxmpp/stanza/message.py
+++ b/sleekxmpp/stanza/message.py
@@ -63,6 +63,17 @@ class Message(RootStanza):
lang_interfaces = sub_interfaces
types = set(['normal', 'chat', 'headline', 'error', 'groupchat'])
+ def __init__(self, *args, **kwargs):
+ """
+ Initialize a new <message /> stanza with an optional 'id' value.
+
+ Overrides StanzaBase.__init__.
+ """
+ StanzaBase.__init__(self, *args, **kwargs)
+ if self['id'] == '':
+ if self.stream is not None and self.stream.use_message_ids:
+ self['id'] = self.stream.new_id()
+
def get_type(self):
"""
Return the message type.
diff --git a/sleekxmpp/stanza/presence.py b/sleekxmpp/stanza/presence.py
index 7951f861..84bcd122 100644
--- a/sleekxmpp/stanza/presence.py
+++ b/sleekxmpp/stanza/presence.py
@@ -72,6 +72,17 @@ class Presence(RootStanza):
'subscribed', 'unsubscribe', 'unsubscribed'])
showtypes = set(['dnd', 'chat', 'xa', 'away'])
+ def __init__(self, *args, **kwargs):
+ """
+ Initialize a new <presence /> stanza with an optional 'id' value.
+
+ Overrides StanzaBase.__init__.
+ """
+ StanzaBase.__init__(self, *args, **kwargs)
+ if self['id'] == '':
+ if self.stream is not None and self.stream.use_presence_ids:
+ self['id'] = self.stream.new_id()
+
def exception(self, e):
"""
Override exception passback for presence.
diff --git a/sleekxmpp/test/sleektest.py b/sleekxmpp/test/sleektest.py
index 47af86cf..901c3a56 100644
--- a/sleekxmpp/test/sleektest.py
+++ b/sleekxmpp/test/sleektest.py
@@ -368,6 +368,11 @@ class SleekTest(unittest.TestCase):
else:
for plugin in plugins:
self.xmpp.register_plugin(plugin)
+
+ # Some plugins require messages to have ID values. Set
+ # this to True in tests related to those plugins.
+ self.xmpp.use_message_ids = False
+
self.xmpp.process(threaded=True)
if skip:
if socket != 'live':
diff --git a/sleekxmpp/xmlstream/handler/__init__.py b/sleekxmpp/xmlstream/handler/__init__.py
index 7bcf0b71..83c87f01 100644
--- a/sleekxmpp/xmlstream/handler/__init__.py
+++ b/sleekxmpp/xmlstream/handler/__init__.py
@@ -7,6 +7,7 @@
"""
from sleekxmpp.xmlstream.handler.callback import Callback
+from sleekxmpp.xmlstream.handler.collector import Collector
from sleekxmpp.xmlstream.handler.waiter import Waiter
from sleekxmpp.xmlstream.handler.xmlcallback import XMLCallback
from sleekxmpp.xmlstream.handler.xmlwaiter import XMLWaiter
diff --git a/sleekxmpp/xmlstream/handler/collector.py b/sleekxmpp/xmlstream/handler/collector.py
new file mode 100644
index 00000000..8f02f8c3
--- /dev/null
+++ b/sleekxmpp/xmlstream/handler/collector.py
@@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+"""
+ sleekxmpp.xmlstream.handler.collector
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Part of SleekXMPP: The Sleek XMPP Library
+
+ :copyright: (c) 2012 Nathanael C. Fritz, Lance J.T. Stout
+ :license: MIT, see LICENSE for more details
+"""
+
+import logging
+
+from sleekxmpp.util import Queue, QueueEmpty
+from sleekxmpp.xmlstream.handler.base import BaseHandler
+
+
+log = logging.getLogger(__name__)
+
+
+class Collector(BaseHandler):
+
+ """
+ The Collector handler allows for collecting a set of stanzas
+ that match a given pattern. Unlike the Waiter handler, a
+ Collector does not block execution, and will continue to
+ accumulate matching stanzas until told to stop.
+
+ :param string name: The name of the handler.
+ :param matcher: A :class:`~sleekxmpp.xmlstream.matcher.base.MatcherBase`
+ derived object for matching stanza objects.
+ :param stream: The :class:`~sleekxmpp.xmlstream.xmlstream.XMLStream`
+ instance this handler should monitor.
+ """
+
+ def __init__(self, name, matcher, stream=None):
+ BaseHandler.__init__(self, name, matcher, stream=stream)
+ self._payload = Queue()
+
+ def prerun(self, payload):
+ """Store the matched stanza when received during processing.
+
+ :param payload: The matched
+ :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` object.
+ """
+ self._payload.put(payload)
+
+ def run(self, payload):
+ """Do not process this handler during the main event loop."""
+ pass
+
+ def stop(self):
+ """
+ Stop collection of matching stanzas, and return the ones that
+ have been stored so far.
+ """
+ self._destroy = True
+ results = []
+ try:
+ while True:
+ results.append(self._payload.get(False))
+ except QueueEmpty:
+ pass
+
+ self.stream().remove_handler(self.name)
+ return results
diff --git a/tests/test_stream_xep_0059.py b/tests/test_stream_xep_0059.py
index 3a99842b..249ecf66 100644
--- a/tests/test_stream_xep_0059.py
+++ b/tests/test_stream_xep_0059.py
@@ -17,7 +17,7 @@ class TestStreamSet(SleekTest):
def iter(self, rev=False):
q = self.xmpp.Iq()
q['type'] = 'get'
- it = ResultIterator(q, 'disco_items', '1', reverse=rev)
+ it = ResultIterator(q, 'disco_items', amount='1', reverse=rev)
for i in it:
for j in i['disco_items']['items']:
self.items.append(j[0])