summaryrefslogtreecommitdiff
path: root/slixmpp/plugins/xep_0313/stanza.py
diff options
context:
space:
mode:
Diffstat (limited to 'slixmpp/plugins/xep_0313/stanza.py')
-rw-r--r--slixmpp/plugins/xep_0313/stanza.py371
1 files changed, 277 insertions, 94 deletions
diff --git a/slixmpp/plugins/xep_0313/stanza.py b/slixmpp/plugins/xep_0313/stanza.py
index 4e43eeba..6633717f 100644
--- a/slixmpp/plugins/xep_0313/stanza.py
+++ b/slixmpp/plugins/xep_0313/stanza.py
@@ -1,159 +1,342 @@
-
# Slixmpp: The Slick XMPP Library
# Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
# This file is part of Slixmpp.
# See the file LICENSE for copying permissio
-import datetime as dt
-
+from datetime import datetime
+from typing import (
+ Any,
+ Iterable,
+ List,
+ Optional,
+ Set,
+ Union,
+)
+
+from slixmpp.stanza import Message
from slixmpp.jid import JID
from slixmpp.xmlstream import ElementBase, ET
-from slixmpp.plugins import xep_0082, xep_0004
+from slixmpp.plugins import xep_0082
class MAM(ElementBase):
+ """A MAM Query element.
+
+ .. code-block:: xml
+
+ <iq type='set' id='juliet1'>
+ <query xmlns='urn:xmpp:mam:2'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE' type='hidden'>
+ <value>urn:xmpp:mam:2</value>
+ </field>
+ <field var='with'>
+ <value>juliet@capulet.lit</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+ """
name = 'query'
namespace = 'urn:xmpp:mam:2'
plugin_attrib = 'mam'
- interfaces = {'queryid', 'start', 'end', 'with', 'results'}
- sub_interfaces = {'start', 'end', 'with'}
+ #: Available interfaces:
+ #:
+ #: - ``queryid``: The MAM query id
+ #: - ``start`` and ``end``: Temporal boundaries of the query
+ #: - ``with``: JID of the other entity the conversation is with
+ #: - ``after_id``: Fetch stanzas after this specific ID
+ #: - ``before_id``: Fetch stanzas before this specific ID
+ #: - ``ids``: Fetch the stanzas matching those IDs
+ #: - ``results``: pseudo-interface used to accumulate MAM results during
+ #: fetch, not relevant for the stanza itself.
+ interfaces = {
+ 'queryid', 'start', 'end', 'with', 'results',
+ 'before_id', 'after_id', 'ids',
+ }
+ sub_interfaces = {'start', 'end', 'with', 'before_id', 'after_id', 'ids'}
def setup(self, xml=None):
ElementBase.setup(self, xml)
- self._form = xep_0004.stanza.Form()
- self._form['type'] = 'submit'
- field = self._form.add_field(var='FORM_TYPE', ftype='hidden',
- value='urn:xmpp:mam:2')
- self.append(self._form)
- self._results = []
-
- def __get_fields(self):
- return self._form.get_fields()
-
- def get_start(self):
- fields = self.__get_fields()
+ self._results: List[Message] = []
+
+ def _setup_form(self):
+ found = self.xml.find(
+ '{jabber:x:data}x/'
+ '{jabber:x:data}field[@var="FORM_TYPE"]/'
+ "{jabber:x:data}value[.='urn:xmpp:mam:2']"
+ )
+ if found is None:
+ self['form']['type'] = 'submit'
+ self['form'].add_field(
+ var='FORM_TYPE', ftype='hidden', value='urn:xmpp:mam:2'
+ )
+
+ def get_fields(self):
+ form = self.get_plugin('form', check=True)
+ if not form:
+ return {}
+ return form.get_fields()
+
+ def get_start(self) -> Optional[datetime]:
+ fields = self.get_fields()
field = fields.get('start')
if field:
return xep_0082.parse(field['value'])
+ return None
- def set_start(self, value):
- if isinstance(value, dt.datetime):
+ def set_start(self, value: Union[str, datetime]):
+ self._setup_form()
+ if isinstance(value, datetime):
value = xep_0082.format_datetime(value)
- fields = self.__get_fields()
- field = fields.get('start')
- if field:
- field['value'] = value
- else:
- field = self._form.add_field(var='start')
- field['value'] = value
+ self.set_custom_field('start', value)
- def get_end(self):
- fields = self.__get_fields()
+ def get_end(self) -> Optional[datetime]:
+ fields = self.get_fields()
field = fields.get('end')
if field:
return xep_0082.parse(field['value'])
+ return None
- def set_end(self, value):
- if isinstance(value, dt.datetime):
+ def set_end(self, value: Union[str, datetime]):
+ if isinstance(value, datetime):
value = xep_0082.format_datetime(value)
- fields = self.__get_fields()
- field = fields.get('end')
- if field:
- field['value'] = value
- else:
- field = self._form.add_field(var='end')
- field['value'] = value
+ self.set_custom_field('end', value)
- def get_with(self):
- fields = self.__get_fields()
+ def get_with(self) -> Optional[JID]:
+ fields = self.get_fields()
field = fields.get('with')
if field:
return JID(field['value'])
+ return None
- def set_with(self, value):
- fields = self.__get_fields()
- field = fields.get('with')
+ def set_with(self, value: JID):
+ self.set_custom_field('with', value)
+
+ def set_custom_field(self, fieldname: str, value: Any):
+ self._setup_form()
+ fields = self.get_fields()
+ field = fields.get(fieldname)
if field:
- field['with'] = str(value)
+ field['value'] = str(value)
else:
- field = self._form.add_field(var='with')
+ field = self['form'].add_field(var=fieldname)
field['value'] = str(value)
+
+ def get_custom_field(self, fieldname: str) -> Optional[str]:
+ fields = self.get_fields()
+ field = fields.get(fieldname)
+ if field:
+ return field['value']
+ return None
+
+ def set_before_id(self, value: str):
+ self.set_custom_field('before-id', value)
+
+ def get_before_id(self):
+ self.get_custom_field('before-id')
+
+ def set_after_id(self, value: str):
+ self.set_custom_field('after-id', value)
+
+ def get_after_id(self):
+ self.get_custom_field('after-id')
+
+ def set_ids(self, value: List[str]):
+ self._setup_form()
+ fields = self.get_fields()
+ field = fields.get('ids')
+ if field:
+ field['ids'] = value
+ else:
+ field = self['form'].add_field(var='ids')
+ field['value'] = value
+
+ def get_ids(self):
+ self.get_custom_field('id')
+
# 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):
+ def get_results(self) -> List[Message]:
return self._results
- def set_results(self, values):
+ def set_results(self, values: List[Message]):
self._results = values
def del_results(self):
self._results = []
-class Preferences(ElementBase):
- name = 'prefs'
+class Fin(ElementBase):
+ """A MAM fin element (end of query).
+
+ .. code-block:: xml
+
+ <iq type='result' id='juliet1'>
+ <fin xmlns='urn:xmpp:mam:2'>
+ <set xmlns='http://jabber.org/protocol/rsm'>
+ <first index='0'>28482-98726-73623</first>
+ <last>09af3-cc343-b409f</last>
+ </set>
+ </fin>
+ </iq>
+
+ """
+ name = 'fin'
namespace = 'urn:xmpp:mam:2'
- plugin_attrib = 'mam_prefs'
- interfaces = {'default', 'always', 'never'}
- sub_interfaces = {'always', 'never'}
+ plugin_attrib = 'mam_fin'
+ interfaces = {'results'}
- def get_always(self):
- results = set()
+ def setup(self, xml=None):
+ ElementBase.setup(self, xml)
+ self._results: List[Message] = []
- jids = self.xml.findall('{%s}always/{%s}jid' % (
- self.namespace, self.namespace))
+ # The results interface is meant only as an easy
+ # way to access the set of collected message responses
+ # from the query.
- for jid in jids:
- results.add(JID(jid.text))
+ def get_results(self) -> List[Message]:
+ return self._results
- return results
+ def set_results(self, values: List[Message]):
+ self._results = values
+
+ def del_results(self):
+ self._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]
+class Result(ElementBase):
+ """A MAM result payload.
+
+ .. code-block:: xml
+
+ <message id='aeb213' to='juliet@capulet.lit/chamber'>
+ <result xmlns='urn:xmpp:mam:2' queryid='f27' id='28482-98726-73623'>
+ <forwarded xmlns='urn:xmpp:forward:0'>
+ <delay xmlns='urn:xmpp:delay' stamp='2010-07-10T23:08:25Z'/>
+ <message xmlns='jabber:client' from="witch@shakespeare.lit"
+ to="macbeth@shakespeare.lit">
+ <body>Hail to thee</body>
+ </message>
+ </forwarded>
+ </result>
+ </message>
+ """
+ name = 'result'
+ namespace = 'urn:xmpp:mam:2'
+ plugin_attrib = 'mam_result'
+ #: Available interfaces:
+ #:
+ #: - ``queryid``: MAM queryid
+ #: - ``id``: ID of the result
+ interfaces = {'queryid', 'id'}
- 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()
+class Metadata(ElementBase):
+ """Element containing archive metadata
- jids = self.xml.findall('{%s}never/{%s}jid' % (
- self.namespace, self.namespace))
+ .. code-block:: xml
- for jid in jids:
- results.add(JID(jid.text))
+ <iq type='result' id='jui8921rr9'>
+ <metadata xmlns='urn:xmpp:mam:2'>
+ <start id='YWxwaGEg' timestamp='2008-08-22T21:09:04Z' />
+ <end id='b21lZ2Eg' timestamp='2020-04-20T14:34:21Z' />
+ </metadata>
+ </iq>
- return results
+ """
+ name = 'metadata'
+ namespace = 'urn:xmpp:mam:2'
+ plugin_attrib = 'mam_metadata'
- 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]
+class Start(ElementBase):
+ """Metadata about the start of an archive.
- for jid in value:
- jid_xml = ET.Element('{%s}jid' % self.namespace)
- jid_xml.text = str(jid)
- never.append(jid_xml)
+ .. code-block:: xml
+ <iq type='result' id='jui8921rr9'>
+ <metadata xmlns='urn:xmpp:mam:2'>
+ <start id='YWxwaGEg' timestamp='2008-08-22T21:09:04Z' />
+ <end id='b21lZ2Eg' timestamp='2020-04-20T14:34:21Z' />
+ </metadata>
+ </iq>
-class Fin(ElementBase):
- name = 'fin'
+ """
+ name = 'start'
namespace = 'urn:xmpp:mam:2'
- plugin_attrib = 'mam_fin'
-
-class Result(ElementBase):
- name = 'result'
+ plugin_attrib = name
+ #: Available interfaces:
+ #:
+ #: - ``id``: ID of the first message of the archive
+ #: - ``timestamp`` (``datetime``): timestamp of the first message of the
+ #: archive
+ interfaces = {'id', 'timestamp'}
+
+ def get_timestamp(self) -> Optional[datetime]:
+ """Get the timestamp.
+
+ :returns: The timestamp.
+ """
+ stamp = self.xml.attrib.get('timestamp', None)
+ if stamp is not None:
+ return xep_0082.parse(stamp)
+ return stamp
+
+ def set_timestamp(self, value: Union[datetime, str]):
+ """Set the timestamp.
+
+ :param value: Value of the timestamp (either a datetime or a
+ XEP-0082 timestamp string.
+ """
+ if isinstance(value, str):
+ value = xep_0082.parse(value)
+ value = xep_0082.format_datetime(value)
+ self.xml.attrib['timestamp'] = value
+
+
+class End(ElementBase):
+ """Metadata about the end of an archive.
+
+ .. code-block:: xml
+
+ <iq type='result' id='jui8921rr9'>
+ <metadata xmlns='urn:xmpp:mam:2'>
+ <start id='YWxwaGEg' timestamp='2008-08-22T21:09:04Z' />
+ <end id='b21lZ2Eg' timestamp='2020-04-20T14:34:21Z' />
+ </metadata>
+ </iq>
+
+ """
+ name = 'end'
namespace = 'urn:xmpp:mam:2'
- plugin_attrib = 'mam_result'
- interfaces = {'queryid', 'id'}
+ plugin_attrib = name
+ #: Available interfaces:
+ #:
+ #: - ``id``: ID of the first message of the archive
+ #: - ``timestamp`` (``datetime``): timestamp of the first message of the
+ #: archive
+ interfaces = {'id', 'timestamp'}
+
+ def get_timestamp(self) -> Optional[datetime]:
+ """Get the timestamp.
+
+ :returns: The timestamp.
+ """
+ stamp = self.xml.attrib.get('timestamp', None)
+ if stamp is not None:
+ return xep_0082.parse(stamp)
+ return stamp
+
+ def set_timestamp(self, value: Union[datetime, str]):
+ """Set the timestamp.
+
+ :param value: Value of the timestamp (either a datetime or a
+ XEP-0082 timestamp string.
+ """
+ if isinstance(value, str):
+ value = xep_0082.parse(value)
+ value = xep_0082.format_datetime(value)
+ self.xml.attrib['timestamp'] = value